barathvasan-dev commited on
Commit
4d284f9
·
1 Parent(s): 9874783

fix: add schema migration to handle missing columns in existing tables

Browse files
Files changed (1) hide show
  1. database.py +332 -388
database.py CHANGED
@@ -1,48 +1,34 @@
1
- """
2
- Database Module
3
- Handles SQL validation, database operations, and LLM-to-SQL conversion
4
- """
5
-
6
  import os
7
- from datetime import datetime
8
- from sqlalchemy import create_engine, text
9
- from dotenv import load_dotenv
10
- from huggingface_hub import InferenceClient
11
  import traceback
12
-
13
-
14
- """
15
- Database Module
16
- Handles SQL validation, database operations, and LLM-to-SQL conversion
17
- """
18
-
19
- import os
20
  from datetime import datetime
21
- from sqlalchemy import create_engine, text
22
  from dotenv import load_dotenv
23
  from huggingface_hub import InferenceClient
24
- import traceback
25
-
26
-
27
- # ==================== INITIALIZATION ====================
28
 
29
  load_dotenv()
30
 
 
 
31
  DATABASE_URL = os.getenv("DATABASE_URL")
32
  engine = None
33
  db_available = False
34
 
35
  if DATABASE_URL:
36
  try:
37
- engine = create_engine(DATABASE_URL)
 
 
 
38
  db_available = True
39
  print("✅ Database engine initialized")
40
  except Exception as e:
41
- print(f"⚠️ Database initialization error: {e}")
42
  db_available = False
43
  else:
44
- print("⚠️ DATABASE_URL not set. Database features disabled.")
45
- print(" Set DATABASE_URL in Hugging Face Spaces secrets")
 
46
 
47
  HF_TOKEN = os.getenv("HF_TOKEN")
48
  client = None
@@ -55,248 +41,207 @@ if HF_TOKEN:
55
  )
56
  print("✅ HuggingFace client initialized")
57
  except Exception as e:
58
- print(f"⚠️ HuggingFace client error: {e}")
59
  else:
60
- print("⚠️ HF_TOKEN not set. NLP-to-SQL features disabled.")
61
- print(" Set HF_TOKEN in Hugging Face Spaces secrets")
62
-
63
- # Query history for analytics
64
- query_history = []
65
-
66
-
67
- # ==================== SQL VALIDATION ====================
68
-
69
- ALLOWED_KEYWORDS = [
70
- "SELECT",
71
- "FROM",
72
- "WHERE",
73
- "COUNT",
74
- "GROUP BY",
75
- "ORDER BY",
76
- "LIMIT",
77
- "AVG",
78
- "SUM",
79
- "MIN",
80
- "MAX",
81
- "DISTINCT",
82
- "AND",
83
- "OR",
84
- "IN",
85
- "BETWEEN",
86
- "LIKE"
87
- ]
88
 
89
- BLOCKED_KEYWORDS = [
90
- "DROP",
91
- "DELETE",
92
- "UPDATE",
93
- "INSERT",
94
- "ALTER",
95
- "TRUNCATE",
96
- "CREATE",
97
- "EXEC",
98
- "EXECUTE",
99
- "--",
100
- "/*"
101
- ]
102
 
 
 
 
 
 
103
 
104
- def validate_sql(sql):
105
- """
106
- Validate SQL query for safety
107
- Only allow SELECT queries, block dangerous operations
108
- """
109
- upper_sql = sql.upper()
110
 
111
- # Block dangerous keywords
112
- for word in BLOCKED_KEYWORDS:
113
- if word in upper_sql:
114
- return False, f"Dangerous keyword '{word}' detected"
115
 
116
- # Must be SELECT query
117
- if not upper_sql.strip().startswith("SELECT"):
118
- return False, "Only SELECT queries allowed"
119
 
120
- return True, "Valid"
121
 
 
122
 
123
- def clean_sql(sql):
124
- """Clean SQL output from LLM"""
125
- sql = sql.replace("```sql", "")
126
- sql = sql.replace("```", "")
127
- sql = sql.strip()
128
 
129
- if sql.endswith(";"):
130
- return sql
131
-
132
- return sql + ";"
133
 
 
134
 
135
- # ==================== DATABASE INITIALIZATION ====================
136
 
