"""Deterministic lateness-risk score. The LLM adds narrative on top of this, but the number itself is auditable: judges can trace every point.""" def clamp(value, low, high): return max(low, min(high, value)) def compute_risk( traffic_delay_pct=0.0, rain_mm=0.0, event_impact=0, festival_impact=0, advisory_count=0, ): """ traffic_delay_pct: traffic delay as % of free-flow travel time (0.20 = 20% slower) rain_mm: max hourly rain (mm) during the journey window event_impact: 0 none, 1 minor, 2 major event near route festival_impact: 0 none, 1 regional, 2 city-wide festival (e.g. Bada Mangal) advisory_count: number of active police diversions """ traffic_pts = clamp(traffic_delay_pct * 100 * 0.8, 0, 40) rain_pts = clamp(rain_mm * 3, 0, 20) event_pts = {0: 0, 1: 8, 2: 15}.get(event_impact, 15) festival_pts = {0: 0, 1: 10, 2: 20}.get(festival_impact, 20) advisory_pts = clamp(advisory_count * 5, 0, 10) score = round(clamp(traffic_pts + rain_pts + event_pts + festival_pts + advisory_pts, 0, 100)) if score < 30: level = "LOW" elif score < 60: level = "MEDIUM" else: level = "HIGH" return { "score": score, "level": level, "breakdown": { "traffic": round(traffic_pts, 1), "rain": round(rain_pts, 1), "events": event_pts, "festivals": festival_pts, "advisories": advisory_pts, }, }