Spaces:
Sleeping
Sleeping
barathvasan-dev commited on
Commit ·
ec39753
1
Parent(s): ae48b27
Fix: NLP-to-SQL now handles multiple vehicles and multiple locations with proper OR logic
Browse files- database.py +94 -12
database.py
CHANGED
|
@@ -363,7 +363,7 @@ class FilterExtractor:
|
|
| 363 |
return None
|
| 364 |
|
| 365 |
def extract_location(self, query):
|
| 366 |
-
"""Extract location with variant matching"""
|
| 367 |
q = query.lower()
|
| 368 |
# Sort by length (longest first) to match longer variants first
|
| 369 |
for canonical, variants in sorted(
|
|
@@ -376,8 +376,34 @@ class FilterExtractor:
|
|
| 376 |
return canonical
|
| 377 |
return None
|
| 378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
def extract_vehicle_type(self, query):
|
| 380 |
-
"""Extract vehicle type with synonym resolution"""
|
| 381 |
q = query.lower()
|
| 382 |
# Sort by length (longest first) to match longer synonyms first
|
| 383 |
for synonym in sorted(self.vehicle_synonyms.keys(), key=len, reverse=True):
|
|
@@ -385,6 +411,27 @@ class FilterExtractor:
|
|
| 385 |
return self.vehicle_synonyms[synonym]
|
| 386 |
return None
|
| 387 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
def extract_date_range(self, query):
|
| 389 |
"""Extract date range (from X to Y, between X and Y)"""
|
| 390 |
# Pattern: "from DD-MM-YYYY to DD-MM-YYYY" or "between DD-MM-YYYY and DD-MM-YYYY"
|
|
@@ -520,8 +567,8 @@ class FilterExtractor:
|
|
| 520 |
return {
|
| 521 |
"plate": self.extract_plate(query),
|
| 522 |
"state": self.extract_state(query),
|
| 523 |
-
"location": self.
|
| 524 |
-
"vehicle_type": self.
|
| 525 |
"date": self.extract_date(query),
|
| 526 |
"date_range": self.extract_date_range(query),
|
| 527 |
"day": self.extract_day(query),
|
|
@@ -549,7 +596,7 @@ class FilterExtractor:
|
|
| 549 |
def build_sql(self, filters, intents):
|
| 550 |
"""
|
| 551 |
Build production-grade SQL from filters and intents.
|
| 552 |
-
Handles complex aggregations, date ranges, time ranges, and
|
| 553 |
"""
|
| 554 |
|
| 555 |
# =========================================================
|
|
@@ -596,7 +643,7 @@ class FilterExtractor:
|
|
| 596 |
""")
|
| 597 |
|
| 598 |
# =========================================================
|
| 599 |
-
# BUILD WHERE CLAUSE FROM FILTERS
|
| 600 |
# =========================================================
|
| 601 |
|
| 602 |
where_conditions = []
|
|
@@ -609,13 +656,31 @@ class FilterExtractor:
|
|
| 609 |
if filters["state"]:
|
| 610 |
where_conditions.append(f"state = '{filters['state']}'")
|
| 611 |
|
| 612 |
-
# Location filter
|
| 613 |
if filters["location"]:
|
| 614 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
|
| 616 |
-
# Vehicle type filter
|
| 617 |
if filters["vehicle_type"]:
|
| 618 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
|
| 620 |
# Date range filter
|
| 621 |
if filters["date_range"]:
|
|
@@ -752,8 +817,25 @@ def ask_llm(user_query):
|
|
| 752 |
print(f" Extracted Filters:")
|
| 753 |
print(f" - Plate: {filters['plate']}")
|
| 754 |
print(f" - State: {filters['state']}")
|
| 755 |
-
|
| 756 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 757 |
print(f" - Date: {filters['date']}")
|
| 758 |
print(f" - Date Range: {filters['date_range']}")
|
| 759 |
print(f" - Day: {filters['day']}")
|
|
|
|
| 363 |
return None
|
| 364 |
|
| 365 |
def extract_location(self, query):
|
| 366 |
+
"""Extract SINGLE location with variant matching (legacy method)"""
|
| 367 |
q = query.lower()
|
| 368 |
# Sort by length (longest first) to match longer variants first
|
| 369 |
for canonical, variants in sorted(
|
|
|
|
| 376 |
return canonical
|
| 377 |
return None
|
| 378 |
|
| 379 |
+
def extract_locations(self, query):
|
| 380 |
+
"""Extract MULTIPLE locations from query (e.g., 'adyar and kottupuram')"""
|
| 381 |
+
q = query.lower()
|
| 382 |
+
locations = []
|
| 383 |
+
|
| 384 |
+
# Sort by length (longest first) to match longer variants first
|
| 385 |
+
for canonical, variants in sorted(
|
| 386 |
+
self.location_variants.items(),
|
| 387 |
+
key=lambda x: max(len(v) for v in x[1]),
|
| 388 |
+
reverse=True
|
| 389 |
+
):
|
| 390 |
+
for variant in variants:
|
| 391 |
+
if variant in q:
|
| 392 |
+
locations.append(canonical)
|
| 393 |
+
break
|
| 394 |
+
|
| 395 |
+
# Remove duplicates while preserving order
|
| 396 |
+
seen = set()
|
| 397 |
+
unique_locations = []
|
| 398 |
+
for loc in locations:
|
| 399 |
+
if loc not in seen:
|
| 400 |
+
seen.add(loc)
|
| 401 |
+
unique_locations.append(loc)
|
| 402 |
+
|
| 403 |
+
return unique_locations if unique_locations else None
|
| 404 |
+
|
| 405 |
def extract_vehicle_type(self, query):
|
| 406 |
+
"""Extract SINGLE vehicle type with synonym resolution (legacy method)"""
|
| 407 |
q = query.lower()
|
| 408 |
# Sort by length (longest first) to match longer synonyms first
|
| 409 |
for synonym in sorted(self.vehicle_synonyms.keys(), key=len, reverse=True):
|
|
|
|
| 411 |
return self.vehicle_synonyms[synonym]
|
| 412 |
return None
|
| 413 |
|
| 414 |
+
def extract_vehicle_types(self, query):
|
| 415 |
+
"""Extract MULTIPLE vehicle types from query (e.g., 'bike and mini_truck')"""
|
| 416 |
+
q = query.lower()
|
| 417 |
+
vehicle_types = []
|
| 418 |
+
|
| 419 |
+
# Sort by length (longest first) to match longer synonyms first
|
| 420 |
+
for synonym in sorted(self.vehicle_synonyms.keys(), key=len, reverse=True):
|
| 421 |
+
if re.search(r'\b' + synonym + r'\b', q):
|
| 422 |
+
normalized = self.vehicle_synonyms[synonym]
|
| 423 |
+
vehicle_types.append(normalized)
|
| 424 |
+
|
| 425 |
+
# Remove duplicates while preserving order
|
| 426 |
+
seen = set()
|
| 427 |
+
unique_types = []
|
| 428 |
+
for vtype in vehicle_types:
|
| 429 |
+
if vtype not in seen:
|
| 430 |
+
seen.add(vtype)
|
| 431 |
+
unique_types.append(vtype)
|
| 432 |
+
|
| 433 |
+
return unique_types if unique_types else None
|
| 434 |
+
|
| 435 |
def extract_date_range(self, query):
|
| 436 |
"""Extract date range (from X to Y, between X and Y)"""
|
| 437 |
# Pattern: "from DD-MM-YYYY to DD-MM-YYYY" or "between DD-MM-YYYY and DD-MM-YYYY"
|
|
|
|
| 567 |
return {
|
| 568 |
"plate": self.extract_plate(query),
|
| 569 |
"state": self.extract_state(query),
|
| 570 |
+
"location": self.extract_locations(query), # Now returns list or None
|
| 571 |
+
"vehicle_type": self.extract_vehicle_types(query), # Now returns list or None
|
| 572 |
"date": self.extract_date(query),
|
| 573 |
"date_range": self.extract_date_range(query),
|
| 574 |
"day": self.extract_day(query),
|
|
|
|
| 596 |
def build_sql(self, filters, intents):
|
| 597 |
"""
|
| 598 |
Build production-grade SQL from filters and intents.
|
| 599 |
+
Handles complex aggregations, date ranges, time ranges, multiple vehicles, and multiple locations.
|
| 600 |
"""
|
| 601 |
|
| 602 |
# =========================================================
|
|
|
|
| 643 |
""")
|
| 644 |
|
| 645 |
# =========================================================
|
| 646 |
+
# BUILD WHERE CLAUSE FROM FILTERS (HANDLE MULTIPLE VALUES)
|
| 647 |
# =========================================================
|
| 648 |
|
| 649 |
where_conditions = []
|
|
|
|
| 656 |
if filters["state"]:
|
| 657 |
where_conditions.append(f"state = '{filters['state']}'")
|
| 658 |
|
| 659 |
+
# Location filter - HANDLE MULTIPLE LOCATIONS
|
| 660 |
if filters["location"]:
|
| 661 |
+
if isinstance(filters["location"], list):
|
| 662 |
+
# Multiple locations with OR logic
|
| 663 |
+
location_conditions = [
|
| 664 |
+
f"LOWER(location) LIKE '%{loc.lower()}%'"
|
| 665 |
+
for loc in filters["location"]
|
| 666 |
+
]
|
| 667 |
+
where_conditions.append(f"({' OR '.join(location_conditions)})")
|
| 668 |
+
else:
|
| 669 |
+
# Single location (legacy)
|
| 670 |
+
where_conditions.append(f"LOWER(location) LIKE '%{filters['location'].lower()}%'")
|
| 671 |
|
| 672 |
+
# Vehicle type filter - HANDLE MULTIPLE VEHICLE TYPES
|
| 673 |
if filters["vehicle_type"]:
|
| 674 |
+
if isinstance(filters["vehicle_type"], list):
|
| 675 |
+
# Multiple vehicle types with OR logic
|
| 676 |
+
vehicle_conditions = [
|
| 677 |
+
f"LOWER(vehicle_type) LIKE '%{vtype.lower()}%'"
|
| 678 |
+
for vtype in filters["vehicle_type"]
|
| 679 |
+
]
|
| 680 |
+
where_conditions.append(f"({' OR '.join(vehicle_conditions)})")
|
| 681 |
+
else:
|
| 682 |
+
# Single vehicle type (legacy)
|
| 683 |
+
where_conditions.append(f"LOWER(vehicle_type) LIKE '%{filters['vehicle_type'].lower()}%'")
|
| 684 |
|
| 685 |
# Date range filter
|
| 686 |
if filters["date_range"]:
|
|
|
|
| 817 |
print(f" Extracted Filters:")
|
| 818 |
print(f" - Plate: {filters['plate']}")
|
| 819 |
print(f" - State: {filters['state']}")
|
| 820 |
+
|
| 821 |
+
# Handle multiple locations
|
| 822 |
+
if filters['location']:
|
| 823 |
+
if isinstance(filters['location'], list):
|
| 824 |
+
print(f" - Locations: {', '.join(filters['location'])}")
|
| 825 |
+
else:
|
| 826 |
+
print(f" - Location: {filters['location']}")
|
| 827 |
+
else:
|
| 828 |
+
print(f" - Location: None")
|
| 829 |
+
|
| 830 |
+
# Handle multiple vehicle types
|
| 831 |
+
if filters['vehicle_type']:
|
| 832 |
+
if isinstance(filters['vehicle_type'], list):
|
| 833 |
+
print(f" - Vehicle Types: {', '.join(filters['vehicle_type'])}")
|
| 834 |
+
else:
|
| 835 |
+
print(f" - Vehicle Type: {filters['vehicle_type']}")
|
| 836 |
+
else:
|
| 837 |
+
print(f" - Vehicle Type: None")
|
| 838 |
+
|
| 839 |
print(f" - Date: {filters['date']}")
|
| 840 |
print(f" - Date Range: {filters['date_range']}")
|
| 841 |
print(f" - Day: {filters['day']}")
|