barathvasan-dev commited on
Commit
17ff25c
Β·
1 Parent(s): 8d9a055

Upgrade: Professional multi-filter NLP-to-SQL engine with support for combined queries, synonyms, and all intent types

Browse files
Files changed (2) hide show
  1. database.py +262 -524
  2. database_old.py +982 -0
database.py CHANGED
@@ -1,6 +1,6 @@
1
  # =========================================================
2
  # ULTRA ADVANCED HYBRID NLP TO SQL ENGINE
3
- # RULE BASED + LLM BASED + SQL SAFETY
4
  # MISTRAL / SQLCODER READY
5
  # =========================================================
6
 
@@ -195,548 +195,286 @@ def validate_sql(sql):
195
 
196
 
197
  # =========================================================
198
- # MAIN NLP TO SQL ENGINE
199
  # =========================================================
200
 
201
- def ask_llm(user_query):
202
-
203
- q = user_query.lower().strip()
204
-
205
- # =====================================================
206
- # ENTITY EXTRACTION
207
- # =====================================================
208
-
209
- plate_match = re.search(
210
- r'([A-Z]{2}\d{1,2}[A-Z]{1,3}\d{3,4})',
211
- user_query.upper()
212
- )
213
-
214
- date_match = re.search(
215
- r'(\d{4}-\d{2}-\d{2})',
216
- q
217
- )
218
-
219
- # =====================================================
220
- # INTENT DETECTION
221
- # =====================================================
222
-
223
- intents = {
224
-
225
- "tracking":
226
- any(k in q for k in [
227
- "track",
228
- "history",
229
- "movement",
230
- "travel",
231
- "route",
232
- "visited",
233
- "where"
234
- ]),
235
-
236
- "count":
237
- any(k in q for k in [
238
- "count",
239
- "how many",
240
- "total"
241
- ]),
242
-
243
- "analytics":
244
- any(k in q for k in [
245
- "top",
246
- "most",
247
- "distribution",
248
- "analysis",
249
- "statistics",
250
- "peak"
251
- ]),
252
-
253
- "latest":
254
- any(k in q for k in [
255
- "latest",
256
- "recent",
257
- "last"
258
- ])
259
- }
260
-
261
- # =====================================================
262
- # RULE BASED ENGINE
263
- # =====================================================
264
-
265
- # =====================================================
266
- # PLATE TRACKING
267
- # =====================================================
268
-
269
- if plate_match:
270
-
271
- plate = plate_match.group(1)
272
-
273
- # TRACKING
274
-
275
- if intents["tracking"]:
276
-
277
- return clean_sql(f"""
278
- SELECT
279
- timestamp,
280
- plate,
281
- state,
282
- vehicle_type,
283
- location,
284
- camera_id,
285
- date,
286
- hour,
287
- day
288
- FROM vehicle_logs
289
- WHERE plate = '{plate}'
290
- ORDER BY timestamp DESC
291
- LIMIT 100
292
- """)
293
-
294
- # COUNT
295
-
296
- if intents["count"]:
297
-
298
- return clean_sql(f"""
299
- SELECT
300
- plate,
301
- COUNT(*) as detections,
302
- COUNT(DISTINCT location) as unique_locations,
303
- COUNT(DISTINCT date) as active_days
304
- FROM vehicle_logs
305
- WHERE plate = '{plate}'
306
- GROUP BY plate
307
- """)
308
-
309
- # DEFAULT
310
-
311
- return clean_sql(f"""
312
- SELECT *
313
- FROM vehicle_logs
314
- WHERE plate = '{plate}'
315
- ORDER BY timestamp DESC
316
- LIMIT 50
317
- """)
318
-
319
- # =====================================================
320
- # STATE QUERIES
321
- # =====================================================
322
-
323
- for key, state in VALID_STATES.items():
324
-
325
- if key in q:
326
-
327
- if intents["count"]:
328
-
329
- return clean_sql(f"""
330
- SELECT
331
- state,
332
- COUNT(*) as total_detections,
333
- COUNT(DISTINCT plate) as unique_vehicles
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  FROM vehicle_logs
335
- WHERE state = '{state}'
336
- GROUP BY state
337
  """)
338
-
339
- return clean_sql(f"""
340
- SELECT *
341
- FROM vehicle_logs
342
- WHERE state = '{state}'
343
- ORDER BY timestamp DESC
344
- LIMIT 100
345
- """)
346
-
347
- # =====================================================
348
- # LOCATION QUERIES
349
- # =====================================================
350
-
351
- for loc in KNOWN_LOCATIONS:
352
-
353
- if loc in q:
354
-
355
- # COUNT
356
-
357
- if intents["count"]:
358
-
359
- return clean_sql(f"""
360
- SELECT
361
- location,
362
- COUNT(*) as detections,
363
- COUNT(DISTINCT plate) as unique_vehicles
364
  FROM vehicle_logs
365
- WHERE LOWER(location) LIKE '%{loc}%'
366
- GROUP BY location
367
  ORDER BY detections DESC
 
368
  """)
369
-
370
- # DEFAULT
371
-
372
- return clean_sql(f"""
373
- SELECT
374
- timestamp,
375
- plate,
376
- state,
377
- vehicle_type,
378
- location,
379
- camera_id
380
- FROM vehicle_logs
381
- WHERE LOWER(location) LIKE '%{loc}%'
382
- ORDER BY timestamp DESC
383
- LIMIT 100
384
- """)
385
-
386
- # =====================================================
387
- # VEHICLE TYPE
388
- # =====================================================
389
-
390
- for vtype in VEHICLE_TYPES:
391
-
392
- if vtype in q:
393
-
394
- if intents["count"]:
395
-
396
- return clean_sql(f"""
397
- SELECT
398
- vehicle_type,
399
- COUNT(*) as count
400
  FROM vehicle_logs
401
- WHERE LOWER(vehicle_type) LIKE '%{vtype}%'
402
- GROUP BY vehicle_type
 
 
403
  """)
