Hemann commited on
Commit
50e8bb1
·
verified ·
1 Parent(s): ec221b2

Update app.py

Browse files

Fixed timing thing

Files changed (1) hide show
  1. app.py +233 -293
app.py CHANGED
@@ -2,20 +2,22 @@ from flask import Flask, request, jsonify,render_template
2
  from flask_cors import CORS
3
  import requests
4
  import json
5
- from datetime import datetime , timedelta
6
  import math
7
- import os
8
  import time
9
- import csv
10
  import sqlite3
11
  from bs4 import BeautifulSoup
12
 
13
-
14
- ORS_API_KEY=os.getenv('OSI_API_KEY')
15
  Base_url="https://api.openrouteservice.org"
16
 
17
 
18
  app=Flask(__name__)
 
 
 
 
 
19
 
20
  APSRTC_HEADERS = {
21
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
@@ -83,19 +85,19 @@ class TNSTCSessionManager:
83
 
84
  self.session.get("https://www.tnstc.in/OTRSOnline/jqreq.do?hiddenAction=SearchService", timeout=10)
85
 
86
- self.last_refresh_time = datetime.now().timestamp()
87
  except Exception as e:
88
  print(f"Failed to refresh session: {e}")
89
 
90
  def get_valid_session(self):
91
- current_time = datetime.now().timestamp()
92
  if (current_time - self.last_refresh_time) > self.refresh_interval:
93
  self.refresh_session()
94
  return self.session
95
 
96
 
97
  class BusCache:
98
- def __init__(self, db_path='/tmp/bus_cache.db'):
99
  self.db_path = db_path
100
  self.init_db()
101
 
@@ -129,8 +131,8 @@ class BusCache:
129
  conn = sqlite3.connect(self.db_path)
130
  cursor = conn.cursor()
131
 
132
- today = datetime.now().strftime("%Y-%m-%d")
133
- cutoff_time = datetime.now() - timedelta(hours=max_age_hours)
134
 
135
  cursor.execute('''
136
  SELECT buses_data, created_at, hit_count, id
@@ -175,7 +177,7 @@ class BusCache:
175
  conn = sqlite3.connect(self.db_path)
176
  cursor = conn.cursor()
177
 
178
- today = datetime.now().strftime("%Y-%m-%d")
179
 
180
  cursor.execute('''
181
  SELECT id FROM bus_searches
@@ -211,7 +213,7 @@ class BusCache:
211
  conn = sqlite3.connect(self.db_path)
212
  cursor = conn.cursor()
213
 
214
- cutoff_date = (datetime.now() - timedelta(days=days_old)).strftime("%Y-%m-%d")
215
 
216
  cursor.execute('''
217
  DELETE FROM bus_searches
@@ -233,7 +235,7 @@ TNSTC_PLACE_CODES = {}
233
  TNSTC_JSON_MAP = {}
234
 
235
  try:
236
-
237
  with open('SETC_tn.csv', 'r', encoding='utf-8') as f:
238
  reader = csv.DictReader(f)
239
  for row in reader:
@@ -263,9 +265,9 @@ def sunposition(lat,lon,timestamp):
263
  long_rad=math.radians(lon)
264
  day_of_year=timestamp.timetuple().tm_yday
265
  hour=timestamp.hour+timestamp.minute/60.0
266
- declination=23.45 *math.sin(math.radians(360 * (day_of_year)/365))
267
  dec_rad=math.radians(declination)
268
- hour_angle=15*(hour-12)
269
 
270
  ha_rad=math.radians(hour_angle)
271
 
@@ -298,82 +300,116 @@ def bearing_calculation(lat1,long1,lat2,long2):
298
  return bearing
299
 
300
 
301
- def shade_finder(coordinates,start_time_str,duration_min):
302
-
303
- hour,minute=map(int,start_time_str.split(':'))
304
-
305
- start_time=datetime.now().replace(hour=hour,minute=minute,second=0,microsecond=0)
306
-
307
- shade_data=[]
 
 
 
 
 
 
 
 
 
 
308
 
309
- total_segments=len(coordinates)-1
310
 
311
- left_side_time=0
312
- right_side_time=0
313
- if hour >= 18 or hour <6:
314
- return{
315
- 'preferred_side': 'N/A',
316
- 'preferred_window': 'Night journey - shade analysis not applicable',
317
  'left_shade_minutes': 0,
318
  'right_shade_minutes': 0,
319
  'shade_percentage': 0,
320
  'total_duration': duration_min,
321
- 'is_night': True,
322
  'segments': []
323
- }
 
 
 
 
324
 
325
- for i in range(total_segments):
 
 
326
 
327
- lat1,long1=coordinates[i][1],coordinates[i][0]
328
- lat2,long2=coordinates[i+1][1],coordinates[i+1][0]
329
 
330
- segment_time=start_time.timestamp() + (duration_min * 60 * i/total_segments)
331
- current_time=datetime.fromtimestamp(segment_time)
332
 
333
- bearing= bearing_calculation(lat1,long1,lat2,long2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
- sun_azimuth,sun_altitude=sunposition(lat1,long1,current_time)
 
 
 
 
 
 
 
 
 
 
336
 
337
- if sun_altitude <0:
338
- continue
339
- relative_angle=(sun_azimuth - bearing + 360) % 360
 
 
 
 
 
340
 
341
- if 90 <= relative_angle <=270:
342
- shade_side="Right"
343
- left_side_time +=(duration_min/total_segments)
344
- else:
345
- shade_side="LEFT"
346
- right_side_time+=(duration_min/total_segments)
347
- shade_data.append({
348
- 'segment':i,
349
- 'bearing':round(bearing,2),
350
- 'sun_azimuth': round(sun_azimuth,2),
351
- 'sun_altitude':round(sun_altitude,2),
352
- 'shade_side':shade_side,
353
- 'time':current_time.strftime('%H:%M')
354
-
355
- })
356
- if left_side_time > right_side_time:
357
- preferred_side="LEFT"
358
- preferred_window="Windows on the left side"
359
- shade_percentage=round((left_side_time/duration_min)*100,1)
360
- else:
361
- preferred_side="Right"
362
- preferred_window="Windows on the Right"
363
- shade_percentage=round((right_side_time/duration_min)*100,1)
364
- return {
365
  'preferred_side': preferred_side,
366
  'preferred_window': preferred_window,
367
  'left_shade_minutes': round(left_side_time, 1),
368
  'right_shade_minutes': round(right_side_time, 1),
369
  'shade_percentage': shade_percentage,
370
  'total_duration': duration_min,
 
371
  'segments': shade_data[:5]
372
  }
373
 
374
  def scrape_fare_only(from_id, to_id):
375
 
376
- today_str = datetime.now().strftime("%d/%m/%Y")
377
  web_url = "https://www.apsrtconline.in/oprs-web/forward/booking/avail/services.do"
378
 
379
  headers = {
@@ -397,13 +433,16 @@ def scrape_fare_only(from_id, to_id):
397
  soup = BeautifulSoup(response.text, 'html.parser')
398
  fare_map = {}
399
 
 
400
  services = soup.find_all('div', class_='rSetForward')
401
 
402
  for service in services:
403
  try:
 
404
  bus_no_tag = service.find('div', class_='srvceNO')
405
  bus_no = bus_no_tag.get_text(strip=True) if bus_no_tag else None
406
 
 
407
  fare_tag = service.find('span', class_='TickRate')
408
  fare = fare_tag.get_text(strip=True) if fare_tag else "N/A"
409
 
@@ -452,7 +491,7 @@ def detect_state_from_label(label):
452
  'ERODE', 'VELLORE', 'THOOTHUKUDI', 'THANJAVUR',
453
  'DINDIGUL', 'CUDDALORE', 'KANCHIPURAM', 'TIRUPPUR',
454
  'KARUR', 'RAJAPALAYAM', 'SIVAKASI', 'NAGERCOIL',
455
- 'KUMBAKONAM', 'PUDUKKOTTAI', 'HOSUR','TN','CHENNAI']
456
 
457
  karnataka_keywords = ['KARNATAKA', 'BENGALURU', 'BANGALORE', 'MANGALORE', 'MYSURU', 'MYSORE', 'HUBLI', 'BELGAUM', 'SHIVAMOGGA', 'HASSAN', 'UDUPI','KR','KA']
458
  tg_cities = ['HYDERABAD', 'WARANGAL', 'NIZAMABAD', 'KARIMNAGAR', 'KHAMMAM', 'SECUNDERABAD', 'KUKATPALLY', 'DILSUKHNAGAR', 'MIYAPUR', 'GACHIBOWLI','TG','TS']
@@ -473,6 +512,7 @@ def detect_state_from_label(label):
473
  if kcity in city:
474
  return 'KSRTC'
475
 
 
476
  for acity in andhra_keywords:
477
  if acity in city:
478
  return 'APSRTC'
@@ -486,7 +526,6 @@ def get_route():
486
 
487
  start_coords = req_data.get('start', [77.2090, 28.6139])
488
  end_coords = req_data.get('end', [78.0081, 27.1767])
489
- start_time_input = req_data.get('startTime')
490
 
491
  url = f"{Base_url}/v2/directions/driving-hgv/geojson"
492
  params = {
@@ -512,14 +551,20 @@ def get_route():
512
  duration_sec=summary['duration']
513
  duration_minutes = int(duration_sec / 60)
514
  hours=int(duration_sec//3600)
515
-
516
- current_time_str = start_time_input if start_time_input else datetime.now().strftime("%H:%M")
517
-
518
- shade_info = shade_finder(r_coordinate, current_time_str, duration_minutes)
 
 
 
 
 
519
  minutes=int((duration_sec %3600)//60)
520
  duration_str=f"{hours}h {minutes}min" if hours>0 else f"{minutes}min"
521
 
522
  leafly = [[coord[1], coord[0]] for coord in r_coordinate]
 
523
 
524
  return jsonify({
525
  'path': leafly,
@@ -551,6 +596,7 @@ def api_search():
551
  for feature in data.get('features',[]):
552
  label=feature['properties']['label']
553
  state=detect_state_from_label(label)
 
554
  suggestions.append({
555
  "label":feature['properties']['label'],
556
  "coords":feature['geometry']['coordinates'],
@@ -565,7 +611,7 @@ def api_search():
565
  def get_place_id():
566
  req_data=request.get_json()
567
  if not req_data or 'address' not in req_data:
568
- return jsonify({"error":"YEH SAHI BHAT NAHI HAI.. I got no place to give ID for"}),400
569
 
570
  search_string = req_data.get('address','').upper()
571
  state=req_data.get('state','APSRTC')
@@ -624,16 +670,7 @@ def findbus():
624
  for bus in data['data']:
625
  bus_no=bus.get('oprsNo','').strip()
626
  bus['fare']=fare_map.get(bus_no,'N/A')
627
- try:
628
- dep_time_obj = datetime.strptime(bus['serviceStartTime'], "%H:%M")
629
- bus['serviceStartTime'] = dep_time_obj.strftime("%I:%M %p")
630
- except (ValueError, TypeError):
631
- pass
632
- try:
633
- arr_time_obj = datetime.strptime(bus['serviceEndTime'], "%H:%M")
634
- bus['serviceEndTime'] = arr_time_obj.strftime("%I:%M %p")
635
- except (ValueError, TypeError):
636
- pass
637
  data['averageFare'] = avg_fare
638
 
639
  bus_cache.save_buses('APSRTC', from_name, to_name, data)
@@ -655,7 +692,7 @@ def findbus2():
655
  if not from_id or not to_id:
656
  return jsonify({"error": "Missing Start or End IDs"}), 400
657
 
658
- today_str = datetime.now().strftime("%d/%m/%Y")
659
 
660
  web_url = "https://www.apsrtconline.in/oprs-web/forward/booking/avail/services.do"
661
 
@@ -708,7 +745,7 @@ def findbus2():
708
  "serviceType": srv_type,
709
  "serviceStartTime": start_time,
710
  "serviceEndTime": end_time,
711
- "depotName": "APSRTC",
712
  "journeyDate": today_str
713
  }
714
  bus_list.append(bus_obj)
@@ -736,6 +773,8 @@ def get_bus_stops():
736
  return jsonify({"error": "Missing docId"}), 400
737
 
738
  url = "https://utsappapicached01.apsrtconline.in/uts-vts-api/servicewaypointdetails/bydocid"
 
 
739
  payload = {"docId": doc_id}
740
 
741
  try:
@@ -763,6 +802,7 @@ def find_buses_kerala():
763
 
764
  base_url = f"https://www.kbuses.in/v3/Find/source/{src_slug}/destination/{dst_slug}/type/all/timing/all"
765
 
 
766
 
767
  headers = {
768
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -868,9 +908,9 @@ def find_buses_kerala():
868
  "duration": duration if duration else "N/A",
869
  "fare": fare if fare else "N/A",
870
  "depotName": "KERALA",
871
- "serviceDocId": detail_url,
872
  "route": route_info,
873
- "journeyDate": datetime.now().strftime("%d/%m/%Y"),
874
  "page": current_page
875
  }
876
 
@@ -1003,6 +1043,8 @@ def get_kerala_bus_stops():
1003
  @app.route('/api/find-buses-tnstc', methods=['POST'])
1004
  def find_buses_tnstc():
1005
  req_data = request.get_json()
 
 
1006
  from_name = req_data.get('fromName', '')
1007
  to_name = req_data.get('toName', '')
1008
 
@@ -1045,7 +1087,7 @@ def find_buses_tnstc():
1045
  if not from_id or not to_id:
1046
  return jsonify({"error": f"Place ID not found for {from_place} or {to_place}"}), 400
1047
 
1048
- today_str = datetime.now().strftime("%d/%m/%Y")
1049
  url = "https://www.tnstc.in/OTRSOnline/jqreq.do"
1050
 
1051
  payload = {
@@ -1075,9 +1117,10 @@ def find_buses_tnstc():
1075
 
1076
  response = session.post(url, data=payload, timeout=20)
1077
 
 
1078
  if "Session Expired" in response.text or response.status_code != 200:
1079
- print(" Session expired during request. Retrying...")
1080
- tnstc_manager.refresh_session()
1081
  session = tnstc_manager.get_valid_session()
1082
  response = session.post(url, data=payload, timeout=20)
1083
 
@@ -1102,18 +1145,9 @@ def find_buses_tnstc():
1102
 
1103
  if len(time_divs) >= 2:
1104
  start_span = time_divs[0].find('span', class_='text-dark')
1105
- start_time_raw = start_span.get_text(strip=True) if start_span else "N/A"
1106
  end_span = time_divs[-1].find('span', class_='text-dark')
1107
- end_time_raw = end_span.get_text(strip=True) if end_span else "N/A"
1108
-
1109
- try:
1110
- start_time = datetime.strptime(start_time_raw, "%H:%M").strftime("%I:%M %p")
1111
- except (ValueError, TypeError):
1112
- start_time = start_time_raw
1113
- try:
1114
- end_time = datetime.strptime(end_time_raw, "%H:%M").strftime("%I:%M %p")
1115
- except (ValueError, TypeError):
1116
- end_time = end_time_raw
1117
 
1118
  duration_elem = item.find('span', class_='duration')
1119
  duration = duration_elem.get_text(strip=True) if duration_elem else "N/A"
@@ -1170,7 +1204,7 @@ def find_buses_ksrtc_karnataka():
1170
  journey_date_iso = req_data.get('journeyDate')
1171
 
1172
  if not journey_date_iso:
1173
- journey_date_iso = datetime.now().strftime("%Y-%m-%d")
1174
 
1175
  from_id = (KSRTC_KARNATAKA_MAP.get(from_name_raw.lower()) or
1176
  KSRTC_KARNATAKA_MAP.get(from_name_raw.upper()) or
@@ -1257,6 +1291,7 @@ def find_buses_ksrtc_karnataka():
1257
  if len(response.content) < 10:
1258
  return jsonify({"error": "Empty response", "data": [], "totalBuses": 0})
1259
 
 
1260
  try:
1261
  raw_data = response.json()
1262
  except json.JSONDecodeError as e:
@@ -1264,6 +1299,7 @@ def find_buses_ksrtc_karnataka():
1264
  print(f"Response preview: {response.text[:300]}")
1265
  return jsonify({"error": "Invalid JSON"}), 500
1266
 
 
1267
  if not isinstance(raw_data, list):
1268
 
1269
  return jsonify({
@@ -1296,8 +1332,8 @@ def find_buses_ksrtc_karnataka():
1296
  minutes = (duration.seconds % 3600) // 60
1297
  duration_str = f"{hours}h {minutes}m"
1298
 
1299
- start_time = dep_dt.strftime("%I:%M %p")
1300
- end_time = arr_dt.strftime("%I:%M %p")
1301
  arrival_date = arr_dt.strftime("%d %b")
1302
 
1303
  bus_obj = {
@@ -1326,7 +1362,7 @@ def find_buses_ksrtc_karnataka():
1326
  formatted_buses.append(bus_obj)
1327
 
1328
  except Exception as parse_err:
1329
- print(f" Parse error: {parse_err}")
1330
  continue
1331
 
1332
  return jsonify({
@@ -1407,224 +1443,128 @@ def get_ksrtc_ka_stops():
1407
 
1408
 
1409
  def get_tgsrtc_db():
1410
- conn = sqlite3.connect('tgsrtc.db')
1411
- conn.row_factory = sqlite3.Row
1412
- return conn
 
 
 
 
1413
 
1414
  @app.route('/api/tgsrtc/resolve-id', methods=['POST'])
1415
  def resolve_tgsrtc_id():
1416
  req_data = request.get_json()
1417
- place_name = req_data.get('address', '').split(',')[0].strip()
1418
 
1419
  if not place_name:
1420
- return jsonify({"ids": [], "matches": []}), 400
1421
-
1422
  conn = get_tgsrtc_db()
 
 
 
1423
  cursor = conn.cursor()
1424
 
1425
- patterns = [
1426
- (place_name, 'exact'),
1427
- (f"{place_name}%", 'starts_with'),
1428
- (f"%{place_name}%", 'contains')
1429
- ]
1430
-
1431
- all_stops = []
1432
- for pattern, match_type in patterns:
1433
- cursor.execute("""
1434
- SELECT stop_id, stop_name, stop_lat, stop_lon
1435
- FROM stops
1436
- WHERE stop_name LIKE ?
1437
- ORDER BY stop_name
1438
- """, (pattern,))
1439
-
1440
- stops = cursor.fetchall()
1441
- if stops:
1442
- all_stops = stops
1443
- break
1444
 
 
 
 
 
1445
  conn.close()
1446
-
1447
- if all_stops:
 
1448
  return jsonify({
1449
- "id": all_stops[0]['stop_id'],
1450
- "match": all_stops[0]['stop_name'],
1451
- "state": "TGSRTC",
1452
- "allMatches": [
1453
- {
1454
- "id": stop['stop_id'],
1455
- "name": stop['stop_name'],
1456
- "lat": stop['stop_lat'],
1457
- "lon": stop['stop_lon']
1458
- } for stop in all_stops
1459
- ]
1460
  })
1461
-
1462
- return jsonify({"id": None, "allMatches": []})
1463
-
1464
  @app.route('/api/find-buses-tgsrtc', methods=['POST'])
1465
  def find_buses_tgsrtc():
1466
  req_data = request.get_json()
1467
- from_id = str(req_data.get('fromId'))
1468
- to_id = str(req_data.get('toId'))
1469
-
1470
  if not from_id or not to_id:
1471
- return jsonify({"error": "Missing Stop IDs"}), 400
1472
-
1473
  conn = get_tgsrtc_db()
1474
  cursor = conn.cursor()
1475
-
1476
- cursor.execute("SELECT stop_id, stop_name FROM stops WHERE stop_id = ?", (from_id,))
1477
- from_stop = cursor.fetchone()
1478
- cursor.execute("SELECT stop_id, stop_name FROM stops WHERE stop_id = ?", (to_id,))
1479
- to_stop = cursor.fetchone()
1480
-
1481
- if not from_stop or not to_stop:
1482
- conn.close()
1483
- return jsonify({"error": "Invalid stop IDs"}), 400
1484
-
1485
- print(f"FROM TG ID: {from_id} ({from_stop['stop_name']})")
1486
- print(f"TO TG ID: {to_id} ({to_stop['stop_name']})")
1487
-
1488
- def extract_base_name(stop_name):
1489
- for delimiter in [' - ', ',', '(']:
1490
- if delimiter in stop_name:
1491
- stop_name = stop_name.split(delimiter)[0]
1492
- return stop_name.strip()
1493
-
1494
- from_base = extract_base_name(from_stop['stop_name'])
1495
- to_base = extract_base_name(to_stop['stop_name'])
1496
-
1497
- cursor.execute("SELECT stop_id FROM stops WHERE stop_name LIKE ? OR stop_name LIKE ?",
1498
- (f"{from_base}%", f"%{from_base}%"))
1499
- from_stop_ids = [str(row['stop_id']) for row in cursor.fetchall()]
1500
-
1501
- cursor.execute("SELECT stop_id FROM stops WHERE stop_name LIKE ? OR stop_name LIKE ?",
1502
- (f"{to_base}%", f"%{to_base}%"))
1503
- to_stop_ids = [str(row['stop_id']) for row in cursor.fetchall()]
1504
-
1505
- if not from_stop_ids or not to_stop_ids:
1506
- conn.close()
1507
- return jsonify({"error": "No matching stops found", "data": [], "totalBuses": 0})
1508
-
1509
- from_placeholders = ','.join('?' * len(from_stop_ids))
1510
- to_placeholders = ','.join('?' * len(to_stop_ids))
1511
-
1512
- utc_now = datetime.utcnow()
1513
- ist_now = utc_now + timedelta(hours=5, minutes=30)
1514
- current_time = ist_now.strftime("%H:%M:%S")
1515
-
1516
- query = f'''
1517
- SELECT DISTINCT
1518
  r.route_short_name,
 
1519
  t.trip_id,
1520
- t.trip_headsign,
1521
  t.bus_class,
1522
- st1.departure_time,
1523
- st1.stop_id as from_stop_id,
1524
- st2.arrival_time,
1525
- st2.stop_id as to_stop_id
1526
  FROM stop_times st1
1527
  JOIN stop_times st2 ON st1.trip_id = st2.trip_id
1528
  JOIN trips t ON st1.trip_id = t.trip_id
1529
- LEFT JOIN routes r ON t.route_id = r.route_id
1530
- WHERE st1.stop_id IN ({from_placeholders})
1531
- AND st2.stop_id IN ({to_placeholders})
1532
  AND CAST(st1.stop_sequence AS INTEGER) < CAST(st2.stop_sequence AS INTEGER)
1533
- AND st1.departure_time >= ?
1534
  ORDER BY st1.departure_time
1535
- LIMIT 200
1536
  '''
1537
-
1538
- cursor.execute(query, from_stop_ids + to_stop_ids + [current_time])
1539
- rows = cursor.fetchall()
1540
-
1541
- results = []
1542
- seen_trips = set()
1543
-
1544
- for row in rows:
1545
- trip_key = f"{row['trip_id']}_{row['departure_time']}"
1546
- if trip_key in seen_trips:
1547
- continue
1548
- seen_trips.add(trip_key)
1549
-
1550
- dep_time_obj = datetime.strptime(row['departure_time'], "%H:%M:%S")
1551
- dep_time_str = dep_time_obj.strftime("%I:%M %p")
1552
-
1553
- arr_time_obj = datetime.strptime(row['arrival_time'], "%H:%M:%S")
1554
- arr_time_str = arr_time_obj.strftime("%I:%M %p")
1555
-
1556
- diff = arr_time_obj - dep_time_obj
1557
- if diff.days < 0:
1558
- diff = timedelta(days=1) + diff
1559
-
1560
- hours = diff.seconds // 3600
1561
- minutes = (diff.seconds // 60) % 60
1562
- duration = f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m"
1563
-
1564
- cursor.execute("SELECT stop_name FROM stops WHERE stop_id = ?", (row['from_stop_id'],))
1565
- actual_from = cursor.fetchone()
1566
- cursor.execute("SELECT stop_name FROM stops WHERE stop_id = ?", (row['to_stop_id'],))
1567
- actual_to = cursor.fetchone()
1568
-
1569
- results.append({
1570
- "oprsNo": row['route_short_name'] or "N/A",
1571
- "serviceType": row['bus_class'] or "Ordinary",
1572
- "serviceStartTime": dep_time_str,
1573
- "serviceEndTime": arr_time_str,
1574
- "duration": duration,
1575
- "via": row['trip_headsign'] or "TGSRTC Route",
1576
- "route": f"{actual_from['stop_name']} → {actual_to['stop_name']}",
1577
- "fromStopName": actual_from['stop_name'],
1578
- "toStopName": actual_to['stop_name'],
1579
- "source": "TGSRTC",
1580
- "serviceDocId": row['trip_id'],
1581
- "journeyDate": datetime.now().strftime("%d-%m-%Y")
1582
- })
1583
-
1584
- conn.close()
1585
- print(results)
1586
- return jsonify({"data": results, "source": "TGSRTC", "totalBuses": len(results)})
1587
 
1588
- @app.route('/api/get-tgsrtc-stops', methods=['POST'])
1589
- def get_tgsrtc_stops():
1590
- req_data = request.get_json()
1591
- trip_id = str(req_data.get('tripId'))
1592
-
1593
- if not trip_id:
1594
- return jsonify({"error": "Missing trip ID"}), 400
1595
-
1596
- conn = get_tgsrtc_db()
1597
- cursor = conn.cursor()
1598
-
1599
- cursor.execute("""
1600
- SELECT
1601
- st.stop_sequence,
1602
- st.arrival_time,
1603
- st.departure_time,
1604
- s.stop_name
1605
- FROM stop_times st
1606
- JOIN stops s ON st.stop_id = s.stop_id
1607
- WHERE st.trip_id = ?
1608
- ORDER BY CAST(st.stop_sequence AS INTEGER)
1609
- """, (trip_id,))
1610
-
1611
- stops = cursor.fetchall()
1612
- conn.close()
1613
-
1614
- if not stops:
1615
- return jsonify({"error": "No stops found", "data": []})
1616
-
1617
- stops_data = []
1618
- for stop in stops:
1619
- time_obj = datetime.strptime(stop['arrival_time'], "%H:%M:%S")
1620
- time_str = time_obj.strftime("%I:%M %p")
1621
 
1622
- stops_data.append({
1623
- "placeName": stop['stop_name'],
1624
- "scheduleArrTime": time_str
1625
- })
1626
-
1627
- return jsonify({"data": stops_data})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1628
  @app.route('/')
1629
  def index():
1630
  return render_template('index.html')
 
2
  from flask_cors import CORS
3
  import requests
4
  import json
5
+ from datetime import datetime, timedelta, timezone, time as dt_time
6
  import math
 
7
  import time
 
8
  import sqlite3
9
  from bs4 import BeautifulSoup
10
 
11
+ ORS_API_KEY=os.getenv('ORS_API_KEY')
 
12
  Base_url="https://api.openrouteservice.org"
13
 
14
 
15
  app=Flask(__name__)
16
+ IST = timezone(timedelta(hours=5, minutes=30))
17
+
18
+
19
+ def now_ist_naive():
20
+ return datetime.now(IST).replace(tzinfo=None)
21
 
22
  APSRTC_HEADERS = {
23
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
 
85
 
86
  self.session.get("https://www.tnstc.in/OTRSOnline/jqreq.do?hiddenAction=SearchService", timeout=10)
87
 
88
+ self.last_refresh_time = now_ist_naive().timestamp()
89
  except Exception as e:
90
  print(f"Failed to refresh session: {e}")
91
 
92
  def get_valid_session(self):
93
+ current_time = now_ist_naive().timestamp()
94
  if (current_time - self.last_refresh_time) > self.refresh_interval:
95
  self.refresh_session()
96
  return self.session
97
 
98
 
99
  class BusCache:
100
+ def __init__(self, db_path='bus_cache.db'):
101
  self.db_path = db_path
102
  self.init_db()
103
 
 
131
  conn = sqlite3.connect(self.db_path)
132
  cursor = conn.cursor()
133
 
134
+ today = now_ist_naive().strftime("%Y-%m-%d")
135
+ cutoff_time = now_ist_naive() - timedelta(hours=max_age_hours)
136
 
137
  cursor.execute('''
138
  SELECT buses_data, created_at, hit_count, id
 
177
  conn = sqlite3.connect(self.db_path)
178
  cursor = conn.cursor()
179
 
180
+ today = now_ist_naive().strftime("%Y-%m-%d")
181
 
182
  cursor.execute('''
183
  SELECT id FROM bus_searches
 
213
  conn = sqlite3.connect(self.db_path)
214
  cursor = conn.cursor()
215
 
216
+ cutoff_date = (now_ist_naive() - timedelta(days=days_old)).strftime("%Y-%m-%d")
217
 
218
  cursor.execute('''
219
  DELETE FROM bus_searches
 
235
  TNSTC_JSON_MAP = {}
236
 
237
  try:
238
+ import csv
239
  with open('SETC_tn.csv', 'r', encoding='utf-8') as f:
240
  reader = csv.DictReader(f)
241
  for row in reader:
 
265
  long_rad=math.radians(lon)
266
  day_of_year=timestamp.timetuple().tm_yday
267
  hour=timestamp.hour+timestamp.minute/60.0
268
+ declination=23.45 *math.sin(math.radians(360 * (day_of_year)/365)) # yes that is 23.45 since day is not like 24 hours rather 1/4 less 24
269
  dec_rad=math.radians(declination)
270
+ hour_angle=15*(hour-12) # 15 degree per hour
271
 
272
  ha_rad=math.radians(hour_angle)
273
 
 
300
  return bearing
301
 
302
 
303
+ def shade_finder(coordinates, start_time_str, duration_min, journey_date_str=None):
304
+ try:
305
+ time_parts = start_time_str.split(':')
306
+ hour = int(time_parts[0])
307
+ minute = int(time_parts[1]) if len(time_parts) > 1 else 0
308
+ except Exception:
309
+ now = now_ist_naive()
310
+ hour = now.hour
311
+ minute = now.minute
312
+
313
+ if journey_date_str:
314
+ try:
315
+ journey_date = datetime.strptime(journey_date_str, "%Y-%m-%d").date()
316
+ except ValueError:
317
+ journey_date = now_ist_naive().date()
318
+ else:
319
+ journey_date = now_ist_naive().date()
320
 
321
+ start_time = datetime.combine(journey_date, dt_time(hour=hour, minute=minute))
322
 
323
+ shade_data = []
324
+ total_segments = len(coordinates) - 1
325
+ if total_segments <= 0 or duration_min <= 0:
326
+ return {
327
+ 'preferred_side': 'N/A',
328
+ 'preferred_window': 'Insufficient route data for shade analysis',
329
  'left_shade_minutes': 0,
330
  'right_shade_minutes': 0,
331
  'shade_percentage': 0,
332
  'total_duration': duration_min,
333
+ 'is_night': False,
334
  'segments': []
335
+ }
336
+
337
+ left_side_time = 0
338
+ right_side_time = 0
339
+ sunlit_segments = 0
340
 
341
+ for i in range(total_segments):
342
+ lat1, long1 = coordinates[i][1], coordinates[i][0]
343
+ lat2, long2 = coordinates[i + 1][1], coordinates[i + 1][0]
344
 
345
+ segment_time = start_time.timestamp() + (duration_min * 60 * i / total_segments)
346
+ current_time = datetime.fromtimestamp(segment_time)
347
 
348
+ bearing = bearing_calculation(lat1, long1, lat2, long2)
349
+ sun_azimuth, sun_altitude = sunposition(lat1, long1, current_time)
350
 
351
+ if sun_altitude < 0:
352
+ continue
353
+
354
+ sunlit_segments += 1
355
+ # Signed angle: +ve means sun on bus right side (clockwise from heading),
356
+ # so LEFT side seats get better shade.
357
+ relative_angle = ((sun_azimuth - bearing + 540) % 360) - 180
358
+
359
+ # If sun is almost directly ahead/behind, side-window shade is ambiguous.
360
+ if abs(relative_angle) < 15 or abs(relative_angle) > 165:
361
+ shade_side = "NONE"
362
+ elif relative_angle > 0:
363
+ shade_side = "LEFT"
364
+ left_side_time += (duration_min / total_segments)
365
+ else:
366
+ shade_side = "RIGHT"
367
+ right_side_time += (duration_min / total_segments)
368
+
369
+ shade_data.append({
370
+ 'segment': i,
371
+ 'bearing': round(bearing, 2),
372
+ 'sun_azimuth': round(sun_azimuth, 2),
373
+ 'sun_altitude': round(sun_altitude, 2),
374
+ 'shade_side': shade_side,
375
+ 'time': current_time.strftime('%H:%M')
376
+ })
377
 
378
+ if sunlit_segments == 0:
379
+ return {
380
+ 'preferred_side': 'N/A',
381
+ 'preferred_window': 'Night journey - shade analysis not applicable',
382
+ 'left_shade_minutes': 0,
383
+ 'right_shade_minutes': 0,
384
+ 'shade_percentage': 0,
385
+ 'total_duration': duration_min,
386
+ 'is_night': True,
387
+ 'segments': []
388
+ }
389
 
390
+ if left_side_time >= right_side_time:
391
+ preferred_side = "LEFT"
392
+ preferred_window = "Windows on the left side"
393
+ shade_percentage = round((left_side_time / duration_min) * 100, 1)
394
+ else:
395
+ preferred_side = "RIGHT"
396
+ preferred_window = "Windows on the right side"
397
+ shade_percentage = round((right_side_time / duration_min) * 100, 1)
398
 
399
+ return {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  'preferred_side': preferred_side,
401
  'preferred_window': preferred_window,
402
  'left_shade_minutes': round(left_side_time, 1),
403
  'right_shade_minutes': round(right_side_time, 1),
404
  'shade_percentage': shade_percentage,
405
  'total_duration': duration_min,
406
+ 'is_night': False,
407
  'segments': shade_data[:5]
408
  }
409
 
410
  def scrape_fare_only(from_id, to_id):
411
 
412
+ today_str = now_ist_naive().strftime("%d/%m/%Y")
413
  web_url = "https://www.apsrtconline.in/oprs-web/forward/booking/avail/services.do"
414
 
415
  headers = {
 
433
  soup = BeautifulSoup(response.text, 'html.parser')
434
  fare_map = {}
435
 
436
+ # Find all bus services
437
  services = soup.find_all('div', class_='rSetForward')
438
 
439
  for service in services:
440
  try:
441
+ # Get bus number
442
  bus_no_tag = service.find('div', class_='srvceNO')
443
  bus_no = bus_no_tag.get_text(strip=True) if bus_no_tag else None
444
 
445
+ # Get fare (THIS IS THE KEY PART)
446
  fare_tag = service.find('span', class_='TickRate')
447
  fare = fare_tag.get_text(strip=True) if fare_tag else "N/A"
448
 
 
491
  'ERODE', 'VELLORE', 'THOOTHUKUDI', 'THANJAVUR',
492
  'DINDIGUL', 'CUDDALORE', 'KANCHIPURAM', 'TIRUPPUR',
493
  'KARUR', 'RAJAPALAYAM', 'SIVAKASI', 'NAGERCOIL',
494
+ 'KUMBAKONAM', 'PUDUKKOTTAI', 'HOSUR','TN']
495
 
496
  karnataka_keywords = ['KARNATAKA', 'BENGALURU', 'BANGALORE', 'MANGALORE', 'MYSURU', 'MYSORE', 'HUBLI', 'BELGAUM', 'SHIVAMOGGA', 'HASSAN', 'UDUPI','KR','KA']
497
  tg_cities = ['HYDERABAD', 'WARANGAL', 'NIZAMABAD', 'KARIMNAGAR', 'KHAMMAM', 'SECUNDERABAD', 'KUKATPALLY', 'DILSUKHNAGAR', 'MIYAPUR', 'GACHIBOWLI','TG','TS']
 
512
  if kcity in city:
513
  return 'KSRTC'
514
 
515
+ # Check Andhra Pradesh
516
  for acity in andhra_keywords:
517
  if acity in city:
518
  return 'APSRTC'
 
526
 
527
  start_coords = req_data.get('start', [77.2090, 28.6139])
528
  end_coords = req_data.get('end', [78.0081, 27.1767])
 
529
 
530
  url = f"{Base_url}/v2/directions/driving-hgv/geojson"
531
  params = {
 
551
  duration_sec=summary['duration']
552
  duration_minutes = int(duration_sec / 60)
553
  hours=int(duration_sec//3600)
554
+ client_local_time = req_data.get('clientLocalTime')
555
+ client_local_date = req_data.get('clientLocalDate')
556
+ if not client_local_time:
557
+ client_local_time = now_ist_naive().strftime("%H:%M")
558
+ if not client_local_date:
559
+ client_local_date = now_ist_naive().strftime("%Y-%m-%d")
560
+
561
+ shade_info = shade_finder(r_coordinate, client_local_time, duration_minutes, client_local_date)
562
+ print('here')
563
  minutes=int((duration_sec %3600)//60)
564
  duration_str=f"{hours}h {minutes}min" if hours>0 else f"{minutes}min"
565
 
566
  leafly = [[coord[1], coord[0]] for coord in r_coordinate]
567
+ print(duration_str)
568
 
569
  return jsonify({
570
  'path': leafly,
 
596
  for feature in data.get('features',[]):
597
  label=feature['properties']['label']
598
  state=detect_state_from_label(label)
599
+ print(state)
600
  suggestions.append({
601
  "label":feature['properties']['label'],
602
  "coords":feature['geometry']['coordinates'],
 
611
  def get_place_id():
612
  req_data=request.get_json()
613
  if not req_data or 'address' not in req_data:
614
+ return jsonify({"error":"YEH SAHI BHAT NAHI HAI.. I got not place to give ID for"}),400
615
 
616
  search_string = req_data.get('address','').upper()
617
  state=req_data.get('state','APSRTC')
 
670
  for bus in data['data']:
671
  bus_no=bus.get('oprsNo','').strip()
672
  bus['fare']=fare_map.get(bus_no,'N/A')
673
+ print(bus['fare'])
 
 
 
 
 
 
 
 
 
674
  data['averageFare'] = avg_fare
675
 
676
  bus_cache.save_buses('APSRTC', from_name, to_name, data)
 
692
  if not from_id or not to_id:
693
  return jsonify({"error": "Missing Start or End IDs"}), 400
694
 
695
+ today_str = now_ist_naive().strftime("%d/%m/%Y")
696
 
697
  web_url = "https://www.apsrtconline.in/oprs-web/forward/booking/avail/services.do"
698
 
 
745
  "serviceType": srv_type,
746
  "serviceStartTime": start_time,
747
  "serviceEndTime": end_time,
748
+ "depotName": "APSRTC", # Web results often hide depot name
749
  "journeyDate": today_str
750
  }
751
  bus_list.append(bus_obj)
 
773
  return jsonify({"error": "Missing docId"}), 400
774
 
775
  url = "https://utsappapicached01.apsrtconline.in/uts-vts-api/servicewaypointdetails/bydocid"
776
+
777
+ # Using the headers you provided
778
  payload = {"docId": doc_id}
779
 
780
  try:
 
802
 
803
  base_url = f"https://www.kbuses.in/v3/Find/source/{src_slug}/destination/{dst_slug}/type/all/timing/all"
804
 
805
+ print(f"🔍 Fetching Kerala buses from: {base_url}")
806
 
807
  headers = {
808
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
 
908
  "duration": duration if duration else "N/A",
909
  "fare": fare if fare else "N/A",
910
  "depotName": "KERALA",
911
+ "serviceDocId": detail_url, # This is the critical field for stops
912
  "route": route_info,
913
+ "journeyDate": now_ist_naive().strftime("%d/%m/%Y"),
914
  "page": current_page
915
  }
916
 
 
1043
  @app.route('/api/find-buses-tnstc', methods=['POST'])
1044
  def find_buses_tnstc():
1045
  req_data = request.get_json()
1046
+
1047
+ # 1. Input Handling
1048
  from_name = req_data.get('fromName', '')
1049
  to_name = req_data.get('toName', '')
1050
 
 
1087
  if not from_id or not to_id:
1088
  return jsonify({"error": f"Place ID not found for {from_place} or {to_place}"}), 400
1089
 
1090
+ today_str = now_ist_naive().strftime("%d/%m/%Y")
1091
  url = "https://www.tnstc.in/OTRSOnline/jqreq.do"
1092
 
1093
  payload = {
 
1117
 
1118
  response = session.post(url, data=payload, timeout=20)
1119
 
1120
+ # Check if session expired during request (Specific TNSTC error check)
1121
  if "Session Expired" in response.text or response.status_code != 200:
1122
+ print("⚠️ Session expired during request. Retrying...")
1123
+ tnstc_manager.refresh_session() # Force refresh
1124
  session = tnstc_manager.get_valid_session()
1125
  response = session.post(url, data=payload, timeout=20)
1126
 
 
1145
 
1146
  if len(time_divs) >= 2:
1147
  start_span = time_divs[0].find('span', class_='text-dark')
1148
+ start_time = start_span.get_text(strip=True) if start_span else "N/A"
1149
  end_span = time_divs[-1].find('span', class_='text-dark')
1150
+ end_time = end_span.get_text(strip=True) if end_span else "N/A"
 
 
 
 
 
 
 
 
 
1151
 
1152
  duration_elem = item.find('span', class_='duration')
1153
  duration = duration_elem.get_text(strip=True) if duration_elem else "N/A"
 
1204
  journey_date_iso = req_data.get('journeyDate')
1205
 
1206
  if not journey_date_iso:
1207
+ journey_date_iso = now_ist_naive().strftime("%Y-%m-%d")
1208
 
1209
  from_id = (KSRTC_KARNATAKA_MAP.get(from_name_raw.lower()) or
1210
  KSRTC_KARNATAKA_MAP.get(from_name_raw.upper()) or
 
1291
  if len(response.content) < 10:
1292
  return jsonify({"error": "Empty response", "data": [], "totalBuses": 0})
1293
 
1294
+ # Parse JSON
1295
  try:
1296
  raw_data = response.json()
1297
  except json.JSONDecodeError as e:
 
1299
  print(f"Response preview: {response.text[:300]}")
1300
  return jsonify({"error": "Invalid JSON"}), 500
1301
 
1302
+ # Validate data type
1303
  if not isinstance(raw_data, list):
1304
 
1305
  return jsonify({
 
1332
  minutes = (duration.seconds % 3600) // 60
1333
  duration_str = f"{hours}h {minutes}m"
1334
 
1335
+ start_time = dep_dt.strftime("%H:%M")
1336
+ end_time = arr_dt.strftime("%H:%M")
1337
  arrival_date = arr_dt.strftime("%d %b")
1338
 
1339
  bus_obj = {
 
1362
  formatted_buses.append(bus_obj)
1363
 
1364
  except Exception as parse_err:
1365
+ print(f" ⚠️ Parse error: {parse_err}")
1366
  continue
1367
 
1368
  return jsonify({
 
1443
 
1444
 
1445
  def get_tgsrtc_db():
1446
+ try:
1447
+ conn = sqlite3.connect('tgsrtc.db')
1448
+ conn.row_factory = sqlite3.Row
1449
+ return conn
1450
+ except Exception as e:
1451
+ print(f"DB Connection Error: {e}")
1452
+ return None
1453
 
1454
  @app.route('/api/tgsrtc/resolve-id', methods=['POST'])
1455
  def resolve_tgsrtc_id():
1456
  req_data = request.get_json()
1457
+ place_name = req_data.get('address', '').split(',')[0].strip() # Extract "Suryapet"
1458
 
1459
  if not place_name:
1460
+ return jsonify({"error": "No place name provided"}), 400
1461
+
1462
  conn = get_tgsrtc_db()
1463
+ if not conn:
1464
+ return jsonify({"error": "Database missing"}), 500
1465
+
1466
  cursor = conn.cursor()
1467
 
1468
+ print(f"🟣 Searching TGSRTC DB for: {place_name}")
1469
+
1470
+ cursor.execute("SELECT stop_id, stop_name FROM stops WHERE stop_name LIKE ? LIMIT 1", (place_name,))
1471
+ row = cursor.fetchone()
1472
+
1473
+ if not row:
1474
+ cursor.execute("SELECT stop_id, stop_name FROM stops WHERE stop_name LIKE ? LIMIT 1", (f"{place_name}%",))
1475
+ row = cursor.fetchone()
 
 
 
 
 
 
 
 
 
 
 
1476
 
1477
+ if not row:
1478
+ cursor.execute("SELECT stop_id, stop_name FROM stops WHERE stop_name LIKE ? LIMIT 1", (f"%{place_name}%",))
1479
+ row = cursor.fetchone()
1480
+
1481
  conn.close()
1482
+
1483
+ if row:
1484
+ print(f"Found TGSRTC ID: {row['stop_id']} for {row['stop_name']}")
1485
  return jsonify({
1486
+ "id": row['stop_id'],
1487
+ "match": row['stop_name'],
1488
+ "state": "TGSRTC"
 
 
 
 
 
 
 
 
1489
  })
1490
+ else:
1491
+ print(f"No ID found in stops.txt for {place_name}")
1492
+ return jsonify({"id": None})
1493
  @app.route('/api/find-buses-tgsrtc', methods=['POST'])
1494
  def find_buses_tgsrtc():
1495
  req_data = request.get_json()
1496
+ from_id = req_data.get('fromId')
1497
+ to_id = req_data.get('toId')
1498
+
1499
  if not from_id or not to_id:
1500
+ return jsonify({"error": "Missing valid GTFS Stop IDs"}), 400
1501
+
1502
  conn = get_tgsrtc_db()
1503
  cursor = conn.cursor()
1504
+
1505
+ print(f"🟣 Finding TGSRTC Buses: {from_id} -> {to_id}")
1506
+
1507
+ query = '''
1508
+ SELECT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1509
  r.route_short_name,
1510
+ r.route_long_name,
1511
  t.trip_id,
 
1512
  t.bus_class,
1513
+ st1.departure_time as start_time,
1514
+ st2.arrival_time as end_time
 
 
1515
  FROM stop_times st1
1516
  JOIN stop_times st2 ON st1.trip_id = st2.trip_id
1517
  JOIN trips t ON st1.trip_id = t.trip_id
1518
+ JOIN routes r ON t.route_id = r.route_id
1519
+ WHERE st1.stop_id = ?
1520
+ AND st2.stop_id = ?
1521
  AND CAST(st1.stop_sequence AS INTEGER) < CAST(st2.stop_sequence AS INTEGER)
 
1522
  ORDER BY st1.departure_time
1523
+ LIMIT 50
1524
  '''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1525
 
1526
+ try:
1527
+ cursor.execute(query, (from_id, to_id))
1528
+ rows = cursor.fetchall()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1529
 
1530
+ results = []
1531
+ for row in rows:
1532
+ # Calculate Duration
1533
+ try:
1534
+ t1 = datetime.strptime(row['start_time'], "%H:%M:%S")
1535
+ t2 = datetime.strptime(row['end_time'], "%H:%M:%S")
1536
+ duration = t2 - t1
1537
+ hours, remainder = divmod(duration.seconds, 3600)
1538
+ minutes = remainder // 60
1539
+ dur_str = f"{hours}h {minutes}m"
1540
+ except:
1541
+ dur_str = "N/A"
1542
+
1543
+ results.append({
1544
+ "oprsNo": row['route_short_name'], # e.g. 107VR
1545
+ "serviceType": row['bus_class'] or "TGSRTC",
1546
+ "serviceStartTime": row['start_time'][:5], # HH:MM
1547
+ "serviceEndTime": row['end_time'][:5],
1548
+ "duration": dur_str,
1549
+ "fare": "N/A", # GTFS usually doesn't have fare
1550
+ "depotName": "TGSRTC",
1551
+ "journeyDate": now_ist_naive().strftime("%d-%m-%Y"),
1552
+ "via": row['route_long_name'],
1553
+ "source": "TGSRTC"
1554
+ })
1555
+
1556
+ conn.close()
1557
+ print(f"Found {len(results)} TGSRTC buses")
1558
+ return jsonify({"data": results, "source": "TGSRTC", "totalBuses": len(results)})
1559
+
1560
+ except Exception as e:
1561
+ print(f"TG SQL Error: {e}")
1562
+ return jsonify({"error": str(e)}), 500
1563
+
1564
+
1565
+
1566
+
1567
+
1568
  @app.route('/')
1569
  def index():
1570
  return render_template('index.html')