42Cummer commited on
Commit
5febae6
·
verified ·
1 Parent(s): a30ce2e

Upload 2 files

Browse files
Files changed (1) hide show
  1. src/app.py +138 -35
src/app.py CHANGED
@@ -26,7 +26,7 @@ app = FastAPI(title="WheresMyBus v2.0 API")
26
  # Setup CORS for your React frontend
27
  app.add_middleware(
28
  CORSMiddleware,
29
- allow_origins=["*"], # In production, use your actual React URL
30
  allow_methods=["*"],
31
  allow_headers=["*"],
32
  )
@@ -289,48 +289,151 @@ async def get_stop_view(stop_code: str):
289
 
290
  from datetime import timezone
291
  now = datetime.now(timezone.utc).timestamp()
 
292
  two_hours_out = now + 7200
293
- arrivals = []
294
-
295
- # 3. Search the FULL itineraries for our target_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  for trip_id, itinerary in predictions.items():
297
  if target_id in itinerary:
298
  pred_time = itinerary[target_id]
299
 
300
- # Only include if the bus hasn't passed the stop yet and is within 2 hours
301
  if now <= pred_time <= two_hours_out:
302
-
303
- # 4. Handshake with DB for destination and schedule
304
- query = """
305
- SELECT t.trip_headsign, st.arrival_time as scheduled_time, r.route_short_name
306
- FROM trips t
307
- JOIN stop_times st ON CAST(t.trip_id AS VARCHAR) = CAST(st.trip_id AS VARCHAR)
308
- JOIN routes r ON t.route_id = r.route_id
309
- WHERE CAST(t.trip_id AS VARCHAR) = ? AND CAST(st.stop_id AS VARCHAR) = ?
310
- LIMIT 1
311
- """
312
- row = db.execute(query, [trip_id, target_id]).fetchone()
313
-
314
- if row:
315
- # Find the actual bus for fullness (if it's on the road)
316
- bus = vehicles.get(trip_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
- # DELAY = SCHEDULED - PREDICTED (negative = late, positive = early)
319
- service_day_ts = get_service_day_start_ts()
320
- scheduled_hms = row[1]
321
- # Handle GTFS times >= 24 hours (next day)
322
- h, m, s = map(int, scheduled_hms.split(':'))
323
- extra_days = h // 24
324
- plan_ts = service_day_ts + (extra_days * 86400) + hms_to_seconds(scheduled_hms)
325
 
326
- arrivals.append({
327
- "route": row[2],
328
- "destination": row[0],
329
- "eta_mins": round((pred_time - now) / 60),
330
- "delay_mins": round((plan_ts - pred_time) / 60),
331
- "fullness": translate_occupancy(bus['occupancy']) if bus else "Unknown",
332
- "vehicle_id": bus['id'] if bus else "In Transit"
333
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
  arrivals.sort(key=lambda x: x['eta_mins'])
336
  return {
 
26
  # Setup CORS for your React frontend
27
  app.add_middleware(
28
  CORSMiddleware,
29
+ allow_origins=["https://wheresmybus.vercel.app"],
30
  allow_methods=["*"],
31
  allow_headers=["*"],
32
  )
 
289
 
290
  from datetime import timezone
291
  now = datetime.now(timezone.utc).timestamp()
292
+ service_day_ts = get_service_day_start_ts()
293
  two_hours_out = now + 7200
294
+
295
+ # 3. Determine today's service_id based on day of week
296
+ # Service ID 1 = Weekdays (Mon-Fri), 2 = Saturday, 3 = Sunday/Holidays
297
+ try:
298
+ from zoneinfo import ZoneInfo
299
+ eastern_tz = ZoneInfo("America/Toronto")
300
+ except ImportError:
301
+ import pytz
302
+ eastern_tz = pytz.timezone("America/Toronto")
303
+
304
+ now_eastern = datetime.now(timezone.utc).astimezone(eastern_tz)
305
+ weekday = now_eastern.weekday() # 0=Monday, 6=Sunday
306
+
307
+ if weekday < 5: # Monday-Friday
308
+ today_service_id = 1
309
+ elif weekday == 5: # Saturday
310
+ today_service_id = 2
311
+ else: # Sunday
312
+ today_service_id = 3
313
+
314
+ # 4. Build a map of trip_id -> arrival info for merging
315
+ arrival_map = {}
316
+
317
+ # 5. Get scheduled arrivals for this stop, filtered by today's service_id
318
+ schedule_query = """
319
+ SELECT
320
+ CAST(t.trip_id AS VARCHAR) as trip_id,
321
+ t.trip_headsign,
322
+ COALESCE(st.departure_time, st.arrival_time) as scheduled_time,
323
+ r.route_short_name
324
+ FROM stop_times st
325
+ JOIN trips t ON CAST(st.trip_id AS VARCHAR) = CAST(t.trip_id AS VARCHAR)
326
+ JOIN routes r ON t.route_id = r.route_id
327
+ WHERE CAST(st.stop_id AS VARCHAR) = ?
328
+ AND CAST(t.service_id AS INTEGER) = ?
329
+ """
330
+ schedule_rows = db.execute(schedule_query, [target_id, today_service_id]).fetchall()
331
+
332
+ # Process scheduled arrivals
333
+ for row in schedule_rows:
334
+ trip_id = row[0]
335
+ destination = row[1]
336
+ scheduled_hms = row[2]
337
+ route = row[3]
338
+
339
+ # Calculate scheduled timestamp
340
+ h, m, s = map(int, scheduled_hms.split(':'))
341
+ extra_days = h // 24
342
+ scheduled_ts = service_day_ts + (extra_days * 86400) + hms_to_seconds(scheduled_hms)
343
+
344
+ # Only include if within 2 hours and hasn't passed
345
+ if now <= scheduled_ts <= two_hours_out:
346
+ arrival_map[trip_id] = {
347
+ "trip_id": trip_id,
348
+ "route": route,
349
+ "destination": destination,
350
+ "scheduled_ts": scheduled_ts,
351
+ "pred_time": None,
352
+ "has_prediction": False
353
+ }
354
+
355
+ # 5. Now, add/update with real-time predictions (even if not in schedule)
356
  for trip_id, itinerary in predictions.items():
357
  if target_id in itinerary:
358
  pred_time = itinerary[target_id]
359
 
360
+ # Only include predictions within 2 hours
361
  if now <= pred_time <= two_hours_out:
362
+ # If we already have this trip from schedule, update it
363
+ if trip_id in arrival_map:
364
+ arrival_map[trip_id]["pred_time"] = pred_time
365
+ arrival_map[trip_id]["has_prediction"] = True
366
+ else:
367
+ # This is a real-time-only prediction (not in static schedule)
368
+ # Try to get route/destination from database
369
+ query = """
370
+ SELECT t.trip_headsign, r.route_short_name
371
+ FROM trips t
372
+ JOIN routes r ON t.route_id = r.route_id
373
+ WHERE CAST(t.trip_id AS VARCHAR) = ?
374
+ LIMIT 1
375
+ """
376
+ row = db.execute(query, [trip_id]).fetchone()
377
+
378
+ if row:
379
+ destination = row[0]
380
+ route = row[1]
381
+ else:
382
+ destination = "Unknown"
383
+ route = "Unknown"
384
+
385
+ # Try to get scheduled time if available
386
+ scheduled_query = """
387
+ SELECT COALESCE(st.departure_time, st.arrival_time) as scheduled_time
388
+ FROM stop_times st
389
+ WHERE CAST(st.trip_id AS VARCHAR) = ? AND CAST(st.stop_id AS VARCHAR) = ?
390
+ LIMIT 1
391
+ """
392
+ sched_row = db.execute(scheduled_query, [trip_id, target_id]).fetchone()
393
 
394
+ if sched_row:
395
+ scheduled_hms = sched_row[0]
396
+ h, m, s = map(int, scheduled_hms.split(':'))
397
+ extra_days = h // 24
398
+ scheduled_ts = service_day_ts + (extra_days * 86400) + hms_to_seconds(scheduled_hms)
399
+ else:
400
+ scheduled_ts = pred_time # Use prediction as fallback
401
 
402
+ arrival_map[trip_id] = {
403
+ "trip_id": trip_id,
404
+ "route": route,
405
+ "destination": destination,
406
+ "scheduled_ts": scheduled_ts,
407
+ "pred_time": pred_time,
408
+ "has_prediction": True
409
+ }
410
+
411
+ # 6. Build final arrivals list
412
+ arrivals = []
413
+ for trip_id, info in arrival_map.items():
414
+ # Use prediction if available, otherwise use scheduled
415
+ if info["has_prediction"] and info["pred_time"]:
416
+ eta_mins = round((info["pred_time"] - now) / 60)
417
+ delay_mins = round((info["scheduled_ts"] - info["pred_time"]) / 60)
418
+ else:
419
+ eta_mins = round((info["scheduled_ts"] - now) / 60)
420
+ delay_mins = 0
421
+
422
+ # Skip entries with Unknown route
423
+ if info["route"] == "Unknown":
424
+ continue
425
+
426
+ # Find the actual bus for fullness (if it's on the road)
427
+ bus = vehicles.get(trip_id)
428
+
429
+ arrivals.append({
430
+ "route": info["route"],
431
+ "destination": info["destination"],
432
+ "eta_mins": eta_mins,
433
+ "delay_mins": delay_mins,
434
+ "fullness": translate_occupancy(bus['occupancy']) if bus else "Unknown",
435
+ "vehicle_id": bus['id'] if bus else None
436
+ })
437
 
438
  arrivals.sort(key=lambda x: x['eta_mins'])
439
  return {