Spaces:
Running
Running
Update app.py
Browse filesFixed timing thing
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
|
| 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 =
|
| 87 |
except Exception as e:
|
| 88 |
print(f"Failed to refresh session: {e}")
|
| 89 |
|
| 90 |
def get_valid_session(self):
|
| 91 |
-
current_time =
|
| 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='
|
| 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 =
|
| 133 |
-
cutoff_time =
|
| 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 =
|
| 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 = (
|
| 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 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
-
|
| 310 |
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
'preferred_window': '
|
| 317 |
'left_shade_minutes': 0,
|
| 318 |
'right_shade_minutes': 0,
|
| 319 |
'shade_percentage': 0,
|
| 320 |
'total_duration': duration_min,
|
| 321 |
-
'is_night':
|
| 322 |
'segments': []
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
-
|
|
|
|
|
|
|
| 326 |
|
| 327 |
-
|
| 328 |
-
|
| 329 |
|
| 330 |
-
|
| 331 |
-
|
| 332 |
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
|
| 335 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
-
|
| 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 =
|
| 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'
|
| 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 |
-
|
| 517 |
-
|
| 518 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 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 =
|
| 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":
|
| 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 =
|
| 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 |
-
|
| 1106 |
end_span = time_divs[-1].find('span', class_='text-dark')
|
| 1107 |
-
|
| 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 =
|
| 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("%
|
| 1300 |
-
end_time = arr_dt.strftime("%
|
| 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 |
-
|
| 1411 |
-
|
| 1412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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({"
|
| 1421 |
-
|
| 1422 |
conn = get_tgsrtc_db()
|
|
|
|
|
|
|
|
|
|
| 1423 |
cursor = conn.cursor()
|
| 1424 |
|
| 1425 |
-
|
| 1426 |
-
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
|
| 1430 |
-
|
| 1431 |
-
|
| 1432 |
-
|
| 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
|
|
|
|
| 1448 |
return jsonify({
|
| 1449 |
-
"id":
|
| 1450 |
-
"match":
|
| 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 |
-
|
| 1463 |
-
|
| 1464 |
@app.route('/api/find-buses-tgsrtc', methods=['POST'])
|
| 1465 |
def find_buses_tgsrtc():
|
| 1466 |
req_data = request.get_json()
|
| 1467 |
-
from_id =
|
| 1468 |
-
to_id =
|
| 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 |
-
|
| 1477 |
-
|
| 1478 |
-
|
| 1479 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1530 |
-
WHERE st1.stop_id
|
| 1531 |
-
AND st2.stop_id
|
| 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
|
| 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 |
-
|
| 1589 |
-
|
| 1590 |
-
|
| 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 |
-
|
| 1623 |
-
|
| 1624 |
-
|
| 1625 |
-
|
| 1626 |
-
|
| 1627 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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')
|