137
- def init_db():
138
- """Create vehicle_logs table with improved schema"""
139
- if not db_available or engine is None:
140
- print("⚠️ Skipping database initialization - DATABASE_URL not configured")
141
- return False
142
-
143
- try:
144
- with engine.connect() as conn:
 
145
  conn.execute(text("""
146
- CREATE TABLE IF NOT EXISTS vehicle_logs (
147
- id BIGSERIAL PRIMARY KEY,
148
-
149
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
150
-
151
- plate TEXT,
152
- state TEXT,
153
-
154
- vehicle_type TEXT,
155
- vehicle_conf FLOAT,
156
-
157
- camera_id TEXT,
158
- location TEXT,
159
-
160
- image_url TEXT,
161
-
162
- date DATE,
163
- hour INTEGER,
164
- day TEXT
165
- )
166
  """))
167
- conn.commit()
168
-
169
- # Create indexes
170
- create_indexes()
171
- print("✅ Database initialized successfully")
172
- return True
173
-
174
- except Exception as e:
175
- print(f"❌ Database initialization error: {e}")
176
- traceback.print_exc()
177
- return False
178
 
 
 
 
 
 
 
 
 
 
179
 
180
- def create_indexes():
181
- """Create database indexes for faster queries"""
182
- if not db_available or engine is None:
183
- return
184
-
185
- try:
186
- with engine.connect() as conn:
187
- # Check if indexes exist before creating
188
- indexes = [
189
- "idx_plate",
190
- "idx_state",
191
- "idx_vehicle_type",
192
- "idx_timestamp",
193
- "idx_date"
194
- ]
195
-
196
- for idx_name in indexes:
197
- try:
198
- if idx_name == "idx_plate":
199
- conn.execute(text("CREATE INDEX IF NOT EXISTS idx_plate ON vehicle_logs(plate);"))
200
- elif idx_name == "idx_state":
201
- conn.execute(text("CREATE INDEX IF NOT EXISTS idx_state ON vehicle_logs(state);"))
202
- elif idx_name == "idx_vehicle_type":
203
- conn.execute(text("CREATE INDEX IF NOT EXISTS idx_vehicle_type ON vehicle_logs(vehicle_type);"))
204
- elif idx_name == "idx_timestamp":
205
- conn.execute(text("CREATE INDEX IF NOT EXISTS idx_timestamp ON vehicle_logs(timestamp);"))
206
- elif idx_name == "idx_date":
207
- conn.execute(text("CREATE INDEX IF NOT EXISTS idx_date ON vehicle_logs(date);"))
208
- except:
209
- pass
210
-
211
- conn.commit()
212
- print("✅ Indexes created successfully")
213
-
214
- except Exception as e:
215
- print(f"⚠️ Index creation warning: {e}")
216
 
 
 
 
 
 
 
 
 
 
217
 
218
- # ==================== DATABASE INSERT ====================
 
 
 
 
 
 
 
 
219
 
220
- def save_detection(plate, state, vehicle_type, vehicle_conf, date_str, time_str):
221
- """Save detection to database"""
222
- if not db_available or engine is None:
223
- return False
224
-
225
- try:
226
- # Extract hour and day
227
- dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M:%S")
228
- hour = dt.hour
229
- day = dt.strftime("%A")
230
-
231
- with engine.connect() as conn:
232
  conn.execute(text("""
233
- INSERT INTO vehicle_logs
234
- (plate, state, vehicle_type, vehicle_conf, date, hour, day)
235
- VALUES (:plate, :state, :vehicle_type, :vehicle_conf, :date, :hour, :day)
236
- """), {
237
- "plate": plate,
238
- "state": state,
239
- "vehicle_type": vehicle_type,
240
- "vehicle_conf": float(vehicle_conf),
241
- "date": date_str,
242
- "hour": hour,
243
- "day": day
244
- })
245
- conn.commit()
246
- return True
247
-
248
- except Exception as e:
249
- print(f"❌ Database insert error: {e}")
250
- traceback.print_exc()
251
- return False
252
 
 
253
 
254
- # ==================== HEALTH CHECK ====================
255
 
256
- def health_check():
257
- """Check database connectivity"""
258
- if not db_available or engine is None:
259
- return False, "⚠️ Database not configured. Set DATABASE_URL secret."
 
 
 
 
 
 
 
 
 
