"""Warm the runtime caches that ship inside data/metrics.db. The deployed Space reads the committed database, cache tables included, so whatever is warm at commit time is what production can serve. Until now that set was incidental — it held whatever a developer happened to run locally, which meant a missing production credential or a blocked endpoint stayed hidden behind a cache entry that made the feature look healthy. This makes the set deliberate. Run it before a deploy, then commit data/, and the preflight will report exactly which tickers production can serve. Two of these fetchers exist precisely because the Space cannot make the call itself: * price reaction — Yahoo's price-history endpoint refuses datacenter addresses far more readily than its fundamentals endpoints * adjusted EPS — needs ALPHAVANTAGE_API_KEY_1, which lives in the local .env and must also be set as a Space secret for anything beyond the warmed snapshot to work Usage:: python scripts/warm_runtime_cache.py # every ingested ticker python scripts/warm_runtime_cache.py AAPL NVDA python scripts/warm_runtime_cache.py --refresh # ignore existing TTLs """ from __future__ import annotations import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from dotenv import load_dotenv load_dotenv() def _known_tickers() -> list[str]: import sqlite3 from storage.metrics_db import DB_PATH if not DB_PATH.exists(): return [] with sqlite3.connect(DB_PATH) as conn: return [row[0] for row in conn.execute( "SELECT DISTINCT ticker FROM metrics ORDER BY ticker" )] def _warm_one(ticker: str, refresh: bool) -> list[tuple[str, str]]: """Return [(feature, outcome)] for one ticker.""" results: list[tuple[str, str]] = [] # ── Trading multiples and peer comparison ── try: from analytics.valuation import fetch_multiples data, err = fetch_multiples(ticker) results.append(("valuation", "ok" if data else f"FAILED: {err}")) except Exception as exc: results.append(("valuation", f"FAILED: {exc}")) # ── 8-K filing stream. Both windows are warmed: the timeline reads 18 # months, the earnings-event provenance reads 36. ── for months in (18, 36): try: from analytics.filing_events import fetch_events events, err = fetch_events(ticker, months=months) results.append(( f"filing_events:{months}m", f"ok ({len(events)} filings)" if not err else f"FAILED: {err}", )) except Exception as exc: results.append((f"filing_events:{months}m", f"FAILED: {exc}")) # ── Balance-sheet ratios ── try: from analytics.working_capital import compute as compute_wc rows, err = compute_wc(ticker) results.append(( "working_capital", f"ok ({len(rows)} quarters)" if rows else f"FAILED: {err}", )) except Exception as exc: results.append(("working_capital", f"FAILED: {exc}")) # ── Adjusted EPS (Alpha Vantage) — also backs the surprise history ── quarters: list[dict] = [] try: from dashboard.financials import _load_surprises quarters, err = _load_surprises(ticker) results.append(( "earnings/surprises", f"ok ({len(quarters)} quarters)" if quarters else f"FAILED: {err}", )) except ValueError: results.append(( "earnings/surprises", "FAILED: ALPHAVANTAGE_API_KEY_1 not set locally", )) except Exception as exc: results.append(("earnings/surprises", f"FAILED: {exc}")) # ── GAAP vs adjusted gap (needs the earnings payload above) ── try: from analytics.eps_quality import compute as compute_eps rows, err = compute_eps(ticker) results.append(( "eps_quality", f"ok ({len(rows)} quarters)" if rows else f"FAILED: {err}", )) except Exception as exc: results.append(("eps_quality", f"FAILED: {exc}")) # ── Post-earnings price moves ── try: from analytics.price_reaction import compute as compute_reaction dates = [q["reported_date"] for q in quarters if q.get("reported_date")] if not dates: results.append(("price_reaction", "skipped: no earnings dates")) else: returns, err = compute_reaction(ticker, dates, refresh=refresh) results.append(( "price_reaction", f"ok ({len(returns)} quarters)" if returns else f"FAILED: {err or 'provider returned nothing'}", )) except Exception as exc: results.append(("price_reaction", f"FAILED: {exc}")) return results def main(argv: list[str]) -> int: refresh = "--refresh" in argv tickers = [a.upper() for a in argv if not a.startswith("--")] or _known_tickers() if not tickers: print("No ingested tickers found. Run ingest.py first.") return 1 failures = 0 for ticker in tickers: print(f"\n=== {ticker} ===") for feature, outcome in _warm_one(ticker, refresh): print(f" {feature:22s} {outcome}") if outcome.startswith("FAILED"): failures += 1 print( f"\nWarmed {len(tickers)} ticker(s)." + (f" {failures} feature(s) could not be warmed." if failures else "") ) print("Commit data/ so the Space serves this snapshot.") return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))