Spaces:
Sleeping
Sleeping
| """Operational layer for DRISHTI. | |
| Backs the six command-and-control features: | |
| 1. cross-officer dispatch board -> file-backed shared store (across sessions) | |
| 2. tow / crane fleet + nearest-unit + time-to-clear SLA | |
| 3. what-if intervention projection (transparent, tied to the CII weights) | |
| 4. patrol-beat & shift planning from the forecast (grouped by police_station) | |
| 5. repeat-offender escalation tiers | |
| 6. event-mode surge projection around a chosen zone (H3 k-ring) | |
| Everything is computed from the provided dataset. The tow fleet and dispatch | |
| records are an operational SIMULATION layer (not external data). | |
| """ | |
| import json | |
| import math | |
| import os | |
| import tempfile | |
| import time | |
| # ---------------------------------------------------------------------- | |
| # 1 + 4. shared dispatch board (cross-session, cross-officer) | |
| # /tmp is writable on HF Spaces and shared across all sessions of the container. | |
| # ---------------------------------------------------------------------- | |
| DISPATCH_PATH = os.path.join(tempfile.gettempdir(), "drishti_dispatch.json") | |
| def _read_json(path, default): | |
| try: | |
| with open(path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except Exception: | |
| return default | |
| def _write_json(path, data): | |
| tmp = path + ".tmp" | |
| with open(tmp, "w", encoding="utf-8") as f: | |
| json.dump(data, f) | |
| os.replace(tmp, path) # atomic on the same filesystem | |
| def read_dispatches(): | |
| data = _read_json(DISPATCH_PATH, []) | |
| return data if isinstance(data, list) else [] | |
| def add_dispatch(rec): | |
| data = read_dispatches() | |
| rec["id"] = (max([r.get("id", 0) for r in data]) + 1) if data else 1 | |
| data.insert(0, rec) | |
| _write_json(DISPATCH_PATH, data[:200]) | |
| return rec["id"] | |
| def resolve_dispatch(did): | |
| data = read_dispatches() | |
| now = time.time() | |
| for r in data: | |
| if r.get("id") == did and r.get("status") != "Resolved": | |
| r["status"] = "Resolved" | |
| r["resolved_ts"] = now | |
| r["clear_min"] = round((now - r.get("ts", now)) / 60.0, 1) | |
| _write_json(DISPATCH_PATH, data) | |
| def clear_dispatches(): | |
| _write_json(DISPATCH_PATH, []) | |
| # ---------------------------------------------------------------------- | |
| # 2. tow / crane fleet (simulated) + nearest-unit assignment + distance | |
| # ---------------------------------------------------------------------- | |
| def make_fleet(hotspots): | |
| """Anchor a small simulated fleet at spread-out points across the data bbox.""" | |
| lat0, lat1 = float(hotspots["lat"].min()), float(hotspots["lat"].max()) | |
| lon0, lon1 = float(hotspots["lon"].min()), float(hotspots["lon"].max()) | |
| names = ["Tow-North", "Tow-South", "Tow-East", "Tow-West", "Crane-Central", "Tow-SE"] | |
| frac = [(0.78, 0.50), (0.22, 0.50), (0.50, 0.82), (0.50, 0.18), (0.50, 0.50), (0.32, 0.72)] | |
| fleet = [] | |
| for nm, (fy, fx) in zip(names, frac): | |
| fleet.append({"unit": nm, | |
| "lat": lat0 + fy * (lat1 - lat0), | |
| "lon": lon0 + fx * (lon1 - lon0)}) | |
| return fleet | |
| def haversine(la1, lo1, la2, lo2): | |
| R = 6371.0 | |
| p1, p2 = math.radians(la1), math.radians(la2) | |
| dp = math.radians(la2 - la1) | |
| dl = math.radians(lo2 - lo1) | |
| a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 | |
| return 2 * R * math.asin(math.sqrt(a)) | |
| def nearest_unit(lat, lon, fleet): | |
| best, bd = None, 1e9 | |
| for u in fleet: | |
| d = haversine(lat, lon, u["lat"], u["lon"]) | |
| if d < bd: | |
| best, bd = u, d | |
| return best, round(bd, 2) | |
| # ---------------------------------------------------------------------- | |
| # 3. what-if intervention projection | |
| # CII is a weighted sum of (volume, persistence, peak) components, so the | |
| # headline CII drop is just the weighted blend of each lever's effect. | |
| # ---------------------------------------------------------------------- | |
| INTERVENTIONS = { | |
| "No-parking signage": {"volume": 0.25, "persistence": 0.05, "peak": 0.10}, | |
| "Bollards / barricade": {"volume": 0.45, "persistence": 0.25, "peak": 0.15}, | |
| "Towing drive": {"volume": 0.15, "persistence": 0.05, "peak": 0.35}, | |
| "Dedicated parking bay": {"volume": 0.40, "persistence": 0.30, "peak": 0.20}, | |
| "Static enforcement post": {"volume": 0.30, "persistence": 0.35, "peak": 0.30}, | |
| } | |
| def combine_interventions(selected): | |
| """Combine chosen levers multiplicatively per component (diminishing returns).""" | |
| comp = {"volume": 0.0, "persistence": 0.0, "peak": 0.0} | |
| for name in selected: | |
| eff = INTERVENTIONS.get(name, {}) | |
| for k in comp: | |
| comp[k] = 1 - (1 - comp[k]) * (1 - eff.get(k, 0.0)) | |
| return comp | |
| def whatif_new_cii(cii, reductions, weights): | |
| """reductions: component -> fraction (0-1). weights: meta cii_weights dict.""" | |
| wv = weights.get("weighted_volume", weights.get("volume", 0.45)) | |
| wp = weights.get("persistence", 0.30) | |
| wk = weights.get("peak_share", weights.get("peak", weights.get("peak_concentration", 0.25))) | |
| s = (wv + wp + wk) or 1.0 | |
| wv, wp, wk = wv / s, wp / s, wk / s | |
| eff = (wv * reductions.get("volume", 0) | |
| + wp * reductions.get("persistence", 0) | |
| + wk * reductions.get("peak", 0)) | |
| eff = max(0.0, min(0.95, eff)) | |
| return round(float(cii) * (1 - eff), 1), round(eff * 100, 1) | |
| def new_rank(new_cii, all_cii_desc): | |
| """Rank a projected CII against the existing distribution (1 = worst).""" | |
| above = sum(1 for c in all_cii_desc if c > new_cii) | |
| return above + 1 | |
| # ---------------------------------------------------------------------- | |
| # 5. repeat-offender escalation tiers (calibrated to the data's 11-55 range) | |
| # ---------------------------------------------------------------------- | |
| def escalation_tier(n): | |
| """Return (tier_label, icon, recommended_action).""" | |
| if n >= 40: | |
| return ("Chronic β license/RC review", "π΄", "RTO referral + court summons") | |
| if n >= 25: | |
| return ("Habitual β court summons", "π ", "Summons + cumulative penalty") | |
| if n >= 16: | |
| return ("Repeat β escalated fine", "π‘", "Escalated fine + formal notice") | |
| return ("Watchlist β formal notice", "π΅", "Formal notice via e-challan") | |
| # ---------------------------------------------------------------------- | |
| # 6. patrol-beat & shift planning (beats = police_station jurisdictions) | |
| # ---------------------------------------------------------------------- | |
| def patrol_plan(forecast_df, hotspots_df, top_zones=40): | |
| cols = ["h3", "police_station", "peak_share", "location", "cii", "junction_name"] | |
| h = hotspots_df[cols].copy() | |
| plan = forecast_df.merge(h, on="h3", how="left", suffixes=("", "_h")) | |
| plan = plan.dropna(subset=["police_station"]) | |
| plan = plan.sort_values("pred_intensity", ascending=False).head(top_zones).copy() | |
| def shift(ps): | |
| try: | |
| ps = float(ps) | |
| except Exception: | |
| return "All-day rotating" | |
| if math.isnan(ps): | |
| return "All-day rotating" | |
| return "Peak hours (08-11, 17-21)" if ps >= 0.45 else "All-day rotating" | |
| plan["shift"] = plan["peak_share"].apply(shift) | |
| pmax = plan["pred_intensity"].max() or 1.0 | |
| plan["units"] = (plan["pred_intensity"] / pmax * 2 + 1).round().astype(int) | |
| # prefer the merged location/junction if present | |
| if "location_h" in plan.columns: | |
| plan["location"] = plan["location"].fillna(plan["location_h"]) | |
| return plan | |
| # ---------------------------------------------------------------------- | |
| # 7. event-mode surge (H3 k-ring around a chosen zone) | |
| # ---------------------------------------------------------------------- | |
| def event_surge(hotspots_df, center_h3, k, multiplier): | |
| try: | |
| import h3 | |
| try: | |
| ring = set(h3.grid_disk(center_h3, k)) # h3 v4 | |
| except Exception: | |
| ring = set(h3.k_ring(center_h3, k)) # h3 v3 | |
| except Exception: | |
| ring = {center_h3} | |
| sub = hotspots_df[hotspots_df["h3"].isin(ring)].copy() | |
| sub["surge_cii"] = (sub["cii"] * float(multiplier)).clip(upper=100).round(1) | |
| sub["delta"] = (sub["surge_cii"] - sub["cii"]).round(1) | |
| return sub.sort_values("surge_cii", ascending=False) | |