perf: scope SEC ticker quality checks
This commit is contained in:
@@ -69,25 +69,42 @@ async def _latest_validation(db: AsyncSession) -> dict:
|
||||
return summary if isinstance(summary, dict) else {}
|
||||
|
||||
|
||||
async def blocked_reasons_by_cik(db: AsyncSession) -> dict[str, str]:
|
||||
async def blocked_reasons_by_cik(
|
||||
db: AsyncSession,
|
||||
ciks: set[str] | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""Current SEC blocker code by CIK; no historical audit scan."""
|
||||
if not await fundamental_data_refresh_service.is_enabled(db):
|
||||
return {}
|
||||
if ciks is not None and not ciks:
|
||||
return {}
|
||||
|
||||
reasons = {gap.cik: "sec_filing_gap" for gap in await active_gaps(db)}
|
||||
reasons = {
|
||||
gap.cik: "sec_filing_gap" for gap in await active_gaps(db, ciks)
|
||||
}
|
||||
summary = await _latest_validation(db)
|
||||
|
||||
def wanted(cik: str) -> bool:
|
||||
return ciks is None or cik in ciks
|
||||
|
||||
# New summaries carry the complete compact CIK set while the detailed lists
|
||||
# stay capped for audit readability. Detailed entries supply the reason.
|
||||
for cik in summary.get("setup_blocked_ciks") or []:
|
||||
if cik:
|
||||
reasons.setdefault(str(cik), "sec_filing_gap")
|
||||
normalized = str(cik) if cik else ""
|
||||
if normalized and wanted(normalized):
|
||||
reasons.setdefault(normalized, "sec_filing_gap")
|
||||
for item in summary.get("missing_xbrl") or []:
|
||||
if item.get("cik"):
|
||||
reasons.setdefault(str(item["cik"]), "sec_filing_gap")
|
||||
normalized = str(item.get("cik") or "")
|
||||
if normalized and wanted(normalized):
|
||||
reasons.setdefault(normalized, "sec_filing_gap")
|
||||
for cik in summary.get("no_xbrl_ciks") or []:
|
||||
normalized = str(cik) if cik else ""
|
||||
if normalized and wanted(normalized):
|
||||
reasons[normalized] = "no_xbrl_filings"
|
||||
for item in summary.get("no_xbrl_filings") or []:
|
||||
if item.get("cik"):
|
||||
reasons[str(item["cik"])] = "no_xbrl_filings"
|
||||
normalized = str(item.get("cik") or "")
|
||||
if normalized and wanted(normalized):
|
||||
reasons[normalized] = "no_xbrl_filings"
|
||||
return reasons
|
||||
|
||||
|
||||
@@ -111,7 +128,7 @@ async def ticker_quality(db: AsyncSession, symbol: str) -> SetupQuality:
|
||||
).scalar_one_or_none()
|
||||
if ticker is None or not ticker.cik:
|
||||
return SetupQuality(eligible=True)
|
||||
reason = (await blocked_reasons_by_cik(db)).get(ticker.cik)
|
||||
reason = (await blocked_reasons_by_cik(db, {ticker.cik})).get(ticker.cik)
|
||||
if reason == "no_xbrl_filings":
|
||||
return SetupQuality(
|
||||
eligible=False,
|
||||
@@ -135,4 +152,9 @@ async def ticker_quality(db: AsyncSession, symbol: str) -> SetupQuality:
|
||||
|
||||
|
||||
async def ticker_is_eligible(db: AsyncSession, ticker_id: int) -> bool:
|
||||
return ticker_id not in await blocked_ticker_ids(db)
|
||||
cik = (
|
||||
await db.execute(select(Ticker.cik).where(Ticker.id == ticker_id))
|
||||
).scalar_one_or_none()
|
||||
if not cik:
|
||||
return True
|
||||
return cik not in await blocked_reasons_by_cik(db, {cik})
|
||||
|
||||
@@ -455,6 +455,11 @@ class SecFundamentalsImporter:
|
||||
"skipped_non_xbrl": len(staged.skipped_non_xbrl),
|
||||
"no_xbrl_filings": staged.no_xbrl_filings[:50],
|
||||
"no_xbrl_filings_count": len(staged.no_xbrl_filings),
|
||||
"no_xbrl_ciks": sorted({
|
||||
str(item["cik"])
|
||||
for item in staged.no_xbrl_filings
|
||||
if item.get("cik")
|
||||
}),
|
||||
"missing_xbrl": staged.missing_xbrl[:50],
|
||||
"missing_xbrl_count": len(staged.missing_xbrl),
|
||||
"missing_xbrl_blocking": len(blocking),
|
||||
|
||||
@@ -122,7 +122,8 @@ async def test_ticker_quality_explains_no_xbrl_block(db_session):
|
||||
status="promoted",
|
||||
validation_json=json.dumps({
|
||||
"setup_blocked_ciks": [ticker.cik],
|
||||
"no_xbrl_filings": [{"cik": ticker.cik}],
|
||||
"no_xbrl_ciks": [ticker.cik],
|
||||
"no_xbrl_filings": [],
|
||||
}),
|
||||
started_at=datetime.now(timezone.utc),
|
||||
),
|
||||
@@ -133,3 +134,6 @@ async def test_ticker_quality_explains_no_xbrl_block(db_session):
|
||||
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
|
||||
|
||||
@@ -680,6 +680,7 @@ async def test_validation_caps_details_but_keeps_complete_blocked_cik_set():
|
||||
|
||||
assert len(result.summary["missing_xbrl"]) == 50
|
||||
assert len(result.summary["no_xbrl_filings"]) == 50
|
||||
assert len(result.summary["no_xbrl_ciks"]) == 60
|
||||
assert len(result.summary["recovered_from_coregistrant"]) == 50
|
||||
assert len(result.summary["setup_blocked_ciks"]) == 120
|
||||
|
||||
|
||||
Reference in New Issue
Block a user