readthrough / scripts /warm_runtime_cache.py
Viney's picture
Claude Fable 5
fix: stop unverified EPS reaching the screen; make the shipped cache deliberate
694c11f
Raw History Blame Contribute Delete
5.72 kB
"""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:]))