import os os.environ["OMP_NUM_THREADS"] = "1" # Suppress all warnings BEFORE importing anything else import warnings warnings.filterwarnings('ignore') warnings.simplefilter('ignore') import asyncio from datetime import datetime import sys import platform import json import pandas as pd import gradio as gr import folium import base64 from datetime import timedelta import matplotlib.pyplot as plt import matplotlib matplotlib.use('Agg') import io from PIL import Image from detector import detect_plate from database import ( save_detection, run_query, health_check, get_vehicles_by_state, get_hourly_traffic, get_top_plates, get_suspicious_vehicles, HF_TOKEN, DATABASE_URL, client, engine ) from vehicle_map import ( search_vehicle_route, default_vehicle_map, map_to_html ) from ai_investigation import ( ask_investigation_question, format_investigation_output ) # ========================================================= # ASYNC FIX & EVENT LOOP MANAGEMENT # ========================================================= # Suppress all warnings to prevent asyncio event loop cleanup messages warnings.filterwarnings('ignore') warnings.filterwarnings('ignore', category=ResourceWarning) warnings.filterwarnings('ignore', category=RuntimeWarning) warnings.filterwarnings('ignore', category=DeprecationWarning) # Suppress asyncio event loop cleanup errors on Linux import sys _original_excepthook = sys.excepthook def _suppress_asyncio_errors(exc_type, exc_value, traceback): """Suppress asyncio cleanup errors but show real errors""" if exc_type and 'Invalid file descriptor' in str(exc_value): return # Silently ignore asyncio cleanup errors if exc_type and 'BaseEventLoop' in str(traceback): return # Silently ignore event loop cleanup _original_excepthook(exc_type, exc_value, traceback) sys.excepthook = _suppress_asyncio_errors # ========================================================= # INVESTIGATION ASSISTANT WRAPPERS # ========================================================= def ask_question_wrapper(question): """Unified AI Investigation Agent - handles any natural language question""" if not question or len(question.strip()) < 2: return ("Please ask a valid question", "", "", None, "") result = ask_investigation_question(question) if result.get("status") == "error": error_msg = result.get("message", "Investigation failed") return (f"Error: {error_msg}", "", "", None, "") return ( result.get("answer", ""), "\n".join(result.get("findings", [])), result.get("analysis", {}), None, result.get("data_summary", "") ) # ========================================================= # DETECTION # ========================================================= def detect_and_save(image): if image is None: return ( "No image uploaded", {"error": "No image uploaded"} ) try: now = datetime.now() date = now.strftime("%Y-%m-%d") time = now.strftime("%H:%M:%S") plate, state, vehicle_type, vehicle_conf, success = detect_plate(image) if success and plate: save_detection( plate, state, vehicle_type, vehicle_conf, date, time ) result_text = f""" Detection Success Date: {date} Time: {time} Vehicle Type: {vehicle_type} Plate: {plate} State: {state} Confidence: {round(vehicle_conf, 3)} Saved: {success} """ result_json = { "date": date, "time": time, "plate": plate, "state": state, "vehicle_type": vehicle_type, "confidence": round(vehicle_conf, 3), "saved": success } return result_text, result_json except Exception as e: return ( f"Error: {str(e)}", {"error": str(e)} ) # ========================================================= # NLP QUERY # ========================================================= def query_database(user_query): try: print(f"\nπ NLP Query: {user_query}") if not user_query.strip(): print("β Empty query") return ( "", pd.DataFrame(), {"error": "β Empty query - Please enter something"} ) if not HF_TOKEN: print("β HF_TOKEN missing") return ( "", pd.DataFrame(), {"error": "β HF_TOKEN not set - NLP features disabled"} ) if not DATABASE_URL: print("β DATABASE_URL missing") return ( "", pd.DataFrame(), {"error": "β DATABASE_URL not set - Database features disabled"} ) print(f"Calling run_query...") response = run_query(user_query) print(f"Response: {response}") sql = response.get("sql", "") results = response.get("result", []) error = response.get("error", "") if error: print(f"β Query error: {error}") return ( sql, pd.DataFrame(), {"error": f"β Query Error: {error}"} ) if len(results) > 0: df = pd.DataFrame(results) print(f"β Found {len(results)} records") else: df = pd.DataFrame({ "message": ["No results found"] }) print("βΉοΈ No results found") return ( sql, df, {"success": f"β Found {len(results)} records"} ) except Exception as e: error_msg = f"β Error: {str(e)}" print(error_msg) import traceback traceback.print_exc() return ( "", pd.DataFrame(), {"error": error_msg} ) # ========================================================= # CHATBOT # ========================================================= def chatbot_query(message, history): try: print(f"\nπ¬ Chatbot message: {message}") print(f"History type: {type(history)}, Length: {len(history) if history else 0}") if not message.strip(): print("β Empty message") if not history: history = [] # Gradio 6.14.0+: Format MUST be {"role": "user/assistant", "content": "text"} return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "β Empty message"}], "" if not HF_TOKEN: print("β HF_TOKEN not configured") if not history: history = [] return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "β HF_TOKEN not configured - NLP disabled"}], "" if not DATABASE_URL: print("β DATABASE_URL not configured") if not history: history = [] return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "β DATABASE_URL not configured - Database disabled"}], "" print("Calling run_query...") response = run_query(message) print(f"Response: {response}") sql = response.get("sql", "") results = response.get("result", []) error = response.get("error", "") count = response.get("count", 0) if not history: history = [] if error: print(f"β Query error: {error}") bot_reply = f"β Error: {error}" else: preview = str(results[:3]) if results else "No results" print(f"β Found {count} records") bot_reply = f"""β Query processed **SQL Generated:** ```sql {sql} ``` **Results:** {count} records found """ # Gradio 6.14.0+: Append dict format {"role", "content"} history = history + [{"role": "user", "content": message}, {"role": "assistant", "content": bot_reply}] print(f"Returning history with {len(history)} messages") return history, "" except Exception as e: print(f"β Exception: {str(e)}") import traceback traceback.print_exc() if not history: history = [] history = history + [[message, f"β Error: {str(e)}"]] return history, "" # ========================================================= # ANALYTICS # ========================================================= # ========== CHARTING FUNCTIONS ========== def create_state_chart(state_df): """Create pie chart for vehicles by state""" try: if state_df.empty or len(state_df) == 0: return None plt.figure(figsize=(10, 6)) # Get state and count columns by name if 'state' in state_df.columns and 'count' in state_df.columns: states = state_df['state'].tolist() counts = state_df['count'].tolist() else: states = state_df.iloc[:, 0].tolist() if len(state_df.columns) > 0 else [] counts = state_df.iloc[:, 1].tolist() if len(state_df.columns) > 1 else [] if not states or not counts: return None # Create pie chart colors = plt.cm.Set3(range(len(states))) plt.pie(counts, labels=states, autopct='%1.1f%%', colors=colors, startangle=90) plt.title('π Vehicles by State Distribution', fontsize=14, fontweight='bold', pad=20) plt.tight_layout() # Convert to PIL Image buf = io.BytesIO() plt.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) img = Image.open(buf) img_copy = img.copy() plt.close() return img_copy except Exception as e: print(f"State chart error: {e}") plt.close() return None def create_hourly_chart(hourly_df): """Create line chart for hourly traffic""" try: if hourly_df.empty or len(hourly_df) == 0: return None plt.figure(figsize=(12, 5)) # Get hour and traffic columns by name if 'hour' in hourly_df.columns and 'traffic' in hourly_df.columns: hours = hourly_df['hour'].tolist() counts = hourly_df['traffic'].tolist() else: hours = hourly_df.iloc[:, 0].tolist() if len(hourly_df.columns) > 0 else [] counts = hourly_df.iloc[:, 1].tolist() if len(hourly_df.columns) > 1 else [] if not hours or not counts: return None # Create line chart plt.plot(hours, counts, marker='o', linewidth=2, markersize=8, color='#FF6B6B') plt.fill_between(range(len(hours)), counts, alpha=0.3, color='#FF6B6B') plt.xlabel('Hour of Day', fontsize=11, fontweight='bold') plt.ylabel('Detection Count', fontsize=11, fontweight='bold') plt.title('π Traffic by Hour', fontsize=14, fontweight='bold', pad=20) plt.grid(True, alpha=0.3) plt.xticks(range(0, len(hours), max(1, len(hours)//12))) plt.tight_layout() # Convert to PIL Image buf = io.BytesIO() plt.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) img = Image.open(buf) img_copy = img.copy() plt.close() return img_copy except Exception as e: print(f"Hourly chart error: {e}") plt.close() return None def create_top_plates_chart(top_df): """Create horizontal bar chart for top plates""" try: if top_df.empty or len(top_df) == 0: return None plt.figure(figsize=(10, 6)) # Get plate and detections columns (limit to top 10) if 'plate' in top_df.columns and 'detections' in top_df.columns: plates = top_df.head(10)['plate'].tolist() counts = top_df.head(10)['detections'].tolist() else: plates = top_df.iloc[:10, 0].tolist() if len(top_df.columns) > 0 else [] counts = top_df.iloc[:10, 1].tolist() if len(top_df.columns) > 1 else [] if not plates or not counts: return None # Create horizontal bar chart colors = plt.cm.viridis(range(len(plates))) bars = plt.barh(plates, counts, color=colors) # Add value labels on bars for i, (bar, count) in enumerate(zip(bars, counts)): plt.text(count + 0.1, i, str(int(count)), va='center', fontsize=9) plt.xlabel('Detection Count', fontsize=11, fontweight='bold') plt.title('π Top Detected License Plates', fontsize=14, fontweight='bold', pad=20) plt.tight_layout() # Convert to PIL Image buf = io.BytesIO() plt.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) img = Image.open(buf) img_copy = img.copy() plt.close() return img_copy except Exception as e: print(f"Top plates chart error: {e}") plt.close() return None def create_suspicious_chart(suspicious_df): """Create donut chart for suspicious vehicles""" try: if suspicious_df.empty or len(suspicious_df) == 0: return None plt.figure(figsize=(10, 6)) # Get data by column name (limit to top 8) if 'plate' in suspicious_df.columns and 'detections' in suspicious_df.columns: labels = suspicious_df.head(8)['plate'].tolist() sizes = suspicious_df.head(8)['detections'].tolist() else: labels = suspicious_df.iloc[:8, 0].tolist() if len(suspicious_df.columns) > 0 else [] sizes = suspicious_df.iloc[:8, 1].tolist() if len(suspicious_df.columns) > 1 else [] if not labels or not sizes: return None # Create donut chart colors = plt.cm.Reds(range(len(labels))) wedges, texts, autotexts = plt.pie(sizes, labels=labels, autopct='%1.1f%%', colors=colors, startangle=90, pctdistance=0.85) # Draw donut hole centre_circle = plt.Circle((0, 0), 0.70, fc='white') plt.gca().add_artist(centre_circle) plt.title('β οΈ Suspicious Vehicle Alerts', fontsize=14, fontweight='bold', pad=20) plt.tight_layout() # Convert to PIL Image buf = io.BytesIO() plt.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) img = Image.open(buf) img_copy = img.copy() plt.close() return img_copy except Exception as e: print(f"Suspicious chart error: {e}") plt.close() return None def refresh_analytics(): try: print("\nπ Refreshing analytics...") if not DATABASE_URL or not engine: print("β Database not configured") err = pd.DataFrame({"error": ["Database not configured"]}) return err, err, err, err, err, err, err, err print("Getting vehicles by state...") state_data = get_vehicles_by_state() state_df = pd.DataFrame(state_data) if state_data else pd.DataFrame() print("Getting hourly traffic...") hourly_data = get_hourly_traffic() hourly_df = pd.DataFrame(hourly_data) if hourly_data else pd.DataFrame() print("Getting top plates...") top_data = get_top_plates() top_df = pd.DataFrame(top_data) if top_data else pd.DataFrame() print("Getting suspicious vehicles...") suspicious_data = get_suspicious_vehicles() suspicious_df = pd.DataFrame(suspicious_data) if suspicious_data else pd.DataFrame() print(f"β Analytics refreshed: {len(state_df)} states, {len(hourly_df)} hours, {len(top_df)} top plates, {len(suspicious_df)} suspicious") # Generate charts print("π¨ Generating charts...") state_chart = create_state_chart(state_df) hourly_chart = create_hourly_chart(hourly_df) top_chart = create_top_plates_chart(top_df) suspicious_chart = create_suspicious_chart(suspicious_df) return ( state_chart, hourly_chart, top_chart, suspicious_chart, state_df, hourly_df, top_df, suspicious_df ) except Exception as e: print(f"β Analytics error: {str(e)}") import traceback traceback.print_exc() err_df = pd.DataFrame({ "error": [str(e)] }) return ( None, None, None, None, err_df, err_df, err_df, err_df ) # ========================================================= # HEALTH # ========================================================= status, msg = health_check() # ========================================================= # CONFIGURATION DEBUG # ========================================================= print("\n" + "="*60) print("π§ CONFIGURATION STATUS") print("="*60) print(f"HF_TOKEN: {'β SET' if HF_TOKEN else 'β NOT SET'}") print(f"DATABASE_URL: {'β SET' if DATABASE_URL else 'β NOT SET'}") print(f"Mistral Client: {'β READY' if client else 'β FAILED'}") print(f"Database Engine: {'β READY' if engine else 'β FAILED'}") print(f"Database Health: {msg}") print("="*60 + "\n") # ========================================================= # UI # ========================================================= with gr.Blocks( title="ActionSync β Vehicle Intelligence Platform", theme=gr.themes.Soft( text_size="lg", ), css=""" .status-badge { display: inline-flex; align-items: center; gap: 0.5em; padding: 0.4em 0.8em; border-radius: 999px; font-size: 0.9em; font-weight: 500; } .status-success { background-color: #16a34a; color: white; border: 1px solid #15803d; } .status-error { background-color: #b91c1c; color: white; border: 1px solid #991b1b; } .status-warn { background-color: #ca8a04; color: white; border: 1px solid #a16207; } .status-label { display: inline-block; margin-left: 0.5em; } """ ) as demo: gr.Markdown(""" # π¦ ActionSync β Vehicle Intelligence Platform AIβpowered vehicle detection and analytics with NLPβtoβSQL querying. """, elem_classes=["text-center"]) # Status panel with gr.Accordion("π§ System Status", open=True): with gr.Row(): # LLM / NLP status with gr.Column(): if HF_TOKEN and client: gr.Markdown("""