sammeeer's picture
Inital schemeimpactnet deployment
f87e795
"""
utils/api_client.py
--------------------
Centralized, cached API wrappers.
HF Spaces compatible: reads API_URL from environment variable so the
same code works locally (localhost:8000) and on HuggingFace (localhost:8000
since both services run in the same container).
"""
import os
import requests
import pandas as pd
import streamlit as st
# HF Spaces: backend always on localhost:8000 inside the container
API = os.environ.get("API_URL", "http://localhost:8000")
TIMEOUT = 15
@st.cache_data(ttl=300)
def _get(endpoint: str, params: dict | None = None):
"""Raw cached GET β€” returns JSON or None on any error."""
try:
r = requests.get(f"{API}{endpoint}", params=params or {}, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
except requests.exceptions.ConnectionError:
return None
except requests.exceptions.Timeout:
return None
except Exception:
return None
def _df(data) -> pd.DataFrame:
if not data:
return pd.DataFrame()
if isinstance(data, list):
return pd.DataFrame(data)
if isinstance(data, dict):
return pd.DataFrame([data])
return pd.DataFrame()
# ── Health ─────────────────────────────────────────────────────────────────────
def is_online() -> bool:
try:
requests.get(f"{API}/health", timeout=5)
return True
except Exception:
return False
# ── /districts/* ───────────────────────────────────────────────────────────────
def fetch_stats() -> dict:
return _get("/districts/stats") or {}
def fetch_states() -> list[str]:
return _get("/districts/states") or []
def fetch_districts(state: str) -> list[str]:
return _get("/districts/list", {"state": state}) or []
def fetch_district_history(state: str, district: str) -> pd.DataFrame:
return _df(_get("/districts/history", {"state": state, "district": district}))
def fetch_top_districts(
state: str | None = None,
metric: str = "person_days_lakhs",
n: int = 12,
) -> pd.DataFrame:
params = {"metric": metric, "n": n}
if state:
params["state"] = state
return _df(_get("/districts/top", params))
def fetch_yearly_trend(state: str | None = None) -> pd.DataFrame:
params = {"state": state} if state else {}
return _df(_get("/districts/trend", params))
# ── /predictions/* ─────────────────────────────────────────────────────────────
def fetch_predictions(
state: str | None = None,
district: str | None = None,
year: int | None = None,
) -> pd.DataFrame:
params = {}
if state: params["state"] = state
if district: params["district"] = district
if year: params["year"] = year
return _df(_get("/predictions/", params))
# ── /optimizer/* ───────────────────────────────────────────────────────────────
def fetch_optimizer_results(state: str | None = None) -> pd.DataFrame:
params = {"state": state} if state else {}
return _df(_get("/optimizer/results", params))
def run_optimizer_live(
state: str | None = None,
budget_scale: float = 1.0,
min_fraction: float = 0.40,
max_fraction: float = 2.50,
) -> dict | None:
payload = {
"state": state,
"budget_scale": budget_scale,
"min_fraction": min_fraction,
"max_fraction": max_fraction,
}
try:
r = requests.post(f"{API}/optimizer/run", json=payload, timeout=60)
r.raise_for_status()
return r.json()
except requests.exceptions.ConnectionError:
st.error("Cannot reach API β€” backend may still be starting up, refresh in a moment.")
return None
except Exception as e:
st.error(f"Optimizer error: {e}")
return None