fix(tickers): let an SEC confirmation upgrade a manual delisting mark

mark_delisted returned early on any already-delisted row, so the sequence an
operator actually hits — mark EA by hand today, Form 25-NSE surfaces three days
later dated 2026-08-04 — left the estimated date and "manual" reason in place
permanently. Form 25 carries the real effective date, so it now replaces an
operator's estimate; a confirmed row is never downgraded or re-probed.

Also cover _get_ohlcv_priority_tickers, the one place active_only wraps a
compound select rather than a bare one — the unit suite reached none of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 16:58:51 +02:00
co-authored by Claude Opus 5
parent 6501b7e9a0
commit d950fcf70e
2 changed files with 66 additions and 5 deletions
+15 -5
View File
@@ -106,9 +106,11 @@ async def mark_delisted(
) -> bool:
"""Record that a symbol stopped trading. True if this changed anything.
Idempotent: re-marking an already-delisted symbol is a no-op, so the
staleness path can call it on every run without churning the row or
re-emitting events.
Idempotent, so the staleness path can call it every run without churning the
row: re-marking is a no-op. The one exception is an SEC confirmation landing
on a row an operator marked by hand — Form 25 carries the real effective
date, so it replaces the operator's estimate. Nothing downgrades a confirmed
row back to a manual one.
"""
normalised = symbol.strip().upper()
result = await db.execute(select(Ticker).where(Ticker.symbol == normalised))
@@ -116,7 +118,11 @@ async def mark_delisted(
if ticker is None:
raise NotFoundError(f"Ticker not found: {normalised}")
if ticker.delisted_on is not None:
return False
upgrading = (
reason == REASON_FORM_25 and ticker.delisted_reason != REASON_FORM_25
)
if not upgrading:
return False
await db.execute(
update(Ticker)
@@ -158,7 +164,11 @@ async def confirm_delisting(
normalised = symbol.strip().upper()
result = await db.execute(select(Ticker).where(Ticker.symbol == normalised))
ticker = result.scalar_one_or_none()
if ticker is None or ticker.delisted_on is not None or not ticker.cik:
if ticker is None or not ticker.cik:
return None
# Already confirmed by SEC — nothing left to learn. A row an operator marked
# by hand is still worth probing: Form 25 upgrades the estimated date.
if ticker.delisted_reason == REASON_FORM_25:
return None
# No bars at all is an ingestion problem, not evidence of a delisting.
if last_bar is None: