Spaces:
Sleeping
Sleeping
| """ | |
| Interface functions for the Gradio application | |
| Contains all the functions that connect the UI to the business logic | |
| """ | |
| import gradio as gr | |
| import pandas as pd | |
| import json | |
| from core.config import MAKE_MODEL_DATA | |
| def update_models(make): | |
| """Update model choices based on selected make""" | |
| if make in MAKE_MODEL_DATA: | |
| models = MAKE_MODEL_DATA[make] | |
| # Add ALL option at the beginning | |
| models_with_all = ["ALL"] + models | |
| return gr.Dropdown(choices=models_with_all, value="ALL") | |
| else: | |
| return gr.Dropdown(choices=["ALL"], value="ALL") | |
| def predict_dealers_interface(matcher, make, model, year, body_type, fuel_type, transmission, | |
| odometer, doors, seats, engine_size, power, cylinders, | |
| safety_rating, drive_type, segment, condition, selected_model): | |
| """Interface function for detailed Gradio prediction""" | |
| # Handle placeholder values - convert to None for backend processing | |
| processed_make = None if (not make or make.strip() == "" or make == "Select Make") else make.strip() | |
| processed_model = None if (not model or model.strip() == "" or model == "ALL") else model.strip() | |
| processed_body_type = None if body_type == "Select Body Type" else body_type | |
| processed_fuel_type = None if fuel_type == "Select Fuel Type" else fuel_type | |
| processed_transmission = None if transmission == "Select Transmission" else transmission | |
| processed_drive_type = None if drive_type == "Select Drive Type" else drive_type | |
| processed_segment = None if segment == "Select Segment" else segment | |
| processed_condition = None if condition == "Select Condition" else condition | |
| processed_selected_model = None if selected_model == "Select AI Model" else selected_model | |
| return matcher.predict_dealers(processed_make, processed_model, year, processed_body_type, | |
| processed_fuel_type, processed_transmission, odometer, doors, | |
| seats, engine_size, power, cylinders, safety_rating, | |
| processed_drive_type, processed_segment, processed_condition, | |
| processed_selected_model) | |
| def simple_predict_dealers_interface(matcher, simple_make, simple_model, simple_year, | |
| simple_odometer, simple_model_selection): | |
| """Simple interface function for Gradio with structured JSON output""" | |
| # Handle text inputs - convert empty strings to None for backend processing | |
| processed_make = None if (not simple_make or simple_make.strip() == "") else simple_make.strip() | |
| processed_model = None if (not simple_model or simple_model.strip() == "") else simple_model.strip() | |
| processed_model_selection = None if simple_model_selection == "Select AI Model" else simple_model_selection | |
| # Get structured JSON result | |
| json_result = matcher.predict_dealers_json( | |
| make=processed_make, | |
| model=processed_model, | |
| year=simple_year, | |
| body_type=None, | |
| fuel_type=None, | |
| transmission=None, | |
| odometer=simple_odometer, | |
| doors=None, | |
| seats=None, | |
| engine_size=None, | |
| power=None, | |
| cylinders=None, | |
| safety_rating=None, | |
| drive_type=None, | |
| segment=None, | |
| condition=None, | |
| selected_model=processed_model_selection | |
| ) | |
| # Handle error cases | |
| if not json_result["success"]: | |
| return "❌ Error", "", f"**❌ Error:** {json_result['error']}" | |
| # Extract data for UI display | |
| data = json_result["data"] | |
| top_dealer = data["top_dealer"]["name"] | |
| confidence = data["top_dealer"]["confidence_percentage"] | |
| # Get all dealer names for batch contact lookup | |
| all_dealer_names = [dealer['dealer_name'] for dealer in data["dealer_rankings"]] | |
| # Batch lookup contact information for all dealers | |
| all_contacts = matcher.get_dealers_contact_info_batch(all_dealer_names) | |
| # Get contact info for top dealer | |
| top_dealer_contact = all_contacts.get(top_dealer) | |
| # Format top dealer info with contact details | |
| if top_dealer_contact and top_dealer_contact.get('phone'): | |
| top_dealer_display = f"🎯 **Best Match: {top_dealer}**\n📞 **Phone:** {top_dealer_contact['phone']}" | |
| if top_dealer_contact.get('city') != 'Unknown' and top_dealer_contact.get('state') != 'Unknown': | |
| top_dealer_display += f"\n📍 **Location:** {top_dealer_contact['city']}, {top_dealer_contact['state']}" | |
| else: | |
| top_dealer_display = f"🎯 **Best Match: {top_dealer}**\n📞 **Phone:** Contact dealer directly" | |
| # Create user-friendly display with structured data | |
| display_text = f""" | |
| ## 🎯 **Prediction Results** | |
| ### 🏆 **Top Dealer** | |
| **{top_dealer}** - {confidence} confidence | |
| """ | |
| # Add contact information for top dealer | |
| if top_dealer_contact: | |
| display_text += f"""**📞 Phone:** {top_dealer_contact.get('phone', 'Contact dealer directly')} | |
| **📍 Location:** {top_dealer_contact.get('city', 'Unknown')}, {top_dealer_contact.get('state', 'Unknown')} | |
| """ | |
| else: | |
| display_text += "**📞 Phone:** Contact dealer directly\n\n" | |
| display_text += """### 📊 **All Dealer Rankings** | |
| """ | |
| # Add dealer rankings with contact info using batch lookup results | |
| for dealer in data["dealer_rankings"]: | |
| rank_emoji = "🥇" if dealer["rank"] == 1 else "🥈" if dealer["rank"] == 2 else "🥉" if dealer["rank"] == 3 else "🔸" | |
| dealer_name = dealer['dealer_name'] | |
| # Get contact info from batch lookup | |
| contact_info = all_contacts.get(dealer_name) | |
| if contact_info and contact_info.get('phone'): | |
| phone_info = f" • 📞 {contact_info['phone']}" | |
| else: | |
| phone_info = " • 📞 Contact dealer" | |
| display_text += f"{rank_emoji} **{dealer['rank']}. {dealer_name}** - {dealer['confidence_percentage']}{phone_info}\n" | |
| display_text += f""" | |
| ### 🚗 **Vehicle Input Summary** | |
| """ | |
| vehicle_info = data["vehicle_input"] | |
| if vehicle_info: | |
| for key, value in vehicle_info.items(): | |
| if value is not None: | |
| display_key = key.replace('_', ' ').title() | |
| display_text += f"• **{display_key}:** {value}\n" | |
| display_text += f""" | |
| ### 🤖 **Model Information** | |
| • **AI Model Used:** {data['model_info']['model_used']} | |
| • **Total Dealers Available:** {data['model_info']['total_dealers_available']} | |
| • **Prediction Timestamp:** {data['timestamp']} | |
| ### 📋 **Structured JSON Output** | |
| *Perfect for API integration:* | |
| ```json | |
| {json.dumps(json_result, indent=2)} | |
| ``` | |
| **API Usage:** This JSON structure can be directly used in your applications for: | |
| - Top dealer extraction: `data.top_dealer.name` | |
| - Confidence scores: `data.top_dealer.confidence_score` | |
| - Full rankings: `data.dealer_rankings[]` | |
| - Input validation: `data.vehicle_input` | |
| - Error handling: `success` and `error` fields | |
| """ | |
| return top_dealer_display, confidence, display_text | |
| def search_data_interface(matcher, keyword_search, make, model, year_from, year_to, body_type, fuel_type, | |
| odometer_from, odometer_to, max_price, selected_file, max_results, show_dealer_stats): | |
| """Interface function for traditional data search""" | |
| # Handle text inputs - convert empty strings to None for backend processing | |
| processed_make = None if (not make or make.strip() == "" or make == "Select Make") else make.strip() | |
| processed_model = None if (not model or model.strip() == "" or model == "ALL") else model.strip() | |
| processed_body_type = None if body_type == "Select Body Type" else body_type | |
| processed_fuel_type = None if fuel_type == "Select Fuel Type" else fuel_type | |
| processed_selected_file = None if selected_file == "Select File" else selected_file | |
| if show_dealer_stats: | |
| # Use API method that returns clean structured data | |
| api_result = matcher.search_data_files_api( | |
| keyword_search=keyword_search, | |
| make=processed_make, | |
| model=processed_model, | |
| year_from=year_from, | |
| year_to=year_to, | |
| body_type=processed_body_type, | |
| fuel_type=processed_fuel_type, | |
| odometer_from=odometer_from, | |
| odometer_to=odometer_to, | |
| max_price=max_price, | |
| selected_file=processed_selected_file, | |
| max_results=max_results | |
| ) | |
| if not api_result["success"]: | |
| return f"❌ {api_result['message']}", "Error occurred", "" | |
| # Apply HTML formatting for UI display | |
| try: | |
| from utils import format_dealer_results_as_html | |
| dealer_html = format_dealer_results_as_html(api_result['data']['dealers']) | |
| summary = api_result['data']['search_summary'] | |
| message = f"✅ Found {summary['total_matches']:,} matches from {summary['original_count']:,} records in {summary['file_searched']}" | |
| info_text = "**🏆 Dealer Rankings with Expandable Car Lists**\n\nClick on any dealer name to see their individual car listings with prices and specifications." | |
| return message, info_text, dealer_html | |
| except ImportError: | |
| # Fallback if HTML formatting fails | |
| return api_result['message'], "API data available but HTML formatting failed", str(api_result['data']) | |
| else: | |
| # For non-dealer stats, use the original method that returns DataFrame | |
| message, result_data = matcher.search_data_files(keyword_search=keyword_search, make=processed_make, | |
| model=processed_model, year_min=None, year_max=None, | |
| year_from=year_from, year_to=year_to, | |
| body_type=processed_body_type, fuel_type=processed_fuel_type, | |
| max_odometer=None, odometer_from=odometer_from, | |
| odometer_to=odometer_to, max_price=max_price, | |
| selected_file=processed_selected_file, | |
| max_results=max_results, show_dealer_stats=False) | |
| if isinstance(result_data, pd.DataFrame) and not result_data.empty: | |
| info_text = f"**📊 Search completed successfully**\n\n" | |
| html_table = result_data.to_html(classes='table table-striped', | |
| table_id='search-results', escape=False, index=False) | |
| return message, info_text, html_table | |
| else: | |
| return message, "No results to display", "" | |
| def simple_search_data_interface(matcher, keyword_search, make, model, year_from, year_to, odometer_from, odometer_to, selected_file, | |
| max_results, show_dealer_stats): | |
| """Interface function for simple traditional data search""" | |
| # Handle text inputs - convert empty strings to None for backend processing | |
| processed_make = None if (not make or make.strip() == "" or make == "Select Make") else make.strip() | |
| processed_model = None if (not model or model.strip() == "" or model == "ALL") else model.strip() | |
| processed_selected_file = None if selected_file == "Select File" else selected_file | |
| if show_dealer_stats: | |
| # Use API method that returns clean structured data | |
| api_result = matcher.search_data_files_api( | |
| keyword_search=keyword_search, | |
| make=processed_make, | |
| model=processed_model, | |
| year_from=year_from, | |
| year_to=year_to, | |
| odometer_from=odometer_from, | |
| odometer_to=odometer_to, | |
| selected_file=processed_selected_file, | |
| max_results=max_results | |
| ) | |
| if not api_result["success"]: | |
| return f"❌ {api_result['message']}", "Error occurred", "" | |
| # Apply HTML formatting for UI display | |
| try: | |
| from utils import format_dealer_results_as_html | |
| dealer_html = format_dealer_results_as_html(api_result['data']['dealers']) | |
| summary = api_result['data']['search_summary'] | |
| message = f"✅ Found {summary['total_matches']:,} matches from {summary['original_count']:,} records in {summary['file_searched']}" | |
| info_text = "**🏆 Dealer Rankings with Expandable Car Lists**\n\nClick on any dealer name to see their individual car listings with prices and specifications." | |
| return message, info_text, dealer_html | |
| except ImportError: | |
| # Fallback if HTML formatting fails | |
| return api_result['message'], "API data available but HTML formatting failed", str(api_result['data']) | |
| else: | |
| # For non-dealer stats, use original method that returns DataFrame | |
| message, result_data = matcher.search_data_files( | |
| keyword_search=keyword_search, | |
| make=processed_make, | |
| model=processed_model, | |
| year_min=None, | |
| year_max=None, | |
| year_from=year_from, | |
| year_to=year_to, | |
| body_type=None, # Not used in simple search | |
| fuel_type=None, # Not used in simple search | |
| max_odometer=None, # Not using max_odometer anymore | |
| odometer_from=odometer_from, | |
| odometer_to=odometer_to, | |
| max_price=None, # Not used in simple search | |
| selected_file=processed_selected_file, | |
| max_results=max_results, | |
| show_dealer_stats=False | |
| ) | |
| if isinstance(result_data, pd.DataFrame) and not result_data.empty: | |
| info_text = f"**📊 Search completed successfully**\n\n" | |
| html_table = result_data.to_html(classes='table table-striped', | |
| table_id='search-results', escape=False, index=False) | |
| return message, info_text, html_table | |
| else: | |
| return message, "No results to display", "" | |
| def range_search_data_interface(matcher, keyword_search, make, model, year_value, year_range, odometer_value, odometer_range, | |
| selected_file, max_results, show_dealer_stats, return_cars): | |
| """Interface function for simple range-based data search - NOW RETURNS PURE JSON""" | |
| # Handle text inputs - convert empty strings to None for backend processing | |
| processed_make = None if (not make or make.strip() == "") else make.strip() | |
| processed_model = None if (not model or model.strip() == "") else model.strip() | |
| processed_selected_file = None if selected_file == "Select File" else selected_file | |
| # Convert target±range to from/to values for the matcher | |
| year_from = year_value - year_range if year_value and year_range is not None else None | |
| year_to = year_value + year_range if year_value and year_range is not None else None | |
| odometer_from = odometer_value - odometer_range if odometer_value and odometer_range is not None else None | |
| odometer_to = odometer_value + odometer_range if odometer_value and odometer_range is not None else None | |
| # Always use API method that returns clean structured JSON data | |
| api_result = matcher.search_data_files_api( | |
| keyword_search=keyword_search, | |
| make=processed_make, | |
| model=processed_model, | |
| year_from=year_from, | |
| year_to=year_to, | |
| odometer_from=odometer_from, | |
| odometer_to=odometer_to, | |
| selected_file=processed_selected_file, | |
| max_results=max_results, | |
| return_cars=return_cars | |
| ) | |
| # Return pure JSON data - no HTML formatting | |
| import json | |
| if not api_result["success"]: | |
| return f"❌ {api_result['message']}", "Error occurred", json.dumps(api_result, indent=2) | |
| # Return structured data as formatted JSON string for display | |
| summary = api_result['data']['search_summary'] | |
| message = f"✅ Found {summary['total_matches']:,} matches from {summary['original_count']:,} records in {summary['file_searched']}" | |
| info_text = "**🏆 Pure JSON Response**\n\nStructured dealer data with contact info and car listings." | |
| # Return the full JSON result as a formatted string | |
| json_output = json.dumps(api_result, indent=2) | |
| return message, info_text, json_output | |
| def range_search_data_interface_api(matcher, keyword_search, make, model, year_value, year_range, odometer_value, odometer_range, | |
| selected_file, max_results, show_dealer_stats): | |
| """Pure API interface function that returns clean JSON data without HTML formatting""" | |
| # Handle text inputs - convert empty strings to None for backend processing | |
| processed_make = None if (not make or make.strip() == "") else make.strip() | |
| processed_model = None if (not model or model.strip() == "") else model.strip() | |
| processed_selected_file = None if selected_file == "Select File" else selected_file | |
| # Convert target±range to from/to values for the matcher | |
| year_from = year_value - year_range if year_value and year_range is not None else None | |
| year_to = year_value + year_range if year_value and year_range is not None else None | |
| odometer_from = odometer_value - odometer_range if odometer_value and odometer_range is not None else None | |
| odometer_to = odometer_value + odometer_range if odometer_value and odometer_range is not None else None | |
| # Always use API method that returns clean structured data | |
| api_result = matcher.search_data_files_api( | |
| keyword_search=keyword_search, | |
| make=processed_make, | |
| model=processed_model, | |
| year_from=year_from, | |
| year_to=year_to, | |
| odometer_from=odometer_from, | |
| odometer_to=odometer_to, | |
| selected_file=processed_selected_file, | |
| max_results=max_results | |
| ) | |
| # Return clean JSON data directly - NO HTML formatting | |
| return api_result | |
| def search_data_interface_api(matcher, keyword_search, make, model, year_from, year_to, body_type, fuel_type, | |
| odometer_from, odometer_to, max_price, selected_file, max_results, show_dealer_stats): | |
| """Pure API interface function that returns clean JSON data without HTML formatting""" | |
| # Handle text inputs - convert empty strings to None for backend processing | |
| processed_make = None if (not make or make.strip() == "" or make == "Select Make") else make.strip() | |
| processed_model = None if (not model or model.strip() == "" or model == "ALL") else model.strip() | |
| processed_body_type = None if body_type == "Select Body Type" else body_type | |
| processed_fuel_type = None if fuel_type == "Select Fuel Type" else fuel_type | |
| processed_selected_file = None if selected_file == "Select File" else selected_file | |
| # Always use API method that returns clean structured data | |
| api_result = matcher.search_data_files_api( | |
| keyword_search=keyword_search, | |
| make=processed_make, | |
| model=processed_model, | |
| year_from=year_from, | |
| year_to=year_to, | |
| body_type=processed_body_type, | |
| fuel_type=processed_fuel_type, | |
| odometer_from=odometer_from, | |
| odometer_to=odometer_to, | |
| max_price=max_price, | |
| selected_file=processed_selected_file, | |
| max_results=max_results | |
| ) | |
| # Return clean JSON data directly - NO HTML formatting | |
| return api_result | |
| def simple_search_data_interface_api(matcher, keyword_search, make, model, year_from, year_to, odometer_from, odometer_to, selected_file, | |
| max_results, show_dealer_stats): | |
| """Pure API interface function that returns clean JSON data without HTML formatting""" | |
| # Handle text inputs - convert empty strings to None for backend processing | |
| processed_make = None if (not make or make.strip() == "" or make == "Select Make") else make.strip() | |
| processed_model = None if (not model or model.strip() == "" or model == "ALL") else model.strip() | |
| processed_selected_file = None if selected_file == "Select File" else selected_file | |
| # Always use API method that returns clean structured data | |
| api_result = matcher.search_data_files_api( | |
| keyword_search=keyword_search, | |
| make=processed_make, | |
| model=processed_model, | |
| year_from=year_from, | |
| year_to=year_to, | |
| odometer_from=odometer_from, | |
| odometer_to=odometer_to, | |
| selected_file=processed_selected_file, | |
| max_results=max_results | |
| ) | |
| # Return clean JSON data directly - NO HTML formatting | |
| return api_result |