260
 
261
- try:
262
- with engine.connect() as conn:
263
- conn.execute(text("SELECT 1"))
264
- return True, "✅ Database Connected"
265
- except Exception as e:
266
- return False, f" Database Error: {e}"
 
 
 
 
 
 
 
 
267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
- # ==================== LLM SQL GENERATION ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  def ask_llm(user_query):
272
- """Convert natural language to SQL using SQLCoder"""
273
-
274
  if client is None:
275
- return None, "LLM not configured. Set HF_TOKEN in secrets."
276
-
277
- schema = """
278
- Table: vehicle_logs
279
-
280
- Columns:
281
- - id (BIGSERIAL PRIMARY KEY)
282
- - timestamp (TIMESTAMP) - When vehicle was detected
283
- - plate (TEXT) - License plate number
284
- - state (TEXT) - State code (TN, KA, KL, etc.)
285
- - vehicle_type (TEXT) - Type of vehicle (car, truck, bike, etc.)
286
- - vehicle_conf (FLOAT) - Detection confidence (0-1)
287
- - camera_id (TEXT) - Camera identifier
288
- - location (TEXT) - Detection location
289
- - image_url (TEXT) - URL to vehicle image
290
- - date (DATE) - Date of detection
291
- - hour (INTEGER) - Hour of day (0-23)
292
- - day (TEXT) - Day of week (Monday, Tuesday, etc.)
293
-
294
- Note: Indian vehicle plates format - XX DD YYY NNNN (e.g., TN 09 AB 1234)
295
- """
296
 
297
- prompt = f"""You are an expert PostgreSQL SQL generator for vehicle surveillance database.
 
298
 
299
- Database schema:
300
 
301
  vehicle_logs(
302
  id,
@@ -305,65 +250,76 @@ vehicle_logs(
305
  state,
306
  vehicle_type,
307
  vehicle_conf,
308
- camera_id,
309
- location,
310
- image_url,
311
  date,
312
  hour,
313
  day
314
  )
315
 
316
- Strict Rules:
317
- - ONLY generate PostgreSQL SELECT queries
318
- - NEVER generate DELETE/UPDATE/INSERT/DROP/ALTER queries
319
- - Always use LIMIT 50 unless specifically counting totals
320
- - Use COUNT(*) for totals
321
- - Use GROUP BY for statistics
322
- - Return SQL ONLY (no markdown, no explanation)
323
- - Ensure query ends with semicolon
 
 
 
324
 
325
- Example queries:
326
- - "How many cars today?" → SELECT COUNT(*) FROM vehicle_logs WHERE vehicle_type='car' AND date=CURRENT_DATE;
327
- - "Show TN vehicles" → SELECT * FROM vehicle_logs WHERE state='TN' ORDER BY timestamp DESC LIMIT 50;
328
- - "Top plates" → SELECT plate, COUNT(*) as count FROM vehicle_logs GROUP BY plate ORDER BY count DESC LIMIT 10;
 
329
 
330
- Schema:
331
- {schema}
332
 
333
- User Question:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  {user_query}
335
 
336
- Generate SQL query:
337
  """
338
 
339
- try:
340
- response = client.text_generation(
341
- prompt,
342
- max_new_tokens=150,
343
- temperature=0.1
344
- )
345
 
346
- sql_query = clean_sql(response.strip())
347
- return sql_query, None
348
-
349
- except Exception as e:
350
- print(f"❌ LLM error: {e}")
351
- traceback.print_exc()
352
- return None, f"LLM Error: {str(e)}"
 
353
 
 
354
 
355
- # ==================== SQL EXECUTION ====================
 
356
 
357
  def run_query(user_query):
358
- """
359
- Main query execution pipeline:
360
- 1. Convert natural language to SQL
361
- 2. Validate SQL
362
- 3. Add result limit
363
- 4. Execute query
364
- 5. Return results
365
- """
366
-
367
  if not db_available or engine is None:
368
  return {
369
  "query": user_query,
@@ -371,149 +327,137 @@ def run_query(user_query):
371
  "sql": None,
372
  "result": []
373
  }