404
-
405
- return clean_sql(f"""
406
- SELECT *
407
- FROM vehicle_logs
408
- WHERE LOWER(vehicle_type) LIKE '%{vtype}%'
409
- ORDER BY timestamp DESC
410
- LIMIT 50
411
- """)
412
-
413
- # =====================================================
414
- # DATE QUERY
415
- # =====================================================
416
-
417
- if date_match:
418
-
419
- d = date_match.group(1)
420
-
421
- return clean_sql(f"""
422
- SELECT *
423
- FROM vehicle_logs
424
- WHERE date = '{d}'
425
- ORDER BY timestamp DESC
426
- LIMIT 100
427
- """)
428
-
429
- # =====================================================
430
- # ANALYTICS
431
- # =====================================================
432
-
433
- if "hourly traffic" in q or "traffic by hour" in q:
434
-
435
- return clean_sql("""
436
- SELECT
437
- hour,
438
- COUNT(*) as traffic
439
- FROM vehicle_logs
440
- GROUP BY hour
441
- ORDER BY hour
442
- """)
443
-
444
- if "top vehicles" in q or "most detected" in q:
445
-
446
- return clean_sql("""
447
- SELECT
448
- plate,
449
- COUNT(*) as detections
450
- FROM vehicle_logs
451
- GROUP BY plate
452
- ORDER BY detections DESC
453
- LIMIT 20
454
- """)
455
-
456
- if "state distribution" in q:
457
-
458
- return clean_sql("""
459
- SELECT
460
- state,
461
- COUNT(*) as count
462
- FROM vehicle_logs
463
- GROUP BY state
464
- ORDER BY count DESC
465
- """)
466
-
467
- if "vehicle type distribution" in q:
468
-
469
- return clean_sql("""
470
- SELECT
471
- vehicle_type,
472
- COUNT(*) as count
473
- FROM vehicle_logs
474
- GROUP BY vehicle_type
475
- ORDER BY count DESC
476
- """)
477
-
478
- if "latest" in q or "recent" in q:
479
-
480
- return clean_sql("""
481
- SELECT *
482
- FROM vehicle_logs
483
- ORDER BY timestamp DESC
484
- LIMIT 50
485
- """)
486
-
487
- # =====================================================
488
- # LLM FALLBACK
489
- # =====================================================
490
-
491
- if not USE_LLM:
492
-
493
- return clean_sql("""
494
- SELECT *
495
- FROM vehicle_logs
496
- ORDER BY timestamp DESC
497
- LIMIT 10
498
- """)
499
-
500
- # =====================================================
501
- # SYSTEM PROMPT
502
- # =====================================================
503
-
504
- system_prompt = f"""
505
- You are an elite PostgreSQL SQL generator.
506
-
507
- Your job:
508
- Convert natural language into VALID PostgreSQL SQL.
509
-
510
- ==================================================
511
- DATABASE
512
- ==================================================
513
-
514
- TABLE:
515
- vehicle_logs
516
-
517
- AVAILABLE COLUMNS:
518
-
519
- timestamp
520
- plate
521
- state
522
- vehicle_type
523
- vehicle_conf
524
- camera_id
525
- location
526
- date
527
- hour
528
- day
529
-
530
- ==================================================
531
- COLUMN MEANINGS
532
- ==================================================
533
-
534
- timestamp:
535
- vehicle detection timestamp
536
-
537
- plate:
538
- vehicle number plate
539
-
540
- state:
541
- vehicle state code
542
-
543
- vehicle_type:
544
- type of vehicle
545
-
546
- vehicle_conf:
547
- AI detection confidence
548
-
549
- camera_id:
550
- CCTV camera ID
551
-
552
- location:
553
- detected location
554
-
555
- date:
556
- YYYY-MM-DD
557
-
558
- hour:
559
- 0-23
560
-
561
- day:
562
- Monday-Sunday
563
-
564
- ==================================================
565
- KNOWN STATES
566
- ==================================================
567
-
568
- TN
569
- KA
570
- KL
571
- AP
572
- TS
573
- MH
574
- DL
575
- GJ
576
- RJ
577
- UP
578
- WB
579
- HR
580
- PB
581
-
582
- ==================================================
583
- KNOWN LOCATIONS
584
- ==================================================
585
-
586
- {KNOWN_LOCATIONS}
587
-
588
- ==================================================
589
- STRICT RULES
590
- ==================================================
591
-
592
- 1. ONLY use vehicle_logs
593
- 2. NEVER use JOIN
594
- 3. NEVER invent tables
595
- 4. NEVER invent columns
596
- 5. ONLY SELECT queries
597
- 6. NEVER use UPDATE
598
- 7. NEVER use DELETE
599
- 8. NEVER use DROP
600
- 9. NEVER use ALTER
601
- 10. PostgreSQL syntax only
602
- 11. Always use LIMIT 50 or LIMIT 100
603
- 12. Return SQL ONLY
604
- 13. No markdown
605
- 14. No explanation
606
-
607
- ==================================================
608
- QUERY UNDERSTANDING
609
- ==================================================
610
-
611
- track vehicle
612
- β†’ WHERE plate=''
613
-
614
- show TN vehicles
615
- β†’ WHERE state='TN'
616
-
617
- show vehicles from adyar
618
- β†’ WHERE LOWER(location) LIKE '%adyar%'
619
-
620
- top vehicles
621
- β†’ GROUP BY plate
622
-
623
- hourly traffic
624
- β†’ GROUP BY hour
625
-
626
- vehicle type distribution
627
- β†’ GROUP BY vehicle_type
628
-
629
- latest detections
630
- β†’ ORDER BY timestamp DESC
631
-
632
- ==================================================
633
- GOOD EXAMPLES
634
- ==================================================
635
-
636
- SELECT *
637
- FROM vehicle_logs
638
- WHERE state='TN'
639
- ORDER BY timestamp DESC
640
- LIMIT 50;
641
-
642
- SELECT *
643
- FROM vehicle_logs
644
- WHERE LOWER(location) LIKE '%adyar%'
645
- ORDER BY timestamp DESC
646
- LIMIT 50;
647
-
648
- SELECT
649
- plate,
650
- COUNT(*) as detections
651
- FROM vehicle_logs
652
- GROUP BY plate
653
- ORDER BY detections DESC
654
- LIMIT 20;
655
-
656
- SELECT *
657
- FROM vehicle_logs
658
- WHERE plate='TN63MB3157'
659
- ORDER BY timestamp DESC
660
- LIMIT 100;
661
- """
662
-
663
- user_prompt = f"""
664
- Generate PostgreSQL SQL query for:
665
-
666
- {user_query}
667
- """
668
-
669
- # =====================================================
670
- # MISTRAL / SQLCODER CALL
671
- # =====================================================
672
-
673
- try:
674
-
675
- if client is None:
676
- print("❌ Mistral client not initialized - HF_TOKEN missing")
677
- raise Exception("LLM service unavailable - HF_TOKEN not configured")
678
-
679
- try:
680
- response = client.chat_completion(
681
- messages=[
682
- {
683
- "role": "system",
684
- "content": system_prompt
685
- },
686
- {
687
- "role": "user",
688
- "content": user_prompt
689
- }
690
- ],
691
- max_tokens=250,
692
- temperature=0.05
693
- )
694
- sql = response.choices[0].message.content.strip()
695
- except Exception as api_error:
696
- print(f"⚠️ API timeout or error: {api_error}")
697
- # Fallback to rule-based query if LLM times out
698
- print("⚠️ Using fallback query due to API timeout")
699
- return clean_sql("""
700
- SELECT *
701
  FROM vehicle_logs
 
702
  ORDER BY timestamp DESC
703
- LIMIT 10
704
- """)
705
-
706
- sql = clean_sql(sql)
707
-
708
- # =================================================
709
- # SAFETY
710
- # =================================================
711
-
712
- if not validate_sql(sql):
713
- print("❌ SQL validation failed - using safe query")
714
- return clean_sql("""
715
  SELECT *
716
  FROM vehicle_logs
 
717
  ORDER BY timestamp DESC
718
- LIMIT 10
719
- """)
720
-
721
- # AUTO LIMIT
722
-
723
- if "LIMIT" not in sql.upper():
724
 
