barathvasan-dev commited on
Commit
be7f905
·
1 Parent(s): 3de8d8f

feat: implement advanced hybrid rule-based + LLM NLP-to-SQL engine

Browse files
Files changed (1) hide show
  1. database.py +443 -52
database.py CHANGED
@@ -163,89 +163,480 @@ def validate_sql(sql):
163
  return upper.startswith("SELECT")
164
 
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  # ================= NLP TO SQL ================= #
167
 
168
  def ask_llm(user_query):
 
 
 
 
 
 
169
 
170
  if client is None:
171
- return "SELECT 1;" # Dummy query if no client
172
 
173
- prompt = f"""
174
- You are an expert PostgreSQL SQL generator.
175
 
176
- ONLY use this table:
 
 
177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  vehicle_logs(
179
- id,
180
  timestamp,
181
  plate,
182
  state,
183
  vehicle_type,
184
  vehicle_conf,
 
 
185
  date,
186
  hour,
187
  day
188
  )
189
 
190
- Rules:
191
- - ONLY SELECT queries
192
- - NEVER hallucinate columns
193
- - state column EXISTS
194
- - LIMIT 50 max
195
- - Output ONLY SQL
196
-
197
- Examples:
198
-
199
- Question:
200
- Show TN vehicles
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
  SQL:
203
- SELECT * FROM vehicle_logs
204
- WHERE state='TN'
205
- ORDER BY timestamp DESC
206
- LIMIT 50;
207
-
208
- Question:
209
- Top repeated plates
210
 
211
- SQL:
212
- SELECT plate, COUNT(*) AS count
213
- FROM vehicle_logs
214
- GROUP BY plate
215
- ORDER BY count DESC
216
- LIMIT 10;
 
217
 
218
- Question:
219
- Hourly traffic
220
 
221
- SQL:
222
- SELECT hour, COUNT(*) AS count
223
- FROM vehicle_logs
224
- GROUP BY hour
225
- ORDER BY hour;
226
 
227
- Question:
228
- {user_query}
229
 
230
- SQL:
231
- """
232
 
233
- response = client.text_generation(
234
- prompt,
235
- max_new_tokens=120,
236
- temperature=0.1
237
- )
238
 
239
- sql = response.strip()
 
240
 
241
- sql = sql.replace("```sql", "")
242
- sql = sql.replace("```", "")
243
- sql = sql.strip()
244
 
245
- if ";" not in sql:
246
- sql += ";"
247
 
248
- return sql
 
 
249
 
250
 
251
  # ================= QUERY ================= #
 
163
  return upper.startswith("SELECT")
164
 
165
 
166
+ # ================= NLP TO SQL ================= #
167
+
168
+ # ================= HELPER FUNCTIONS ================= #
169
+
170
+ def clean_sql(sql_str):
171
+ """Clean and normalize SQL output"""
172
+
173
+ sql = sql_str.strip()
174
+ sql = sql.replace("```sql", "").replace("```", "")
175
+ sql = sql.strip()
176
+
177
+ if not sql.endswith(";"):
178
+ sql += ";"
179
+
180
+ return sql
181
+
182
+
183
  # ================= NLP TO SQL ================= #
184
 
185
  def ask_llm(user_query):
186
+ """
187
+ Advanced NLP-to-SQL Generator
188
+ Hybrid Rule-Based + LLM Approach for Vehicle Intelligence
189
+ """
190
+
191
+ import re
192
 
193
  if client is None:
194
+ return "SELECT * FROM vehicle_logs LIMIT 10;"
195
 
196
+ q = user_query.lower().strip()
 
197
 
198
+ # =========================================================
199
+ # RULE-BASED FAST PATHS (VERY IMPORTANT)
200
+ # =========================================================
201
 
