Spaces:
Sleeping
Sleeping
| """routers/optimizer.py — Budget optimizer endpoints.""" | |
| from fastapi import APIRouter, Depends, Query | |
| from sqlalchemy.orm import Session | |
| from typing import Optional | |
| from backend.database import get_db | |
| from backend import crud | |
| from backend.schemas import OptimizerRequest, OptimizerResponse | |
| router = APIRouter(prefix="/optimizer", tags=["Optimizer"]) | |
| def get_optimizer_results( | |
| state: Optional[str] = Query(None), | |
| db: Session = Depends(get_db) | |
| ): | |
| return crud.get_optimizer_results(db, state) | |
| def run_optimizer_live(req: OptimizerRequest, db: Session = Depends(get_db)): | |
| """ | |
| Run LP optimizer live with custom parameters. | |
| Reads predictions from DB, runs scipy LP, returns results. | |
| """ | |
| import numpy as np | |
| from scipy.optimize import linprog | |
| from sqlalchemy import text | |
| # Get latest year predictions + budget | |
| state_clause = "AND p.state=:s" if req.state else "" | |
| params = {"s": req.state} if req.state else {} | |
| rows = db.execute(text(f""" | |
| SELECT p.state, p.district, | |
| p.predicted_persondays, | |
| o.budget_allocated_lakhs, | |
| o.persondays_per_lakh | |
| FROM predictions p | |
| JOIN optimizer o ON p.district = o.district AND p.state = o.state | |
| WHERE p.financial_year = (SELECT MAX(financial_year) FROM predictions) | |
| {state_clause} | |
| """), params).fetchall() | |
| if not rows: | |
| return OptimizerResponse( | |
| scope=req.state or "All-India", | |
| total_budget_lakhs=0, sq_persondays_total=0, | |
| opt_persondays_total=0, gain_lakhs=0, gain_pct=0, districts=[] | |
| ) | |
| import pandas as pd | |
| df = pd.DataFrame([dict(r._mapping) for r in rows]).dropna() | |
| budgets = df["budget_allocated_lakhs"].values * req.budget_scale | |
| efficiency = df["persondays_per_lakh"].values | |
| total_bud = budgets.sum() | |
| lb = budgets * req.min_fraction | |
| ub = budgets * req.max_fraction | |
| res = linprog(-efficiency, A_ub=[np.ones(len(df))], | |
| b_ub=[total_bud], bounds=list(zip(lb, ub)), method="highs") | |
| opt_budgets = res.x if res.success else budgets | |
| sq_total = float((efficiency * budgets).sum()) | |
| opt_total = float((efficiency * opt_budgets).sum()) | |
| districts_out = [] | |
| for i, row in df.iterrows(): | |
| orig = budgets[df.index.get_loc(i)] | |
| opt = opt_budgets[df.index.get_loc(i)] | |
| sq_pd = float(efficiency[df.index.get_loc(i)] * orig) | |
| opt_pd = float(efficiency[df.index.get_loc(i)] * opt) | |
| districts_out.append({ | |
| "state": row["state"], | |
| "district": row["district"], | |
| "budget_allocated_lakhs": round(orig, 2), | |
| "optimized_budget": round(opt, 2), | |
| "budget_change": round(opt - orig, 2), | |
| "budget_change_pct": round((opt - orig) / orig * 100, 2), | |
| "sq_persondays": round(sq_pd, 3), | |
| "opt_persondays": round(opt_pd, 3), | |
| "persondays_gain": round(opt_pd - sq_pd, 3), | |
| "persondays_gain_pct": round((opt_pd - sq_pd) / sq_pd * 100, 2) if sq_pd else 0, | |
| "persondays_per_lakh": round(float(efficiency[df.index.get_loc(i)]), 4), | |
| }) | |
| gain = opt_total - sq_total | |
| return OptimizerResponse( | |
| scope=req.state or "All-India", | |
| total_budget_lakhs=round(total_bud, 2), | |
| sq_persondays_total=round(sq_total, 2), | |
| opt_persondays_total=round(opt_total, 2), | |
| gain_lakhs=round(gain, 2), | |
| gain_pct=round(gain / sq_total * 100, 2) if sq_total else 0, | |
| districts=districts_out | |
| ) | |