feat: bank forceasts

This commit is contained in:
OpenSquared
2026-06-30 21:47:20 +02:00
parent bb614936c3
commit 292d2c6413
4 changed files with 655 additions and 3 deletions

View File

@@ -235,3 +235,127 @@ def score_text(body: ScoreTextRequest):
except Exception as e:
logger.error(f"Text scoring error: {e}")
raise HTTPException(500, str(e))
# ── Bank Forecasts ─────────────────────────────────────────────────────────────
class BankSourceUpsert(BaseModel):
name: str
url: str = ""
active: bool = True
notes: str = ""
@router.get("/bank-forecasts/sources")
def list_bank_sources():
from services.database import get_conn
conn = get_conn()
try:
rows = conn.execute(
"SELECT id, name, url, active, last_scraped, notes FROM bank_forecast_sources ORDER BY name"
).fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
@router.put("/bank-forecasts/sources/{source_id}")
def upsert_bank_source(source_id: str, body: BankSourceUpsert):
from services.database import get_conn
conn = get_conn()
try:
conn.execute(
"""INSERT INTO bank_forecast_sources (id, name, url, active, notes)
VALUES (?,?,?,?,?)
ON CONFLICT(id) DO UPDATE SET
name=excluded.name, url=excluded.url,
active=excluded.active, notes=excluded.notes""",
(source_id, body.name, body.url, int(body.active), body.notes)
)
conn.commit()
return {"saved": source_id}
finally:
conn.close()
@router.get("/bank-forecasts")
def list_bank_forecasts(series_id: str = "", event_date: str = ""):
from services.database import get_conn
conn = get_conn()
try:
wheres = ["1=1"]
params = []
if series_id:
wheres.append("bf.series_id = ?"); params.append(series_id)
if event_date:
wheres.append("bf.event_date = ?"); params.append(event_date)
rows = conn.execute(
f"""SELECT bf.*, bs.name as bank_name
FROM bank_forecasts bf
JOIN bank_forecast_sources bs ON bs.id = bf.source_id
WHERE {' AND '.join(wheres)}
ORDER BY bf.event_date DESC, bf.extracted_at DESC
LIMIT 500""",
params
).fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
@router.get("/bank-forecasts/consensus")
def get_consensus():
"""Return one consensus row per (series_id, event_date) = average of bank forecasts."""
from services.database import get_conn
conn = get_conn()
try:
rows = conn.execute(
"""SELECT series_id, event_name, event_date,
ROUND(AVG(forecast_value), 4) as consensus,
COUNT(*) as bank_count,
MIN(forecast_value) as min_forecast,
MAX(forecast_value) as max_forecast
FROM bank_forecasts
WHERE forecast_value IS NOT NULL
GROUP BY series_id, event_date
ORDER BY event_date DESC"""
).fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
@router.post("/bank-forecasts/scrape")
def trigger_scrape(source_id: str = ""):
"""Scrape one source (source_id) or all active sources (empty)."""
from services.database import get_conn
from services.bank_forecast_scraper import scrape_source, scrape_all_active
conn = get_conn()
try:
if source_id:
s = conn.execute("SELECT * FROM bank_forecast_sources WHERE id=?", (source_id,)).fetchone()
if not s:
raise HTTPException(404, "Source not found")
result = [scrape_source(conn, dict(s))]
else:
result = scrape_all_active(conn)
return {"results": result, "total_sources": len(result)}
finally:
conn.close()
@router.post("/bank-forecasts/push-consensus")
def push_consensus_endpoint(series_id: str = "", event_date: str = ""):
"""Push bank consensus to macro_series_log. Filters optional."""
from services.database import get_conn
from services.bank_forecast_scraper import push_consensus_to_log, push_all_consensus
conn = get_conn()
try:
if series_id and event_date:
val = push_consensus_to_log(conn, series_id, event_date)
return {"pushed": [{"series_id": series_id, "event_date": event_date, "consensus": val}]}
else:
results = push_all_consensus(conn)
return {"pushed": results}
finally:
conn.close()