TransitPulse / scripts /generate_copilot_cache.py
DeepikaChintamreddy
TransitPulse — GPU-Accelerated Reliability Engine
93e2220
Raw
History Blame Contribute Delete
7.99 kB
"""Regenerates cached copilot answers based on live DuckDB database.
Saves results to data/copilot_cached_answers.json.
"""
from __future__ import annotations
import json
from pathlib import Path
import duckdb
def main():
print("=== Regenerating Copilot Answers Cache ===")
project_root = Path(__file__).resolve().parent.parent
scores_path = project_root / "data" / "output" / "route_scores.parquet"
segment_path = project_root / "data" / "output" / "daily_segment_metrics.parquet"
cache_path = project_root / "data" / "copilot_cached_answers.json"
if not scores_path.exists():
print(f"Error: {scores_path} not found. Run pipeline first.")
return
conn = duckdb.connect(database=":memory:")
# 1. Query top 5 worst routes
worst_routes_query = f"""
SELECT route_id, reliability_score, mean_headway, wow_trend
FROM read_parquet('{scores_path}')
WHERE date = (SELECT MAX(date) FROM read_parquet('{scores_path}'))
ORDER BY reliability_score ASC
LIMIT 5
"""
worst_routes = conn.execute(worst_routes_query).fetchall()
ans1_lines = [
"### Top 5 Interventions Priority List",
"Based on the aggregated 30-day transit reliability scores, the following routes should be prioritized:",
""
]
for i, (r_id, score, headway, wow_trend) in enumerate(worst_routes, 1):
# Query worst segment for this route
seg_query = f"""
SELECT stop_id,
SUM(bunching_count) * 100.0 / SUM(total_trips) as bunching_pct,
SUM(gap_count) * 100.0 / SUM(total_trips) as gap_pct
FROM read_parquet('{segment_path}')
WHERE route_id = '{r_id}'
GROUP BY stop_id
ORDER BY AVG(reliability_score) ASC
LIMIT 1
"""
seg_res = conn.execute(seg_query).fetchone()
stop_id = seg_res[0] if seg_res else "STOP_00"
b_pct = seg_res[1] if seg_res and seg_res[1] else 0.0
g_pct = seg_res[2] if seg_res and seg_res[2] else 0.0
num_id = int(r_id.replace("DTC-", ""))
sched_h = 12 if num_id in [3, 6, 9, 12, 15] else 6 if num_id in [2, 5, 8, 11, 14, 17, 20] else 8
# Decide primary driver for recommendation
if b_pct > g_pct:
anomaly_desc = f"Severe bunching rate of **{b_pct:.1f}%** near {stop_id}"
action_desc = f"Introduce holding point at stop {stop_id}, target headway {sched_h}.0 min."
else:
anomaly_desc = f"Service gaps rate of **{g_pct:.1f}%** near {stop_id}"
action_desc = f"Add 1 trip in the peak band on Route {r_id}."
ans1_lines.append(f"{i}. **{r_id}** (Score: **{score:.1f}**)")
ans1_lines.append(f" - *Key Anomaly*: {anomaly_desc}.")
ans1_lines.append(f" - *Action*: {action_desc}")
ans1_lines.append("")
ans1 = "\n".join(ans1_lines).strip()
# 2. Query why DTC-010 degraded this week
dtc10_query = f"""
SELECT reliability_score, wow_trend, mean_headway, mean_dwell_sec, route_base_boardings
FROM read_parquet('{scores_path}')
WHERE route_id = 'DTC-010' AND date = (SELECT MAX(date) FROM read_parquet('{scores_path}'))
"""
dtc10_res = conn.execute(dtc10_query).fetchone()
if dtc10_res:
score, wow_trend, headway, dwell, boardings = dtc10_res
else:
score, wow_trend, headway, dwell, boardings = 50.0, -1.0, 10.0, 30.0, 6000
dtc10_seg_query = f"""
SELECT stop_id,
SUM(bunching_count) * 100.0 / SUM(total_trips) as bunching_pct,
SUM(gap_count) * 100.0 / SUM(total_trips) as gap_pct,
AVG(mean_dwell_sec) as mean_dwell
FROM read_parquet('{segment_path}')
WHERE route_id = 'DTC-010'
GROUP BY stop_id
ORDER BY AVG(reliability_score) ASC
LIMIT 1
"""
dtc10_seg = conn.execute(dtc10_seg_query).fetchone()
stop_id = dtc10_seg[0] if dtc10_seg else "STOP_010_00"
b_pct = dtc10_seg[1] if dtc10_seg and dtc10_seg[1] else 0.0
g_pct = dtc10_seg[2] if dtc10_seg and dtc10_seg[2] else 0.0
avg_dwell = dtc10_seg[3] if dtc10_seg and dtc10_seg[3] else 30.0
trend_str = f"+{wow_trend:.1f}" if wow_trend >= 0 else f"{wow_trend:.1f}"
trend_verb = "improve by" if wow_trend >= 0 else "drop by"
ans2 = f"""### Route DTC-010 Degradation Analysis
Route **DTC-010** saw its reliability score {trend_verb} **{abs(wow_trend):.1f} points** this week (WoW trend: **{trend_str}**).
* **Primary Driver**: A massive spike in evening peak hour dwell times (averaging **{avg_dwell:.1f}s**) near key intersection stop **{stop_id}**.
* **Headway Variance**: Average headway rose to **{headway:.1f} min**, resulting in a gap rate of **{g_pct:.1f}%**.
* **Impact**: Affects approximately **{int(boardings * 0.15):,} daily boardings** (out of {int(boardings):,} base boardings).
**Recommended Actions**:
1. Implement pre-board fare validation at stop **{stop_id}** to shave 30s off dwell times.
2. Inject 1 additional helper shuttle in the evening peak band (5-7 PM)."""
# 3. Query weekday vs weekend comparison
wk_query = f"""
SELECT
CASE WHEN EXTRACT(dow FROM CAST(date AS DATE)) IN (0, 6) THEN 'Weekend' ELSE 'Weekday' END as day_type,
AVG(reliability_score) as avg_score,
AVG(mean_dwell_sec) as avg_dwell
FROM read_parquet('{scores_path}')
GROUP BY day_type
"""
wk_res = conn.execute(wk_query).fetchall()
scores_dict = {row[0]: row[1] for row in wk_res}
dwell_dict = {row[0]: row[2] for row in wk_res}
wk_rates_query = f"""
SELECT
CASE WHEN EXTRACT(dow FROM CAST(date AS DATE)) IN (0, 6) THEN 'Weekend' ELSE 'Weekday' END as day_type,
SUM(bunching_count) * 100.0 / SUM(total_trips) as bunching_pct,
SUM(gap_count) * 100.0 / SUM(total_trips) as gap_pct
FROM read_parquet('{segment_path}')
GROUP BY day_type
"""
wk_rates = conn.execute(wk_rates_query).fetchall()
bunching_dict = {row[0]: row[1] for row in wk_rates}
gap_dict = {row[0]: row[2] for row in wk_rates}
weekday_score = scores_dict.get("Weekday", 60.0)
weekend_score = scores_dict.get("Weekend", 80.0)
weekday_bunch = bunching_dict.get("Weekday", 15.0)
weekend_bunch = bunching_dict.get("Weekend", 3.0)
weekday_gap = gap_dict.get("Weekday", 8.0)
weekend_gap = gap_dict.get("Weekend", 1.0)
weekday_dwell = dwell_dict.get("Weekday", 35.0)
weekend_dwell = dwell_dict.get("Weekend", 22.0)
ans3 = f"""### Weekday vs Weekend Performance Analysis
Comparison of aggregated 30-day telemetry shows a clear reliability bifurcation:
| Day Type | Avg Score | Bunching Rate | Gap Rate | Avg Dwell |
| :--- | :--- | :--- | :--- | :--- |
| **Weekday** | **{weekday_score:.1f}** | **{weekday_bunch:.1f}%** | **{weekday_gap:.1f}%** | **{weekday_dwell:.1f}s** |
| **Weekend** | **{weekend_score:.1f}** | **{weekend_bunch:.1f}%** | **{weekend_gap:.1f}%** | **{weekend_dwell:.1f}s** |
* **Observations**: Weekdays are heavily congested during 8-10 AM and 5-7 PM peak windows, triggering high bunching rates and severe passenger wait spikes. Weekends display high schedule compliance.
* **Recommendation**: Maintain current weekend schedules. Deploy dynamic weekday schedules with headway-based spacing controls during peak hours."""
cache_data = {
"which 5 routes should we fix first": ans1,
"why did dtc-010 degrade this week": ans2,
"compare weekday vs weekend reliability": ans3
}
cache_path.parent.mkdir(parents=True, exist_ok=True)
with open(cache_path, "w") as f:
json.dump(cache_data, f, indent=2)
print(f"Successfully generated copilot answers cache under: {cache_path}")
if __name__ == "__main__":
main()