File size: 20,925 Bytes
0a89685
 
 
 
 
 
 
316a145
0a89685
 
 
 
 
 
 
1468869
 
 
0a89685
1468869
0a89685
 
 
 
 
 
1468869
316a145
 
1468869
 
 
 
 
 
 
 
 
 
 
 
 
0a89685
 
 
 
316a145
 
 
 
1468869
0a89685
316a145
 
1468869
 
0a89685
 
 
 
 
 
 
 
 
 
 
 
 
 
1468869
0a89685
316a145
 
 
 
 
 
 
 
 
 
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316a145
 
 
 
 
 
 
 
 
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
 
316a145
 
e4c90f5
 
 
 
 
 
 
 
 
 
 
316a145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4c90f5
0a89685
 
1468869
 
0a89685
316a145
 
 
1468869
 
 
 
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a89685
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
 
0a89685
e4c90f5
 
 
 
 
 
 
 
 
 
 
0a89685
 
 
 
 
 
 
 
1468869
0a89685
 
316a145
 
 
1468869
0a89685
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd9c901
 
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
bd9c901
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4cc94a
1468869
 
 
 
 
 
 
e4c90f5
0a89685
 
e4c90f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
"""
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