374
-
375
- # Generate SQL from natural language
376
- sql_query, llm_error = ask_llm(user_query)
377
-
378
- if llm_error:
379
- return {
380
- "query": user_query,
381
- "error": llm_error,
382
- "sql": None,
383
- "result": []
384
- }
385
-
386
- # Validate SQL
387
- is_valid, validation_msg = validate_sql(sql_query)
388
- if not is_valid:
389
- return {
390
- "query": user_query,
391
- "error": f"SQL Validation Failed: {validation_msg}",
392
- "sql": sql_query,
393
- "result": []
394
- }
395
-
396
- # Add result limit if not present
397
- if "LIMIT" not in sql_query.upper():
398
- sql_query = sql_query.replace(";", " LIMIT 50;")
399
-
400
- # Store in history
401
- query_history.append({
402
- "query": user_query,
403
- "sql": sql_query,
404
- "timestamp": datetime.now().isoformat()
405
- })
406
-
407
- # Execute query
408
  try:
 
 
 
 
 
 
 
 
 
 
 
 
409
  with engine.connect() as conn:
410
- result = conn.execute(text(sql_query))
411
- rows = [dict(row._mapping) for row in result]
 
 
 
 
 
412
 
413
  return {
414
  "query": user_query,
415
- "sql": sql_query,
416
  "result": rows,
417
- "row_count": len(rows)
418
  }
419
 
420
  except Exception as e:
421
- print(f"❌ Query execution error: {e}")
422
  traceback.print_exc()
 
423
  return {
424
  "query": user_query,
425
- "error": f"Execution Error: {str(e)}",
426
- "sql": sql_query,
427
  "result": []
428
  }
429
 
430
 
431
- # ==================== ANALYTICS QUERIES ====================
432
 
433
  def get_vehicles_by_state():
434
- """Get vehicle count by state"""
435
  if not db_available or engine is None:
436
  return []
437
-
438
- try:
439
- with engine.connect() as conn:
440
- result = conn.execute(text("""
441
- SELECT state, COUNT(*) as count
442
- FROM vehicle_logs
443
- GROUP BY state
444
- ORDER BY count DESC
445
- LIMIT 20;
446
- """))
447
- return [dict(row._mapping) for row in result]
448
- except Exception as e:
449
- print(f"Error: {e}")
450
- return []
451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
 
453
- def get_hourly_traffic(date=None):
454
- """Get traffic by hour"""
455
  if not db_available or engine is None:
456
  return []
457
-
458
- try:
459
- date_filter = f"WHERE date = '{date}'" if date else ""
460
-
461
- with engine.connect() as conn:
462
- result = conn.execute(text(f"""
463
- SELECT hour, COUNT(*) as count
464
- FROM vehicle_logs
465
- {date_filter}
466
- GROUP BY hour
467
- ORDER BY hour;
468
- """))
469
- return [dict(row._mapping) for row in result]
470
- except Exception as e:
471
- print(f"Error: {e}")
472
- return []
473
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
- def get_top_plates(limit=10):
476
- """Get most frequently detected plates"""
477
  if not db_available or engine is None:
478
  return []
479
-
480
- try:
481
- with engine.connect() as conn:
482
- result = conn.execute(text(f"""
483
- SELECT plate, COUNT(*) as count
484
- FROM vehicle_logs
485
- WHERE plate != ''
486
- GROUP BY plate
487
- ORDER BY count DESC
488
- LIMIT {limit};
489
- """))
490
- return [dict(row._mapping) for row in result]
491
- except Exception as e:
492
- print(f"Error: {e}")
493
- return []
494
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
 
496
- def get_suspicious_vehicles(min_detections=20):
497
- """Get vehicles detected multiple times (potential suspicious activity)"""
498
  if not db_available or engine is None:
499
  return []
500
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  try:
 
502
  with engine.connect() as conn:
503
- result = conn.execute(text(f"""
504
- SELECT plate, COUNT(*) as detection_count, state, vehicle_type
505
- FROM vehicle_logs
506
- WHERE plate != ''
507
- GROUP BY plate, state, vehicle_type
508
- HAVING COUNT(*) >= {min_detections}
509
- ORDER BY detection_count DESC
510
- LIMIT 50;
511
- """))
512
- return [dict(row._mapping) for row in result]
513
- except Exception as e:
514
- print(f"Error: {e}")
515
- return []
516
 
 
517
 