202
+ # ----- PLATE TRACKING (Generic) -----
203
+ plate_match = re.search(r'([A-Z]{2}\d{1,2}[A-Z]{1,3}\d{3,4})', user_query.upper())
204
+
205
+ if plate_match:
206
+ plate = plate_match.group(1)
207
+
208
+ if any(k in q for k in ["location", "route", "travel", "movement", "where", "pass", "track", "history"]):
209
+ return clean_sql(f"""
210
+ SELECT
211
+ timestamp,
212
+ plate,
213
+ state,
214
+ vehicle_type,
215
+ location,
216
+ camera_id
217
+ FROM vehicle_logs
218
+ WHERE plate = '{plate}'
219
+ ORDER BY timestamp DESC
220
+ LIMIT 100;
221
+ """)
222
+
223
+ if any(k in q for k in ["count", "how many", "detections"]):
224
+ return clean_sql(f"""
225
+ SELECT
226
+ plate,
227
+ COUNT(*) as detection_count,
228
+ COUNT(DISTINCT location) as unique_locations,
229
+ COUNT(DISTINCT date) as days_detected
230
+ FROM vehicle_logs
231
+ WHERE plate = '{plate}'
232
+ GROUP BY plate;
233
+ """)
234
+
235
+ return clean_sql(f"""
236
+ SELECT *
237
+ FROM vehicle_logs
238
+ WHERE plate = '{plate}'
239
+ ORDER BY timestamp DESC
240
+ LIMIT 50;
241
+ """)
242
+
243
+ # ----- STATE SEARCH (Generic) -----
244
+ states_map = {
245
+ "tn": "TN", "tamil": "TN", "tamil nadu": "TN",
246
+ "ka": "KA", "karnataka": "KA",
247
+ "kl": "KL", "kerala": "KL",
248
+ "ap": "AP", "andhra": "AP",
249
+ "ts": "TS", "telangana": "TS",
250
+ "mh": "MH", "maharashtra": "MH",
251
+ "dl": "DL", "delhi": "DL",
252
+ "gj": "GJ", "gujarat": "GJ",
253
+ "rj": "RJ", "rajasthan": "RJ",
254
+ "up": "UP", "uttar": "UP",
255
+ "wb": "WB", "bengal": "WB",
256
+ "hr": "HR", "haryana": "HR",
257
+ "pb": "PB", "punjab": "PB"
258
+ }
259
+
260
+ for key, state_code in states_map.items():
261
+ if key in q:
262
+ if "count" in q:
263
+ return clean_sql(f"""
264
+ SELECT
265
+ state,
266
+ COUNT(*) as total_vehicles,
267
+ COUNT(DISTINCT plate) as unique_plates,
268
+ COUNT(DISTINCT location) as locations_active
269
+ FROM vehicle_logs
270
+ WHERE state = '{state_code}'
271
+ GROUP BY state;
272
+ """)
273
+
274
+ if "distribution" in q or "breakdown" in q:
275
+ return clean_sql(f"""
276
+ SELECT
277
+ vehicle_type,
278
+ COUNT(*) as count
279
+ FROM vehicle_logs
280
+ WHERE state = '{state_code}'
281
+ GROUP BY vehicle_type
282
+ ORDER BY count DESC;
283
+ """)
284
+
285
+ return clean_sql(f"""
286
+ SELECT *
287
+ FROM vehicle_logs
288
+ WHERE state = '{state_code}'
289
+ ORDER BY timestamp DESC
290
+ LIMIT 50;
291
+ """)
292
+
293
+ # ----- LOCATION SEARCH (Generic) -----
294
+ locations = [
295
+ "adyar", "guindy", "velachery", "besant", "thiruvanmiyur",
296
+ "tnagar", "mylapore", "annanagar", "koyambedu", "nungambakkam",
297
+ "kotturpuram", "porur", "indiranagar", "whitefield", "koramangala",
298
+ "bangalore", "hyderabad", "trivandrum", "kochi", "pune", "mumbai"
299
+ ]
300
+
301
+ for loc in locations:
302
+ if loc in q:
303
+ if "count" in q:
304
+ return clean_sql(f"""
305
+ SELECT
306
+ location,
307
+ COUNT(*) as detection_count,
308
+ COUNT(DISTINCT plate) as unique_vehicles
309
+ FROM vehicle_logs
310
+ WHERE LOWER(location) LIKE '%{loc}%'
311
+ GROUP BY location
312
+ ORDER BY detection_count DESC;
313
+ """)
314
+
315
+ return clean_sql(f"""
316
+ SELECT
317
+ timestamp,
318
+ plate,
319
+ state,
320
+ vehicle_type,
321
+ location
322
+ FROM vehicle_logs
323
+ WHERE LOWER(location) LIKE '%{loc}%'
324
+ ORDER BY timestamp DESC
325
+ LIMIT 100;
326
+ """)
327
+
328
+ # ----- VEHICLE TYPE SEARCH (Generic) -----
329
+ vehicle_types = {
330
+ "suv": "SUV", "sedan": "Sedan", "hatchback": "Hatchback",
331
+ "truck": "Truck", "bus": "Bus", "bike": "Bike",
332
+ "motorcycle": "Bike", "auto": "Auto", "taxi": "Taxi",
333
+ "car": "Car", "van": "Van", "tempo": "Tempo"
334
+ }
335
+
336
+ for vtype_key, vtype_val in vehicle_types.items():
337
+ if vtype_key in q:
338
+ if "count" in q:
339
+ return clean_sql(f"""
340
+ SELECT
341
+ vehicle_type,
342
+ COUNT(*) as count,
343
+ ROUND(AVG(vehicle_conf), 2) as avg_confidence
344
+ FROM vehicle_logs
345
+ WHERE LOWER(vehicle_type) LIKE '%{vtype_val.lower()}%'
346
+ GROUP BY vehicle_type;
347
+ """)
348
+
349
+ return clean_sql(f"""
350
+ SELECT *
351
+ FROM vehicle_logs
352
+ WHERE LOWER(vehicle_type) LIKE '%{vtype_val.lower()}%'
353
+ ORDER BY timestamp DESC
354
+ LIMIT 50;
355
+ """)
356
+
357
+ # ----- DATE SEARCH -----
358
+ date_match = re.search(r'(\d{4}-\d{2}-\d{2})', q)
359
+
360
+ if date_match:
361
+ date_value = date_match.group(1)
362
+
363
+ if "count" in q:
364
+ return clean_sql(f"""
365
+ SELECT
366
+ date,
367
+ COUNT(*) as total_detections,
368
+ COUNT(DISTINCT plate) as unique_vehicles,
369
+ COUNT(DISTINCT location) as unique_locations
370
+ FROM vehicle_logs
371
+ WHERE date = '{date_value}'
372
+ GROUP BY date;
373
+ """)
374
+
375
+ return clean_sql(f"""
376
+ SELECT *
377
+ FROM vehicle_logs
378
+ WHERE date = '{date_value}'
379
+ ORDER BY timestamp DESC
380
+ LIMIT 100;
381
+ """)
382
+
383
+ # ----- TIME-BASED QUERIES -----
384
+ if "morning" in q:
385
+ return clean_sql("""
386
+ SELECT *
387
+ FROM vehicle_logs
388
+ WHERE hour BETWEEN 6 AND 11
389
+ ORDER BY timestamp DESC
390
+ LIMIT 100;
391
+ """)
392
+
393
+ if "afternoon" in q:
394
+ return clean_sql("""
395
+ SELECT *
396
+ FROM vehicle_logs
397
+ WHERE hour BETWEEN 12 AND 17
398
+ ORDER BY timestamp DESC
399
+ LIMIT 100;
400
+ """)
401
+
402
+ if "evening" in q or "night" in q:
403
+ return clean_sql("""
404
+ SELECT *
405
+ FROM vehicle_logs
406
+ WHERE hour BETWEEN 18 AND 23 OR hour BETWEEN 0 AND 5
407
+ ORDER BY timestamp DESC
408
+ LIMIT 100;
409
+ """)
410
+
411
+ if "busiest hour" in q or "peak hour" in q:
412
+ return clean_sql("""
413
+ SELECT
414
+ hour,
415
+ COUNT(*) as traffic_volume
416
+ FROM vehicle_logs
417
+ GROUP BY hour
418
+ ORDER BY traffic_volume DESC
419
+ LIMIT 1;
420
+ """)
421
+
422
+ if "hourly traffic" in q or "traffic by hour" in q:
423
+ return clean_sql("""
424
+ SELECT
425
+ hour,
426
+ COUNT(*) as traffic_count,
427
+ COUNT(DISTINCT plate) as unique_vehicles
428
+ FROM vehicle_logs
429
+ GROUP BY hour
430
+ ORDER BY hour;
431
+ """)
432
+
433
+ # ----- ANALYTICS QUERIES -----
434
+ if "top plates" in q or "most detected" in q or "repeated plates" in q:
435
+ return clean_sql("""
436
+ SELECT
437
+ plate,
438
+ COUNT(*) as detections,
439
+ COUNT(DISTINCT location) as locations,
440
+ COUNT(DISTINCT date) as days
441
+ FROM vehicle_logs
442
+ GROUP BY plate
443
+ ORDER BY detections DESC
444
+ LIMIT 20;
445
+ """)
446
+
447
+ if "suspicious" in q or "high frequency" in q or "unusual" in q:
448
+ return clean_sql("""
449
+ SELECT
450
+ plate,
451
+ COUNT(*) as detection_count,
452
+ COUNT(DISTINCT location) as unique_locations,
453
+ ROUND(AVG(vehicle_conf), 2) as avg_confidence
454
+ FROM vehicle_logs
455
+ GROUP BY plate
456
+ HAVING COUNT(*) > 10
457
+ ORDER BY detection_count DESC
458
+ LIMIT 50;
459
+ """)
460
+
461
+ if "vehicle type" in q and ("count" in q or "distribution" in q or "breakdown" in q):
462
+ return clean_sql("""
463
+ SELECT
464
+ vehicle_type,
465
+ COUNT(*) as count,
466
+ ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM vehicle_logs), 2) as percentage,
467
+ ROUND(AVG(vehicle_conf), 2) as avg_confidence
468
+ FROM vehicle_logs
469
+ GROUP BY vehicle_type
470
+ ORDER BY count DESC;
471
+ """)
472
+
473
+ if "state distribution" in q or ("count" in q and "state" in q):
474
+ return clean_sql("""
475
+ SELECT
476
+ state,
477
+ COUNT(*) as count,
478
+ COUNT(DISTINCT plate) as unique_plates,
479
+ ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM vehicle_logs), 2) as percentage
480
+ FROM vehicle_logs
481
+ GROUP BY state
482
+ ORDER BY count DESC;
483
+ """)
484
+
485
+ if "latest" in q or "recent" in q or "last detection" in q:
486
+ return clean_sql("""
487
+ SELECT *
488
+ FROM vehicle_logs
489
+ ORDER BY timestamp DESC
490
+ LIMIT 50;
491
+ """)
492
+
493
+ if "total vehicle" in q or "total count" in q:
494
+ return clean_sql("""
495
+ SELECT
496
+ COUNT(*) as total_detections,
497
+ COUNT(DISTINCT plate) as unique_vehicles,
498
+ COUNT(DISTINCT state) as states_active,
499
+ COUNT(DISTINCT location) as locations_active
500
+ FROM vehicle_logs;
501
+ """)
502
+
503
+ if "camera" in q or "detection point" in q:
504
+ return clean_sql("""
505
+ SELECT
506
+ camera_id,
507
+ location,
508
+ COUNT(*) as detections,
509
+ COUNT(DISTINCT plate) as unique_vehicles
510
+ FROM vehicle_logs
511
+ WHERE camera_id IS NOT NULL
512
+ GROUP BY camera_id, location
513
+ ORDER BY detections DESC
514
+ LIMIT 20;
515
+ """)
516
+
517
+ # ----- ADVANCED COMBINATION QUERIES -----
518
+ if "passed through" in q or "traveled through" in q:
519
+ return clean_sql("""
520
+ SELECT
521
+ plate,
522
+ location,
523
+ COUNT(*) as times_detected,
524
+ MIN(timestamp) as first_detection,
525
+ MAX(timestamp) as last_detection
526
+ FROM vehicle_logs
527
+ WHERE location IS NOT NULL
528
+ GROUP BY plate, location
529
+ ORDER BY plate, MIN(timestamp) DESC
530
+ LIMIT 100;
531
+ """)
532
+
533
+ # =========================================================
534
+ # FALLBACK: ADVANCED LLM
535
+ # =========================================================
536
+
537
+ prompt = f"""
538
+ You are an expert PostgreSQL SQL generator for vehicle intelligence.
539
+
540
+ DATABASE SCHEMA:
541
  vehicle_logs(
 
542
  timestamp,
543
  plate,
544
  state,
545
  vehicle_type,
546
  vehicle_conf,
547
+ camera_id,
548
+ location,
549
  date,
550
  hour,
551
  day
552
  )
553
 
554
+ VALID COLUMNS:
555
+ timestamp - Detection timestamp
556
+ plate - License plate number
557
+ state - State code (TN, KA, KL, AP, TS, MH, DL, GJ, RJ, UP, WB, HR, PB)
558
+ vehicle_type - Car, SUV, Truck, Bus, Bike, Auto, Taxi, Van, etc.
559
+ vehicle_conf - Detection confidence (0.0-1.0)
560
+ camera_id - Camera identifier
561
+ location - Detection location/area name
562
+ date - Detection date (YYYY-MM-DD)
563
+ hour - Hour of day (0-23)
564
+ day - Day of week (Monday-Sunday)
565
+
566
+ STRICT RULES:
567
+ 1. ONLY SELECT queries
568
+ 2. NEVER JOIN tables
569
+ 3. NEVER use subqueries (except COUNT aggregates)
570
+ 4. ONLY vehicle_logs table
571
+ 5. ALWAYS use LIMIT 50 or LIMIT 100
572
+ 6. NEVER use DELETE, UPDATE, DROP, ALTER, CREATE, TRUNCATE
573
+ 7. NEVER invent columns or tables
574
+ 8. Return SQL ONLY (no explanation)
575
+ 9. No markdown formatting
576
+ 10. Always end with semicolon
577
+
578
+ EXAMPLES:
579
+
580
+ Q: Show TN vehicles
581
+ A: SELECT * FROM vehicle_logs WHERE state='TN' ORDER BY timestamp DESC LIMIT 50;
582
+
583
+ Q: Show all vehicles from Adyar
584
+ A: SELECT * FROM vehicle_logs WHERE LOWER(location) LIKE '%adyar%' ORDER BY timestamp DESC LIMIT 50;
585
+
586
+ Q: Show suspicious vehicles
587
+ A: SELECT plate, COUNT(*) as count FROM vehicle_logs GROUP BY plate HAVING COUNT(*) > 10 ORDER BY count DESC LIMIT 50;
588
+
589
+ Q: Show vehicle type distribution
590
+ A: SELECT vehicle_type, COUNT(*) as count FROM vehicle_logs GROUP BY vehicle_type ORDER BY count DESC;
591
+
592
+ Q: Show hourly traffic
593
+ A: SELECT hour, COUNT(*) as count FROM vehicle_logs GROUP BY hour ORDER BY hour;
594
+
595
+ Q: Show latest detections
596
+ A: SELECT * FROM vehicle_logs ORDER BY timestamp DESC LIMIT 50;
597
+
598
+ Q: Track vehicle TN63AB1234
599
+ A: SELECT * FROM vehicle_logs WHERE plate='TN63AB1234' ORDER BY timestamp DESC LIMIT 100;
600
+
601
+ USER QUESTION:
602
+ {user_query}
603
 
604
  SQL:
605
+ """
 
 
 
 
 
 
606
 