725
- sql = sql.replace(";", " LIMIT 50;")
726
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
  return sql
728
-
729
  except Exception as e:
730
-
731
- print(f"❌ LLM ERROR: {e}")
732
  traceback.print_exc()
 
733
 
734
- return clean_sql("""
735
- SELECT *
736
- FROM vehicle_logs
737
- ORDER BY timestamp DESC
738
- LIMIT 10
739
- """)
740
 
741
  # =========================================================
742
  # QUERY EXECUTION
@@ -979,4 +717,4 @@ def get_suspicious_vehicles():
979
 
980
  except Exception as e:
981
  print(f"❌ Suspicious Vehicles Error (timeout?): {e}")
982
- return []
 
1
  # =========================================================
2
  # ULTRA ADVANCED HYBRID NLP TO SQL ENGINE
3
+ # PROFESSIONAL MULTI-FILTER ENGINE
4
  # MISTRAL / SQLCODER READY
5
  # =========================================================
6
 
 
195
 
196
 
197
  # =========================================================
198
+ # PROFESSIONAL MULTI-FILTER NLP ENGINE
199
  # =========================================================
200
 
201
+ class FilterExtractor:
202
+ """Smart filter extraction engine for multi-condition queries"""
203
+
204
+ def __init__(self):
205
+ # Synonym mappings for vehicle types
206
+ self.vehicle_synonyms = {
207
+ "car": "car", "cars": "car", "sedan": "car", "compact": "car",
208
+ "suv": "suv", "suvs": "suv",
209
+ "truck": "truck", "trucks": "truck", "lorry": "truck", "lorries": "truck",
210
+ "bus": "bus", "buses": "bus",
211
+ "bike": "bike", "bikes": "bike", "motorcycle": "bike", "motorcycles": "bike",
212
+ "auto": "auto", "autos": "auto", "autorickshaw": "auto", "auto-rickshaw": "auto",
213
+ "jeep": "jeep", "jeeps": "jeep",
214
+ "taxi": "taxi", "taxis": "taxi"
215
+ }
216
+
217
+ # Day mappings
218
+ self.day_map = {
219
+ "monday": "Monday", "tuesday": "Tuesday", "wednesday": "Wednesday",
220
+ "thursday": "Thursday", "friday": "Friday", "saturday": "Saturday", "sunday": "Sunday",
221
+ "weekend": ["Saturday", "Sunday"],
222
+ "weekday": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
223
+ }
224
+
225
+ # Location variants
226
+ self.location_variants = {
227
+ "adyar": ["adyar"],
228
+ "besant nagar": ["besant", "besant nagar"],
229
+ "t nagar": ["t nagar", "tnagar"],
230
+ "anna nagar": ["anna", "anna nagar"],
231
+ "velachery": ["velachery"],
232
+ "guindy": ["guindy"],
233
+ "thiruvanmiyur": ["thiruvanmiyur"],
234
+ "mylapore": ["mylapore"],
235
+ "koyambedu": ["koyambedu"],
236
+ "nungambakkam": ["nungambakkam"],
237
+ "kotturpuram": ["kotturpuram"]
238
+ }
239
+
240
+ # State codes
241
+ self.state_map = {
242
+ "tn": "TN", "tamil nadu": "TN",
243
+ "ka": "KA", "karnataka": "KA",
244
+ "kl": "KL", "kerala": "KL",
245
+ "ap": "AP", "andhra": "AP",
246
+ "ts": "TS", "telangana": "TS",
247
+ "mh": "MH", "maharashtra": "MH",
248
+ "dl": "DL", "delhi": "DL",
249
+ "gj": "GJ", "gujarat": "GJ",
250
+ "rj": "RJ", "rajasthan": "RJ",
251
+ "up": "UP", "uttar pradesh": "UP",
252
+ "wb": "WB", "west bengal": "WB",
253
+ "hr": "HR", "haryana": "HR",
254
+ "pb": "PB", "punjab": "PB"
255
+ }
256
+
257
+ def extract_plate(self, query):
258
+ """Extract license plate number"""
259
+ match = re.search(r'([A-Z]{2}\d{1,2}[A-Z]{1,3}\d{3,4})', query.upper())
260
+ return match.group(1) if match else None
261
+
262
+ def extract_state(self, query):
263
+ """Extract state code"""
264
+ q = query.lower()
265
+ for key, state_code in self.state_map.items():
266
+ if key in q:
267
+ return state_code
268
+ return None
269
+
270
+ def extract_location(self, query):
271
+ """Extract location"""
272
+ q = query.lower()
273
+ for canonical, variants in self.location_variants.items():
274
+ for variant in variants:
275
+ if variant in q:
276
+ return canonical
277
+ return None
278
+
279
+ def extract_vehicle_type(self, query):
280
+ """Extract vehicle type"""
281
+ q = query.lower()
282
+ for synonym, canonical in self.vehicle_synonyms.items():
283
+ if synonym in q:
284
+ return canonical
285
+ return None
286
+
287
+ def extract_date(self, query):
288
+ """Extract and normalize date"""
289
+ # YYYY-MM-DD format
290
+ match = re.search(r'(\d{4}-\d{2}-\d{2})', query)
291
+ if match:
292
+ return match.group(1)
293
+
294
+ # DD-MM-YYYY or DD/MM/YYYY
295
+ match = re.search(r'(\d{1,2})[-/](\d{1,2})[-/](\d{4})', query)
296
+ if match:
297
+ day, month, year = match.groups()
298
+ return f"{year}-{month.zfill(2)}-{day.zfill(2)}"
299
+
300
+ return None
301
+
302
+ def extract_day(self, query):
303
+ """Extract day of week"""
304
+ q = query.lower()
305
+ for day_key, day_values in self.day_map.items():
306
+ if day_key in q:
307
+ return day_values
308
+ return None
309
+
310
+ def extract_hour(self, query):
311
+ """Extract hour"""
312
+ match = re.search(r'(\d{1,2}):?\d{0,2}\s*(am|pm|h)?', query.lower())
313
+ if match:
314
+ hour = int(match.group(1))
315
+ return hour if 0 <= hour < 24 else None
316
+ return None
317
+
318
+ def extract_filters(self, query):
319
+ """Extract all filters from query"""
320
+ return {
321
+ "plate": self.extract_plate(query),
322
+ "state": self.extract_state(query),
323
+ "location": self.extract_location(query),
324
+ "vehicle_type": self.extract_vehicle_type(query),
325
+ "date": self.extract_date(query),
326
+ "day": self.extract_day(query),
327
+ "hour": self.extract_hour(query)
328
+ }
329
+
330
+ def detect_intents(self, query):
331
+ """Detect query intents"""
332
+ q = query.lower()
333
+ return {
334
+ "tracking": any(k in q for k in ["track", "history", "movement", "travel", "route", "where"]),
335
+ "count": any(k in q for k in ["count", "how many", "total", "number of"]),
336
+ "analytics": any(k in q for k in ["top", "most", "distribution", "analysis", "statistics"]),
337
+ "latest": any(k in q for k in ["latest", "recent", "last"]),
338
+ "hourly": "hourly" in q or "by hour" in q,
339
+ "suspicious": "suspicious" in q or "repeated" in q
340
+ }
341
+
342
+ def build_sql(self, filters, intents):
343
+ """Build SQL query from filters and intents"""
344
+
345
+ # =====================================================
346
+ # PURE ANALYTICS QUERIES (no filters needed)
347
+ # =====================================================
348
+
349
+ if intents["analytics"]:
350
+ if intents["hourly"]:
351
+ return clean_sql("""
352
+ SELECT hour, COUNT(*) as traffic
353
  FROM vehicle_logs
354
+ GROUP BY hour
355
+ ORDER BY hour;
356
  """)
