Spaces:
Running
Running
Upload app.py
Browse files- src/app.py +27 -17
src/app.py
CHANGED
|
@@ -107,12 +107,11 @@ async def get_route_view(route_id: str):
|
|
| 107 |
placeholders = ','.join(['?'] * len(trip_ids))
|
| 108 |
|
| 109 |
# We use CAST(? AS VARCHAR) to force DuckDB to match strings to strings
|
| 110 |
-
# Use COALESCE(departure_time, arrival_time) to match prediction logic
|
| 111 |
query = f"""
|
| 112 |
SELECT
|
| 113 |
CAST(st.trip_id AS VARCHAR),
|
| 114 |
CAST(st.stop_id AS VARCHAR),
|
| 115 |
-
|
| 116 |
t.trip_headsign
|
| 117 |
FROM stop_times st
|
| 118 |
JOIN trips t ON CAST(st.trip_id AS VARCHAR) = CAST(t.trip_id AS VARCHAR)
|
|
@@ -140,9 +139,12 @@ async def get_route_view(route_id: str):
|
|
| 140 |
if pred_time and stop_id:
|
| 141 |
sched_hms = schedule_map.get((str(bus['trip_id']), str(stop_id)))
|
| 142 |
if sched_hms:
|
| 143 |
-
#
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
enriched.append({
|
| 148 |
"number": bus['id'],
|
|
@@ -183,7 +185,7 @@ async def get_vehicle_view(vehicle_id: str):
|
|
| 183 |
query = """
|
| 184 |
SELECT
|
| 185 |
t.trip_headsign,
|
| 186 |
-
|
| 187 |
FROM trips t
|
| 188 |
JOIN stop_times st ON CAST(t.trip_id AS VARCHAR) = CAST(st.trip_id AS VARCHAR)
|
| 189 |
WHERE CAST(t.trip_id AS VARCHAR) = ?
|
|
@@ -196,13 +198,14 @@ async def get_vehicle_view(vehicle_id: str):
|
|
| 196 |
destination = row[0]
|
| 197 |
scheduled_hms = row[1]
|
| 198 |
|
| 199 |
-
#
|
| 200 |
-
# Note: predicted_time uses departure.time if available, else arrival.time
|
| 201 |
-
# So we use COALESCE(departure_time, arrival_time) to match
|
| 202 |
if predicted_time:
|
| 203 |
service_day_ts = get_service_day_start_ts()
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
| 206 |
else:
|
| 207 |
# If no next_stop_id, try to get destination from trip_id only
|
| 208 |
query = """
|
|
@@ -246,7 +249,8 @@ async def get_stop_view(stop_code: str):
|
|
| 246 |
# Build vehicles map for quick lookup
|
| 247 |
vehicles = {str(v['trip_id']): v for v in vehicles_list}
|
| 248 |
|
| 249 |
-
|
|
|
|
| 250 |
two_hours_out = now + 7200
|
| 251 |
arrivals = []
|
| 252 |
|
|
@@ -259,9 +263,8 @@ async def get_stop_view(stop_code: str):
|
|
| 259 |
if now <= pred_time <= two_hours_out:
|
| 260 |
|
| 261 |
# 4. Handshake with DB for destination and schedule
|
| 262 |
-
# Use COALESCE(departure_time, arrival_time) to match prediction logic
|
| 263 |
query = """
|
| 264 |
-
SELECT t.trip_headsign,
|
| 265 |
FROM trips t
|
| 266 |
JOIN stop_times st ON CAST(t.trip_id AS VARCHAR) = CAST(st.trip_id AS VARCHAR)
|
| 267 |
JOIN routes r ON t.route_id = r.route_id
|
|
@@ -274,13 +277,19 @@ async def get_stop_view(stop_code: str):
|
|
| 274 |
# Find the actual bus for fullness (if it's on the road)
|
| 275 |
bus = vehicles.get(trip_id)
|
| 276 |
|
| 277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
|
| 279 |
arrivals.append({
|
| 280 |
"route": row[2],
|
| 281 |
"destination": row[0],
|
| 282 |
"eta_mins": round((pred_time - now) / 60),
|
| 283 |
-
"delay_mins": round((
|
| 284 |
"fullness": translate_occupancy(bus['occupancy']) if bus else "Unknown",
|
| 285 |
"vehicle_id": bus['id'] if bus else "In Transit"
|
| 286 |
})
|
|
@@ -293,9 +302,10 @@ async def get_all_alerts():
|
|
| 293 |
"""
|
| 294 |
Returns every active service alert for the entire TTC network.
|
| 295 |
"""
|
|
|
|
| 296 |
data = await ttc_cache.get_data()
|
| 297 |
return {
|
| 298 |
-
"timestamp": datetime.now().timestamp(),
|
| 299 |
"count": len(data["alerts"]),
|
| 300 |
"alerts": data["alerts"]
|
| 301 |
}
|
|
|
|
| 107 |
placeholders = ','.join(['?'] * len(trip_ids))
|
| 108 |
|
| 109 |
# We use CAST(? AS VARCHAR) to force DuckDB to match strings to strings
|
|
|
|
| 110 |
query = f"""
|
| 111 |
SELECT
|
| 112 |
CAST(st.trip_id AS VARCHAR),
|
| 113 |
CAST(st.stop_id AS VARCHAR),
|
| 114 |
+
st.arrival_time as scheduled_time,
|
| 115 |
t.trip_headsign
|
| 116 |
FROM stop_times st
|
| 117 |
JOIN trips t ON CAST(st.trip_id AS VARCHAR) = CAST(t.trip_id AS VARCHAR)
|
|
|
|
| 139 |
if pred_time and stop_id:
|
| 140 |
sched_hms = schedule_map.get((str(bus['trip_id']), str(stop_id)))
|
| 141 |
if sched_hms:
|
| 142 |
+
# DELAY = SCHEDULED - PREDICTED (negative = late, positive = early)
|
| 143 |
+
# Handle GTFS times >= 24 hours (next day)
|
| 144 |
+
h, m, s = map(int, sched_hms.split(':'))
|
| 145 |
+
extra_days = h // 24
|
| 146 |
+
plan_ts = service_day_ts + (extra_days * 86400) + hms_to_seconds(sched_hms)
|
| 147 |
+
raw_delay_mins = round((plan_ts - pred_time) / 60)
|
| 148 |
|
| 149 |
enriched.append({
|
| 150 |
"number": bus['id'],
|
|
|
|
| 185 |
query = """
|
| 186 |
SELECT
|
| 187 |
t.trip_headsign,
|
| 188 |
+
st.arrival_time as scheduled_time
|
| 189 |
FROM trips t
|
| 190 |
JOIN stop_times st ON CAST(t.trip_id AS VARCHAR) = CAST(st.trip_id AS VARCHAR)
|
| 191 |
WHERE CAST(t.trip_id AS VARCHAR) = ?
|
|
|
|
| 198 |
destination = row[0]
|
| 199 |
scheduled_hms = row[1]
|
| 200 |
|
| 201 |
+
# DELAY = SCHEDULED - PREDICTED (negative = late, positive = early)
|
|
|
|
|
|
|
| 202 |
if predicted_time:
|
| 203 |
service_day_ts = get_service_day_start_ts()
|
| 204 |
+
# Handle GTFS times >= 24 hours (next day)
|
| 205 |
+
h, m, s = map(int, scheduled_hms.split(':'))
|
| 206 |
+
extra_days = h // 24
|
| 207 |
+
plan_ts = service_day_ts + (extra_days * 86400) + hms_to_seconds(scheduled_hms)
|
| 208 |
+
delay_mins = round((plan_ts - predicted_time) / 60)
|
| 209 |
else:
|
| 210 |
# If no next_stop_id, try to get destination from trip_id only
|
| 211 |
query = """
|
|
|
|
| 249 |
# Build vehicles map for quick lookup
|
| 250 |
vehicles = {str(v['trip_id']): v for v in vehicles_list}
|
| 251 |
|
| 252 |
+
from datetime import timezone
|
| 253 |
+
now = datetime.now(timezone.utc).timestamp()
|
| 254 |
two_hours_out = now + 7200
|
| 255 |
arrivals = []
|
| 256 |
|
|
|
|
| 263 |
if now <= pred_time <= two_hours_out:
|
| 264 |
|
| 265 |
# 4. Handshake with DB for destination and schedule
|
|
|
|
| 266 |
query = """
|
| 267 |
+
SELECT t.trip_headsign, st.arrival_time as scheduled_time, r.route_short_name
|
| 268 |
FROM trips t
|
| 269 |
JOIN stop_times st ON CAST(t.trip_id AS VARCHAR) = CAST(st.trip_id AS VARCHAR)
|
| 270 |
JOIN routes r ON t.route_id = r.route_id
|
|
|
|
| 277 |
# Find the actual bus for fullness (if it's on the road)
|
| 278 |
bus = vehicles.get(trip_id)
|
| 279 |
|
| 280 |
+
# DELAY = SCHEDULED - PREDICTED (negative = late, positive = early)
|
| 281 |
+
service_day_ts = get_service_day_start_ts()
|
| 282 |
+
scheduled_hms = row[1]
|
| 283 |
+
# Handle GTFS times >= 24 hours (next day)
|
| 284 |
+
h, m, s = map(int, scheduled_hms.split(':'))
|
| 285 |
+
extra_days = h // 24
|
| 286 |
+
plan_ts = service_day_ts + (extra_days * 86400) + hms_to_seconds(scheduled_hms)
|
| 287 |
|
| 288 |
arrivals.append({
|
| 289 |
"route": row[2],
|
| 290 |
"destination": row[0],
|
| 291 |
"eta_mins": round((pred_time - now) / 60),
|
| 292 |
+
"delay_mins": round((plan_ts - pred_time) / 60),
|
| 293 |
"fullness": translate_occupancy(bus['occupancy']) if bus else "Unknown",
|
| 294 |
"vehicle_id": bus['id'] if bus else "In Transit"
|
| 295 |
})
|
|
|
|
| 302 |
"""
|
| 303 |
Returns every active service alert for the entire TTC network.
|
| 304 |
"""
|
| 305 |
+
from datetime import timezone
|
| 306 |
data = await ttc_cache.get_data()
|
| 307 |
return {
|
| 308 |
+
"timestamp": datetime.now(timezone.utc).timestamp(),
|
| 309 |
"count": len(data["alerts"]),
|
| 310 |
"alerts": data["alerts"]
|
| 311 |
}
|