fix(sec): name aged filings in deferred warnings
Deploy / lint (push) Successful in 10s
Deploy / test (push) Successful in 2m3s
Deploy / deploy (push) Successful in 42s

This commit is contained in:
2026-07-31 14:58:22 +02:00
parent c8c660e63d
commit 7bcdf77ef9
4 changed files with 96 additions and 17 deletions
+31 -16
View File
@@ -33,7 +33,7 @@ from dataclasses import dataclass, field
from datetime import date, datetime, timedelta, timezone
from typing import Any, Protocol, runtime_checkable
from sqlalchemy import select, text
from sqlalchemy import exists, select, text
from sqlalchemy.engine import Engine # noqa: F401 (typing only)
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
@@ -74,6 +74,7 @@ class ValidationResult:
# when ok=False.
retryable: bool = False
deferred_alert_after_days: int | None = None
deferred_alert_messages: list[str] = field(default_factory=list)
@runtime_checkable
@@ -129,17 +130,22 @@ async def _last_promoted_revision(db: AsyncSession, source: str) -> str | None:
return row.scalar_one_or_none()
async def _has_promoted_since(db: AsyncSession, source: str, cutoff: datetime) -> bool:
row = await db.execute(
select(DataImportRun.id)
.where(
DataImportRun.source == source,
DataImportRun.status == STATUS_PROMOTED,
DataImportRun.started_at >= cutoff,
)
.limit(1)
async def _promotion_state_since(
db: AsyncSession, source: str, cutoff: datetime
) -> str:
promoted = (
DataImportRun.source == source,
DataImportRun.status == STATUS_PROMOTED,
)
return row.scalar_one_or_none() is not None
ever, recent = (
await db.execute(
select(
exists().where(*promoted),
exists().where(*promoted, DataImportRun.started_at >= cutoff),
)
)
).one()
return "recent" if recent else "stale" if ever else "never"
def _now() -> datetime:
@@ -246,16 +252,25 @@ async def run_import(
if alert_days is not None:
alert_days = max(1, alert_days)
cutoff = run.started_at - timedelta(days=alert_days)
if not await _has_promoted_since(session, source, cutoff):
promotion_state = await _promotion_state_since(
session, source, cutoff
)
if promotion_state != "recent":
history = (
f"{source} import has never promoted successfully"
if promotion_state == "never"
else f"{source} import has not promoted successfully "
f"within {alert_days} day(s)"
)
await _alert(
session,
source,
"deferred_stale",
[
f"No successful promotion for at least "
f"{alert_days} day(s); deferred source lag may "
f"now hide aged-out unresolved items: "
f"{run.error_details or 'validation deferred'}"
f"{history}; import remains deferred",
*result.deferred_alert_messages,
f"Current deferral: "
f"{run.error_details or 'validation deferred'}",
],
severity="warning",
dedup_hours=alert_days * 24,
+11
View File
@@ -362,6 +362,7 @@ class SecFundamentalsImporter:
# the filings: "which ones" has to be in the alert itself, not merely
# reconstructible by re-walking the index.
blocking = _within_retry_window(staged.missing_xbrl)
aged_out = _past_retry_window(staged.missing_xbrl)
if blocking:
messages.append(
f"{len(blocking)} tracked XBRL filing(s) unresolved within the "
@@ -422,6 +423,16 @@ class SecFundamentalsImporter:
and all(m.get("reason") == "not_in_companyfacts" for m in blocking)
),
deferred_alert_after_days=MISSING_XBRL_RETRY_DAYS,
deferred_alert_messages=(
[
f"{len(aged_out)} tracked SEC filing(s) remain unresolved past "
f"the {MISSING_XBRL_RETRY_DAYS}-day retry window and risk being "
f"promoted around without automatic retry: "
f"{_missing_detail(aged_out)}"
]
if aged_out
else []
),
)
async def promote(self, db, staged: StagedFundamentals, run_id: int) -> dict[str, int]: