fix: harden Structural S/R after OHLCV writes and surface cleanup failures
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m18s
Deploy / deploy (push) Successful in 40s

Honor custom S/R tolerance as a transient detect, refresh levels after OHLCV
mutations without failing committed price writes, report per-ticker S/R
rebuild failures from admin cleanup, and warn in the admin UI when refresh is partial.
This commit is contained in:
2026-07-18 13:44:34 +02:00
parent b0e33e1606
commit cad4b49e7c
11 changed files with 181 additions and 23 deletions
+18 -1
View File
@@ -23,6 +23,15 @@ from app.services import price_service
logger = logging.getLogger(__name__)
async def _refresh_structural_sr(db: AsyncSession, symbol: str) -> None:
"""Rebuild Structural S/R after batch OHLCV writes (best-effort).
Price bars are already committed; an S/R failure must not discard the
ingestion result. Shared with single-bar upsert via price_service.
"""
await price_service._refresh_structural_sr_best_effort(db, symbol)
@dataclass
class IngestionResult:
"""Result of an ingestion run."""
@@ -213,6 +222,8 @@ async def fetch_and_ingest(
low=record.low,
close=record.close,
volume=record.volume,
# One S/R rebuild at the end of the batch, not per bar.
refresh_sr=False,
)
ingested_count += 1
last_ingested = record.date
@@ -221,12 +232,15 @@ async def fetch_and_ingest(
await _update_progress(db, ticker.id, record.date)
except RateLimitError:
# Mid-ingestion rate limit — return partial progress
# Mid-ingestion rate limit — return partial progress after
# refreshing S/R from whatever bars we already wrote.
logger.warning(
"Rate limited during ingestion for %s after %d records",
ticker.symbol,
ingested_count,
)
if ingested_count > 0:
await _refresh_structural_sr(db, ticker.symbol)
return IngestionResult(
symbol=ticker.symbol,
records_ingested=ingested_count,
@@ -235,6 +249,9 @@ async def fetch_and_ingest(
message=f"Rate limited. Ingested {ingested_count} records. Resume available.",
)
if ingested_count > 0:
await _refresh_structural_sr(db, ticker.symbol)
return IngestionResult(
symbol=ticker.symbol,
records_ingested=ingested_count,