hs-zz27/Flipkart-Gridlock_2.0
GitHub: hs-zz27/Flipkart-Gridlock_2.0
一个面向交通管理控制室的全栈 AI 辅助决策平台,利用机器学习对交通事件进行严重性分级、畅通时间预测和调度优化,帮助操作员以数据驱动方式处理道路突发事件。
Stars: 0 | Forks: 0
# SPANDANA:用于权威响应的畅通与物流引擎
公开产品的品牌名为 **SPANDANA**;内部 Python 包和运行命令仍命名为 `clear`(例如 `clear.app:app`)。
## 目录
1. [简明扼要](#1-in-plain-english)
2. [SPANDANA 是什么(及不是什么)](#2-what-spandana-is-and-is-not)
3. [系统架构](#3-system-architecture)
4. [技术栈](#4-tech-stack)
5. [仓库结构](#5-repository-structure)
6. [数据模型](#6-data-model)
7. [模型与分析](#7-models--analytics)
8. [请求与推理生命周期](#8-request--inference-lifecycle)
9. [API 参考(22 个 endpoint)](#9-api-reference-22-endpoints)
10. [横切后端行为](#10-cross-cutting-backend-behavior)
11. [可选 / 标志控制功能](#11-optional--flag-gated-features)
12. [前端](#12-frontend)
13. [配置与环境变量](#13-configuration--environment-variables)
14. [本地开发](#14-local-development)
15. [部署与 CI/CD](#15-deployment--cicd)
16. [测试](#16-testing)
17. [重训生命周期](#17-retrain-lifecycle)
18. [已知限制与演示说明](#18-known-limitations--demo-notes)
19. [团队](#19-team)
## 1. 简明扼要
想象一下 Bengaluru 的交通控制室。报告不断涌入:Hosur Road 发生故障、MG Road 附近树木倒伏、Sarjapur Road 积水。操作员必须立即做出决定——*哪个事件最重要、道路会堵塞多久,以及应该派哪个巡逻队去。* 如今,这完全是凭记忆和直觉完成的。
SPANDANA 是位于操作员身旁的**决策支持层**,用数据回答这些问题:
| 操作员会问…… | SPANDANA 的回答是…… |
| --- | --- |
| “这个有多严重?” | 一个**严重性等级**(低 → 严重),并附带真实的**置信度 %**。 |
| “道路多久能畅通?” | 一个**时间范围**(例如“约 52 分钟,可能在 28–95 分钟之间”),绝不出具虚假的精确数字。 |
| “接下来哪里会出问题?” | 每条走廊的**3 小时风险评分**。 |
| “我应该派谁去?” | 一份**排序的调度计划**,操作员必须在采取任何行动前予以批准。 |
| “下雨会让情况变得更糟吗?” | 每条走廊的**实时雨水拥堵风险**评分。 |
黄金法则:**SPANDANA 只提供建议——由操作员做决定。** 绝不会自动执行任何操作。并且每个数字都是“诚实”的:置信度是经过校准的,时间估计是区间,如果缺少模型,系统会*优雅降级*(显示最近已知的数据)而不是崩溃。
## 2. SPANDANA 是什么(及不是什么)
SPANDANA 是一个全栈项目(团队 **FlipKart-GridLock**,Thapar Institute),它接收交通事件数据,并通过基于角色的 REST API 和 React 仪表板提供分析和机器学习预测。
**设计理念(作为编号的工程约束被固化在代码中):**
- **只做决策支持,绝不自主运行。** 每个调度建议都带有 `requires_confirmation: true` 和 `autonomous_actuation: false`。该服务绝不自动控制信号。
- **优雅降级优于崩溃。** 缺失或损坏的模型只会导致单条路由降级(提供陈旧数据、FIFO 队列或返回 `503`),而不会导致整个 API 宕机。
- **诚实的输出。** 置信度是*经过校准的*;畅通时间是一个*截断生存区间*(P10–P90),而不是虚假的预计到达时间点;SLA 仅根据实际解决的事件进行计算。
- **基于环境变量的密钥。** 每个 token/key 仅来自环境变量——没有任何硬编码。
## 3. 系统架构
通过 HTTPS 通信的两个独立部署的部分:
- **后端** → 部署在 **Render** 上的 Docker 镜像 (`https://flipkart-gridlock.onrender.com`)。ML 模型在**构建时被打包进镜像中**,因此容器启动后即可立即提供服务。
- **前端** → **Vercel** (`https://flip-kart-grid-lock.vercel.app`)。Vite 在构建时内联 `VITE_*` 环境变量。
- **数据库** → **Neon Postgres**(无服务器连接池)。
```
flowchart LR
subgraph Client["Client — Browser"]
LP["Landing /"]
OP["Operator Console
token-gated /operator"] CIT["Citizen Portal /citizen"] end subgraph FE["Frontend — Vercel (Vite + React 19)"] APITS["api.ts ClearApi
fetch + Bearer token"] end subgraph BE["Backend — Render (FastAPI + Gunicorn)"] MW["Middleware:
CORS (outer) + Rate-limit"] RT["22 REST endpoints"] ML["In-memory models on app.state:
severity / severity_text / clearance / forecast"] end DB[("Neon Postgres")] OM["Open-Meteo API
(rainfall enrich)"] WU["Weather Union API
(live rain-clog)"] LP --> OP OP --> APITS CIT --> APITS APITS -->|"HTTPS + CORS-allowed origin"| MW MW --> RT RT --> ML RT <-->|"psycopg 3"| DB RT -.->|"enrich on ingest"| OM RT -.->|"per-corridor nowcast"| WU ``` ## 4. 技术栈 | 层级 | 技术 | | --- | --- | | 后端 API | Python 3.11, FastAPI, Gunicorn 下的 Uvicorn worker, Pydantic v2 / pydantic-settings | | ML / 分析 | scikit-learn, LightGBM, lifelines (生存分析), OR-Tools (分配), NumPy, pandas, joblib | | 可选 NLP | MuRIL (transformers + torch) — **仅限本地**的附加功能,绝不出现在生产镜像中 | | 数据库 | 通过 psycopg 3 连接的 Neon Postgres(批量插入) | | 前端 | React 19, TypeScript, Vite, Tailwind CSS v4, TanStack Query v5, React Router 7, MapLibre GL, lucide-react | | 基础设施 / CI | Docker, Render (blueprint + deploy hook), Vercel, GitHub Actions (lint + pytest + boot check + deploy) | ## 5. 仓库结构 ``` FlipKart-GridLock/ ├─ README.md # this file ├─ render.yaml # Render blueprint (Docker web service) ├─ .github/workflows/ # ci-cd.yml, monitor.yml ├─ backend/ │ ├─ Dockerfile # py3.11-slim; bakes models at build │ ├─ entrypoint.sh # bootstrap → gunicorn │ ├─ gunicorn.conf.py # uvicorn workers, bind $PORT │ ├─ pyproject.toml requirements.txt .env.example RUNBOOK.md │ ├─ data/raw/ data/seed/ # seed CSV + muril_cache.joblib │ ├─ tests/ # pytest suite + conftest.py │ └─ src/clear/ │ ├─ app.py # FastAPI app + all 22 routes │ ├─ config.py # Settings (env-driven, CLEAR_* prefix) │ ├─ auth.py ratelimit.py # bearer scopes + fixed-window limiter │ ├─ db.py schema.py # Postgres I/O + 46-column contract │ ├─ preprocessing.py # normalize, UTC→IST, censoring, OSM snap │ ├─ ingestion.py datagen.py │ ├─ validation.py degradation.py metrics.py monitor.py │ ├─ weather.py weather_union.py rain_clog.py backfill_weather.py │ ├─ event_intel.py resource_planner.py diversion.py │ ├─ nlp_corpus.py nlp_phrasebank.py nlp_muril.py precompute_muril.py │ ├─ train.py retrain.py bootstrap.py smoketest.py telegram_bot.py │ └─ models/ │ ├─ severity.py severity_text.py clearance.py │ └─ forecast.py hotspot.py dispatch.py └─ frontend/ ├─ index.html vite.config.ts vercel.json package.json └─ src/ ├─ App.tsx main.tsx api.ts types.ts auth.ts ├─ pages/ LandingPage, OperatorLogin, OperatorDashboard, │ OperatorStats, OperatorPlanning, CitizenView └─ components/ HealthBadge, IncidentQueue, IncidentDetails, MapLayer, DispatchPanel, SlaWidget, MetricsWidget, RainRiskWidget, RouteRainCheck, DiversionAid, CitizenReportForm, RoadNetworkBackground ``` ## 6. 数据模型 ### 6.1 原始事件契约(46 列) `schema.py` 定义了一个固定的 **46 列** Bengaluru 事件 schema (`RAW_COLUMNS`),合成数据生成器输出的和接收程序接受的均为该格式。(它故意包含了一个大写的 `Pot_holes`,以测试列名标准化的能力。) - **规范的事件原因:** `breakdown`, `accident`, `tree_fall`, `water_logging`, `pot_holes`, `public_event`, `others`。真实世界的变体(`vehicle_breakdown`, `procession`, `vip_movement`, `construction`, `fog`, …)通过**别名 + 关键词匹配**映射到这些规范原因上,因此*同一个* pipeline 可以接收合成数据和真实导出的数据。 - **优先级 / 严重性等级:** `low`, `medium`, `high`, `critical`。 - **验证 (`IncidentIn`):** 只有 `event_id`, `start_datetime`, `latitude`, `longitude` 是严格必需的;存在但为空的字段将回退到声明的默认值;datetime 会被宽松解析(处理 Postgres `timestamptz` 文本和字面量 `NULL`);lat/lon 会进行范围检查。 ### 6.2 Postgres 表 (`db.py`) | 表 | 用途 | | --- | --- | | `incidents` | 规范事件存储(基于 `event_id` 幂等);包含派生的 `duration_minutes`, `event_observed`, `admin_close`, `junction_node`, IST 时间戳。 | | `dead_letter` | 验证失败或重试耗尽的行——**没有任何东西会被静默丢弃**。 | | `predictions` | 记录的模型输出(严重性 / 畅通时间)以及模型版本。 | | `recommendations` | 调度建议 + 确认/批准审计跟踪。 | | `corridor_risk` | 最近已知的走廊风险(用于降级 / 陈旧数据服务)。 | | `model_registry` | 模型版本 + 生命周期阶段(candidate → shadow → canary → production → archived)。 | | `correctness_metrics` | MAE / PSI 漂移指标的时间序列。 | | `junction_cache` | 捕捉到 OSM 节点的缓存。 | **摄取路径 (`ingestion.py`):** 验证 → 幂等性检查 (`ON CONFLICT (event_id) DO NOTHING`) → 存储,带有受限重试 + 指数退避和死信队列处理。批量 CSV 加载是向量化的,并以 1000 行的 `executemany` 批次刷新(每批次一次网络往返)。 ## 7. 模型与分析 四个估计器在启动时加载一次到 `app.state` 中;每一个都独立加载,并在**缺失时优雅降级**。热点和调度是按需计算的(非持久化的估计器)。 ``` flowchart TD subgraph Severity["Severity — two distinct models"] S1["GET /incidents/{id}/severity (operator)"] --> M1["SeverityModel
LightGBM + CalibratedClassifierCV (sigmoid)
structured features · priority EXCLUDED (de-leak)"] S2["POST /nlp/severity (citizen, text-only)"] --> M2["SeverityTextModel
LogReg + MLP soft-vote · isotonic calibration
multilingual lexical cues + optional MuRIL"] end M1 --> O["band + calibrated confidence"] M2 --> O ``` ### 7.1 严重性 — 结构化 (`models/severity.py`) - **算法:** LightGBM 分类器包装在 `CalibratedClassifierCV` (Platt/sigmoid) 中,以便报告的置信度是有意义的(选择 sigmoid 是因为保序回归会将置信度固定在 1.0)。 - **特征:** 道路封闭、是否有车辆、自由文本线索计数、降雨量、受阻车道、lat/lon、周期性的小时和星期几、走廊频率、事件原因 one-hots 编码。**故意排除了 `priority`** 以避免泄露标签。 - **输出:** `{ band, confidence }`。 ### 7.2 严重性 — 纯文本 (`models/severity_text.py`) - **存在的原因:** 市民的 `/nlp/severity` endpoint 仅发送自由文本;结构化模型的特征会退化为常量填充(训练/服务偏差)。此模型仅在**可从文本推导的信号**上进行训练,从而保证了*训练环境 == 服务环境*。 - **算法:** 软投票 `LogisticRegression` + `MLPClassifier`,**保序回归校准**,如果校准失败则退回线性模型。 - **特征:** 分层的多语种词汇线索计数(英语 / हिन्दी / ಕನ್ನಡ / 罗马音化)、事件原因 one-hots 编码、是否有车辆,以及可选的经过 PCA 降维的 **MuRIL** embedding。在训练期间应用 **embedding dropout**,使其能够仅依靠词汇线索保持置信度——这正是生产环境中没有 torch 且 embedding 全为零的情况。 ### 7.3 畅通时间 (`models/clearance.py`) - **算法:** **截断生存分析** — lifelines `LogNormalAFTFitter`。 - **标签:** 畅通时间 = `resolved`(回退到 `closed`)− `start`;仍然开放的事件在其当前时长处被**右截断**;状态为已关闭但没有时间戳的行被丢弃(信息缺失);超过 24 小时上限的持续时间被丢弃,以避免预测值卡在极值上。 - **输出:** `{ median_minutes, p10_minutes, p90_minutes, interval_note }` — 一个诚实的 P10–P90 区间。 ### 7.4 走廊风险临近预报 (`models/forecast.py`) - **算法:** 基于每条走廊每小时事件计数的 LightGBM 回归器(滞后 1/2/3、3 小时滚动平均值、小时、星期几、走廊频率),带有提前停止。 - **严格来说是一个 3 小时的临近预报**(没有长期的长期预测)。输出被标准化为每条走廊 0–100 的 `risk`;`/corridors/risk` 对走廊进行排名,并过滤掉无法排名的桶(`non-corridor`, `unknown`, `none`)。 ### 7.5 热点 (`models/hotspot.py`) - **算法:** 使用 **haversine** 度量(ball-tree,以米为单位的 `eps`,可配置的 `min_samples`)对事件坐标进行 DBSCAN 聚类。对存储的事件进行批处理;支持 `min_size` 和 `limit` 的服务端修剪。返回带有质心 + 顶级走廊的聚类。 ### 7.6 调度优化 (`models/dispatch.py`) - **算法:** 联合选择 + 分配 — 服务于影响最大的事件,然后通过 **OR-Tools 线性和分配**最小化总行程时间(如果 OR-Tools 不可用,则采用确定性的贪心回退)。 - 与 FIFO/最近的基准进行同等条件比较 → `improvement_pct`(可证明 ≥ 0)。单位位置被标记为 *假定/外部*;输出**需要操作员确认**。 ### 7.7 输出验证 (`validation.py`) 在任何持久化/服务之前,每个模型输出都会被验证:拒绝 NaN/inf,强制要求 band ∈ bands,confidence ∈ [0,1],畅通时间在 (0, cap] 范围内且 P10 ≤ median ≤ P90,risk ∈ [0,100]。 ## 8. 请求与推理生命周期 ``` flowchart TD A["POST /ingest (operator)"] --> B["Validate IncidentIn
normalize cause · parse UTC→IST · range-check lat/lon"] B -->|invalid| DL["dead_letter table"] B -->|valid| C["Idempotent insert
ON CONFLICT(event_id) DO NOTHING"] C --> D["Best-effort inference at ingest:
severity + clearance"] D --> E["Output validation
(reject NaN / out-of-range)"] E --> F["Persist predictions"] C -.optional.-> W["Weather enrich (Open-Meteo)
only if CLEAR_WEATHER_ENABLED"] ``` ## 9 API 参考(22 个 endpoint) **认证:** `Authorization: Bearer`。在 `auth.py` 中解析两种 scope — `operator` 和 `citizen`。`401` = 缺少/无效的 token,`403` = 错误的 scope。`/healthz` 不需要 token。
| 方法 | 路径 | Scope | 用途 |
| --- | --- | --- | --- |
| GET | `/healthz` | 无 | 状态 + 各模型的 up/down 标志(severity, severity_text, clearance, forecast)。 |
| POST | `/ingest` | operator | 验证并幂等存储一个事件;运行尽力而为的推理。 |
| GET | `/incidents` | operator, citizen | 列出最近的事件 (`limit`)。 |
| GET | `/incidents/{id}/severity` | operator | 结构化的严重性等级 + 置信度。 |
| GET | `/incidents/{id}/clearance` | operator | 畅通时间中位数 + P10–P90。 |
| GET | `/corridors/risk` | operator, citizen | 3 小时走廊风险临近预报(降级为最近已知的陈旧风险)。 |
| GET | `/hotspots` | operator | DBSCAN 聚类 (`min_size`, `limit`)。 |
| POST | `/dispatch/suggest` | operator | 排序后的调度分配对比 FIFO 基准(或降级的 FIFO 队列)。 |
| POST | `/dispatch/confirm` | operator | 对建议进行 human-in-the-loop(人机协同)确认。 |
| POST | `/nlp/severity` | citizen | 纯文本多语种严重性预测。(目前通过 curl/CLI 访问——尚未接入 UI。) |
| POST | `/citizen/report` | citizen | 市民事件报告(在服务端对自由文本进行清理)。 |
| GET | `/sla` | operator, citizen | 仅针对实际解决的事件子集计算的 SLA%。 |
| GET | `/events/types` | operator, citizen | 影响模拟器已知的事件类型。 |
| POST | `/events/impact` | operator, citizen | 通过事件乘数缩放基准的畅通时间/风险。 |
| POST | `/resources/plan` | operator | 启发式人员配置/设备计划(警员、路障、拖车)。 |
| GET | `/diversions` | operator, citizen | 最近的备选走廊(haversine)以及行程时间差值。 |
| GET | `/weather/rain-risk` | operator, citizen | 实时雨水拥堵风险 0–100 + ETA 乘数(Weather Union)。 |
| GET | `/metrics/by-event` | operator | 按事件原因分组的畅通时间 MAE。 |
| GET | `/metrics` | operator | 畅通时间误差摘要 + 最近的指标历史。 |
| POST | `/metrics/backfill` | operator | 为已解决的事件回填预测,以免指标为空。(CLI/curl。) |
| GET | `/admin/drift` | operator | 样本外 MAE + PSI 漂移报告。(CLI/curl。) |
| POST | `/admin/retrain` | operator | 触发重训生命周期。(CLI/curl。) |
## 10. 横切后端行为
- **认证与 scope (`auth.py`):** bearer token → `operator` 或 `citizen`;依赖工厂强制执行每条路由允许的 scope。
- **速率限制 (`ratelimit.py`):** 进程内固定窗口限制器(默认 **120 req / 60 s**,基于每个 token 或 IP),返回 `429` + `Retry-After`;`/healthz` 豁免。在 CORS **之前**注册,以便 429 响应保留其 CORS 标头。
- **CORS (`app.py` + `config.py`):** 允许的来源来自 `CLEAR_CORS_ALLOW_ORIGINS`(逗号分隔;默认仅限 localhost)。**必须添加已部署的前端来源,否则浏览器会阻止所有调用。**
- **优雅降级 (`degradation.py`):** 预测宕机 → 最近已知的 *陈旧* 走廊风险;调度宕机 → 确定性的优先级 FIFO 队列;任何模型宕机 → 该路由返回 `503`,而其余部分保持正常。
- **提示词注入防护 (`app.py`):** 在任何下游使用之前,对自由文本进行清理(控制字符剥离、长度上限、注入模式脱敏)。
- **日志记录 (`logging_setup.py`):** 单一的 stdout logger,导入时不包含网络处理程序。
## 11. 可选 / 标志控制功能
所有这些默认都是**关闭的**——当它们的标志关闭时,后端与核心服务逐字节完全一致。
| 功能 | 模块 | 备注 |
| --- | --- | --- |
| Open-Meteo 降雨数据丰富 | `weather.py`, `backfill_weather.py` | 在摄取 / 离线回填时填充 `rainfall_mm`。`CLEAR_WEATHER_ENABLED`;每个失败路径都返回 `0.0`。 |
| 实时雨水拥堵风险 | `weather_union.py`, `rain_clog.py` | 将实时的 Weather Union 降雨数据(强度 + 累积量)与每条走廊历史上的积水倾向混合,生成 0–100 的评分 + 1.0–1.6 的 ETA 乘数。需要 `CLEAR_RAIN_CLOG_ENABLED` + API key。 |
| 事件影响模拟器 | `event_intel.py` | 可调的每种事件类型的拥堵乘数(无 ML);缩放基准的畅通时间/风险。 |
| 资源规划器 | `resource_planner.py` | 启发式人员配置:警员/参会人数、路障/封闭数量、拖车/参会人数,按事件乘数进行缩放。 |
| 疏导路线 | `diversion.py` | 根据配置的走廊 lat/long 映射,通过 haversine 距离计算最近的备选走廊(主要/次要 + 增量分钟)。 |
| MuRIL 多语种 embedding | `nlp_muril.py`, `nlp_corpus.py`, `nlp_phrasebank.py`, `precompute_muril.py` | 仅限本地(torch)。预置了一个 sha1 embedding 缓存 (`muril_cache.joblib`),以便构建时的训练和服务能够获得缓存命中,从而实现**在生产环境中零 torch 依赖**。 |
| Telegram 机器人 | `telegram_bot.py` | 可选的独立进程;纯粹的 citizen scope HTTP 客户端。默认禁用;绝不触碰 Web 应用。 |
## 12. 前端
### 12.1 路由 (`App.tsx`)
TanStack Query (`retry: false`, `refetchOnWindowFocus: false`) 包装了一个 React Router 应用。全局 `HealthBadge` 每 30 秒轮询一次 `/healthz`。
| 路由 | 页面 | 访问权限 |
| --- | --- | --- |
| `/` | LandingPage | 公开 — 主视觉图(动态的路网背景 + 交通信号灯标题)、问题/解决方案、团队模态框 |
| `/operator/login` | OperatorLogin | 通过调用 `/metrics` 验证访问代码;token 存储在 `sessionStorage` 中 |
| `/operator` | OperatorDashboard | 受保护 — 事件队列 + 地图 + 分析模态框 + 调度 |
| `/operator/stats` | OperatorStats | 受保护 — SLA、指标、走廊风险、热点、事后准确度 |
| `/operator/planning` | OperatorPlanning | 受保护 — 事件影响模拟器 + 资源规划器 |
| `/citizen` | CitizenView | 仅在设置了 `VITE_CLEAR_CITIZEN_TOKEN` 时渲染 |
### 12.2 组件 → endpoint
| 组件 | 调用 | 角色 |
| --- | --- | --- |
| HealthBadge | `/healthz` | 绿色“Operational” / 琥珀色“Models Degraded” / 红色“System Degraded”(红色 = 获取失败)。 |
| IncidentQueue | `/incidents` | 可搜索、按优先级过滤的实时队列。 |
| IncidentDetails | `/incidents/{id}/severity`, `/clearance` (+ Diversion/Rain cards) | 针对每个事件的情报;根据 `/healthz` 的模型标志控制调用。 |
| MapLayer | `/hotspots`, `/corridors/risk`, `/weather/rain-risk` | 带有热点标记 + 雨水拥堵叠加层的 MapLibre 地图。 |
| DispatchPanel | `/dispatch/suggest`, `/dispatch/confirm` | 建议 → 人工确认;显示降级的 FIFO 通知。 |
| SlaWidget / MetricsWidget | `/sla`, `/metrics` | SLA% 和畅通时间误差指标。 |
| RainRiskWidget / RouteRainCheck | `/weather/rain-risk` | 针对走廊的雨水拥堵卡片;市民路线检查器。 |
| DiversionAid | `/diversions` | 带有时间增量的备选走廊列表。 |
| CitizenReportForm | `/citizen/report` | 公开事件报告。 |
| OperatorPlanning | `/events/types`, `/events/impact`, `/resources/plan`, `/corridors/risk` | 事件模拟器 + 资源规划器。 |
| OperatorStats | `/metrics/by-event`, `/hotspots`, `/corridors/risk`, `/sla` | 事后准确度 + 系统统计信息。 |
## 13. 配置与环境变量
### 13.1 后端(前缀 `CLEAR_`,来自 `config.py` / `.env.example`)
| 变量 | 默认值 | 含义 |
| --- | --- | --- |
| `CLEAR_DATABASE_URL` | localhost | Neon **连接池化的** Postgres URL(在生产环境中必须设置)。 |
| `CLEAR_OPERATOR_TOKEN` / `CLEAR_CITIZEN_TOKEN` | dev tokens | 角色对应的 bearer token(必须与前端 `VITE_*` 的 token 一致)。 |
| `CLEAR_CORS_ALLOW_ORIGINS` | localhost:5173,3000 | 逗号分隔的允许的浏览器来源——**在生产环境中请添加 Vercel URL**。 |
| `CLEAR_RATE_LIMIT_ENABLED` / `_REQUESTS` / `_WINDOW_SECONDS` | true / 120 / 60 | 固定窗口速率限制。 |
| `CLEAR_SLA_THRESHOLD_MINUTES` | 60 | SLA 阈值。 |
| `CLEAR_FORECAST_HORIZON_HOURS` | 3 | 临近预报范围。 |
| `CLEAR_HOTSPOT_EPS_METERS` / `_MIN_SAMPLES` | 150 / 5 | DBSCAN 参数。 |
| `CLEAR_MAX_CLEARANCE_MINUTES` | 1440 | 畅通时间上限(24小时)。 |
| `CLEAR_DRIFT_PSI_THRESHOLD` | 0.2 | 漂移警报阈值。 |
| `WEB_CONCURRENCY` | 2 | Gunicorn worker 数量。 |
| `CLEAR_WEATHER_ENABLED` | false | Open-Meteo 降雨数据丰富。 |
| `CLEAR_RAIN_CLOG_ENABLED` + `CLEAR_WEATHER_UNION_API_KEY` | off | 实时雨水拥堵功能 + key。 |
| `CLEAR_USE_MURIL` | false | MuRIL embedding(仅限本地)。 |
| `CLEAR_TELEGRAM_ENABLED` + `CLEAR_TELEGRAM_BOT_TOKEN` | off | 可选的 Telegram 机器人。 |
### 13.2 前端(Vite,在构建时内联)
| 变量 | 含义 |
| --- | --- |
| `VITE_CLEAR_API_BASE` | 后端基础 URL(在生产环境中为 Render URL)。回退到 `http://localhost:8000`。单个值,`https`,没有尾部斜杠。 |
| `VITE_CLEAR_OPERATOR_TOKEN` | Operator token(登录后也按会话存储)。 |
| `VITE_CLEAR_CITIZEN_TOKEN` | Citizen token;同时也控制是否渲染 `/citizen` 路由。 |
## 14. 本地开发
**后端**
```
python -m venv .venv && source .venv/bin/activate
cp .env.example .env
pip install -e .[dev]
python -m clear.datagen --out data/raw/incidents.csv --n 20000 # synthetic data
python -m clear.ingestion --csv data/raw/incidents.csv # idempotent load
python -m clear.train all # severity, severity_text, clearance, forecast
uvicorn clear.app:app --reload --port 8000 # serve; /docs for Swagger
```
**前端**
```
cd frontend
npm install
# .env.local: VITE_CLEAR_API_BASE=http://localhost:8000 + dev tokens
npm run dev # http://localhost:5173
```
**对运行中的 API 进行每个 endpoint 的冒烟测试**
```
python -m clear.smoketest https://flipkart-gridlock.onrender.com
```
## 15. 部署与 CI/CD
```
flowchart LR
Dev["git push (master)"] --> GH["GitHub Actions"]
GH --> Lint["Lint — ruff (advisory)"]
GH --> Test["Tests — pytest + Postgres service
+ /healthz boot check"] Test -->|"pass AND push to master"| Deploy["Deploy job
curl RENDER_DEPLOY_HOOK_URL"] Deploy --> Render["Render rebuilds Docker image
(models baked at build)"] FE["frontend push"] --> Vercel["Vercel build (Vite)"] ``` - **Docker (`backend/Dockerfile`):** python:3.11-slim + `libgomp1` (LightGBM);安装包;复制种子 CSV + MuRIL 缓存;**在构建时训练所有模型** (`CLEAR_SKIP_MODEL_REGISTRY=1`) 并断言产物存在 → 容器即时启动,没有冷启动重训过程。 - **入口点:** `bootstrap.py` (初始化 schema + 如果为空则种子摄取 + 如果没有模型则训练) → Gunicorn (uvicorn worker,绑定 `$PORT`)。 - **Render blueprint (`render.yaml`):** Docker web service, `dockerContext: ./backend`, 免费套餐,健康检查 `/healthz`;环境变量 `CLEAR_DATABASE_URL` (secret)、生成的 token、`WEB_CONCURRENCY`、雨水拥堵标志 + key。**在仪表板中添加 `CLEAR_CORS_ALLOW_ORIGINS` = Vercel URL**。 - **Vercel (`vercel.json`):** 将所有路由的 SPA 重写到 `/index.html`。将 **Production Branch 设置为 `master`** 并配置 `VITE_*` 环境变量,然后重新部署。 - **CI (`ci-cd.yml`):** 并发取消被取代的运行;lint 建议;针对一次性 Postgres 运行 pytest + 无网络的 `/healthz` 启动断言;仅在 `master` 推送时部署。 ## 16. 测试 `backend/tests/` (使用 `pytest -q` 运行):`test_idempotency.py`, `test_output_validation.py`, `test_preprocessing.py`, `test_planning_modules.py`, `test_rain_clog.py`, `test_nlp_severity_text.py`, `test_telegram_bot.py`,以及 `conftest.py` (阻止针对非本地 URL 进行 DB 测试)。`smoketest.py` 是对每个 endpoint 进行的实时端到端检查。 ## 17. 重训生命周期 (`retrain.py`) ``` stateDiagram-v2 [*] --> candidate: train_model() candidate --> shadow: shadow() shadow --> canary: canary() canary --> production: promote() (archives previous prod) production --> archived: replaced by promote() archived --> production: rollback() (restore most recent) ``` 阶段存在于 `model_registry` 中;实时的服务指针是无版本的 `.joblib`,由 promote/rollback 重新指向。`auto_cycle()`(仅针对 clearance)是一个**受控的**时间分片重训:仅当候选模型在留出数据上以可配置的幅度超越生产模型时,它才会提升该候选模型。
## 18. 已知限制与演示说明
- **免费层冷启动:** Render 的免费套餐在闲置约 15 分钟后会休眠;第一个请求可能需要约 50 秒——比前端的获取超时时间更长 → 在预热之前会短暂出现“System Degraded”。
- **CORS / 基础 URL:** 仅当 Vercel 来源位于 `CLEAR_CORS_ALLOW_ORIGINS` 中**并且** `VITE_CLEAR_API_BASE` 指向 Render URL(更改后需重新构建)时,已部署的前端才能工作。
- **Token 一致性:** 前端的 `VITE_CLEAR_*_TOKEN` 值必须与后端的 token 匹配,否则调用将返回 `401`。
- **弱标签边界:** 畅通时间训练会丢弃超过 24 小时上限的持续时间;低样本量的事件原因会被标记为“low sample”。
- **疏导路线**是直线(haversine)距离估算,而不是规划的行程时间。
- 四个管理/NLP endpoint 尚无 UI(仅限 curl/CLI)。
## 19. 团队
**Jaiveer Singh** · **Harkamal Singh** · **Aditya Kumar** · **Dhruv Srivastava**.
- Thapar Institute of Engineering and Technology.
token-gated /operator"] CIT["Citizen Portal /citizen"] end subgraph FE["Frontend — Vercel (Vite + React 19)"] APITS["api.ts ClearApi
fetch + Bearer token"] end subgraph BE["Backend — Render (FastAPI + Gunicorn)"] MW["Middleware:
CORS (outer) + Rate-limit"] RT["22 REST endpoints"] ML["In-memory models on app.state:
severity / severity_text / clearance / forecast"] end DB[("Neon Postgres")] OM["Open-Meteo API
(rainfall enrich)"] WU["Weather Union API
(live rain-clog)"] LP --> OP OP --> APITS CIT --> APITS APITS -->|"HTTPS + CORS-allowed origin"| MW MW --> RT RT --> ML RT <-->|"psycopg 3"| DB RT -.->|"enrich on ingest"| OM RT -.->|"per-corridor nowcast"| WU ``` ## 4. 技术栈 | 层级 | 技术 | | --- | --- | | 后端 API | Python 3.11, FastAPI, Gunicorn 下的 Uvicorn worker, Pydantic v2 / pydantic-settings | | ML / 分析 | scikit-learn, LightGBM, lifelines (生存分析), OR-Tools (分配), NumPy, pandas, joblib | | 可选 NLP | MuRIL (transformers + torch) — **仅限本地**的附加功能,绝不出现在生产镜像中 | | 数据库 | 通过 psycopg 3 连接的 Neon Postgres(批量插入) | | 前端 | React 19, TypeScript, Vite, Tailwind CSS v4, TanStack Query v5, React Router 7, MapLibre GL, lucide-react | | 基础设施 / CI | Docker, Render (blueprint + deploy hook), Vercel, GitHub Actions (lint + pytest + boot check + deploy) | ## 5. 仓库结构 ``` FlipKart-GridLock/ ├─ README.md # this file ├─ render.yaml # Render blueprint (Docker web service) ├─ .github/workflows/ # ci-cd.yml, monitor.yml ├─ backend/ │ ├─ Dockerfile # py3.11-slim; bakes models at build │ ├─ entrypoint.sh # bootstrap → gunicorn │ ├─ gunicorn.conf.py # uvicorn workers, bind $PORT │ ├─ pyproject.toml requirements.txt .env.example RUNBOOK.md │ ├─ data/raw/ data/seed/ # seed CSV + muril_cache.joblib │ ├─ tests/ # pytest suite + conftest.py │ └─ src/clear/ │ ├─ app.py # FastAPI app + all 22 routes │ ├─ config.py # Settings (env-driven, CLEAR_* prefix) │ ├─ auth.py ratelimit.py # bearer scopes + fixed-window limiter │ ├─ db.py schema.py # Postgres I/O + 46-column contract │ ├─ preprocessing.py # normalize, UTC→IST, censoring, OSM snap │ ├─ ingestion.py datagen.py │ ├─ validation.py degradation.py metrics.py monitor.py │ ├─ weather.py weather_union.py rain_clog.py backfill_weather.py │ ├─ event_intel.py resource_planner.py diversion.py │ ├─ nlp_corpus.py nlp_phrasebank.py nlp_muril.py precompute_muril.py │ ├─ train.py retrain.py bootstrap.py smoketest.py telegram_bot.py │ └─ models/ │ ├─ severity.py severity_text.py clearance.py │ └─ forecast.py hotspot.py dispatch.py └─ frontend/ ├─ index.html vite.config.ts vercel.json package.json └─ src/ ├─ App.tsx main.tsx api.ts types.ts auth.ts ├─ pages/ LandingPage, OperatorLogin, OperatorDashboard, │ OperatorStats, OperatorPlanning, CitizenView └─ components/ HealthBadge, IncidentQueue, IncidentDetails, MapLayer, DispatchPanel, SlaWidget, MetricsWidget, RainRiskWidget, RouteRainCheck, DiversionAid, CitizenReportForm, RoadNetworkBackground ``` ## 6. 数据模型 ### 6.1 原始事件契约(46 列) `schema.py` 定义了一个固定的 **46 列** Bengaluru 事件 schema (`RAW_COLUMNS`),合成数据生成器输出的和接收程序接受的均为该格式。(它故意包含了一个大写的 `Pot_holes`,以测试列名标准化的能力。) - **规范的事件原因:** `breakdown`, `accident`, `tree_fall`, `water_logging`, `pot_holes`, `public_event`, `others`。真实世界的变体(`vehicle_breakdown`, `procession`, `vip_movement`, `construction`, `fog`, …)通过**别名 + 关键词匹配**映射到这些规范原因上,因此*同一个* pipeline 可以接收合成数据和真实导出的数据。 - **优先级 / 严重性等级:** `low`, `medium`, `high`, `critical`。 - **验证 (`IncidentIn`):** 只有 `event_id`, `start_datetime`, `latitude`, `longitude` 是严格必需的;存在但为空的字段将回退到声明的默认值;datetime 会被宽松解析(处理 Postgres `timestamptz` 文本和字面量 `NULL`);lat/lon 会进行范围检查。 ### 6.2 Postgres 表 (`db.py`) | 表 | 用途 | | --- | --- | | `incidents` | 规范事件存储(基于 `event_id` 幂等);包含派生的 `duration_minutes`, `event_observed`, `admin_close`, `junction_node`, IST 时间戳。 | | `dead_letter` | 验证失败或重试耗尽的行——**没有任何东西会被静默丢弃**。 | | `predictions` | 记录的模型输出(严重性 / 畅通时间)以及模型版本。 | | `recommendations` | 调度建议 + 确认/批准审计跟踪。 | | `corridor_risk` | 最近已知的走廊风险(用于降级 / 陈旧数据服务)。 | | `model_registry` | 模型版本 + 生命周期阶段(candidate → shadow → canary → production → archived)。 | | `correctness_metrics` | MAE / PSI 漂移指标的时间序列。 | | `junction_cache` | 捕捉到 OSM 节点的缓存。 | **摄取路径 (`ingestion.py`):** 验证 → 幂等性检查 (`ON CONFLICT (event_id) DO NOTHING`) → 存储,带有受限重试 + 指数退避和死信队列处理。批量 CSV 加载是向量化的,并以 1000 行的 `executemany` 批次刷新(每批次一次网络往返)。 ## 7. 模型与分析 四个估计器在启动时加载一次到 `app.state` 中;每一个都独立加载,并在**缺失时优雅降级**。热点和调度是按需计算的(非持久化的估计器)。 ``` flowchart TD subgraph Severity["Severity — two distinct models"] S1["GET /incidents/{id}/severity (operator)"] --> M1["SeverityModel
LightGBM + CalibratedClassifierCV (sigmoid)
structured features · priority EXCLUDED (de-leak)"] S2["POST /nlp/severity (citizen, text-only)"] --> M2["SeverityTextModel
LogReg + MLP soft-vote · isotonic calibration
multilingual lexical cues + optional MuRIL"] end M1 --> O["band + calibrated confidence"] M2 --> O ``` ### 7.1 严重性 — 结构化 (`models/severity.py`) - **算法:** LightGBM 分类器包装在 `CalibratedClassifierCV` (Platt/sigmoid) 中,以便报告的置信度是有意义的(选择 sigmoid 是因为保序回归会将置信度固定在 1.0)。 - **特征:** 道路封闭、是否有车辆、自由文本线索计数、降雨量、受阻车道、lat/lon、周期性的小时和星期几、走廊频率、事件原因 one-hots 编码。**故意排除了 `priority`** 以避免泄露标签。 - **输出:** `{ band, confidence }`。 ### 7.2 严重性 — 纯文本 (`models/severity_text.py`) - **存在的原因:** 市民的 `/nlp/severity` endpoint 仅发送自由文本;结构化模型的特征会退化为常量填充(训练/服务偏差)。此模型仅在**可从文本推导的信号**上进行训练,从而保证了*训练环境 == 服务环境*。 - **算法:** 软投票 `LogisticRegression` + `MLPClassifier`,**保序回归校准**,如果校准失败则退回线性模型。 - **特征:** 分层的多语种词汇线索计数(英语 / हिन्दी / ಕನ್ನಡ / 罗马音化)、事件原因 one-hots 编码、是否有车辆,以及可选的经过 PCA 降维的 **MuRIL** embedding。在训练期间应用 **embedding dropout**,使其能够仅依靠词汇线索保持置信度——这正是生产环境中没有 torch 且 embedding 全为零的情况。 ### 7.3 畅通时间 (`models/clearance.py`) - **算法:** **截断生存分析** — lifelines `LogNormalAFTFitter`。 - **标签:** 畅通时间 = `resolved`(回退到 `closed`)− `start`;仍然开放的事件在其当前时长处被**右截断**;状态为已关闭但没有时间戳的行被丢弃(信息缺失);超过 24 小时上限的持续时间被丢弃,以避免预测值卡在极值上。 - **输出:** `{ median_minutes, p10_minutes, p90_minutes, interval_note }` — 一个诚实的 P10–P90 区间。 ### 7.4 走廊风险临近预报 (`models/forecast.py`) - **算法:** 基于每条走廊每小时事件计数的 LightGBM 回归器(滞后 1/2/3、3 小时滚动平均值、小时、星期几、走廊频率),带有提前停止。 - **严格来说是一个 3 小时的临近预报**(没有长期的长期预测)。输出被标准化为每条走廊 0–100 的 `risk`;`/corridors/risk` 对走廊进行排名,并过滤掉无法排名的桶(`non-corridor`, `unknown`, `none`)。 ### 7.5 热点 (`models/hotspot.py`) - **算法:** 使用 **haversine** 度量(ball-tree,以米为单位的 `eps`,可配置的 `min_samples`)对事件坐标进行 DBSCAN 聚类。对存储的事件进行批处理;支持 `min_size` 和 `limit` 的服务端修剪。返回带有质心 + 顶级走廊的聚类。 ### 7.6 调度优化 (`models/dispatch.py`) - **算法:** 联合选择 + 分配 — 服务于影响最大的事件,然后通过 **OR-Tools 线性和分配**最小化总行程时间(如果 OR-Tools 不可用,则采用确定性的贪心回退)。 - 与 FIFO/最近的基准进行同等条件比较 → `improvement_pct`(可证明 ≥ 0)。单位位置被标记为 *假定/外部*;输出**需要操作员确认**。 ### 7.7 输出验证 (`validation.py`) 在任何持久化/服务之前,每个模型输出都会被验证:拒绝 NaN/inf,强制要求 band ∈ bands,confidence ∈ [0,1],畅通时间在 (0, cap] 范围内且 P10 ≤ median ≤ P90,risk ∈ [0,100]。 ## 8. 请求与推理生命周期 ``` flowchart TD A["POST /ingest (operator)"] --> B["Validate IncidentIn
normalize cause · parse UTC→IST · range-check lat/lon"] B -->|invalid| DL["dead_letter table"] B -->|valid| C["Idempotent insert
ON CONFLICT(event_id) DO NOTHING"] C --> D["Best-effort inference at ingest:
severity + clearance"] D --> E["Output validation
(reject NaN / out-of-range)"] E --> F["Persist predictions"] C -.optional.-> W["Weather enrich (Open-Meteo)
only if CLEAR_WEATHER_ENABLED"] ``` ## 9 API 参考(22 个 endpoint) **认证:** `Authorization: Bearer
+ /healthz boot check"] Test -->|"pass AND push to master"| Deploy["Deploy job
curl RENDER_DEPLOY_HOOK_URL"] Deploy --> Render["Render rebuilds Docker image
(models baked at build)"] FE["frontend push"] --> Vercel["Vercel build (Vite)"] ``` - **Docker (`backend/Dockerfile`):** python:3.11-slim + `libgomp1` (LightGBM);安装包;复制种子 CSV + MuRIL 缓存;**在构建时训练所有模型** (`CLEAR_SKIP_MODEL_REGISTRY=1`) 并断言产物存在 → 容器即时启动,没有冷启动重训过程。 - **入口点:** `bootstrap.py` (初始化 schema + 如果为空则种子摄取 + 如果没有模型则训练) → Gunicorn (uvicorn worker,绑定 `$PORT`)。 - **Render blueprint (`render.yaml`):** Docker web service, `dockerContext: ./backend`, 免费套餐,健康检查 `/healthz`;环境变量 `CLEAR_DATABASE_URL` (secret)、生成的 token、`WEB_CONCURRENCY`、雨水拥堵标志 + key。**在仪表板中添加 `CLEAR_CORS_ALLOW_ORIGINS` = Vercel URL**。 - **Vercel (`vercel.json`):** 将所有路由的 SPA 重写到 `/index.html`。将 **Production Branch 设置为 `master`** 并配置 `VITE_*` 环境变量,然后重新部署。 - **CI (`ci-cd.yml`):** 并发取消被取代的运行;lint 建议;针对一次性 Postgres 运行 pytest + 无网络的 `/healthz` 启动断言;仅在 `master` 推送时部署。 ## 16. 测试 `backend/tests/` (使用 `pytest -q` 运行):`test_idempotency.py`, `test_output_validation.py`, `test_preprocessing.py`, `test_planning_modules.py`, `test_rain_clog.py`, `test_nlp_severity_text.py`, `test_telegram_bot.py`,以及 `conftest.py` (阻止针对非本地 URL 进行 DB 测试)。`smoketest.py` 是对每个 endpoint 进行的实时端到端检查。 ## 17. 重训生命周期 (`retrain.py`) ``` stateDiagram-v2 [*] --> candidate: train_model() candidate --> shadow: shadow() shadow --> canary: canary() canary --> production: promote() (archives previous prod) production --> archived: replaced by promote() archived --> production: rollback() (restore most recent) ``` 阶段存在于 `model_registry` 中;实时的服务指针是无版本的 `
标签:AI预测, Python, 严重性分类, 交通疏通, 决策支持系统, 应急调度, 无后门, 智慧交通, 请求拦截, 逆向工具