File size: 5,719 Bytes
694c11f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""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:]))