518
- # Initialize database on module load
519
- init_db()
 
 
 
 
 
 
1
  import os
 
 
 
 
2
  import traceback
 
 
 
 
 
 
 
 
3
  from datetime import datetime
4
+
5
  from dotenv import load_dotenv
6
  from huggingface_hub import InferenceClient
7
+ from sqlalchemy import create_engine, text
 
 
 
8
 
9
  load_dotenv()
10
 
11
+ # ================= DATABASE ================= #
12
+
13
  DATABASE_URL = os.getenv("DATABASE_URL")
14
  engine = None
15
  db_available = False
16
 
17
  if DATABASE_URL:
18
  try:
19
+ engine = create_engine(
20
+ DATABASE_URL,
21
+ pool_pre_ping=True
22
+ )
23
  db_available = True
24
  print("✅ Database engine initialized")
25
  except Exception as e:
26
+ print(f"⚠️ Database initialization error: {e}")
27
  db_available = False
28
  else:
29
+ print("⚠️ DATABASE_URL not set. Database features disabled.")
30
+
31
+ # ================= HUGGINGFACE ================= #
32
 
33
  HF_TOKEN = os.getenv("HF_TOKEN")
34
  client = None
 
41
  )
42
  print("✅ HuggingFace client initialized")
43
  except Exception as e:
44
+ print(f"⚠️ HuggingFace client error: {e}")
45
  else:
