sammeeer's picture
genai inclusion
90bfc25
# pages/home.py β€” Landing dashboard.
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
import streamlit as st
import numpy as np
import plotly.graph_objects as go
from theme import (
inject_theme, page_header, section_label, kpi_html,
signal_card_html, PLOTLY_LAYOUT, SAFFRON, SAFFRON_SCALE, GREEN, RED, AMBER,
)
from utils.api_client import (
is_online, fetch_stats, fetch_predictions, fetch_optimizer_results,
)
from utils.gemini_utils import build_context, call_gemini, preset_prompt
inject_theme()
# ── Status pill ───────────────────────────────────────────────────────────────
online = is_online()
pill_color = "#16A34A" if online else "#DC2626"
pill_text = "API LIVE" if online else "API OFFLINE β€” run `uvicorn backend.main:app --port 8000`"
st.markdown(
f'<div style="display:flex;align-items:center;gap:8px;margin-bottom:1.4rem;">'
f'<span style="width:7px;height:7px;border-radius:50%;background:{pill_color};display:inline-block;"></span>'
f'<span style="font-family:DM Mono,monospace;font-size:0.62rem;letter-spacing:2px;'
f'text-transform:uppercase;color:{pill_color};">{pill_text}</span></div>',
unsafe_allow_html=True,
)
page_header(
"β—ˆ MNREGA Β· India Β· 2014–2024",
"SchemeImpactNet",
"Predictive impact analysis and budget optimisation for India's rural employment scheme",
)
# ── Data fetch ────────────────────────────────────────────────────────────────
stats = fetch_stats()
pred_df = fetch_predictions()
opt_df = fetch_optimizer_results()
# Derived KPIs
n_dist = stats.get("total_districts", "β€”")
n_states = stats.get("total_states", "β€”")
yr_range = stats.get("year_range", "β€”")
total_pd = stats.get("total_persondays_lakhs", 0)
covid_pct = stats.get("covid_spike_pct", 0)
nat_gain = gain_pct = 0.0
if not opt_df.empty and "persondays_gain" in opt_df.columns:
nat_gain = opt_df["persondays_gain"].sum()
sq_sum = opt_df["sq_persondays"].sum() if "sq_persondays" in opt_df.columns else 1
gain_pct = nat_gain / sq_sum * 100 if sq_sum else 0
# ── KPI strip ─────────────────────────────────────────────────────────────────
c1, c2, c3, c4, c5 = st.columns(5, gap="small")
cards = [
(str(n_dist), "Districts", SAFFRON, ""),
(str(n_states), "States / UTs", "#1C1917", ""),
(f"{total_pd:,.0f}L", "Person-Days", "#1C1917", "historical total"),
(f"{covid_pct:+.1f}%", "COVID-20 Spike", RED, "2020 peak"),
(f"{gain_pct:+.2f}%", "LP Opt. Gain", GREEN, "budget-neutral"),
]
for col, (val, label, color, note) in zip([c1, c2, c3, c4, c5], cards):
with col:
st.markdown(kpi_html(val, label, color, note), unsafe_allow_html=True)
st.markdown("<div style='margin-top:2rem'></div>", unsafe_allow_html=True)
# ── Two-column layout ─────────────────────────────────────────────────────────
left, right = st.columns([3, 2], gap="large")
# ── LEFT: state bubble map ────────────────────────────────────────────────────
STATE_COORDS = {
"Andhra Pradesh": (15.9, 79.7), "Arunachal Pradesh": (28.2, 94.7),
"Assam": (26.2, 92.9), "Bihar": (25.1, 85.3),
"Chhattisgarh": (21.3, 81.7), "Goa": (15.3, 74.0),
"Gujarat": (22.3, 71.2), "Haryana": (29.1, 76.1),
"Himachal Pradesh": (31.1, 77.2), "Jharkhand": (23.6, 85.3),
"Karnataka": (15.3, 75.7), "Kerala": (10.9, 76.3),
"Madhya Pradesh": (22.9, 78.7), "Maharashtra": (19.7, 75.7),
"Manipur": (24.7, 93.9), "Meghalaya": (25.5, 91.4),
"Mizoram": (23.2, 92.7), "Nagaland": (26.2, 94.6),
"Odisha": (20.9, 85.1), "Punjab": (31.1, 75.3),
"Rajasthan": (27.0, 74.2), "Sikkim": (27.5, 88.5),
"Tamil Nadu": (11.1, 78.7), "Telangana": (17.4, 79.1),
"Tripura": (23.9, 91.5), "Uttar Pradesh": (26.8, 80.9),
"Uttarakhand": (30.1, 79.3), "West Bengal": (22.9, 87.9),
"Jammu and Kashmir": (33.7, 76.9), "Ladakh": (34.2, 77.6),
"Delhi": (28.7, 77.1), "Puducherry": (11.9, 79.8),
}
with left:
section_label("State-Level Employment Β· Latest Year")
if not pred_df.empty and "financial_year" in pred_df.columns:
ly = pred_df["financial_year"].max()
agg = (
pred_df[pred_df["financial_year"] == ly]
.groupby("state", as_index=False)
.agg(
pd_sum =("person_days_lakhs", "sum"),
pred_sum =("predicted_persondays", "sum"),
n_dist =("district", "count"),
avg_err =("prediction_error", "mean"),
)
)
rng = np.random.default_rng(42)
lats, lons, szs = [], [], []
for _, r in agg.iterrows():
lat, lon = STATE_COORDS.get(r["state"], (22.0, 78.0))
lats.append(lat + rng.uniform(-0.12, 0.12))
lons.append(lon + rng.uniform(-0.12, 0.12))
szs.append(float(r["pd_sum"]))
mn, mx = min(szs), max(szs)
bsz = [float(np.clip((v - mn) / (mx - mn + 1e-9) * 14 + 5, 5, 19)) for v in szs]
fig = go.Figure()
fig.add_scattergeo(
lat=lats, lon=lons, mode="markers",
marker=dict(
size=bsz, color=szs,
colorscale=SAFFRON_SCALE,
colorbar=dict(
title=dict(text="Lakh PD", font=dict(color="#78716C", size=9)),
tickfont=dict(color="#78716C", size=8),
thickness=8, len=0.45,
bgcolor="rgba(255,255,255,0.85)",
),
opacity=0.88,
line=dict(width=1, color="#FFFFFF"),
),
text=agg["state"],
customdata=list(zip(
agg["pd_sum"].round(1),
agg["pred_sum"].round(1),
agg["n_dist"],
agg["avg_err"].round(2),
)),
hovertemplate=(
"<b>%{text}</b><br>"
"Actual PD: <b>%{customdata[0]}L</b><br>"
"Predicted: <b>%{customdata[1]}L</b><br>"
"Districts: %{customdata[2]}<br>"
"Avg Model Error: %{customdata[3]}L"
"<extra></extra>"
),
)
fig.update_geos(
scope="asia", showland=True, landcolor="#F5F5F4",
showocean=True, oceancolor="#EFF6FF",
showcountries=True, countrycolor="#D6D3D1",
showsubunits=True, subunitcolor="#E7E5E4",
center=dict(lat=22, lon=80), projection_scale=5.2,
bgcolor="rgba(0,0,0,0)",
)
fig.update_layout(
height=420, paper_bgcolor="rgba(0,0,0,0)",
margin=dict(l=0, r=0, t=0, b=0),
font=dict(family="DM Mono, monospace", color="#1C1917"),
showlegend=False,
)
st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False})
st.caption(f"FY {ly} · bubble size ∝ employment volume · hover for model predictions")
else:
st.info("Start the backend to load state-level data.")
# ── RIGHT: Intelligence Brief + signals ───────────────────────────────────────
with right:
# Derive signal numbers
n_declining = n_underfunded = 0
top_state = "β€”"
ly_label = pred_df["financial_year"].max() if not pred_df.empty else "β€”"
if not pred_df.empty:
ly = pred_df["financial_year"].max()
lat = pred_df[pred_df["financial_year"] == ly]
prv = pred_df[pred_df["financial_year"] == ly - 1]
if not prv.empty:
mg = lat.merge(
prv[["state", "district", "person_days_lakhs"]].rename(
columns={"person_days_lakhs": "prev"}
),
on=["state", "district"], how="left",
)
n_declining = int((mg["predicted_persondays"] < mg["prev"]).sum())
if not opt_df.empty and "budget_allocated_lakhs" in opt_df.columns:
th = opt_df["budget_allocated_lakhs"].quantile(0.33)
n_underfunded = int((opt_df["budget_allocated_lakhs"] < th).sum())
if not opt_df.empty and "persondays_gain" in opt_df.columns:
top_state = opt_df.groupby("state")["persondays_gain"].sum().idxmax()
gain_str = f"{nat_gain:+,.1f}L" if nat_gain else "β€”"
# ── Intelligence Brief: AI or manual fallback ─────────────────────────────
section_label("Intelligence Brief")
api_key = st.session_state.get("gemini_api_key", "")
if api_key:
cache_key = "home_brief"
if cache_key not in st.session_state:
with st.spinner("Generating AI brief..."):
ctx = build_context(None)
prompt = preset_prompt(ctx, "summary")
st.session_state[cache_key] = call_gemini(api_key, prompt, temperature=0.3)
st.markdown(f"""
<div style="background:#FFF7ED; border:1px solid #FED7AA; border-left:3px solid #FB923C;
border-radius:8px; padding:1.2rem 1.4rem; margin-bottom:1rem;">
<p style="font-family:'DM Mono',monospace; font-size:0.56rem; letter-spacing:2.5px;
text-transform:uppercase; color:#FB923C; margin:0 0 9px 0;">
β—ˆ AI Generated Β· Gemini 2.5 Flash Lite Β· FY {ly_label}</p>
<p style="font-family:'Source Serif 4',serif; font-size:0.88rem; color:#431407;
line-height:1.75; margin:0;">{st.session_state[cache_key]}</p>
</div>
""", unsafe_allow_html=True)
else:
# Manual fallback when no Gemini key
st.markdown(f"""
<div style="background:#FFF7ED; border:1px solid #FED7AA; border-left:3px solid #FB923C;
border-radius:8px; padding:1.2rem 1.4rem; margin-bottom:1rem;">
<p style="font-family:'DM Mono',monospace; font-size:0.56rem; letter-spacing:2.5px;
text-transform:uppercase; color:#FB923C; margin:0 0 9px 0;">
β—ˆ Auto-generated Β· Pipeline FY {ly_label}</p>
<p style="font-family:'Source Serif 4',serif; font-size:0.88rem; color:#431407;
line-height:1.75; margin:0;">
Budget-neutral LP reallocation yields a projected
<strong>{gain_str}</strong> of additional employment β€”
a <strong>{gain_pct:+.2f}%</strong> uplift at zero additional outlay.
<strong>{n_declining} districts</strong> face declining employment trajectories.
Highest reallocation opportunity: <strong>{top_state}</strong>.
<strong>{n_underfunded} districts</strong> in the bottom budget tercile show
above-average delivery efficiency.
</p>
</div>
""", unsafe_allow_html=True)
# ── Live Signals ──────────────────────────────────────────────────────────
section_label("Live Signals")
signals = [
(str(n_declining), "High-Risk Districts", "Predicted employment decline", RED),
(str(n_underfunded), "Underfunded Β· High Eff.", "Bottom-tercile budget", AMBER),
(gain_str, "LP Reallocation Gain", f"Budget-neutral Β· {gain_pct:+.2f}%", GREEN),
(str(n_dist), "Districts in Model", "GBR Β· Walk-fwd CV RΒ²β‰ˆ0.91", SAFFRON),
]
for val, title, body, accent in signals:
st.markdown(signal_card_html(val, title, body, accent), unsafe_allow_html=True)