fix(sec): A3 sign-off hardening — validate per-concept units structure
- Extend the companyfacts structural check to reject a concept with a missing/non-dict `units` mapping (not just the top-level `facts`), so a partially-malformed payload fails promotion instead of silently dropping that concept's facts. New fixture proves it fails. - Strengthen the newly-added-issuer test: keep latest_index equal to the prior run so ONLY the universe fingerprint changes the revision — proving the fingerprint alone prevents a new ticker from being starved/no_op'd. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -322,10 +322,13 @@ async def test_newly_added_issuer_backfills_without_filing(engine):
|
||||
789019: {"cik": 789019, "sic": "7372", "sic_description": "Prepackaged Software",
|
||||
"filings": [_filing("M", "10-K", "2025-06-30", "2025-07-30", "2025-07-30T10:00:00.000Z")]},
|
||||
},
|
||||
latest_index=date(2026, 2, 3),
|
||||
# SAME index date as the prior run and no filing: only the universe
|
||||
# fingerprint (MSFT added) changes the revision, so this proves the
|
||||
# fingerprint alone prevents starvation.
|
||||
latest_index=date(2026, 1, 31),
|
||||
daily={}, # MSFT did not file
|
||||
)
|
||||
run = await run_import(_importer(incr, today=date(2026, 2, 4)), engine=engine)
|
||||
run = await run_import(_importer(incr, today=date(2026, 2, 1)), engine=engine)
|
||||
assert run.status == STATUS_PROMOTED
|
||||
async with factory() as s:
|
||||
msft = (await s.execute(
|
||||
@@ -355,6 +358,29 @@ async def test_malformed_companyfacts_fails_validation(engine):
|
||||
assert await _count(factory, FundamentalSnapshot) == 0 # nothing promoted
|
||||
|
||||
|
||||
async def test_missing_units_structure_fails_validation(engine):
|
||||
factory = _factory(engine)
|
||||
await _seed(factory, ["AAPL", "MSFT"])
|
||||
client = FakeSecClient(
|
||||
tickers={"AAPL": 320193, "MSFT": 789019},
|
||||
companyfacts={
|
||||
320193: _companyfacts([CF_K, CF_Q1], [SH_K, SH_Q1]),
|
||||
# facts present, but a concept is missing its units mapping
|
||||
789019: {"cik": 789019, "facts": {"us-gaap": {"Revenues": {"label": "Revenues"}}}},
|
||||
},
|
||||
submissions={
|
||||
320193: _submissions(SUB_FILINGS),
|
||||
789019: {"cik": 789019, "sic": "7372", "sic_description": "x", "filings": []},
|
||||
},
|
||||
latest_index=date(2026, 1, 31),
|
||||
)
|
||||
run = await run_import(_importer(client), engine=engine)
|
||||
assert run.status == STATUS_FAILED
|
||||
assert "malformed" in (run.error_details or "")
|
||||
assert "units" in (run.validation_json or "")
|
||||
assert await _count(factory, FundamentalSnapshot) == 0
|
||||
|
||||
|
||||
async def test_discrepancy_in_shares_is_detected_and_reported(engine):
|
||||
from app.models.system_event import SystemEvent
|
||||
utc = timezone.utc
|
||||
|
||||
Reference in New Issue
Block a user