46
+ print("⚠️ HF_TOKEN not set. NLP-to-SQL features disabled.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ # ================= INIT DB ================= #
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ def init_db():
51
+
52
+ if not db_available or engine is None:
53
+ print("⚠️ Skipping database initialization - DATABASE_URL not configured")
54
+ return
55
 
56
+ with engine.connect() as conn:
 
 
 
 
 
57
 
58
+ # Create table if it doesn't exist
59
+ conn.execute(text("""
60
+ CREATE TABLE IF NOT EXISTS vehicle_logs (
 
61
 
62
+ id BIGSERIAL PRIMARY KEY,
 
 
63
 
64
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
65
 
66
+ plate TEXT,
67
 
68
+ state TEXT,
 
 
 
 
69
 
70
+ vehicle_type TEXT,
 
 
 
71
 
72
+ vehicle_conf FLOAT,
73
 
74
+ date DATE,
75
 
76
+ hour INTEGER,
77
+
78
+ day TEXT
79
+ )
80
+ """))
81
+
82
+ # Add missing columns if they don't exist
83
+ # Check and add 'state' column
84
+ try:
85
  conn.execute(text("""
86
+ ALTER TABLE vehicle_logs
87
+ ADD COLUMN state TEXT;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  """))
89
+ print("✅ Added 'state' column")
90
+ except:
91
+ pass # Column already exists
 
 
 
 
 
 
 
 
92
 
93
+ # Check and add 'vehicle_type' column
94
+ try:
95
+ conn.execute(text("""
96
+ ALTER TABLE vehicle_logs
97
+ ADD COLUMN vehicle_type TEXT;
98
+ """))
99
+ print("✅ Added 'vehicle_type' column")
100
+ except:
101
+ pass # Column already exists
102
 
103
+ # Check and add 'vehicle_conf' column
104
+ try:
105
+ conn.execute(text("""
106
+ ALTER TABLE vehicle_logs
107
+ ADD COLUMN vehicle_conf FLOAT;
108
+ """))
109
+ print("✅ Added 'vehicle_conf' column")
110
+ except:
111
+ pass # Column already exists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ # Check and add 'date' column
114
+ try:
115
+ conn.execute(text("""
116
+ ALTER TABLE vehicle_logs
117
+ ADD COLUMN date DATE;
118
+ """))
119
+ print("✅ Added 'date' column")
120
+ except:
121
+ pass # Column already exists
122
 
123
+ # Check and add 'hour' column
124
+ try:
125
+ conn.execute(text("""
126
+ ALTER TABLE vehicle_logs
127
+ ADD COLUMN hour INTEGER;
128
+ """))
129
+ print("✅ Added 'hour' column")
130
+ except:
131
+ pass # Column already exists
132
 
133
+ # Check and add 'day' column
134
+ try:
 
 
 
 
 
 
 
 
 
 
135
  conn.execute(text("""
136
+ ALTER TABLE vehicle_logs
137
+ ADD COLUMN day TEXT;
138
+ """))
139
+ print("✅ Added 'day' column")
140
+ except:
141
+ pass # Column already exists
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
+ conn.commit()
144
 
145
+ print("✅ Database Initialized")
146
 
147
+
148
+ init_db()
149
+
150
+ # ================= SAVE ================= #
151
+
152
+ def save_detection(
153
+ plate,
154
+ state,
155
+ vehicle_type,
156
+ vehicle_conf,
157
+ date_str,
158
+ time_str
159
+ ):
160
 
161
+ if not db_available or engine is None:
162
+ print("⚠️ Database not available for saving detection")
163
+ return
164
+
165
+ dt = datetime.strptime(
166
+ f"{date_str} {time_str}",
167
+ "%Y-%m-%d %H:%M:%S"
168
+ )
169
+
170
+ hour = dt.hour
171
+
172
+ day = dt.strftime("%A")
173
+
174
+ with engine.connect() as conn:
175
 
176
+ conn.execute(text("""
177
+ INSERT INTO vehicle_logs
178
+ (
179
+ plate,
180
+ state,
181
+ vehicle_type,
182
+ vehicle_conf,
183
+ date,
184
+ hour,
185
+ day
186
+ )
187
+ VALUES
188
+ (
189
+ :plate,
190
+ :state,
191
+ :vehicle_type,
192
+ :vehicle_conf,
193
+ :date,
194
+ :hour,
195
+ :day
196
+ )
197
+ """), {
198
+
199
+ "plate": plate,
200
+ "state": state,
201
+ "vehicle_type": vehicle_type,
202
+ "vehicle_conf": float(vehicle_conf),
203
+ "date": date_str,
204
+ "hour": hour,
205
+ "day": day
206
+ })
207
+
208
+ conn.commit()
209
 
210
+
211
+ # ================= SQL SAFETY ================= #
212
+
213
+ BLOCKED = [
214
+ "DROP",
215
+ "DELETE",
216
+ "UPDATE",
217
+ "INSERT",
218
+ "ALTER",
219
+ "TRUNCATE"
220
+ ]
221
+
222
+ def validate_sql(sql):
223
+
224
+ upper = sql.upper()
225
+
226
+ for word in BLOCKED:
227
+
228
+ if word in upper:
229
+ return False
230
+
231
+ return upper.startswith("SELECT")
232
+
233
+
234
+ # ================= NLP TO SQL ================= #
235
 
236
  def ask_llm(user_query):
237
+
 
238
  if client is None:
239
+ return "SELECT 1;" # Dummy query if no client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
+ prompt = f"""
242
+ You are an expert PostgreSQL SQL generator.
243
 
244
+ ONLY use this table:
245
 
246
  vehicle_logs(
247
  id,
 
250
  state,
251
  vehicle_type,
252
  vehicle_conf,
 
 
 
253
  date,
254
  hour,
255
  day
256
  )
257
 
258
+ Rules:
259
+ - ONLY SELECT queries
260
+ - NEVER hallucinate columns
261
+ - state column EXISTS
262
+ - LIMIT 50 max
263
+ - Output ONLY SQL
264
+
265
+ Examples:
266
+
267
+ Question:
268
+ Show TN vehicles
269
 
270
+ SQL:
271
+ SELECT * FROM vehicle_logs
272
+ WHERE state='TN'
273
+ ORDER BY timestamp DESC
274
+ LIMIT 50;
275
 
276
+ Question:
277
+ Top repeated plates
278
 
279
+ SQL:
280
+ SELECT plate, COUNT(*) AS count
281
+ FROM vehicle_logs
282
+ GROUP BY plate
283
+ ORDER BY count DESC
284
+ LIMIT 10;
285
+
286
+ Question:
287
+ Hourly traffic
288
+
289
+ SQL:
290
+ SELECT hour, COUNT(*) AS count
291
+ FROM vehicle_logs
292
+ GROUP BY hour
293
+ ORDER BY hour;
294
+
295
+ Question:
296
  {user_query}
297
 
298
+ SQL:
299
  """
300
 
301
+ response = client.text_generation(
302
+ prompt,
303
+ max_new_tokens=120,
304
+ temperature=0.1
305
+ )
 
306
 
307
+ sql = response.strip()
308
+
309
+ sql = sql.replace("```sql", "")
310
+ sql = sql.replace("```", "")
311
+ sql = sql.strip()
312
+
313
+ if ";" not in sql:
314
+ sql += ";"
315
 
316
+ return sql
317
 
318
+
319
+ # ================= QUERY ================= #
320
 
321
  def run_query(user_query):
322
+
 
 
 
 
 
 
 
 
323
  if not db_available or engine is None:
324
  return {
325
  "query": user_query,
 
327
  "sql": None,
328
  "result": []
329
  }
330
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  try:
332
+
333
+ sql = ask_llm(user_query)
334
+
335
+ if not validate_sql(sql):
336
+
337
+ return {
338
+ "query": user_query,
339
+ "error": "Unsafe SQL blocked",
340
+ "sql": sql,
341
+ "result": []
342
+ }
343
+
344
  with engine.connect() as conn:
345
+
346
+ result = conn.execute(text(sql))
347
+
348
+ rows = [
349
+ dict(r._mapping)
350
+ for r in result
351
+ ]
352
 
353
  return {
354
  "query": user_query,
355
+ "sql": sql,
356
  "result": rows,
357
+ "count": len(rows)
358
  }
359
 
360
  except Exception as e:
361
+
362
  traceback.print_exc()
363
+
364
  return {
365
  "query": user_query,
366
+ "error": str(e),
367
+ "sql": sql if "sql" in locals() else "",
368
  "result": []
369
  }
370
 
371
 
372
+ # ================= ANALYTICS ================= #
373
 
374
  def get_vehicles_by_state():
375
+
376
  if not db_available or engine is None:
377
  return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
+ with engine.connect() as conn:
380
+
381
+ result = conn.execute(text("""
382
+ SELECT state, COUNT(*) AS count
383
+ FROM vehicle_logs
384
+ GROUP BY state
385
+ ORDER BY count DESC
386
+ """))
387
+
388
+ return [dict(r._mapping) for r in result]
389
+
390
+
391
+ def get_hourly_traffic():
392
 
 
 
393
  if not db_available or engine is None:
394
  return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
 
396
+ with engine.connect() as conn:
397
+
398
+ result = conn.execute(text("""
399
+ SELECT hour, COUNT(*) AS count
400
+ FROM vehicle_logs
401
+ GROUP BY hour
402
+ ORDER BY hour
403
+ """))
404
+
405
+ return [dict(r._mapping) for r in result]
406
+
407
+
408
+ def get_top_plates():
409
 
 
 
410
  if not db_available or engine is None:
411
  return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
+ with engine.connect() as conn:
414
+
415
+ result = conn.execute(text("""
416
+ SELECT plate, COUNT(*) AS count
417
+ FROM vehicle_logs
418
+ WHERE plate != ''
419
+ GROUP BY plate
420
+ ORDER BY count DESC
421
+ LIMIT 10
422
+ """))
423
+
424
+ return [dict(r._mapping) for r in result]
425
+
426
+
427
+ def get_suspicious_vehicles():
428
 
 
 
429
  if not db_available or engine is None:
430
  return []
431
+
432
+ with engine.connect() as conn:
433
+
434
+ result = conn.execute(text("""
435
+ SELECT plate,
436
+ COUNT(*) AS detections
437
+ FROM vehicle_logs
438
+ GROUP BY plate
439
+ HAVING COUNT(*) > 5
440
+ ORDER BY detections DESC
441
+ LIMIT 20
442
+ """))
443
+
444
+ return [dict(r._mapping) for r in result]
445
+
446
+
447
+ # ================= HEALTH ================= #
448
+
449
+ def health_check():
450
+
451
+ if not db_available or engine is None:
452
+ return False, "⚠️ Database not configured. Set DATABASE_URL in Hugging Face Spaces secrets."
453
+
454
  try:
455
+
456
  with engine.connect() as conn:
457
+ conn.execute(text("SELECT 1"))
458
+
459
+ return True, "✅ Database Connected"
 
 
 
 
 
 
 
 
 
 
460
 
461
+ except Exception as e:
462
 
463
+ return False, str(e)