357
+
358
+ if "top" in " ".join([k for k, v in intents.items() if v]):
359
+ return clean_sql("""
360
+ SELECT plate, COUNT(*) as detections
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  FROM vehicle_logs
362
+ GROUP BY plate
 
363
  ORDER BY detections DESC
364
+ LIMIT 20;
365
  """)
366
+
367
+ if intents["suspicious"]:
368
+ return clean_sql("""
369
+ SELECT plate, state, COUNT(*) as detections
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  FROM vehicle_logs
371
+ GROUP BY plate, state
372
+ HAVING COUNT(*) > 5
373
+ ORDER BY detections DESC
374
+ LIMIT 20;
375
  """)
376
+
377
+ # =====================================================
378
+ # BUILD WHERE CLAUSE FROM FILTERS
379
+ # =====================================================
380
+
381
+ where_conditions = []
382
+
383
+ if filters["plate"]:
384
+ where_conditions.append(f"plate = '{filters['plate']}'")
385
+
386
+ if filters["state"]:
387
+ where_conditions.append(f"state = '{filters['state']}'")
388
+
389
+ if filters["location"]:
390
+ where_conditions.append(f"LOWER(location) LIKE '%{filters['location'].lower()}%'")
391
+
392
+ if filters["vehicle_type"]:
393
+ where_conditions.append(f"LOWER(vehicle_type) LIKE '%{filters['vehicle_type'].lower()}%'")
394
+
395
+ if filters["date"]:
396
+ where_conditions.append(f"date = '{filters['date']}'")
397
+
398
+ if filters["day"]:
399
+ if isinstance(filters["day"], list):
400
+ day_conditions = [f"day = '{d}'" for d in filters["day"]]
401
+ where_conditions.append(f"({' OR '.join(day_conditions)})")
402
+ else:
403
+ where_conditions.append(f"day = '{filters['day']}'")
404
+
405
+ if filters["hour"] is not None:
406
+ where_conditions.append(f"hour = {filters['hour']}")
407
+
408
+ # =====================================================
409
+ # GENERATE FINAL SQL
410
+ # =====================================================
411
+
412
+ where_clause = " AND ".join(where_conditions) if where_conditions else "1=1"
413
+
414
+ if intents["count"]:
415
+ if filters["plate"]:
416
+ sql = f"""
417
+ SELECT plate, COUNT(*) as detections
418
+ FROM vehicle_logs
419
+ WHERE {where_clause}
420
+ GROUP BY plate;
421
+ """
422
+ else:
423
+ sql = f"""
424
+ SELECT COUNT(*) as total
425
+ FROM vehicle_logs
426
+ WHERE {where_clause};
427
+ """
428
+ elif intents["tracking"]:
429
+ sql = f"""
430
+ SELECT timestamp, plate, state, vehicle_type, location, camera_id, date, hour, day
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  FROM vehicle_logs
432
+ WHERE {where_clause}
433
  ORDER BY timestamp DESC
434
+ LIMIT 100;
435
+ """
436
+ else:
437
+ sql = f"""
 
 
 
 
 
 
 
 
438
  SELECT *
439
  FROM vehicle_logs
440
+ WHERE {where_clause}
441
  ORDER BY timestamp DESC
442
+ LIMIT 100;
443
+ """
444
+
445
+ return clean_sql(sql)
 
 
446
 
 
447
 
448
+ def ask_llm(user_query):
449
+ """
450
+ Professional NLP-to-SQL engine with multi-filter support.
451
+ Handles combined filters for complex queries.
452
+ """
453
+
454
+ try:
455
+ # Initialize filter extractor
456
+ extractor = FilterExtractor()
457
+
458
+ # Extract all filters from query
459
+ filters = extractor.extract_filters(user_query)
460
+
461
+ # Detect intents
462
+ intents = extractor.detect_intents(user_query)
463
+
464
+ print(f"\nπŸ” Query Analysis:")
465
+ print(f" Filters: plate={filters['plate']}, state={filters['state']}, location={filters['location']}, vehicle_type={filters['vehicle_type']}, date={filters['date']}, day={filters['day']}")
466
+ print(f" Intents: tracking={intents['tracking']}, count={intents['count']}, analytics={intents['analytics']}")
467
+
468
+ # Build and return SQL
469
+ sql = extractor.build_sql(filters, intents)
470
+
471
  return sql
472
+
473
  except Exception as e:
474
+ print(f"❌ Filter extraction error: {e}")
 
475
  traceback.print_exc()
476
+ return clean_sql("SELECT * FROM vehicle_logs ORDER BY timestamp DESC LIMIT 10;")
477
 
 
 
 
 
 
 
478
 
479
  # =========================================================
480
  # QUERY EXECUTION
 
717
 
718
  except Exception as e:
719
  print(f"❌ Suspicious Vehicles Error (timeout?): {e}")