607
+ try:
608
+ response = client.text_generation(
609
+ prompt,
610
+ max_new_tokens=150,
611
+ temperature=0.05,
612
+ repetition_penalty=1.2
613
+ )
614
 
615
+ sql_query = clean_sql(response.strip())
 
616
 
617
+ # =====================================================
618
+ # EXTRA SAFETY VALIDATION
619
+ # =====================================================
 
 
620
 
621
+ sql_upper = sql_query.upper()
 
622
 
623
+ blocked = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "CREATE", "TRUNCATE", "JOIN", "UNION"]
 
624
 
625
+ for b in blocked:
626
+ if b in sql_upper:
627
+ return f"SELECT * FROM vehicle_logs LIMIT 10;" # Fallback
 
 
628
 
629
+ if "VEHICLE_LOGS" not in sql_upper:
630
+ return f"SELECT * FROM vehicle_logs LIMIT 10;" # Fallback
631
 
632
+ if not sql_upper.startswith("SELECT"):
633
+ return f"SELECT * FROM vehicle_logs LIMIT 10;" # Fallback
 
634
 
635
+ return sql_query
 
636
 
637
+ except Exception as e:
638
+ print(f"⚠️ LLM Error: {e}")
639
+ return f"SELECT * FROM vehicle_logs LIMIT 10;" # Fallback
640
 
641
 
642
  # ================= QUERY ================= #