#!/usr/bin/env python3
"""
patch_fred_cache.py
===================
Stops the dashboard hammering FRED (429 errors) by caching SOFR/EFFR for the
day. They are daily banking-day series, so 1 fetch/day is plenty. Was being
called every dashboard refresh (~240/min vs FRED's 120/min limit).

Adds a module-level _fred_daily_cache and wraps the two call sites:
  sofr_val, sofr_date = _fred_api_fetch("SOFR", sofr_path)
  ffr_val,  ffr_date  = _fred_api_fetch("EFFR", ffr_path)
so they reuse today's value if already fetched.

The existing stale-CSV fallback remains as a second safety net.
"""
import sys, shutil, ast
from pathlib import Path

candidates=[
    Path(r"C:\Users\Administrator\OneDrive\fx_macro_intraday\src\execution\dashboard_server.py"),
    Path(r"C:\Users\paul_\OneDrive\fx_macro_intraday\src\execution\dashboard_server.py"),
]
target=next((c for c in candidates if c.exists()), None)
if target is None: print("ERROR: dashboard_server.py not found"); sys.exit(1)
print(f"Target: {target}")
shutil.copy2(target, target.with_suffix(".py.backup_fredcache"))

src=target.read_text(encoding="utf-8")

if "_fred_daily_cache" in src:
    print("Already applied. Nothing to do."); sys.exit(0)

# ---- 1) Add module-level cache + a cached wrapper. Insert before fetch_yield_data ----
anchor_fn = "def fetch_yield_data():"
if anchor_fn not in src:
    print("ERROR: fetch_yield_data() not found."); sys.exit(2)

cache_block = '''# Daily cache for FRED SOFR/EFFR (daily banking-day series; 1 fetch/day is plenty).
# Prevents 429 rate-limit errors from the dashboard refreshing every 30s.
_fred_daily_cache = {"date": None, "SOFR": (None, None), "EFFR": (None, None)}
def _fred_cached(series_id, csv_path, fetch_fn):
    """Return today's cached (value, date) for a FRED series, fetching at most
    once per calendar day. fetch_fn is the actual fetcher (the nested
    _fred_api_fetch). On a failed fetch, leaves cache as-is and returns the
    fetch result (caller still has the stale-CSV fallback)."""
    import datetime as _dt
    today = _dt.datetime.now().date()
    if _fred_daily_cache["date"] != today:
        # New day - reset cache slots
        _fred_daily_cache["date"] = today
        _fred_daily_cache["SOFR"] = (None, None)
        _fred_daily_cache["EFFR"] = (None, None)
    cached = _fred_daily_cache.get(series_id, (None, None))
    if cached[0] is not None:
        return cached
    val, vdate = fetch_fn(series_id, csv_path)
    if val is not None:
        _fred_daily_cache[series_id] = (val, vdate)
    return (val, vdate)

'''
src = src.replace(anchor_fn, cache_block + anchor_fn, 1)

# ---- 2) Wrap the two call sites ----
old_sofr = 'sofr_val, sofr_date = _fred_api_fetch("SOFR", sofr_path)'
new_sofr = 'sofr_val, sofr_date = _fred_cached("SOFR", sofr_path, _fred_api_fetch)'
old_effr = 'ffr_val, ffr_date  = _fred_api_fetch("EFFR", ffr_path)'
new_effr = 'ffr_val, ffr_date  = _fred_cached("EFFR", ffr_path, _fred_api_fetch)'

ok_sofr = old_sofr in src
ok_effr = old_effr in src
if ok_sofr: src = src.replace(old_sofr, new_sofr, 1)
if ok_effr: src = src.replace(old_effr, new_effr, 1)

# EFFR call may have different spacing; try a tolerant variant if needed
if not ok_effr:
    import re
    m = re.search(r'ffr_val,\s*ffr_date\s*=\s*_fred_api_fetch\("EFFR",\s*ffr_path\)', src)
    if m:
        src = src[:m.start()] + new_effr + src[m.end():]
        ok_effr = True

try:
    ast.parse(src); print("Syntax: OK")
except SyntaxError as e:
    print(f"SYNTAX ERROR: {e} -- not writing"); sys.exit(4)

target.write_text(src, encoding="utf-8")
print(f"Cache block inserted: True")
print(f"SOFR call wrapped   : {ok_sofr}")
print(f"EFFR call wrapped   : {ok_effr}")
if ok_sofr and ok_effr:
    print("\\nSUCCESS - restart the dashboard server. 429s will stop (now 1 fetch/day).")
else:
    print("\\nPARTIAL - one call site not matched. Send the exact line; backup is safe.")
