Files
signal-platform/tests/unit/test_fundamentals_quality_service.py
T

184 lines
5.5 KiB
Python

from __future__ import annotations
import json
from datetime import date, datetime, timezone
from app.models.data_import_run import DataImportRun
from app.models.fundamental_snapshot import FundamentalSnapshot
from app.models.sec_filing_gap import SecFilingGap
from app.models.settings import SystemSetting
from app.models.ticker import Ticker
from app.services import fundamentals_quality_service
async def test_latest_sec_validation_blocks_deferred_and_no_history_ciks(
db_session,
):
missing = Ticker(symbol="MISSING", cik="0000000001")
no_history = Ticker(symbol="NEWREG", cik="0000000002")
healthy = Ticker(symbol="HEALTHY", cik="0000000003")
db_session.add_all([missing, no_history, healthy])
await db_session.flush()
db_session.add(
SystemSetting(
key="fundamental_data_sec_dolt_cutover_enabled",
value="true",
)
)
db_session.add(
DataImportRun(
source="sec_facts",
status="deferred",
validation_json=json.dumps({
"missing_xbrl": [{"cik": missing.cik, "accession": "MISSING-Q"}],
"no_xbrl_filings": [{"cik": no_history.cik}],
}),
started_at=datetime.now(timezone.utc),
)
)
await db_session.flush()
assert await fundamentals_quality_service.blocked_ticker_ids(db_session) == {
missing.id,
no_history.id,
}
async def test_sec_quality_gate_is_inactive_before_cutover(db_session):
ticker = Ticker(symbol="SHADOW", cik="0000000042")
db_session.add(ticker)
await db_session.flush()
now = datetime.now(timezone.utc)
db_session.add(
SecFilingGap(
cik=ticker.cik,
accession="SHADOW-Q",
form="10-Q",
index_date=date.today(),
reason="not_in_companyfacts",
first_seen_at=now,
last_attempted_at=now,
)
)
await db_session.flush()
assert await fundamentals_quality_service.blocked_ticker_ids(db_session) == set()
async def test_active_gap_is_blocked_until_a_later_filing_supersedes_it(db_session):
ticker = Ticker(symbol="HIST", cik="0000000043")
now = datetime.now(timezone.utc)
db_session.add_all([
ticker,
SystemSetting(
key="fundamental_data_sec_dolt_cutover_enabled",
value="true",
),
SecFilingGap(
cik=ticker.cik,
accession="HIST-Q",
form="10-Q",
index_date=date.today().replace(day=1),
reason="coregistrant_facts_rejected",
first_seen_at=now,
last_attempted_at=now,
),
])
await db_session.flush()
assert await fundamentals_quality_service.blocked_ticker_ids(db_session) == {
ticker.id
}
db_session.add(
FundamentalSnapshot(
cik=ticker.cik,
accession="LATER-Q",
form="10-Q",
filed_date=date.today(),
accepted_at=datetime.now(timezone.utc),
period_end=date.today(),
fiscal_year=date.today().year,
fiscal_period="Q2",
)
)
await db_session.flush()
assert await fundamentals_quality_service.blocked_ticker_ids(db_session) == set()
async def test_gap_without_index_date_uses_first_seen_date_for_supersession(
db_session,
):
ticker = Ticker(symbol="DATELESS", cik="0000000045")
first_seen = datetime(2026, 5, 1, 12, tzinfo=timezone.utc)
db_session.add_all([
ticker,
SystemSetting(
key="fundamental_data_sec_dolt_cutover_enabled",
value="true",
),
SecFilingGap(
cik=ticker.cik,
accession="DATELESS-Q",
form="10-Q",
index_date=None,
reason="not_in_companyfacts",
first_seen_at=first_seen,
last_attempted_at=first_seen,
),
])
await db_session.flush()
assert await fundamentals_quality_service.blocked_ticker_ids(db_session) == {
ticker.id
}
db_session.add(
FundamentalSnapshot(
cik=ticker.cik,
accession="LATER-DATELESS-Q",
form="10-Q",
filed_date=date(2026, 5, 2),
accepted_at=datetime(2026, 5, 2, 12, tzinfo=timezone.utc),
period_end=date(2026, 3, 31),
fiscal_year=2026,
fiscal_period="Q1",
)
)
await db_session.flush()
assert await fundamentals_quality_service.blocked_ticker_ids(db_session) == set()
async def test_ticker_quality_explains_no_xbrl_block(db_session):
ticker = Ticker(symbol="NEWREG", cik="0000000044")
db_session.add_all([
ticker,
SystemSetting(
key="fundamental_data_sec_dolt_cutover_enabled",
value="true",
),
DataImportRun(
source="sec_facts",
status="promoted",
validation_json=json.dumps({
"setup_blocked_ciks": [ticker.cik],
"no_xbrl_ciks": [ticker.cik],
"no_xbrl_filings": [],
}),
started_at=datetime.now(timezone.utc),
),
])
await db_session.flush()
quality = await fundamentals_quality_service.ticker_quality(db_session, "NEWREG")
assert quality.eligible is False
assert quality.code == "no_xbrl_filings"
assert "CIK override" in (quality.message or "")
assert await fundamentals_quality_service.ticker_is_eligible(
db_session, ticker.id
) is False