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 {}
|
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."""
|
"""Current SEC blocker code by CIK; no historical audit scan."""
|
||||||
if not await fundamental_data_refresh_service.is_enabled(db):
|
if not await fundamental_data_refresh_service.is_enabled(db):
|
||||||
return {}
|
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)
|
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
|
# New summaries carry the complete compact CIK set while the detailed lists
|
||||||
# stay capped for audit readability. Detailed entries supply the reason.
|
# stay capped for audit readability. Detailed entries supply the reason.
|
||||||
for cik in summary.get("setup_blocked_ciks") or []:
|
for cik in summary.get("setup_blocked_ciks") or []:
|
||||||
if cik:
|
normalized = str(cik) if cik else ""
|
||||||
reasons.setdefault(str(cik), "sec_filing_gap")
|
if normalized and wanted(normalized):
|
||||||
|
reasons.setdefault(normalized, "sec_filing_gap")
|
||||||
for item in summary.get("missing_xbrl") or []:
|
for item in summary.get("missing_xbrl") or []:
|
||||||
if item.get("cik"):
|
normalized = str(item.get("cik") or "")
|
||||||
reasons.setdefault(str(item["cik"]), "sec_filing_gap")
|
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 []:
|
for item in summary.get("no_xbrl_filings") or []:
|
||||||
if item.get("cik"):
|
normalized = str(item.get("cik") or "")
|
||||||
reasons[str(item["cik"])] = "no_xbrl_filings"
|
if normalized and wanted(normalized):
|
||||||
|
reasons[normalized] = "no_xbrl_filings"
|
||||||
return reasons
|
return reasons
|
||||||
|
|
||||||
|
|
||||||
@@ -111,7 +128,7 @@ async def ticker_quality(db: AsyncSession, symbol: str) -> SetupQuality:
|
|||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
if ticker is None or not ticker.cik:
|
if ticker is None or not ticker.cik:
|
||||||
return SetupQuality(eligible=True)
|
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":
|
if reason == "no_xbrl_filings":
|
||||||
return SetupQuality(
|
return SetupQuality(
|
||||||
eligible=False,
|
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:
|
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),
|
"skipped_non_xbrl": len(staged.skipped_non_xbrl),
|
||||||
"no_xbrl_filings": staged.no_xbrl_filings[:50],
|
"no_xbrl_filings": staged.no_xbrl_filings[:50],
|
||||||
"no_xbrl_filings_count": len(staged.no_xbrl_filings),
|
"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": staged.missing_xbrl[:50],
|
||||||
"missing_xbrl_count": len(staged.missing_xbrl),
|
"missing_xbrl_count": len(staged.missing_xbrl),
|
||||||
"missing_xbrl_blocking": len(blocking),
|
"missing_xbrl_blocking": len(blocking),
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ async def test_ticker_quality_explains_no_xbrl_block(db_session):
|
|||||||
status="promoted",
|
status="promoted",
|
||||||
validation_json=json.dumps({
|
validation_json=json.dumps({
|
||||||
"setup_blocked_ciks": [ticker.cik],
|
"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),
|
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.eligible is False
|
||||||
assert quality.code == "no_xbrl_filings"
|
assert quality.code == "no_xbrl_filings"
|
||||||
assert "CIK override" in (quality.message or "")
|
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["missing_xbrl"]) == 50
|
||||||
assert len(result.summary["no_xbrl_filings"]) == 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["recovered_from_coregistrant"]) == 50
|
||||||
assert len(result.summary["setup_blocked_ciks"]) == 120
|
assert len(result.summary["setup_blocked_ciks"]) == 120
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user