720
+ return []
database_old.py ADDED
@@ -0,0 +1,982 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =========================================================
2
+ # ULTRA ADVANCED HYBRID NLP TO SQL ENGINE
3
+ # RULE BASED + LLM BASED + SQL SAFETY
4
+ # MISTRAL / SQLCODER READY
5
+ # =========================================================
6
+
7
+ import re
8
+ import traceback
9
+ import os
10
+
11
+ from huggingface_hub import InferenceClient
12
+ from dotenv import load_dotenv
13
+ from sqlalchemy import create_engine, text
14
+
15
+ # =========================================================
16
+ # ENVIRONMENT SETUP
17
+ # =========================================================
18
+
19
+ load_dotenv()
20
+
21
+ HF_TOKEN = os.getenv("HF_TOKEN")
22
+ DATABASE_URL = os.getenv("DATABASE_URL")
23
+
24
+ # Initialize Mistral client
25
+ client = None
26
+ try:
27
+ if HF_TOKEN:
28
+ client = InferenceClient(
29
+ model="mistralai/Mistral-7B-Instruct-v0.2",
30
+ token=HF_TOKEN
31
+ )
32
+ print("βœ… Mistral client initialized")
33
+ else:
34
+ print("⚠️ HF_TOKEN not set - LLM features disabled")
35
+ except Exception as e:
36
+ print(f"⚠️ Mistral client error: {e}")
37
+ client = None
38
+
39
+ # Initialize database engine
40
+ engine = None
41
+ try:
42
+ if DATABASE_URL:
43
+ engine = create_engine(DATABASE_URL)
44
+ print("βœ… Database connection initialized")
45
+ else:
46
+ print("⚠️ DATABASE_URL not set - Database features disabled")
47
+ except Exception as e:
48
+ print(f"⚠️ Database connection warning: {e}")
49
+ engine = None
50
+
51
+ # =========================================================
52
+ # CONFIG
53
+ # =========================================================
54
+
55
+ USE_LLM = True
56
+
57
+ # =========================================================
58
+ # DATABASE KNOWLEDGE
59
+ # =========================================================
60
+
61
+ SCHEMA = {
62
+ "table": "vehicle_logs",
63
+ "columns": [
64
+ "timestamp",
65
+ "plate",
66
+ "state",
67
+ "vehicle_type",
68
+ "vehicle_conf",
69
+ "camera_id",
70
+ "location",
71
+ "date",
72
+ "hour",
73
+ "day"
74
+ ]
75
+ }
76
+
77
+ VALID_STATES = {
78
+ "tn": "TN",
79
+ "tamil nadu": "TN",
80
+
81
+ "ka": "KA",
82
+ "karnataka": "KA",
83
+
84
+ "kl": "KL",
85
+ "kerala": "KL",
86
+
87
+ "ap": "AP",
88
+ "andhra": "AP",
89
+
90
+ "ts": "TS",
91
+ "telangana": "TS",
92
+
93
+ "mh": "MH",
94
+ "maharashtra": "MH",
95
+
96
+ "dl": "DL",
97
+ "delhi": "DL",
98
+
99
+ "gj": "GJ",
100
+ "gujarat": "GJ",
101
+
102
+ "rj": "RJ",
103
+ "rajasthan": "RJ",
104
+
105
+ "up": "UP",
106
+ "uttar pradesh": "UP",
107
+
108
+ "wb": "WB",
109
+ "west bengal": "WB",
110
+
111
+ "hr": "HR",
112
+ "haryana": "HR",
113
+
114
+ "pb": "PB",
115
+ "punjab": "PB"
116
+ }
117
+
118
+ KNOWN_LOCATIONS = [
119
+ "adyar",
120
+ "guindy",
121
+ "velachery",
122
+ "besantnagar",
123
+ "besant nagar",
124
+ "thiruvanmiyur",
125
+ "tnagar",
126
+ "t nagar",
127
+ "mylapore",
128
+ "annanagar",
129
+ "anna nagar",
130
+ "koyambedu",
131
+ "nungambakkam",
132
+ "kotturpuram"
133
+ ]
134
+
135
+ VEHICLE_TYPES = [
136
+ "suv",
137
+ "bus",
138
+ "truck",
139
+ "bike",
140
+ "auto",
141
+ "taxi",
142
+ "car",
143
+ "jeep",
144
+ "sedan"
145
+ ]
146
+
147
+ # =========================================================
148
+ # SQL CLEANER
149
+ # =========================================================
150
+
151
+ def clean_sql(sql):
152
+
153
+ sql = sql.replace("```sql", "")
154
+ sql = sql.replace("```", "")
155
+ sql = sql.strip()
156
+
157
+ if not sql.endswith(";"):
158
+ sql += ";"
159
+
160
+ return sql
161
+
162
+
163
+ # =========================================================
164
+ # SQL VALIDATOR
165
+ # =========================================================
166
+
167
+ def validate_sql(sql):
168
+
169
+ blocked = [
170
+ "DROP",
171
+ "DELETE",
172
+ "UPDATE",
173
+ "INSERT",
174
+ "ALTER",
175
+ "CREATE",
176
+ "TRUNCATE",
177
+ "JOIN",
178
+ "UNION"
179
+ ]
180
+
181
+ upper = sql.upper()
182
+
183
+ for word in blocked:
184
+
185
+ if word in upper:
186
+ return False
187
+
188
+ if not upper.startswith("SELECT"):
189
+ return False
190
+
191
+ if "VEHICLE_LOGS" not in upper:
192
+ return False
193
+
194
+ return True
195
+
196
+
197
+ # =========================================================
198
+ # MAIN NLP TO SQL ENGINE
199
+ # =========================================================
200
+
201
+ def ask_llm(user_query):
202
+
203
+ q = user_query.lower().strip()
204
+
205
+ # =====================================================
206
+ # ENTITY EXTRACTION
207
+ # =====================================================
208
+
209
+ plate_match = re.search(
210
+ r'([A-Z]{2}\d{1,2}[A-Z]{1,3}\d{3,4})',
211
+ user_query.upper()
212
+ )
213
+
214
+ date_match = re.search(
215
+ r'(\d{4}-\d{2}-\d{2})',
216
+ q
217
+ )
218
+
219
+ # =====================================================
220
+ # INTENT DETECTION
221
+ # =====================================================
222
+
223
+ intents = {
224
+
225
+ "tracking":
226
+ any(k in q for k in [
227
+ "track",
228
+ "history",
229
+ "movement",
230
+ "travel",
231
+ "route",
232
+ "visited",
233
+ "where"
234
+ ]),
235
+
236
+ "count":
237
+ any(k in q for k in [
238
+ "count",
239
+ "how many",
240
+ "total"
241
+ ]),
242
+
243
+ "analytics":
244
+ any(k in q for k in [
245
+ "top",
246
+ "most",
247
+ "distribution",
248
+ "analysis",
249
+ "statistics",
250
+ "peak"
251
+ ]),
252
+
253
+ "latest":
254
+ any(k in q for k in [
255
+ "latest",
256
+ "recent",
257
+ "last"
258
+ ])
259
+ }
260
+
261
+ # =====================================================
262
+ # RULE BASED ENGINE
263
+ # =====================================================
264
+
265
+ # =====================================================
266
+ # PLATE TRACKING
267
+ # =====================================================
268
+
269
+ if plate_match:
270
+
271
+ plate = plate_match.group(1)
272
+
273
+ # TRACKING
274
+
275
+ if intents["tracking"]:
276
+
277
+ return clean_sql(f"""
278
+ SELECT
279
+ timestamp,
280
+ plate,
281
+ state,
282
+ vehicle_type,
283
+ location,
284
+ camera_id,
285
+ date,
286
+ hour,
287
+ day
288
+ FROM vehicle_logs
289
+ WHERE plate = '{plate}'
290
+ ORDER BY timestamp DESC
291
+ LIMIT 100
292
+ """)
293
+
294
+ # COUNT
295
+
296
+ if intents["count"]:
297
+
298
+ return clean_sql(f"""
299
+ SELECT
300
+ plate,
301
+ COUNT(*) as detections,
302
+ COUNT(DISTINCT location) as unique_locations,
303
+ COUNT(DISTINCT date) as active_days
304
+ FROM vehicle_logs
305
+ WHERE plate = '{plate}'
306
+ GROUP BY plate
307
+ """)
308
+
309
+ # DEFAULT
310
+
311
+ return clean_sql(f"""
312
+ SELECT *
313
+ FROM vehicle_logs
314
+ WHERE plate = '{plate}'
315
+ ORDER BY timestamp DESC
316
+ LIMIT 50
317
+ """)
318
+
319
+ # =====================================================
320
+ # STATE QUERIES
321
+ # =====================================================
322
+
323
+ for key, state in VALID_STATES.items():
324
+
325
+ if key in q:
326
+
327
+ if intents["count"]:
328
+
329
+ return clean_sql(f"""
330
+ SELECT
331
+ state,
332
+ COUNT(*) as total_detections,
333
+ COUNT(DISTINCT plate) as unique_vehicles
334
+ FROM vehicle_logs
335
+ WHERE state = '{state}'
336
+ GROUP BY state
337
+ """)
338
+
339
+ return clean_sql(f"""
340
+ SELECT *
341
+ FROM vehicle_logs
342
+ WHERE state = '{state}'
343
+ ORDER BY timestamp DESC
344
+ LIMIT 100
345
+ """)
346
+
347
+ # =====================================================
348
+ # LOCATION QUERIES
349
+ # =====================================================
350
+
351
+ for loc in KNOWN_LOCATIONS:
352
+
353
+ if loc in q:
354
+
355
+ # COUNT
356
+
357
+ if intents["count"]:
358
+
359
+ return clean_sql(f"""
360
+ SELECT
361
+ location,
362
+ COUNT(*) as detections,
363
+ COUNT(DISTINCT plate) as unique_vehicles
364
+ FROM vehicle_logs
365
+ WHERE LOWER(location) LIKE '%{loc}%'
366
+ GROUP BY location
367
+ ORDER BY detections DESC
368
+ """)
369
+
370
+ # DEFAULT
371
+
372
+ return clean_sql(f"""
373
+ SELECT
374
+ timestamp,
375
+ plate,
376
+ state,
377
+ vehicle_type,
378
+ location,
379
+ camera_id
380
+ FROM vehicle_logs
381
+ WHERE LOWER(location) LIKE '%{loc}%'
382
+ ORDER BY timestamp DESC
383
+ LIMIT 100
384
+ """)
385
+
386
+ # =====================================================
387
+ # VEHICLE TYPE
388
+ # =====================================================
389
+
390
+ for vtype in VEHICLE_TYPES:
391
+
392
+ if vtype in q:
393
+
394
+ if intents["count"]:
395
+
396
+ return clean_sql(f"""
397
+ SELECT
398
+ vehicle_type,
399
+ COUNT(*) as count
400
+ FROM vehicle_logs
401
+ WHERE LOWER(vehicle_type) LIKE '%{vtype}%'
402
+ GROUP BY vehicle_type
403
+ """)
404
+
405
+ return clean_sql(f"""
406
+ SELECT *
407
+ FROM vehicle_logs
408
+ WHERE LOWER(vehicle_type) LIKE '%{vtype}%'
409
+ ORDER BY timestamp DESC
410
+ LIMIT 50
411
+ """)
412
+
413
+ # =====================================================
414
+ # DATE QUERY
415
+ # =====================================================
416
+
417
+ if date_match:
418
+
419
+ d = date_match.group(1)
420
+
421
+ return clean_sql(f"""
422
+ SELECT *
423
+ FROM vehicle_logs
424
+ WHERE date = '{d}'
425
+ ORDER BY timestamp DESC
426
+ LIMIT 100
427
+ """)
428
+
429
+ # =====================================================
430
+ # ANALYTICS
431
+ # =====================================================
432
+
433
+ if "hourly traffic" in q or "traffic by hour" in q:
434
+
435
+ return clean_sql("""
436
+ SELECT
437
+ hour,
438
+ COUNT(*) as traffic
439
+ FROM vehicle_logs
440
+ GROUP BY hour
441
+ ORDER BY hour
442
+ """)
443
+
444
+ if "top vehicles" in q or "most detected" in q:
445
+
446
+ return clean_sql("""
447
+ SELECT
448
+ plate,
449
+ COUNT(*) as detections
450
+ FROM vehicle_logs
451
+ GROUP BY plate
452
+ ORDER BY detections DESC
453
+ LIMIT 20
454
+ """)
455
+
456
+ if "state distribution" in q:
457
+
458
+ return clean_sql("""
459
+ SELECT
460
+ state,
461
+ COUNT(*) as count
462
+ FROM vehicle_logs
463
+ GROUP BY state
464
+ ORDER BY count DESC
465
+ """)
466
+
467
+ if "vehicle type distribution" in q:
468
+
469
+ return clean_sql("""
470
+ SELECT
471
+ vehicle_type,
472
+ COUNT(*) as count
473
+ FROM vehicle_logs
474
+ GROUP BY vehicle_type
475
+ ORDER BY count DESC
476
+ """)
477
+
478
+ if "latest" in q or "recent" in q:
479
+
480
+ return clean_sql("""
481
+ SELECT *
482
+ FROM vehicle_logs
483
+ ORDER BY timestamp DESC
484
+ LIMIT 50
485
+ """)
486
+
487
+ # =====================================================
488
+ # LLM FALLBACK
489
+ # =====================================================
490
+
491
+ if not USE_LLM:
492
+
493
+ return clean_sql("""
494
+ SELECT *
495
+ FROM vehicle_logs
496
+ ORDER BY timestamp DESC
497
+ LIMIT 10
498
+ """)
499
+
500
+ # =====================================================
501
+ # SYSTEM PROMPT
502
+ # =====================================================
503
+
504
+ system_prompt = f"""
505
+ You are an elite PostgreSQL SQL generator.
506
+
507
+ Your job:
508
+ Convert natural language into VALID PostgreSQL SQL.
509
+
510
+ ==================================================
511
+ DATABASE
512
+ ==================================================
513
+
514
+ TABLE:
515
+ vehicle_logs
516
+
517
+ AVAILABLE COLUMNS:
518
+
519
+ timestamp
520
+ plate
521
+ state
522
+ vehicle_type
523
+ vehicle_conf
524
+ camera_id
525
+ location
526
+ date
527
+ hour
528
+ day
529
+
530
+ ==================================================
531
+ COLUMN MEANINGS
532
+ ==================================================
533
+
534
+ timestamp:
535
+ vehicle detection timestamp
536
+
537
+ plate:
538
+ vehicle number plate
539
+
540
+ state:
541
+ vehicle state code
542
+
543
+ vehicle_type:
544
+ type of vehicle
545
+
546
+ vehicle_conf:
547
+ AI detection confidence
548
+
549
+ camera_id:
550
+ CCTV camera ID
551
+
552
+ location:
553
+ detected location
554
+
555
+ date:
556
+ YYYY-MM-DD
557
+
558
+ hour:
559
+ 0-23
560
+
561
+ day:
562
+ Monday-Sunday
563
+
564
+ ==================================================
565
+ KNOWN STATES
566
+ ==================================================
567
+
568
+ TN
569
+ KA
570
+ KL
571
+ AP
572
+ TS
573
+ MH
574
+ DL
575
+ GJ
576
+ RJ
577
+ UP
578
+ WB
579
+ HR
580
+ PB
581
+
582
+ ==================================================
583
+ KNOWN LOCATIONS
584
+ ==================================================
585
+
586
+ {KNOWN_LOCATIONS}
587
+
588
+ ==================================================
589
+ STRICT RULES
590
+ ==================================================
591
+
592
+ 1. ONLY use vehicle_logs
593
+ 2. NEVER use JOIN
594
+ 3. NEVER invent tables
595
+ 4. NEVER invent columns
596
+ 5. ONLY SELECT queries
597
+ 6. NEVER use UPDATE
598
+ 7. NEVER use DELETE
599
+ 8. NEVER use DROP
600
+ 9. NEVER use ALTER
601
+ 10. PostgreSQL syntax only
602
+ 11. Always use LIMIT 50 or LIMIT 100
603
+ 12. Return SQL ONLY
604
+ 13. No markdown
605
+ 14. No explanation
606
+
607
+ ==================================================
608
+ QUERY UNDERSTANDING
609
+ ==================================================
610
+
611
+ track vehicle
612
+ β†’ WHERE plate=''
613
+
614
+ show TN vehicles
615
+ β†’ WHERE state='TN'
616
+
617
+ show vehicles from adyar
618
+ β†’ WHERE LOWER(location) LIKE '%adyar%'
619
+
620
+ top vehicles
621
+ β†’ GROUP BY plate
622
+
623
+ hourly traffic
624
+ β†’ GROUP BY hour
625
+
626
+ vehicle type distribution
627
+ β†’ GROUP BY vehicle_type
628
+
629
+ latest detections
630
+ β†’ ORDER BY timestamp DESC
631
+
632
+ ==================================================
633
+ GOOD EXAMPLES
634
+ ==================================================
635
+
636
+ SELECT *
637
+ FROM vehicle_logs
638
+ WHERE state='TN'
639
+ ORDER BY timestamp DESC
640
+ LIMIT 50;
641
+
642
+ SELECT *
643
+ FROM vehicle_logs
644
+ WHERE LOWER(location) LIKE '%adyar%'
645
+ ORDER BY timestamp DESC
646
+ LIMIT 50;
647
+
648
+ SELECT
649
+ plate,
650
+ COUNT(*) as detections
651
+ FROM vehicle_logs
652
+ GROUP BY plate
653
+ ORDER BY detections DESC
654
+ LIMIT 20;
655
+
656
+ SELECT *
657
+ FROM vehicle_logs
658
+ WHERE plate='TN63MB3157'
659
+ ORDER BY timestamp DESC
660
+ LIMIT 100;
661
+ """
662
+
663
+ user_prompt = f"""
664
+ Generate PostgreSQL SQL query for:
665
+
666
+ {user_query}
667
+ """
668
+
669
+ # =====================================================
670
+ # MISTRAL / SQLCODER CALL
671
+ # =====================================================
672
+
673
+ try:
674
+
675
+ if client is None:
676
+ print("❌ Mistral client not initialized - HF_TOKEN missing")
677
+ raise Exception("LLM service unavailable - HF_TOKEN not configured")
678
+
679
+ try:
680
+ response = client.chat_completion(
681
+ messages=[
682
+ {
683
+ "role": "system",
684
+ "content": system_prompt
685
+ },
686
+ {
687
+ "role": "user",
688
+ "content": user_prompt
689
+ }
690
+ ],
691
+ max_tokens=250,
692
+ temperature=0.05
693
+ )
694
+ sql = response.choices[0].message.content.strip()
695
+ except Exception as api_error:
696
+ print(f"⚠️ API timeout or error: {api_error}")
697
+ # Fallback to rule-based query if LLM times out
698
+ print("⚠️ Using fallback query due to API timeout")
699
+ return clean_sql("""
700
+ SELECT *
701
+ FROM vehicle_logs
702
+ ORDER BY timestamp DESC
703
+ LIMIT 10
704
+ """)
705
+
706
+ sql = clean_sql(sql)
707
+
708
+ # =================================================
709
+ # SAFETY
710
+ # =================================================
711
+
712
+ if not validate_sql(sql):
713
+ print("❌ SQL validation failed - using safe query")
714
+ return clean_sql("""
715
+ SELECT *
716
+ FROM vehicle_logs
717
+ ORDER BY timestamp DESC
718
+ LIMIT 10
719
+ """)
720
+
721
+ # AUTO LIMIT
722
+
723
+ if "LIMIT" not in sql.upper():
724
+
725
+ sql = sql.replace(";", " LIMIT 50;")
726
+
727
+ return sql
728
+
729
+ except Exception as e:
730
+
731
+ print(f"❌ LLM ERROR: {e}")
732
+ traceback.print_exc()
733
+
734
+ return clean_sql("""
735
+ SELECT *
736
+ FROM vehicle_logs
737
+ ORDER BY timestamp DESC
738
+ LIMIT 10
739
+ """)
740
+
741
+ # =========================================================
742
+ # QUERY EXECUTION
743
+ # =========================================================
744
+
745
+ def run_query(user_query):
746
+ """Execute NLP-to-SQL query with timeout protection"""
747
+
748
+ sql = ""
749
+ try:
750
+
751
+ sql = ask_llm(user_query)
752
+
753
+ print("\n" + "="*40)
754
+ print("USER QUERY:")
755
+ print(user_query)
756
+
757
+ print("\nGENERATED SQL:")
758
+ print(sql)
759
+ print("="*40)
760
+
761
+ if engine is None:
762
+ return {
763
+ "query": user_query,
764
+ "error": "❌ Database not configured - DATABASE_URL missing",
765
+ "sql": sql,
766
+ "result": [],
767
+ "count": 0
768
+ }
769
+
770
+ try:
771
+ # Execute with timeout protection
772
+ with engine.connect() as conn:
773
+ # Set statement timeout to 30 seconds
774
+ conn.execute(text("SET statement_timeout = 30000")) # 30 seconds
775
+
776
+ result = conn.execute(text(sql))
777
+
778
+ rows = [
779
+ dict(r._mapping)
780
+ for r in result
781
+ ]
782
+
783
+ return {
784
+ "query": user_query,
785
+ "sql": sql,
786
+ "count": len(rows),
787
+ "result": rows
788
+ }
789
+
790
+ except Exception as query_error:
791
+ print(f"❌ Query Execution Error (possible timeout): {query_error}")
792
+ return {
793
+ "query": user_query,
794
+ "error": f"Query timeout or error: {str(query_error)}",
795
+ "sql": sql,
796
+ "result": [],
797
+ "count": 0
798
+ }
799
+
800
+ except Exception as e:
801
+
802
+ print(f"❌ Run Query Error: {e}")
803
+ traceback.print_exc()
804
+
805
+ return {
806
+ "query": user_query,
807
+ "error": str(e),
808
+ "sql": sql if sql else "",
809
+ "result": [],
810
+ "count": 0
811
+ }
812
+
813
+ # =========================================================
814
+ # DATABASE OPERATIONS
815
+ # =========================================================
816
+
817
+ def save_detection(plate, state, vehicle_type, vehicle_conf, date, time):
818
+ """Save a vehicle detection to the database
819
+
820
+ Note: The table schema uses timestamp, date, hour, day columns.
821
+ The 'time' parameter is extracted to hour for the hour column.
822
+ """
823
+
824
+ try:
825
+
826
+ if engine is None:
827
+ print("⚠️ Engine not initialized - save_detection skipped")
828
+ return False
829
+
830
+ # Extract hour from time string (HH:MM:SS)
831
+ try:
832
+ hour = int(time.split(":")[0]) if time else 0
833
+ except:
834
+ hour = 0
835
+
836
+ # Extract day of week from date (simplified)
837
+ from datetime import datetime
838
+ try:
839
+ dt = datetime.strptime(date, "%Y-%m-%d")
840
+ day = dt.strftime("%A")
841
+ except:
842
+ day = "Unknown"
843
+
844
+ # Use timestamp for current time, date for the date field, hour for hourly grouping
845
+ query = f"""
846
+ INSERT INTO vehicle_logs
847
+ (plate, state, vehicle_type, vehicle_conf, date, hour, day, timestamp, camera_id, location)
848
+ VALUES ('{plate}', '{state}', '{vehicle_type}', {vehicle_conf}, '{date}', {hour}, '{day}', NOW(), 'CAM-01', 'default')
849
+ """
850
+
851
+ with engine.connect() as conn:
852
+ conn.execute(text(query))
853
+ conn.commit()
854
+
855
+ print(f"βœ… Saved: {plate} from {state} at {time}")
856
+ return True
857
+
858
+ except Exception as e:
859
+ print(f"❌ Save Error: {e}")
860
+ traceback.print_exc()
861
+ return False
862
+
863
+
864
+ def health_check():
865
+ """Check database health with timeout protection"""
866
+
867
+ try:
868
+
869
+ if engine is None:
870
+ return False, "❌ Database not configured"
871
+
872
+ with engine.connect() as conn:
873
+ conn.execute(text("SET statement_timeout = 10000")) # 10 second timeout
874
+ result = conn.execute(text("SELECT COUNT(*) FROM vehicle_logs"))
875
+ count = result.scalar()
876
+
877
+ return True, f"βœ… Database OK - {count} records"
878
+
879
+ except Exception as e:
880
+ print(f"❌ Health Check Error (timeout?): {e}")
881
+ return False, f"❌ Database Error: {str(e)}"
882
+
883
+
884
+ def get_vehicles_by_state():
885
+ """Get vehicle count by state with timeout protection"""
886
+
887
+ try:
888
+
889
+ sql = """
890
+ SELECT state, COUNT(*) as count
891
+ FROM vehicle_logs
892
+ GROUP BY state
893
+ ORDER BY count DESC
894
+ """
895
+
896
+ with engine.connect() as conn:
897
+ conn.execute(text("SET statement_timeout = 15000")) # 15 second timeout
898
+ result = conn.execute(text(sql))
899
+ rows = [dict(r._mapping) for r in result]
900
+
901
+ return rows
902
+
903
+ except Exception as e:
904
+ print(f"❌ State Query Error (timeout?): {e}")
905
+ return []
906
+
907
+
908
+ def get_hourly_traffic():
909
+ """Get traffic by hour with timeout protection"""
910
+
911
+ try:
912
+
913
+ sql = """
914
+ SELECT hour, COUNT(*) as traffic
915
+ FROM vehicle_logs
916
+ GROUP BY hour
917
+ ORDER BY hour
918
+ """
919
+
920
+ with engine.connect() as conn:
921
+ conn.execute(text("SET statement_timeout = 15000")) # 15 second timeout
922
+ result = conn.execute(text(sql))
923
+ rows = [dict(r._mapping) for r in result]
924
+
925
+ return rows
926
+
927
+ except Exception as e:
928
+ print(f"❌ Hourly Traffic Error (timeout?): {e}")
929
+ return []
930
+
931
+
932
+ def get_top_plates():
933
+ """Get top detected plates with timeout protection"""
934
+
935
+ try:
936
+
937
+ sql = """
938
+ SELECT plate, COUNT(*) as detections
939
+ FROM vehicle_logs
940
+ GROUP BY plate
941
+ ORDER BY detections DESC
942
+ LIMIT 20
943
+ """
944
+
945
+ with engine.connect() as conn:
946
+ conn.execute(text("SET statement_timeout = 15000")) # 15 second timeout
947
+ result = conn.execute(text(sql))
948
+ rows = [dict(r._mapping) for r in result]
949
+
950
+ return rows
951
+
952
+ except Exception as e:
953
+ print(f"❌ Top Plates Error (timeout?): {e}")
954
+ return []
955
+
956
+
957
+ def get_suspicious_vehicles():
958
+ """Get vehicles detected multiple times (potentially suspicious) with timeout protection"""
959
+
960
+ try:
961
+
962
+ sql = """
963
+ SELECT plate, state, COUNT(*) as detections,
964
+ COUNT(DISTINCT location) as locations,
965
+ COUNT(DISTINCT date) as days
966
+ FROM vehicle_logs
967
+ GROUP BY plate, state
968
+ HAVING COUNT(*) > 5
969
+ ORDER BY detections DESC
970
+ LIMIT 20
971
+ """
972
+
973
+ with engine.connect() as conn:
974
+ conn.execute(text("SET statement_timeout = 15000")) # 15 second timeout
975
+ result = conn.execute(text(sql))
976
+ rows = [dict(r._mapping) for r in result]
977
+
978
+ return rows
979
+
980
+ except Exception as e:
981
+ print(f"❌ Suspicious Vehicles Error (timeout?): {e}")
982
+ return []