ayush200399391001's picture
Deploying Full Simhastha Crowd Intelligence System
6887bb9
Raw
History Blame Contribute Delete
6.52 kB
"""
Route optimizer and congestion alert engine.
Uses Dijkstra's algorithm on the live dynamic graph to find the
lowest-weight (safest / fastest) path between two nodes.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import networkx as nx
from config.settings import LOCATION_NODES
# ── Dataclasses ──────────────────────────────────────────────────────────
@dataclass
class RouteResult:
"""A computed safe route between two nodes."""
source: int
destination: int
path: list[int]
path_labels: list[str]
total_weight: float
total_dist_km: float
hops: int
safe: bool # True if no CRITICAL node in path
def to_dict(self) -> dict[str, Any]:
return {
"source": self.source,
"destination": self.destination,
"path": self.path,
"path_labels": self.path_labels,
"total_weight": round(self.total_weight, 2),
"total_dist_km": round(self.total_dist_km, 2),
"hops": self.hops,
"safe": self.safe,
}
@dataclass
class CongestionAlert:
"""A congestion alert for a single node."""
node_id: int
name: str
risk_level: str
risk_color: str
load_pct: float
fused_occupancy: float
effective_capacity: int
recommended_diversion: list[int] | None = None
def to_dict(self) -> dict[str, Any]:
return {
"node_id": self.node_id,
"name": self.name,
"risk_level": self.risk_level,
"risk_color": self.risk_color,
"load_pct": round(self.load_pct, 1),
"fused_occupancy": round(self.fused_occupancy),
"effective_capacity": self.effective_capacity,
"recommended_diversion": self.recommended_diversion,
}
# ── Router ───────────────────────────────────────────────────────────────
class Router:
"""
Computes safe routes and detects congestion on the live graph.
All methods are stateless β€” pass the current ``nx.DiGraph`` each time.
"""
def safe_route(
self,
G: nx.DiGraph,
src: int,
dst: int,
) -> RouteResult | None:
"""
Shortest path (by dynamic weight) from src to dst.
Returns None when no path exists.
"""
if src not in G or dst not in G:
return None
try:
path = nx.dijkstra_path(G, src, dst, weight="weight")
total_w = nx.dijkstra_path_length(G, src, dst, weight="weight")
except nx.NetworkXNoPath:
return None
total_dist = sum(
G[path[i]][path[i + 1]].get("dist_km", 0.0)
for i in range(len(path) - 1)
)
path_labels = [
LOCATION_NODES.get(n, (str(n), "", ""))[0] for n in path
]
safe = all(
G.nodes[n].get("risk_level", "LOW") != "CRITICAL" for n in path
)
return RouteResult(
source=src,
destination=dst,
path=path,
path_labels=path_labels,
total_weight=total_w,
total_dist_km=total_dist,
hops=len(path) - 1,
safe=safe,
)
def congestion_alerts(
self,
G: nx.DiGraph,
) -> list[CongestionAlert]:
"""
Return alerts for every HIGH or CRITICAL node,
sorted by load_pct descending.
"""
alerts: list[CongestionAlert] = []
for node_id, data in G.nodes(data=True):
risk = data.get("risk_level", "LOW")
if risk not in {"HIGH", "CRITICAL"}:
continue
diversion = self._nearest_buffer(G, node_id)
alerts.append(
CongestionAlert(
node_id=node_id,
name=data.get("label", str(node_id)),
risk_level=risk,
risk_color=data.get("risk_color", "#FF2D2D"),
load_pct=data.get("load_pct", 100.0),
fused_occupancy=data.get("fused_occupancy", 0.0),
effective_capacity=data.get("effective_capacity", 1),
recommended_diversion=diversion,
)
)
alerts.sort(key=lambda a: a.load_pct, reverse=True)
return alerts
def recommend_diversions(
self,
G: nx.DiGraph,
top_n: int = 5,
) -> list[dict[str, Any]]:
"""Top-N diversion recommendations for overloaded nodes."""
alerts = self.congestion_alerts(G)
result: list[dict[str, Any]] = []
for alert in alerts[:top_n]:
div_labels = [
LOCATION_NODES.get(n, (str(n), "", ""))[0]
for n in (alert.recommended_diversion or [])
]
result.append(
{
"congested_node": alert.node_id,
"congested_name": alert.name,
"load_pct": alert.load_pct,
"diversion_path": alert.recommended_diversion,
"diversion_labels": div_labels,
}
)
return result
# ── private ──────────────────────────────────────────────────────────
def _nearest_buffer(
self,
G: nx.DiGraph,
congested_node: int,
) -> list[int] | None:
"""Find the nearest P5 buffer node from a congested node."""
p5_nodes = [
n for n, d in G.nodes(data=True) if d.get("priority") == "P5"
]
if not p5_nodes:
return None
best_path: list[int] | None = None
best_w = float("inf")
for p5 in p5_nodes:
try:
w = nx.dijkstra_path_length(
G, congested_node, p5, weight="weight"
)
if w < best_w:
best_w = w
best_path = nx.dijkstra_path(
G, congested_node, p5, weight="weight"
)
except nx.NetworkXNoPath:
continue
return best_path