Ubuntu commited on
Commit
0a89685
Β·
1 Parent(s): dd13376

modifications made

Browse files
.gitignore CHANGED
@@ -4,4 +4,4 @@ __pycache__/
4
  hf_cache/
5
  autogluon_model/
6
  data/
7
- simple_autogluon_models/
 
4
  hf_cache/
5
  autogluon_model/
6
  data/
7
+ simple_autogluon_models/
MODULAR_STRUCTURE.md ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Swiper Match - Modular Structure
2
+
3
+ ## Overview
4
+
5
+ The Gradio application has been refactored from a single 1251-line file into a clean, modular structure for better maintainability, readability, and scalability.
6
+
7
+ ## Directory Structure
8
+
9
+ ```
10
+ huggingface-frontend/
11
+ β”œβ”€β”€ app.py # 🎯 Main entry point (clean & minimal)
12
+ β”œβ”€β”€ app_original.py # πŸ“ Original monolithic app (backup)
13
+ β”œβ”€β”€ core/
14
+ β”‚ β”œβ”€β”€ __init__.py
15
+ β”‚ β”œβ”€β”€ config.py # πŸ”§ Configuration constants & settings
16
+ β”‚ └── matcher.py # πŸ€– CarDealerMatcher class & business logic
17
+ β”œβ”€β”€ ui/
18
+ β”‚ β”œβ”€β”€ __init__.py
19
+ β”‚ β”œβ”€β”€ interface.py # πŸ”Œ Interface functions (UI ↔ Business Logic)
20
+ β”‚ └── tabs/
21
+ β”‚ β”œβ”€β”€ __init__.py
22
+ β”‚ β”œβ”€β”€ simple_tab.py # πŸ” Simple prediction tab
23
+ β”‚ β”œβ”€β”€ detailed_tab.py # βš™οΈ Advanced prediction tab
24
+ β”‚ β”œβ”€β”€ traditional_tab.py # πŸ“Š Traditional CSV search tab
25
+ β”‚ └── simple_search_tab.py # πŸ” Simple CSV search tab
26
+ └── utils/
27
+ β”œβ”€β”€ __init__.py
28
+ └── helpers.py # πŸ› οΈ Utility functions & event handlers
29
+ ```
30
+
31
+ ## Key Benefits
32
+
33
+ ### βœ… **Maintainability**
34
+ - **Single Responsibility**: Each module has a clear, focused purpose
35
+ - **Easy Updates**: Modify individual components without affecting others
36
+ - **Bug Isolation**: Issues are contained within specific modules
37
+
38
+ ### βœ… **Readability**
39
+ - **Logical Organization**: Related code is grouped together
40
+ - **Clear Dependencies**: Import statements show module relationships
41
+ - **Reduced Complexity**: Each file is manageable in size
42
+
43
+ ### βœ… **Scalability**
44
+ - **Easy Extension**: Add new tabs/features without touching existing code
45
+ - **Reusable Components**: UI components can be reused across tabs
46
+ - **Team Development**: Multiple developers can work on different modules
47
+
48
+ ### βœ… **Testing**
49
+ - **Unit Testing**: Each module can be tested independently
50
+ - **Mock Dependencies**: Easy to mock external dependencies
51
+ - **Isolated Testing**: Test business logic separately from UI
52
+
53
+ ## Module Descriptions
54
+
55
+ ### πŸ“ `core/config.py`
56
+ **Purpose**: Central configuration management
57
+ - Car make/model data structures
58
+ - Dropdown options and choices
59
+ - Default values and constants
60
+ - UI configuration settings
61
+ - File paths and settings
62
+
63
+ ### πŸ€– `core/matcher.py`
64
+ **Purpose**: Business logic and model operations
65
+ - `CarDealerMatcher` class implementation
66
+ - Model loading and management
67
+ - Prediction logic
68
+ - CSV data search functionality
69
+ - Data processing utilities
70
+
71
+ ### πŸ”Œ `ui/interface.py`
72
+ **Purpose**: Bridge between UI and business logic
73
+ - Interface wrapper functions
74
+ - Parameter processing
75
+ - Response formatting
76
+ - Gradio-specific adaptations
77
+
78
+ ### 🎨 `ui/tabs/`
79
+ **Purpose**: Individual tab components
80
+ - **`simple_tab.py`**: Quick prediction interface
81
+ - **`detailed_tab.py`**: Advanced prediction with all parameters
82
+ - **`traditional_tab.py`**: Full-featured CSV search
83
+ - **`simple_search_tab.py`**: Basic CSV search interface
84
+
85
+ ### πŸ› οΈ `utils/helpers.py`
86
+ **Purpose**: Utility functions and event management
87
+ - Event handler setup
88
+ - Status information generation
89
+ - Common helper functions
90
+ - Application orchestration
91
+
92
+ ## How to Use
93
+
94
+ ### πŸš€ **Running the Application**
95
+ ```bash
96
+ # Same as before - the interface is identical
97
+ python app.py
98
+ ```
99
+
100
+ ### πŸ”§ **Adding New Features**
101
+
102
+ #### Adding a New Tab:
103
+ 1. Create `ui/tabs/new_tab.py`
104
+ 2. Implement `create_new_tab(matcher)` function
105
+ 3. Import and add to `app.py`
106
+ 4. Add event handlers in `utils/helpers.py`
107
+
108
+ #### Adding New Configuration:
109
+ 1. Add constants to `core/config.py`
110
+ 2. Import in relevant modules
111
+ 3. Use throughout the application
112
+
113
+ #### Extending Business Logic:
114
+ 1. Add methods to `CarDealerMatcher` in `core/matcher.py`
115
+ 2. Create interface functions in `ui/interface.py`
116
+ 3. Connect to UI components
117
+
118
+ ### πŸ§ͺ **Testing**
119
+
120
+ #### Unit Testing Core Logic:
121
+ ```python
122
+ # Test the matcher independently
123
+ from core.matcher import CarDealerMatcher
124
+ matcher = CarDealerMatcher("./test_model")
125
+ result = matcher.predict_dealers(make="Toyota", model="Camry")
126
+ ```
127
+
128
+ #### Testing UI Components:
129
+ ```python
130
+ # Test individual tabs
131
+ from ui.tabs.simple_tab import create_simple_tab
132
+ # Test with mock matcher
133
+ ```
134
+
135
+ ### πŸ”„ **Migration Notes**
136
+
137
+ - **Original app preserved**: `app_original.py` contains the original monolithic version
138
+ - **Identical functionality**: All features work exactly the same
139
+ - **Same API**: No changes to user interface or behavior
140
+ - **Drop-in replacement**: Can switch back by renaming files
141
+
142
+ ## Best Practices
143
+
144
+ ### πŸ“¦ **Module Design**
145
+ - Keep modules focused and cohesive
146
+ - Minimize coupling between modules
147
+ - Use clear, descriptive names
148
+ - Document module purposes
149
+
150
+ ### πŸ”Œ **Interface Design**
151
+ - Keep business logic in `core/` modules
152
+ - Use `ui/interface.py` for UI-specific adaptations
153
+ - Avoid mixing UI and business logic
154
+
155
+ ### βš™οΈ **Configuration Management**
156
+ - Centralize all constants in `core/config.py`
157
+ - Use descriptive variable names
158
+ - Group related configurations together
159
+ - Document configuration purposes
160
+
161
+ ### 🧰 **Adding New Features**
162
+ 1. **Plan the architecture**: Determine which modules need changes
163
+ 2. **Start with core logic**: Implement business logic first
164
+ 3. **Add UI components**: Create reusable UI components
165
+ 4. **Connect with interfaces**: Bridge UI and logic
166
+ 5. **Test thoroughly**: Test each module independently
167
+
168
+ ## Troubleshooting
169
+
170
+ ### Import Errors
171
+ - Ensure you're running from the correct directory
172
+ - Check that all `__init__.py` files exist
173
+ - Verify import paths are correct
174
+
175
+ ### Missing Dependencies
176
+ - All original dependencies are still required
177
+ - No new dependencies added in the refactoring
178
+
179
+ ### Functionality Issues
180
+ - Compare behavior with `app_original.py`
181
+ - Check that all event handlers are properly set up
182
+ - Verify configuration values are correctly imported
183
+
184
+ ## Future Enhancements
185
+
186
+ The modular structure enables easy implementation of:
187
+
188
+ - **New prediction models**: Add to `core/matcher.py`
189
+ - **Additional data sources**: Extend data loading in `core/matcher.py`
190
+ - **New UI themes**: Modify `core/config.py`
191
+ - **API endpoints**: Add REST API alongside Gradio interface
192
+ - **Background tasks**: Implement async processing
193
+ - **Caching systems**: Add caching layers
194
+ - **User authentication**: Add auth modules
195
+ - **Database integration**: Replace CSV with database backends
196
+
197
+ ---
198
+
199
+ ## Summary
200
+
201
+ The refactored structure transforms a 1251-line monolithic file into a clean, maintainable, and extensible architecture while preserving 100% of the original functionality. This enables better development practices, easier maintenance, and future scalability.
README.md CHANGED
@@ -1,188 +1,124 @@
1
  ---
2
- title: Swiper Match
3
- emoji: 🌍
4
- colorFrom: purple
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 5.31.0
8
- app_file: app.py
9
- pinned: false
10
- short_description: Match cars B2B
11
- models:
12
- - mzx/Swiper-Match
 
 
 
 
 
 
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
16
 
17
- # πŸš— Swiper-Match: HuggingFace-Enabled Car Dealer Predictor
18
 
19
- This Gradio app now uses pretrained models from HuggingFace Hub instead of local models, with automatic fallback support.
20
 
21
- ## Features
22
-
23
- - **Smart Dealer Matching**: Uses trained AutoGluon ML models to predict the best dealers for your car preferences
24
- - **Interactive Interface**: Easy-to-use web interface with dropdown selections and number inputs
25
- - **Top-K Predictions**: Get the top 5 recommended dealers with confidence scores
26
- - **Real-time Predictions**: Instant results as you adjust your car specifications
27
- - **No Data Leakage**: Models trained with carefully selected features to avoid bias
28
-
29
- ## How It Works
30
-
31
- The app uses a trained AutoGluon TabularPredictor model that:
32
-
33
- 1. **Takes car specifications** as input (make, model, year, body type, fuel type, etc.)
34
- 2. **Predicts dealer preferences** based on historical car sales and inventory data
35
- 3. **Returns ranked dealers** with confidence scores for each recommendation
36
 
37
  ## πŸš€ Quick Start
38
 
39
- ### 1. Install Dependencies
40
-
41
- ```bash
42
- pip install -r requirements.txt
43
- ```
44
-
45
- ### 2. Run the App
46
-
47
- ```bash
48
- python app.py
49
- ```
50
-
51
- The app will automatically:
52
- - βœ… Try to load the model from HuggingFace Hub (`mzx/Swiper-Match`)
53
- - πŸ”„ Fall back to local AutoGluon models if HF is unavailable
54
- - πŸ“Š Display which model source is being used in the interface
55
-
56
- ## πŸ€— HuggingFace Integration
57
-
58
- ### Model Loading Priority:
59
-
60
- 1. **Primary**: HuggingFace Hub model (`mzx/Swiper-Match`)
61
- - Uses custom `AutoGluonSwiperModel` wrapper
62
- - Enhanced feature compatibility
63
- - No local files required
64
-
65
- 2. **Fallback**: Local AutoGluon models
66
- - Searches in `../../../src/experiments/autogluon/models_swiper_hf/`
67
- - Original AutoGluon TabularPredictor interface
68
-
69
- ### Model Features:
70
-
71
- - **No Data Leakage**: Excludes dealer-identifying features
72
- - **GPU Optimized**: Trained with CUDA acceleration
73
- - **Ensemble Methods**: XGBoost, Neural Networks, Random Forest
74
- - **Auto-Stacking**: Combines best models automatically
75
-
76
- ## πŸ“Š Model Information
77
-
78
- The app displays real-time information about:
79
- - βœ… Model loading status
80
- - πŸ”§ Model type (HuggingFace Hub vs Local)
81
- - πŸ“ Repository/file location
82
- - πŸ‘₯ Number of trained dealers
83
- - 🎯 Feature engineering approach
84
-
85
- ## πŸ”§ Troubleshooting
86
-
87
- ### If HuggingFace loading fails:
88
- - Check internet connection
89
- - Verify `transformers` and `huggingface-hub` are installed
90
- - The app will automatically fall back to local models
91
-
92
- ### If both models fail:
93
- - Ensure AutoGluon is installed: `pip install autogluon.tabular`
94
- - Check that local model files exist in the expected directory
95
- - Review the console logs for detailed error messages
96
-
97
- ## πŸš€ Training Your Own Models
98
-
99
- To upload new models to HuggingFace Hub:
100
-
101
- 1. Run the training script:
102
- ```bash
103
- cd ../../../src/experiments/autogluon/
104
- python buyer_prediction_v4_hf.py
105
- ```
106
-
107
- 2. Set your HuggingFace token:
108
- ```bash
109
- export HF_TOKEN="your_token_here"
110
- # or add HF_TOKEN=your_token_here to .env file
111
- ```
112
-
113
- 3. The script will automatically upload to `mzx/Swiper-Match`
114
-
115
- ## πŸ“ Example Usage
116
-
117
  ```python
118
- # The app automatically handles model loading and prediction
119
- # Just use the Gradio interface or call directly:
120
-
121
- matcher = CarDealerMatcher(hf_repo="mzx/Swiper-Match", use_hf_hub=True)
122
- results = matcher.predict_dealers(
123
- make="Toyota", model="Camry", year=2020,
124
- body_type="Sedan", fuel_type="Petrol", transmission="Automatic",
125
- price=25000, odometer=50000, doors=4, seats=5
126
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  ```
128
 
129
- ## 🎯 Features
130
 
131
- - **Smart Loading**: Automatic HuggingFace Hub integration
132
- - **Graceful Fallback**: Local model support when HF is unavailable
133
- - **Real-time Status**: Live model information display
134
- - **Enhanced Compatibility**: Comprehensive feature mapping
135
- - **Error Handling**: Clear error messages and recovery
 
136
 
137
- ## πŸ“š Dependencies
 
 
 
 
138
 
139
- - `gradio>=4.0.0` - Web interface
140
- - `transformers>=4.30.0` - HuggingFace model support
141
- - `huggingface-hub>=0.16.0` - Model downloading
142
- - `autogluon.tabular>=1.0.0` - Local model fallback
143
- - `torch>=2.0.0` - Neural network support
144
- - `pandas`, `numpy`, `scikit-learn` - Data processing
145
 
146
- ## Example Use Cases
 
 
 
 
147
 
148
- - **Car Buyers**: Find dealers most likely to have your dream car
149
- - **Market Research**: Understand dealer-vehicle relationships
150
- - **Inventory Planning**: Predict which dealers to approach for specific vehicles
151
 
152
- ## Technical Details
 
 
 
153
 
154
- The model uses these key features for prediction:
155
- - Vehicle specifications (make, model, year, body type)
156
- - Technical details (fuel type, transmission, engine specs)
157
- - Market factors (price range, mileage, physical attributes)
158
 
159
- **Note**: The model deliberately excludes dealer-identifying features to prevent data leakage and ensure fair predictions based on vehicle characteristics alone.
 
 
 
160
 
161
- ## Development
162
 
163
- ### Project Structure
164
- ```
165
- swiper-match/
166
- β”œβ”€β”€ src/experiments/autogluon/ # Model training code
167
- β”œβ”€β”€ huggingface-frontend/Swiper-match/ # Gradio app
168
- β”‚ β”œβ”€β”€ app.py # Main Gradio application
169
- β”‚ β”œβ”€β”€ requirements.txt # Python dependencies
170
- β”‚ └── README.md # This file
171
- └── data/ # Training data
172
- ```
173
 
174
- ### Extending the App
175
 
176
- To add new features:
 
 
 
 
 
 
 
 
177
 
178
- 1. **Modify the input form** in `app.py` by adding new Gradio components
179
- 2. **Update the prediction function** to handle new inputs
180
- 3. **Enhance the model** by retraining with additional features
181
- 4. **Improve the UI** by customizing the Gradio Blocks interface
182
 
183
- ## Support
 
 
 
 
184
 
185
- For issues or questions:
186
- 1. Check the model loading logs for detailed error messages
187
- 2. Verify all dependencies are correctly installed
188
- 3. Ensure the trained model files exist in the expected location
 
1
  ---
2
+ license: mit
3
+ tags:
4
+ - autogluon
5
+ - tabular
6
+ - automotive
7
+ - dealer-prediction
8
+ - swiper-match
9
+ - no-data-leakage
10
+ - gpu-optimized
11
+ language:
12
+ - en
13
+ datasets:
14
+ - custom
15
+ metrics:
16
+ - accuracy
17
+ - top-k-accuracy
18
+ library_name: autogluon
19
  ---
20
 
21
+ # πŸš— Swiper-Match: Car Dealer Prediction Model
22
 
23
+ This model predicts which car dealer is most likely to have a specific vehicle based **solely on vehicle characteristics**, ensuring no data leakage from dealer-identifying features.
24
 
25
+ ## 🎯 Model Details
26
 
27
+ - **Framework**: AutoGluon Tabular v1.3+
28
+ - **Training**: GPU-accelerated ensemble with early stopping
29
+ - **Dealers**: 73 different car dealers
30
+ - **Features**: Vehicle characteristics only (no dealer-identifying info)
31
+ - **No Leakage**: Strict exclusion of 29 dealer-identifying features
 
 
 
 
 
 
 
 
 
 
32
 
33
  ## πŸš€ Quick Start
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  ```python
36
+ from transformers import AutoModel
37
+ import pandas as pd
38
+
39
+ # Load model
40
+ model = AutoModel.from_pretrained("mzx/Swiper-Match", trust_remote_code=True)
41
+
42
+ # Prepare input
43
+ vehicle_data = pd.DataFrame({
44
+ 'make': ['Toyota'],
45
+ 'model': ['Camry'],
46
+ 'year': [2020],
47
+ 'vehicle_type': ['Passenger'],
48
+ 'odometer': [50000],
49
+ 'condition': ['Used'],
50
+ 'car_age': [4]
51
+ })
52
+
53
+ # Get top-5 predictions
54
+ results = model.predict_top_k(vehicle_data, k=5)
55
+ print(f"Most likely dealer: {results['top_prediction']}")
56
+ print(f"Confidence: {results['top_confidence']:.2%}")
57
+ print(f"Top 5: {results['top_k_dict']}")
58
  ```
59
 
60
+ ## πŸ“Š Features Used
61
 
62
+ **Vehicle Characteristics**:
63
+ - Make, Model, Year, Variant, Series
64
+ - Body Type, Vehicle Type, Drive Type
65
+ - Engine specs (power, size, cylinders, fuel type)
66
+ - Transmission, Seats, Doors
67
+ - Condition, Odometer reading
68
 
69
+ **Excluded (No Leakage)**:
70
+ - Dealer names, IDs, locations
71
+ - Geographic information
72
+ - Dealer-specific business features
73
+ - URLs and source identifiers
74
 
75
+ ## πŸ”¬ Methodology
 
 
 
 
 
76
 
77
+ 1. **Data Preprocessing**: Removed all 29 dealer-identifying features
78
+ 2. **Balanced Training**: Oversampling ensures all dealers represented
79
+ 3. **GPU Training**: CUDA-accelerated with ensemble methods
80
+ 4. **Early Stopping**: Prevents overfitting, optimizes training time
81
+ 5. **Auto-Stacking**: AutoGluon combines best models automatically
82
 
83
+ ## πŸ“ˆ Performance
 
 
84
 
85
+ - **Training Data**: 34a7fad7... hash
86
+ - **GPU Enabled**: True
87
+ - **Models**: Ensemble of XGBoost, Neural Networks, CatBoost, Random Forest
88
+ - **Accuracy**: Top-1 and Top-5 accuracy on vehicle-dealer matching
89
 
90
+ ## ⚠️ Limitations
 
 
 
91
 
92
+ - Predictions based on historical patterns in training data
93
+ - Performance depends on similarity to training distribution
94
+ - May not generalize to dealers not seen during training
95
+ - Results are for research/demonstration purposes
96
 
97
+ ## πŸ”§ Technical Implementation
98
 
99
+ - **AutoGluon Backend**: High-performance ensemble learning
100
+ - **HuggingFace Wrapper**: Seamless integration with HF ecosystem
101
+ - **GPU Optimization**: CUDA acceleration for training and inference
102
+ - **Smart Caching**: Efficient model storage and loading
 
 
 
 
 
 
103
 
104
+ ## πŸ“ Citation
105
 
106
+ ```bibtex
107
+ @misc{swiper-match-2024,
108
+ title={Swiper-Match: GPU-Optimized Car Dealer Prediction},
109
+ author={Swiper-Match Team},
110
+ year={2024},
111
+ publisher={HuggingFace},
112
+ url={https://huggingface.co/mzx/Swiper-Match}
113
+ }
114
+ ```
115
 
116
+ ## 🀝 Usage Guidelines
 
 
 
117
 
118
+ This model is designed for:
119
+ - Research and educational purposes
120
+ - Automotive market analysis
121
+ - Dealer recommendation systems
122
+ - Machine learning demonstrations
123
 
124
+ **Please ensure compliance with applicable data privacy and usage regulations.**
 
 
 
app.py CHANGED
@@ -1,1038 +1,82 @@
 
 
 
 
 
1
  import gradio as gr
2
- import pandas as pd
3
- import numpy as np
4
- import os
5
  import logging
6
- from huggingface_hub import snapshot_download
7
- import glob
8
 
9
- # Hugging Face configuration
10
- #print(os.getenv('HF_TOKEN'))
11
- HF_REPO_ID = "mzx/Swiper-Match" # Replace with your actual repo ID
12
- HF_TOKEN = os.getenv('HF_TOKEN') # Will be set when provided by user
 
 
 
 
 
 
 
 
 
13
 
14
- # AutoGluon imports
15
- from autogluon.tabular import TabularPredictor
16
 
17
  # Set up logging
18
  logging.basicConfig(level=logging.INFO)
19
  logger = logging.getLogger(__name__)
20
 
21
- class CarDealerMatcher:
22
- def __init__(self, model_path: str = "./autogluon_model"):
23
- self.model_path = model_path
24
- self.predictor = None
25
- self.trained_dealers = []
26
- self.available_models = []
27
- self.model_loaded = False
28
- self.data_files = []
29
- self.load_model()
30
- self.load_data_files()
31
-
32
- def load_model(self):
33
- """Load AutoGluon model from local directory or download from Hugging Face"""
34
- try:
35
- logger.info(f"πŸ€– Loading AutoGluon model from: {self.model_path}")
36
-
37
- # Check if model exists locally
38
- if not os.path.exists(self.model_path):
39
- logger.info(f"πŸ“₯ Model not found locally. Downloading from Hugging Face: {HF_REPO_ID}")
40
- try:
41
- # Download the model from Hugging Face
42
- downloaded_path = snapshot_download(
43
- repo_id=HF_REPO_ID,
44
- cache_dir="./hf_cache",
45
- token=HF_TOKEN,
46
- #allow_patterns=["autogluon_model/**"],
47
- local_dir="./",
48
- local_dir_use_symlinks=False
49
- )
50
- logger.info(f"βœ… Model downloaded successfully to: {downloaded_path}")
51
- except Exception as download_error:
52
- logger.error(f"❌ Failed to download model from Hugging Face: {download_error}")
53
- self.model_loaded = False
54
- return
55
-
56
- # Load the model
57
- if os.path.exists(self.model_path):
58
- self.predictor = TabularPredictor.load(self.model_path)
59
- self._extract_trained_dealers()
60
- self._extract_available_models()
61
- self.model_loaded = True
62
- logger.info(f"βœ… Model loaded successfully! Can predict for {len(self.trained_dealers)} dealers")
63
- logger.info(f"🎯 Available models: {self.available_models}")
64
- else:
65
- logger.error(f"❌ Model directory still not found after download attempt: {self.model_path}")
66
- self.model_loaded = False
67
-
68
- except Exception as e:
69
- logger.error(f"❌ Failed to load model: {e}")
70
- self.model_loaded = False
71
 
72
- def _extract_trained_dealers(self):
73
- """Extract trained dealers from the predictor"""
74
- try:
75
- if hasattr(self.predictor, 'class_labels'):
76
- self.trained_dealers = list(self.predictor.class_labels)
77
- else:
78
- # Use a dummy prediction to extract dealer names
79
- dummy_data = self._create_dummy_data()
80
- proba_result = self.predictor.predict_proba(dummy_data)
81
- if hasattr(proba_result, 'columns'):
82
- self.trained_dealers = list(proba_result.columns)
83
- else:
84
- self.trained_dealers = ['Model loaded successfully']
85
- except Exception as e:
86
- self.trained_dealers = ['Model loaded successfully']
87
- logger.warning(f"Could not extract dealer list: {e}")
88
 
89
- def _extract_available_models(self):
90
- """Extract available models from the predictor"""
91
- try:
92
- if hasattr(self.predictor, 'model_names'):
93
- raw_models = self.predictor.model_names()
94
- # Add ensemble options with user-friendly names
95
- self.available_models = ['WeightedEnsemble_L3 (Best)', 'WeightedEnsemble_L2'] + [
96
- name for name in raw_models if 'WeightedEnsemble' not in name
97
- ]
98
- else:
99
- self.available_models = ['WeightedEnsemble_L3 (Best)', 'RandomForest_BAG_L1', 'XGBoost_BAG_L1', 'NeuralNetTorch_BAG_L1']
100
- except Exception as e:
101
- self.available_models = ['WeightedEnsemble_L3 (Best)']
102
- logger.warning(f"Could not extract model list: {e}")
103
-
104
- def _create_dummy_data(self):
105
- """Create dummy data with all required features"""
106
- return pd.DataFrame([{
107
- 'make': 'toyota',
108
- 'model': 'camry',
109
- 'year': 2020,
110
- 'car_age': 4, # Add the missing car_age feature (2024 - 2020 = 4)
111
- 'vehicle_body_type': 'sedan',
112
- 'vehicle_fuel_type': 'petrol',
113
- 'vehicle_transmission_type': 'automatic',
114
- 'odometer': 50000,
115
- 'vehicle_doors': 4,
116
- 'vehicle_seats': 5,
117
- 'series': 'unknown',
118
- 'variant': 'unknown',
119
- 'vehicle_body_type_group': 'Passenger',
120
- 'vehicle_body_type_style': '4 Door',
121
- 'vehicle_cylinder_description': '4 Cylinder',
122
- 'vehicle_cylinders': 4.0,
123
- 'vehicle_drive_type': 'Front Wheel Drive',
124
- 'vehicle_engine_size': 2.0,
125
- 'vehicle_power': 150.0,
126
- 'vehicle_safety_rating': 5,
127
- 'vehicle_segment': 'Medium',
128
- 'condition': 'Used',
129
- 'vehicle_type': 1
130
- }])
131
-
132
- def predict_dealers(self, make=None, model=None, year=None, body_type=None, fuel_type=None, transmission=None,
133
- odometer=None, doors=None, seats=None, engine_size=None, power=None, cylinders=None,
134
- safety_rating=None, drive_type=None, segment=None, condition=None, selected_model=None):
135
- """Predict top dealers for the given car specifications using selected model"""
136
-
137
- if not self.model_loaded:
138
- return "❌ AutoGluon model not loaded. Please check model directory availability.", "", ""
139
 
140
- try:
141
- # Calculate car age (current year - vehicle year)
142
- current_year = 2024 # You can use datetime.now().year for dynamic year
143
- car_age = current_year - int(year) if year else None
144
-
145
- # Create input dataframe with only non-None values for AutoGluon model
146
- car_data_dict = {}
147
-
148
- # Add parameters only if they are not None
149
- if make is not None:
150
- car_data_dict['make'] = make.lower()
151
- if model is not None:
152
- car_data_dict['model'] = model.lower()
153
- if year is not None:
154
- car_data_dict['year'] = int(year)
155
- if car_age is not None:
156
- car_data_dict['car_age'] = car_age
157
- if body_type is not None:
158
- car_data_dict['vehicle_body_type'] = body_type.lower()
159
- if fuel_type is not None:
160
- car_data_dict['vehicle_fuel_type'] = fuel_type.lower()
161
- if transmission is not None:
162
- car_data_dict['vehicle_transmission_type'] = transmission.lower()
163
- if odometer is not None:
164
- car_data_dict['odometer'] = int(odometer)
165
- if doors is not None:
166
- car_data_dict['vehicle_doors'] = float(doors)
167
- if seats is not None:
168
- car_data_dict['vehicle_seats'] = float(seats)
169
- if engine_size is not None:
170
- car_data_dict['vehicle_engine_size'] = float(engine_size)
171
- if power is not None:
172
- car_data_dict['vehicle_power'] = float(power)
173
- if cylinders is not None:
174
- car_data_dict['vehicle_cylinders'] = float(cylinders)
175
- if safety_rating is not None:
176
- car_data_dict['vehicle_safety_rating'] = float(safety_rating)
177
- if drive_type is not None:
178
- car_data_dict['vehicle_drive_type'] = drive_type
179
- if segment is not None:
180
- car_data_dict['vehicle_segment'] = segment
181
- if condition is not None:
182
- car_data_dict['condition'] = condition
183
-
184
- # Auto-generated features based on inputs (only if base inputs exist)
185
- if body_type is not None:
186
- car_data_dict['vehicle_body_type_group'] = self._map_body_type_group(body_type)
187
- if doors is not None:
188
- car_data_dict['vehicle_body_type_style'] = self._map_body_style(doors)
189
- if cylinders is not None:
190
- car_data_dict['vehicle_cylinder_description'] = f'{int(cylinders)} Cylinder'
191
-
192
- # Always include these if not specified (required for model compatibility)
193
- if 'series' not in car_data_dict:
194
- car_data_dict['series'] = 'unknown'
195
- if 'variant' not in car_data_dict:
196
- car_data_dict['variant'] = 'unknown'
197
- if 'vehicle_type' not in car_data_dict:
198
- car_data_dict['vehicle_type'] = 1 # Passenger vehicle
199
-
200
- car_data = pd.DataFrame([car_data_dict])
201
-
202
- # Get predictions using AutoGluon predictor with selected model
203
- if selected_model and selected_model != 'WeightedEnsemble_L3 (Best)':
204
- # Clean model name
205
- clean_model = selected_model.replace(' (Best)', '')
206
- try:
207
- proba_result = self.predictor.predict_proba(car_data, model=clean_model)
208
- model_used = selected_model
209
- except Exception as model_error:
210
- logger.warning(f"Failed to use specific model {clean_model}: {model_error}")
211
- proba_result = self.predictor.predict_proba(car_data)
212
- model_used = "WeightedEnsemble_L3 (fallback)"
213
- else:
214
- proba_result = self.predictor.predict_proba(car_data)
215
- model_used = "WeightedEnsemble_L3 (Best)"
216
-
217
- # Convert to dict format and get top-k predictions
218
- if hasattr(proba_result, 'iloc'):
219
- proba_dict = proba_result.iloc[0].to_dict()
220
- else:
221
- proba_dict = dict(zip(self.trained_dealers, proba_result[0]))
222
-
223
- # Sort by probability and get top 5
224
- sorted_dealers = sorted(proba_dict.items(), key=lambda x: x[1], reverse=True)[:5]
225
-
226
- # Format results
227
- top_dealer = sorted_dealers[0][0]
228
- confidence = f"{sorted_dealers[0][1]:.2%}"
229
-
230
- # Create detailed results
231
- results_text = "πŸ† **Top 5 Recommended Dealers:**\n\n"
232
- for i, (dealer, prob) in enumerate(sorted_dealers, 1):
233
- emoji = "πŸ₯‡" if i == 1 else "πŸ₯ˆ" if i == 2 else "πŸ₯‰" if i == 3 else "πŸ”Έ"
234
- results_text += f"{emoji} **{i}. {dealer}** - {prob:.1%} confidence\n"
235
-
236
- # Add car summary
237
- car_summary = f"""
238
-
239
- **πŸš— Vehicle Specifications:**
240
- β€’ **Make & Model:** {make} {model} ({year})
241
- β€’ **Body Type:** {body_type} β€’ **Segment:** {segment}
242
- β€’ **Engine:** {engine_size}L, {cylinders} cylinders, {power}HP
243
- β€’ **Drivetrain:** {fuel_type} β€’ {transmission} β€’ {drive_type}
244
- β€’ **Details:** {doors} doors, {seats} seats β€’ {odometer:,} km
245
- β€’ **Condition:** {condition} β€’ **Safety:** {safety_rating}β˜…
246
-
247
- **πŸ€– Model:** {model_used}
248
- """
249
-
250
- return f"🎯 **Best Match: {top_dealer}**", confidence, results_text + car_summary
251
-
252
- except Exception as e:
253
- logger.error(f"Prediction error: {e}")
254
- return f"❌ Error making prediction: {str(e)}", "", ""
255
-
256
- def _map_body_type_group(self, body_type):
257
- """Map body type to group"""
258
- if not body_type:
259
- return 'Passenger'
260
- body_lower = body_type.lower()
261
- if body_lower in ['ute', 'truck', 'van']:
262
- return 'Commercial'
263
- return 'Passenger'
264
-
265
- def _map_body_style(self, doors):
266
- """Map doors to body style"""
267
- if not doors:
268
- return '4 Door'
269
- doors = int(doors)
270
- if doors == 2:
271
- return '2 Door'
272
- elif doors == 3:
273
- return '3 Door'
274
- elif doors == 5:
275
- return '5 Door'
276
- else:
277
- return '4 Door'
278
-
279
- def load_data_files(self):
280
- """Load available CSV data files"""
281
- try:
282
- data_dir = "./data"
283
- if os.path.exists(data_dir):
284
- csv_files = glob.glob(os.path.join(data_dir, "*.csv"))
285
- self.data_files = [os.path.basename(f) for f in csv_files]
286
- logger.info(f"βœ… Found {len(self.data_files)} CSV files: {self.data_files}")
287
- else:
288
- self.data_files = []
289
- logger.warning("❌ Data directory not found")
290
- except Exception as e:
291
- logger.error(f"❌ Failed to load data files: {e}")
292
- self.data_files = []
293
-
294
- def search_data_files(self, make=None, model=None, year_min=None, year_max=None,
295
- body_type=None, fuel_type=None, max_odometer=None,
296
- max_price=None, selected_file=None, max_results=100, show_dealer_stats=True):
297
- """Search through CSV data files using pandas filtering"""
298
- try:
299
- if not self.data_files:
300
- return "❌ No CSV data files available", pd.DataFrame()
301
-
302
- # Use selected file or default to first available
303
- if selected_file and selected_file in self.data_files:
304
- file_to_search = selected_file
305
- else:
306
- file_to_search = self.data_files[0] if self.data_files else None
307
-
308
- if not file_to_search:
309
- return "❌ No valid file selected", pd.DataFrame()
310
-
311
- file_path = os.path.join("./data", file_to_search)
312
-
313
- # Load the CSV file
314
- logger.info(f"πŸ” Loading data from: {file_to_search}")
315
-
316
- # Read CSV with error handling for different encodings
317
- try:
318
- df = pd.read_csv(file_path, encoding='utf-8')
319
- except UnicodeDecodeError:
320
- try:
321
- df = pd.read_csv(file_path, encoding='latin-1')
322
- except:
323
- df = pd.read_csv(file_path, encoding='cp1252')
324
-
325
- # Convert column names to lowercase for easier matching
326
- df.columns = df.columns.str.lower().str.strip()
327
-
328
- original_count = len(df)
329
-
330
- # Apply filters using correct column names from the dataset
331
- if make and 'make' in df.columns:
332
- df = df[df['make'].str.contains(make, case=False, na=False)]
333
-
334
- if model and 'model' in df.columns:
335
- df = df[df['model'].str.contains(model, case=False, na=False)]
336
-
337
- if year_min and 'manu_year' in df.columns:
338
- df = df[pd.to_numeric(df['manu_year'], errors='coerce') >= year_min]
339
-
340
- if year_max and 'manu_year' in df.columns:
341
- df = df[pd.to_numeric(df['manu_year'], errors='coerce') <= year_max]
342
-
343
- if body_type and 'vehicle_body_type' in df.columns:
344
- df = df[df['vehicle_body_type'].str.contains(body_type, case=False, na=False)]
345
-
346
- if fuel_type and 'vehicle_fuel_type' in df.columns:
347
- df = df[df['vehicle_fuel_type'].str.contains(fuel_type, case=False, na=False)]
348
-
349
- if max_odometer and 'odometer' in df.columns:
350
- df = df[pd.to_numeric(df['odometer'], errors='coerce') <= max_odometer]
351
-
352
- if max_price and 'advertised_price' in df.columns:
353
- df = df[pd.to_numeric(df['advertised_price'], errors='coerce') <= max_price]
354
-
355
- filtered_count = len(df)
356
-
357
- if df.empty:
358
- return f"βœ… Searched {file_to_search} ({original_count:,} records) - No matches found", pd.DataFrame()
359
-
360
- # Create dealer ranking summary
361
- if show_dealer_stats and 'dealer_trading_name' in df.columns:
362
- dealer_summary = self._create_dealer_summary(df)
363
- return f"βœ… Found {filtered_count:,} matches from {original_count:,} records in {file_to_search}", dealer_summary
364
- else:
365
- # Return basic summary without dealer rankings
366
- summary_df = pd.DataFrame({
367
- 'Total Matches': [filtered_count],
368
- 'File Searched': [file_to_search]
369
- })
370
- return f"βœ… Found {filtered_count:,} matches from {original_count:,} records in {file_to_search}", summary_df
371
-
372
- except Exception as e:
373
- logger.error(f"❌ Search error: {e}")
374
- return f"❌ Error searching data: {str(e)}", pd.DataFrame()
375
-
376
- def _create_dealer_summary(self, df):
377
- """Create dealer ranking summary showing top dealers by car count"""
378
- try:
379
- logger.info(f"πŸ“Š Creating dealer summary from {len(df)} matching cars...")
380
-
381
- # Count cars per dealer
382
- dealer_counts = df['dealer_trading_name'].value_counts()
383
-
384
- # Get top 5 dealers
385
- top_dealers = dealer_counts.head(5)
386
-
387
- # Create summary dataframe
388
- summary_data = []
389
- for rank, (dealer_name, car_count) in enumerate(top_dealers.items(), 1):
390
- summary_data.append({
391
- 'Rank': f"#{rank}",
392
- 'Dealer Name': dealer_name,
393
- 'Matching Cars': car_count
394
- })
395
-
396
- summary_df = pd.DataFrame(summary_data)
397
-
398
- logger.info(f"βœ… Created dealer summary. Top dealer: {top_dealers.index[0]} with {top_dealers.iloc[0]} cars")
399
- return summary_df
400
-
401
- except Exception as e:
402
- logger.error(f"❌ Error creating dealer summary: {e}")
403
- return pd.DataFrame({'Error': ['Failed to create dealer summary']})
404
-
405
- # Initialize the matcher with local model
406
- matcher = CarDealerMatcher(model_path="./autogluon_model")
407
-
408
- # Initialize a separate simple matcher for the simple tab
409
- simple_matcher = CarDealerMatcher(model_path="./simple_autogluon_models")
410
-
411
- # Define structured make-model data for dynamic dropdowns
412
- MAKE_MODEL_DATA = {
413
- "Toyota": [
414
- "Camry",
415
- "Corolla",
416
- "HiAce",
417
- "Hilux",
418
- "Kluger",
419
- "Landcruiser",
420
- "Landcruiser Prado",
421
- "Prius V",
422
- "RAV4",
423
- "Yaris",
424
- "Yaris Cross"
425
- ],
426
- "Audi": [
427
- "Q3",
428
- "SQ7",
429
- "TT"
430
- ],
431
- "BMW": [
432
- "125I",
433
- "5",
434
- "M2"
435
- ],
436
- "Fiat": [
437
- "500",
438
- "500C",
439
- "Ducato"
440
- ],
441
- "Ford": [
442
- "Everest",
443
- "F150",
444
- "Falcon",
445
- "Fiesta",
446
- "Ranger",
447
- "Territory"
448
- ],
449
- "GWM": [
450
- "Haval H6"
451
- ],
452
- "Great Wall": [
453
- "Steed"
454
- ],
455
- "Holden": [
456
- "Calais",
457
- "Captiva",
458
- "Colorado",
459
- "Colorado 7",
460
- "Commodore",
461
- "Cruze",
462
- "Trax",
463
- "UTE"
464
- ],
465
- "Honda": [
466
- "CR-V"
467
- ],
468
- "Hyundai": [
469
- "Accent",
470
- "Elantra",
471
- "I30",
472
- "IX35",
473
- "Iload",
474
- "Kona",
475
- "Santa FE",
476
- "Tucson",
477
- "Veloster"
478
- ],
479
- "Isuzu": [
480
- "D-MAX"
481
- ],
482
- "Jaguar": [
483
- "E-Pace"
484
- ],
485
- "Jeep": [
486
- "Grand Cherokee"
487
- ],
488
- "Kia": [
489
- "Cerato",
490
- "Optima",
491
- "Sorento",
492
- "Sportage"
493
- ],
494
- "LDV": [
495
- "D90",
496
- "Deliver 9"
497
- ],
498
- "Land Rover": [
499
- "Discovery Sport"
500
- ],
501
- "MG": [
502
- "MG3 Auto"
503
- ],
504
- "Mazda": [
505
- "3",
506
- "6",
507
- "BT-50",
508
- "CX-3",
509
- "CX-30",
510
- "CX-5",
511
- "CX-9",
512
- "MX-5"
513
- ],
514
- "Mercedes-Benz": [
515
- "C180",
516
- "C250",
517
- "E350",
518
- "EQS",
519
- "GL320",
520
- "GLC250",
521
- "SL400",
522
- "Sprinter"
523
- ],
524
- "Mini": [
525
- "3D Hatch"
526
- ],
527
- "Mitsubishi": [
528
- "ASX",
529
- "Eclipse Cross",
530
- "Lancer",
531
- "Outlander",
532
- "Pajero Sport",
533
- "Triton"
534
- ],
535
- "Nissan": [
536
- "Maxima",
537
- "Navara",
538
- "Pathfinder",
539
- "Patrol",
540
- "Qashqai",
541
- "Skyline",
542
- "X-Trail"
543
- ],
544
- "Porsche": [
545
- "Cayenne",
546
- "Macan"
547
- ],
548
- "Renault": [
549
- "Captur",
550
- "Megane"
551
- ],
552
- "Skoda": [
553
- "Octavia"
554
- ],
555
- "Subaru": [
556
- "Forester",
557
- "Impreza",
558
- "Liberty",
559
- "XV"
560
- ],
561
- "Suzuki": [
562
- "Jimny",
563
- "Swift"
564
- ],
565
- "Volkswagen": [
566
- "Amarok",
567
- "Golf",
568
- "Polo",
569
- "T-ROC",
570
- "Tiguan"
571
- ],
572
- "Volvo": [
573
- "XC40",
574
- "XC60"
575
- ]
576
- }
577
-
578
- # Extract makes list for dropdown
579
- CAR_MAKES = list(MAKE_MODEL_DATA.keys())
580
-
581
- # Define other dropdown options
582
- BODY_TYPES = ['Sedan', 'Hatchback', 'SUV', 'Wagon', 'Convertible', 'Coupe', 'Ute', 'Van']
583
- FUEL_TYPES = ['Petrol', 'Diesel', 'Hybrid', 'Electric', 'LPG']
584
- TRANSMISSION_TYPES = ['Automatic', 'Manual', 'CVT']
585
- DRIVE_TYPES = ['Front Wheel Drive', 'Rear Wheel Drive', 'All Wheel Drive', '4x4']
586
- SEGMENTS = ['Light', 'Small', 'Medium', 'Large', 'Upper Large', 'Luxury', 'Sports']
587
- CONDITIONS = ['New', 'Used', 'Demo']
588
-
589
- def update_models(make):
590
- """Update model choices based on selected make"""
591
- if make in MAKE_MODEL_DATA:
592
- models = MAKE_MODEL_DATA[make]
593
- return gr.Dropdown(choices=models, value=models[0] if models else None)
594
- else:
595
- return gr.Dropdown(choices=[], value=None)
596
-
597
- def predict_dealers_interface(make, model, year, body_type, fuel_type, transmission,
598
- odometer, doors, seats, engine_size, power, cylinders,
599
- safety_rating, drive_type, segment, condition, selected_model):
600
- """Interface function for Gradio"""
601
- return matcher.predict_dealers(make, model, year, body_type, fuel_type, transmission,
602
- odometer, doors, seats, engine_size, power, cylinders,
603
- safety_rating, drive_type, segment, condition, selected_model)
604
-
605
- def search_data_interface(make, model, year_min, year_max, body_type, fuel_type,
606
- max_odometer, max_price, selected_file, max_results, show_dealer_stats):
607
- """Interface function for traditional data search"""
608
- message, result_df = matcher.search_data_files(make, model, year_min, year_max,
609
- body_type, fuel_type, max_odometer,
610
- max_price, selected_file, max_results, show_dealer_stats)
611
-
612
- if result_df.empty:
613
- return message, "No results to display", ""
614
- else:
615
- if show_dealer_stats and 'Rank' in result_df.columns:
616
- # Dealer summary results
617
- total_dealers = len(result_df)
618
- total_cars = result_df['Matching Cars'].sum() if 'Matching Cars' in result_df.columns else 0
619
-
620
- info_text = f"**πŸ† Top {total_dealers} Dealers** | **πŸ“Š Total Matching Cars:** {total_cars:,}\n\n"
621
-
622
- if total_dealers > 0:
623
- top_dealer = result_df.iloc[0]
624
- info_text += f"**πŸ‘‘ Leading Dealer:** {top_dealer['Dealer Name']} with {top_dealer['Matching Cars']} cars\n\n"
625
- else:
626
- # Basic search results
627
- info_text = f"**πŸ“Š Search completed successfully**\n\n"
628
 
629
- # Convert DataFrame to HTML table for better display
630
- html_table = result_df.to_html(classes='table table-striped',
631
- table_id='dealer-summary', escape=False, index=False)
 
 
 
632
 
633
- return message, info_text, html_table
634
-
635
- # Create the modern Gradio interface
636
- with gr.Blocks(
637
- title="πŸš— Swiper Match - Car Dealer Predictor",
638
- theme=gr.themes.Default(),
639
- css="""
640
- .gradio-container {
641
- max-width: 1200px !important;
642
- margin: 0 auto !important;
643
- }
644
- """
645
- ) as demo:
646
-
647
- gr.Markdown("""
648
- # πŸš— Swiper Match - Car Dealer Predictor
649
- ### AI-Powered Dealer Matching Based on Vehicle Specifications
650
- Find the perfect dealer for your dream car using machine learning
651
- """)
652
-
653
- gr.Markdown("""
654
- **🎯 How it works:** This tool analyzes vehicle specifications to predict which dealers are most likely to have cars matching your preferences. The model focuses on technical specifications, not pricing.
655
- """)
656
-
657
- with gr.Tabs():
658
- # Simple Tab
659
- with gr.Tab("πŸ” Simple"):
660
- gr.Markdown("### Quick Car Dealer Search")
661
- gr.Markdown("Enter just the basic details for a quick recommendation")
662
-
663
- # Model Selection for Simple Tab
664
- simple_model_selection = gr.Dropdown(
665
- choices=simple_matcher.available_models if simple_matcher.model_loaded else ['Model not loaded'],
666
- label="πŸ€– Select AI Model",
667
- value=simple_matcher.available_models[0] if simple_matcher.available_models else 'Model not loaded',
668
- info="WeightedEnsemble_L3 provides the best overall performance"
669
- )
670
-
671
- with gr.Row():
672
- # Basic inputs
673
- with gr.Column(scale=1):
674
- simple_make = gr.Dropdown(choices=CAR_MAKES, label="Make", value="Toyota")
675
- simple_model = gr.Dropdown(choices=MAKE_MODEL_DATA["Toyota"], label="Model", value="Camry")
676
-
677
- with gr.Column(scale=1):
678
- simple_year = gr.Number(label="Year", value=2020, minimum=1990, maximum=2025)
679
- simple_odometer = gr.Number(label="Odometer (km)", value=50000, minimum=0)
680
-
681
- # Simple prediction button
682
- simple_predict_btn = gr.Button(
683
- "🎯 Find Best Dealers (Simple)",
684
- variant="primary",
685
- size="lg"
686
- )
687
-
688
- # Simple Results Section
689
- with gr.Row():
690
- with gr.Column(scale=1):
691
- simple_top_dealer = gr.Textbox(
692
- label="πŸ† Top Recommended Dealer",
693
- interactive=False,
694
- lines=2
695
- )
696
- simple_confidence = gr.Textbox(
697
- label="🎯 Confidence Score",
698
- interactive=False,
699
- lines=1
700
- )
701
- with gr.Column(scale=2):
702
- simple_detailed_results = gr.Markdown(
703
- label="πŸ“Š Results",
704
- value="Click 'Find Best Dealers (Simple)' to see recommendations..."
705
- )
706
 
707
- # Detailed Tab
708
- with gr.Tab("βš™οΈ Detailed"):
709
- gr.Markdown("### Advanced Car Dealer Search")
710
- gr.Markdown("Specify detailed vehicle characteristics for more precise recommendations")
711
-
712
- # Model Selection Section
713
- with gr.Row():
714
- model_selection = gr.Dropdown(
715
- choices=matcher.available_models if matcher.model_loaded else ['Model not loaded'],
716
- label="πŸ€– Select AI Model",
717
- value=matcher.available_models[0] if matcher.available_models else 'Model not loaded',
718
- info="WeightedEnsemble_L3 provides the best overall performance",
719
- scale=2
720
- )
721
-
722
- with gr.Column(scale=1):
723
- gr.Markdown("""
724
- **Available Models:**
725
- - **WeightedEnsemble**: Best performance (combines all models)
726
- - **RandomForest**: Tree-based, interpretable
727
- - **XGBoost**: Gradient boosting, fast
728
- - **NeuralNet**: Deep learning, complex patterns
729
- """)
730
-
731
- with gr.Row():
732
- # Basic Vehicle Info
733
- with gr.Column(scale=1):
734
- gr.Markdown("### πŸš— Basic Vehicle Information")
735
-
736
- make = gr.Dropdown(choices=CAR_MAKES, label="Make", value="Toyota")
737
- model = gr.Dropdown(choices=MAKE_MODEL_DATA["Toyota"], label="Model", value="Camry")
738
- year = gr.Number(label="Year", value=2020, minimum=1990, maximum=2025)
739
- condition = gr.Dropdown(choices=CONDITIONS, label="Condition", value="Used")
740
-
741
- # Body & Style
742
- with gr.Column(scale=1):
743
- gr.Markdown("### πŸ—οΈ Body & Style")
744
-
745
- body_type = gr.Dropdown(choices=BODY_TYPES, label="Body Type", value="Sedan")
746
- segment = gr.Dropdown(choices=SEGMENTS, label="Vehicle Segment", value="Medium")
747
- doors = gr.Number(label="Doors", value=4, minimum=2, maximum=6)
748
- seats = gr.Number(label="Seats", value=5, minimum=2, maximum=9)
749
-
750
- # Engine & Performance
751
- with gr.Column(scale=1):
752
- gr.Markdown("### ⚑ Engine & Performance")
753
-
754
- fuel_type = gr.Dropdown(choices=FUEL_TYPES, label="Fuel Type", value="Petrol")
755
- transmission = gr.Dropdown(choices=TRANSMISSION_TYPES, label="Transmission", value="Automatic")
756
- engine_size = gr.Number(label="Engine Size (L)", value=2.0, minimum=0.5, maximum=8.0)
757
- cylinders = gr.Number(label="Cylinders", value=4, minimum=2, maximum=12)
758
-
759
- with gr.Row():
760
- # Technical Details
761
- with gr.Column(scale=1):
762
- gr.Markdown("### πŸ”§ Technical Details")
763
-
764
- power = gr.Number(label="Power (HP)", value=150, minimum=50, maximum=1000)
765
- drive_type = gr.Dropdown(choices=DRIVE_TYPES, label="Drive Type", value="Front Wheel Drive")
766
- safety_rating = gr.Number(label="Safety Rating (1-5)", value=5, minimum=1, maximum=5)
767
-
768
- # Usage & History
769
- with gr.Column(scale=1):
770
- gr.Markdown("### πŸ“Š Usage & History")
771
-
772
- odometer = gr.Number(label="Odometer (km)", value=50000, minimum=0)
773
-
774
- # Detailed prediction button
775
- predict_btn = gr.Button(
776
- "🎯 Find Best Dealers (Detailed)",
777
- variant="primary",
778
- size="lg"
779
- )
780
-
781
- # Detailed Results Section
782
- with gr.Row():
783
- with gr.Column(scale=1):
784
- top_dealer = gr.Textbox(
785
- label="πŸ† Top Recommended Dealer",
786
- interactive=False,
787
- lines=2
788
- )
789
- confidence = gr.Textbox(
790
- label="🎯 Confidence Score",
791
- interactive=False,
792
- lines=1
793
- )
794
- with gr.Column(scale=2):
795
- detailed_results = gr.Markdown(
796
- label="πŸ“Š Detailed Results",
797
- value="Click 'Find Best Dealers (Detailed)' to see AI recommendations..."
798
- )
799
 
800
- # Traditional Search Tab
801
- with gr.Tab("πŸ“Š Traditional Search"):
802
- gr.Markdown("### CSV Data File Search")
803
- gr.Markdown("Search through car listing CSV files and rank dealers by inventory size")
804
-
805
- with gr.Row():
806
- with gr.Column(scale=1):
807
- gr.Markdown("### πŸ—‚οΈ File Selection")
808
- selected_file = gr.Dropdown(
809
- choices=matcher.data_files,
810
- label="Select CSV File",
811
- value=matcher.data_files[0] if matcher.data_files else None,
812
- info=f"Available files: {len(matcher.data_files)}"
813
- )
814
-
815
- max_results = gr.Number(
816
- label="Max Results",
817
- value=100,
818
- minimum=1,
819
- maximum=1000,
820
- info="Limit number of results returned"
821
- )
822
-
823
- show_dealer_stats = gr.Checkbox(
824
- label="πŸ“Š Show Dealer Rankings",
825
- value=True,
826
- info="Rank dealers by number of matching cars"
827
- )
828
-
829
- with gr.Column(scale=2):
830
- gr.Markdown("### πŸ” Search Filters")
831
-
832
- with gr.Row():
833
- search_make = gr.Textbox(
834
- label="Make (contains)",
835
- placeholder="e.g., Toyota, Ford",
836
- info="Search for car manufacturer"
837
- )
838
- search_model = gr.Textbox(
839
- label="Model (contains)",
840
- placeholder="e.g., Camry, Focus",
841
- info="Search for car model"
842
- )
843
-
844
- with gr.Row():
845
- year_min = gr.Number(
846
- label="Year (Min)",
847
- value=2015,
848
- minimum=1980,
849
- maximum=2025,
850
- info="Minimum manufacturing year"
851
- )
852
- year_max = gr.Number(
853
- label="Year (Max)",
854
- value=2024,
855
- minimum=1980,
856
- maximum=2025,
857
- info="Maximum manufacturing year"
858
- )
859
-
860
- with gr.Row():
861
- search_body_type = gr.Textbox(
862
- label="Body Type (contains)",
863
- placeholder="e.g., sedan, suv, hatch",
864
- info="Vehicle body style"
865
- )
866
- search_fuel_type = gr.Textbox(
867
- label="Fuel Type (contains)",
868
- placeholder="e.g., petrol, diesel, electric",
869
- info="Fuel/energy type"
870
- )
871
-
872
- with gr.Row():
873
- max_odometer = gr.Number(
874
- label="Max Odometer (km)",
875
- minimum=0,
876
- info="Maximum mileage"
877
- )
878
- max_price = gr.Number(
879
- label="Max Price (AUD)",
880
- minimum=0,
881
- info="Maximum advertised price"
882
- )
883
-
884
- # Traditional search button
885
- search_btn = gr.Button(
886
- "πŸ” Search CSV Data",
887
- variant="primary",
888
- size="lg"
889
- )
890
-
891
- # Traditional Search Results Section
892
- with gr.Row():
893
- search_status = gr.Textbox(
894
- label="πŸ” Search Status",
895
- interactive=False,
896
- lines=2
897
- )
898
-
899
- with gr.Row():
900
- search_info = gr.Markdown(
901
- label="πŸ“Š Results Info",
902
- value="Click 'Search CSV Data' to start searching..."
903
- )
904
-
905
- with gr.Row():
906
- search_results_table = gr.HTML(
907
- label="πŸ“‹ Search Results",
908
- value="<p>No search performed yet</p>"
909
- )
910
-
911
- # Data file info section
912
- gr.Markdown(f"""
913
- ---
914
- ### πŸ“ Available Data Files
915
-
916
- **Files Found:** {len(matcher.data_files)}
917
-
918
- **File List:**
919
- {chr(10).join([f'β€’ {file}' for file in matcher.data_files]) if matcher.data_files else 'β€’ No CSV files found in ./data directory'}
920
-
921
- ### πŸ” Search Features
922
-
923
- **πŸ“Š Dealer Ranking:** Dealers ranked by number of cars matching your criteria
924
- **πŸ† Inventory Priority:** Dealers with more matching inventory appear first
925
- **πŸ“ˆ Column Mapping:** Uses actual dataset columns:
926
- - **Make/Model:** `make`, `model`
927
- - **Year:** `manu_year` (manufacturing year)
928
- - **Body Type:** `vehicle_body_type`
929
- - **Fuel Type:** `vehicle_fuel_type`
930
- - **Transmission:** `vehicle_transmission_type`
931
- - **Mileage:** `odometer`
932
- - **Price:** `advertised_price`
933
- - **Dealer:** `dealer_trading_name`
934
- - **Location:** `dealer_city`, `dealer_state`
935
-
936
- ### πŸ“Š Dealer Ranking System
937
-
938
- **How it works:** Counts how many cars each dealer has that match your search criteria
939
- **Ranking Logic:** Dealers with more matching cars get better ranks (Rank 1 = most inventory)
940
- **Sorting:** Results sorted by dealer inventory count first, then by price
941
- **Performance:** Fast counting using pandas group operations
942
- """)
943
-
944
- # Model Information Footer
945
- detailed_status_emoji = "βœ…" if matcher.model_loaded else "❌"
946
- simple_status_emoji = "βœ…" if simple_matcher.model_loaded else "❌"
947
-
948
- gr.Markdown(f"""
949
- ---
950
- ### πŸ“Š Model Information
951
-
952
- #### πŸ”§ **Detailed Model** (Advanced Search)
953
- **Status:** {detailed_status_emoji} {"Model Loaded Successfully" if matcher.model_loaded else "Model Not Available"}
954
- **Path:** ./autogluon_model
955
- **Trained Dealers:** {len(matcher.trained_dealers) if matcher.model_loaded else "N/A"}
956
- **Available Models:** {len(matcher.available_models) if matcher.model_loaded else "N/A"} ({', '.join(matcher.available_models[:3]) + ('...' if len(matcher.available_models) > 3 else '') if matcher.model_loaded and matcher.available_models else "N/A"})
957
- **Features:** 23 vehicle specifications (no pricing data)
958
-
959
- #### πŸ” **Simple Model** (Quick Search)
960
- **Status:** {simple_status_emoji} {"Model Loaded Successfully" if simple_matcher.model_loaded else "Model Not Available"}
961
- **Path:** ./simple_autogluon_models
962
- **Trained Dealers:** {len(simple_matcher.trained_dealers) if simple_matcher.model_loaded else "N/A"}
963
- **Available Models:** {len(simple_matcher.available_models) if simple_matcher.model_loaded else "N/A"} ({', '.join(simple_matcher.available_models[:3]) + ('...' if len(simple_matcher.available_models) > 3 else '') if simple_matcher.model_loaded and simple_matcher.available_models else "N/A"})
964
- **Features:** Subset of vehicle specifications for faster inference
965
-
966
- #### πŸ€– **Architecture**
967
- **Framework:** AutoGluon TabularPredictor
968
- **Ensemble Learning:** Multiple algorithms combined via weighted voting
969
- **Algorithms:** RandomForest, XGBoost, NeuralNetTorch, CatBoost, LightGBM
970
- **Task:** Multi-class classification for dealer prediction
971
- """)
972
-
973
- # Set up the prediction functions
974
-
975
- # Simple prediction function (with default values for missing inputs)
976
- def simple_predict_dealers_interface(simple_make, simple_model, simple_year, simple_odometer, simple_model_selection):
977
- """Simple interface function for Gradio with conditional parameters"""
978
 
979
- return simple_matcher.predict_dealers(
980
- make=simple_make,
981
- model=simple_model,
982
- year=simple_year,
983
- body_type=None,
984
- fuel_type=None,
985
- transmission=None,
986
- odometer=simple_odometer,
987
- doors=None,
988
- seats=None,
989
- engine_size=None,
990
- power=None,
991
- cylinders=None,
992
- safety_rating=None,
993
- drive_type=None,
994
- segment=None,
995
- condition=None,
996
- selected_model=simple_model_selection
997
- )
998
-
999
- # Simple tab click event"
1000
- simple_predict_btn.click(
1001
- fn=simple_predict_dealers_interface,
1002
- inputs=[simple_make, simple_model, simple_year, simple_odometer, simple_model_selection],
1003
- outputs=[simple_top_dealer, simple_confidence, simple_detailed_results]
1004
- )
1005
 
1006
- # Detailed tab click event
1007
- predict_btn.click(
1008
- fn=predict_dealers_interface,
1009
- inputs=[make, model, year, body_type, fuel_type, transmission,
1010
- odometer, doors, seats, engine_size, power, cylinders,
1011
- safety_rating, drive_type, segment, condition, model_selection],
1012
- outputs=[top_dealer, confidence, detailed_results]
1013
- )
1014
-
1015
- # Traditional search click event
1016
- search_btn.click(
1017
- fn=search_data_interface,
1018
- inputs=[search_make, search_model, year_min, year_max, search_body_type,
1019
- search_fuel_type, max_odometer, max_price, selected_file, max_results, show_dealer_stats],
1020
- outputs=[search_status, search_info, search_results_table]
1021
- )
1022
-
1023
- # Set up dynamic model updating based on make selection
1024
- simple_make.change(
1025
- fn=update_models,
1026
- inputs=simple_make,
1027
- outputs=simple_model
1028
- )
1029
-
1030
- make.change(
1031
- fn=update_models,
1032
- inputs=make,
1033
- outputs=model
1034
- )
1035
 
1036
  # Launch the app
1037
  if __name__ == "__main__":
1038
- demo.launch()
 
 
 
1
+ """
2
+ Swiper Match - Car Dealer Predictor
3
+ Main application file using modular components
4
+ """
5
+
6
  import gradio as gr
 
 
 
7
  import logging
 
 
8
 
9
+ # Import core modules
10
+ from core.matcher import CarDealerMatcher
11
+ from core.config import DETAILED_MODEL_PATH, SIMPLE_MODEL_PATH, UI_CONFIG, GRADIO_CSS
12
+
13
+ # Import UI components
14
+ from ui.interface import (
15
+ update_models, predict_dealers_interface, simple_predict_dealers_interface,
16
+ search_data_interface, simple_search_data_interface
17
+ )
18
+ from ui.tabs.simple_tab import create_simple_tab
19
+ from ui.tabs.detailed_tab import create_detailed_tab
20
+ from ui.tabs.traditional_tab import create_traditional_tab
21
+ from ui.tabs.simple_search_tab import create_simple_search_tab
22
 
23
+ # Import utilities
24
+ from utils.helpers import get_model_status_info, get_app_header, get_app_description, setup_event_handlers
25
 
26
  # Set up logging
27
  logging.basicConfig(level=logging.INFO)
28
  logger = logging.getLogger(__name__)
29
 
30
+
31
+ def create_app():
32
+ """Create and configure the Gradio application"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
+ # Initialize the matchers
35
+ logger.info("πŸš€ Initializing Car Dealer Matchers...")
36
+ matcher = CarDealerMatcher(model_path=DETAILED_MODEL_PATH)
37
+ simple_matcher = CarDealerMatcher(model_path=SIMPLE_MODEL_PATH)
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ # Create the Gradio interface
40
+ with gr.Blocks(
41
+ title=UI_CONFIG['title'],
42
+ theme=gr.themes.Default(),
43
+ css=GRADIO_CSS
44
+ ) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ # App header
47
+ gr.Markdown(get_app_header())
48
+ gr.Markdown(get_app_description())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ # Create tabs
51
+ with gr.Tabs():
52
+ simple_tab = create_simple_tab(simple_matcher)
53
+ detailed_tab = create_detailed_tab(matcher)
54
+ traditional_tab = create_traditional_tab(matcher)
55
+ simple_search_tab = create_simple_search_tab(matcher)
56
 
57
+ # Model Information Footer
58
+ gr.Markdown("---")
59
+ gr.Markdown(get_model_status_info(matcher, simple_matcher))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ # Setup event handlers
62
+ interface_functions = {
63
+ 'update_models': update_models,
64
+ 'predict': predict_dealers_interface,
65
+ 'simple_predict': simple_predict_dealers_interface,
66
+ 'search': search_data_interface,
67
+ 'simple_search': simple_search_data_interface
68
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ tabs_data = (simple_tab, detailed_tab, traditional_tab, simple_search_tab)
71
+ matchers = (matcher, simple_matcher)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ setup_event_handlers(tabs_data, interface_functions, matchers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ return demo
76
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  # Launch the app
79
  if __name__ == "__main__":
80
+ logger.info("🎯 Starting Swiper Match application...")
81
+ demo = create_app()
82
+ demo.launch()
app_new.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Swiper Match - Car Dealer Predictor
3
+ Main application file using modular components
4
+ """
5
+
6
+ import gradio as gr
7
+ import logging
8
+
9
+ # Import core modules
10
+ from core.matcher import CarDealerMatcher
11
+ from core.config import DETAILED_MODEL_PATH, SIMPLE_MODEL_PATH, UI_CONFIG, GRADIO_CSS
12
+
13
+ # Import UI components
14
+ from ui.interface import (
15
+ update_models, predict_dealers_interface, simple_predict_dealers_interface,
16
+ search_data_interface, simple_search_data_interface
17
+ )
18
+ from ui.tabs.simple_tab import create_simple_tab
19
+ from ui.tabs.detailed_tab import create_detailed_tab
20
+ from ui.tabs.traditional_tab import create_traditional_tab
21
+ from ui.tabs.simple_search_tab import create_simple_search_tab
22
+
23
+ # Import utilities
24
+ from utils.helpers import get_model_status_info, get_app_header, get_app_description, setup_event_handlers
25
+
26
+ # Set up logging
27
+ logging.basicConfig(level=logging.INFO)
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def create_app():
32
+ """Create and configure the Gradio application"""
33
+
34
+ # Initialize the matchers
35
+ logger.info("πŸš€ Initializing Car Dealer Matchers...")
36
+ matcher = CarDealerMatcher(model_path=DETAILED_MODEL_PATH)
37
+ simple_matcher = CarDealerMatcher(model_path=SIMPLE_MODEL_PATH)
38
+
39
+ # Create the Gradio interface
40
+ with gr.Blocks(
41
+ title=UI_CONFIG['title'],
42
+ theme=gr.themes.Default(),
43
+ css=GRADIO_CSS
44
+ ) as demo:
45
+
46
+ # App header
47
+ gr.Markdown(get_app_header())
48
+ gr.Markdown(get_app_description())
49
+
50
+ # Create tabs
51
+ with gr.Tabs():
52
+ simple_tab = create_simple_tab(simple_matcher)
53
+ detailed_tab = create_detailed_tab(matcher)
54
+ traditional_tab = create_traditional_tab(matcher)
55
+ simple_search_tab = create_simple_search_tab(matcher)
56
+
57
+ # Model Information Footer
58
+ gr.Markdown("---")
59
+ gr.Markdown(get_model_status_info(matcher, simple_matcher))
60
+
61
+ # Setup event handlers
62
+ interface_functions = {
63
+ 'update_models': update_models,
64
+ 'predict': predict_dealers_interface,
65
+ 'simple_predict': simple_predict_dealers_interface,
66
+ 'search': search_data_interface,
67
+ 'simple_search': simple_search_data_interface
68
+ }
69
+
70
+ tabs_data = (simple_tab, detailed_tab, traditional_tab, simple_search_tab)
71
+ matchers = (matcher, simple_matcher)
72
+
73
+ setup_event_handlers(tabs_data, interface_functions, matchers)
74
+
75
+ return demo
76
+
77
+
78
+ # Launch the app
79
+ if __name__ == "__main__":
80
+ logger.info("🎯 Starting Swiper Match application...")
81
+ demo = create_app()
82
+ demo.launch()
app_original.py ADDED
@@ -0,0 +1,1250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import os
5
+ import logging
6
+ from huggingface_hub import snapshot_download
7
+ import glob
8
+
9
+ # Hugging Face configuration
10
+ #print(os.getenv('HF_TOKEN'))
11
+ HF_REPO_ID = "mzx/Swiper-Match" # Replace with your actual repo ID
12
+ HF_TOKEN = os.getenv('HF_TOKEN') # Will be set when provided by user
13
+
14
+ # AutoGluon imports
15
+ from autogluon.tabular import TabularPredictor
16
+
17
+ # Set up logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class CarDealerMatcher:
22
+ def __init__(self, model_path: str = "./autogluon_model"):
23
+ self.model_path = model_path
24
+ self.predictor = None
25
+ self.trained_dealers = []
26
+ self.available_models = []
27
+ self.model_loaded = False
28
+ self.data_files = []
29
+ self.load_model()
30
+ self.load_data_files()
31
+
32
+ def load_model(self):
33
+ """Load AutoGluon model from local directory or download from Hugging Face"""
34
+ try:
35
+ logger.info(f"πŸ€– Loading AutoGluon model from: {self.model_path}")
36
+
37
+ # Check if model exists locally
38
+ if not os.path.exists(self.model_path):
39
+ logger.info(f"πŸ“₯ Model not found locally. Downloading from Hugging Face: {HF_REPO_ID}")
40
+ try:
41
+ # Download the model from Hugging Face
42
+ downloaded_path = snapshot_download(
43
+ repo_id=HF_REPO_ID,
44
+ cache_dir="./hf_cache",
45
+ token=HF_TOKEN,
46
+ #allow_patterns=["autogluon_model/**"],
47
+ local_dir="./",
48
+ local_dir_use_symlinks=False
49
+ )
50
+ logger.info(f"βœ… Model downloaded successfully to: {downloaded_path}")
51
+ except Exception as download_error:
52
+ logger.error(f"❌ Failed to download model from Hugging Face: {download_error}")
53
+ self.model_loaded = False
54
+ return
55
+
56
+ # Load the model
57
+ if os.path.exists(self.model_path):
58
+ self.predictor = TabularPredictor.load(self.model_path)
59
+ self._extract_trained_dealers()
60
+ self._extract_available_models()
61
+ self.model_loaded = True
62
+ logger.info(f"βœ… Model loaded successfully! Can predict for {len(self.trained_dealers)} dealers")
63
+ logger.info(f"🎯 Available models: {self.available_models}")
64
+ else:
65
+ logger.error(f"❌ Model directory still not found after download attempt: {self.model_path}")
66
+ self.model_loaded = False
67
+
68
+ except Exception as e:
69
+ logger.error(f"❌ Failed to load model: {e}")
70
+ self.model_loaded = False
71
+
72
+ def _extract_trained_dealers(self):
73
+ """Extract trained dealers from the predictor"""
74
+ try:
75
+ if hasattr(self.predictor, 'class_labels'):
76
+ self.trained_dealers = list(self.predictor.class_labels)
77
+ else:
78
+ # Use a dummy prediction to extract dealer names
79
+ dummy_data = self._create_dummy_data()
80
+ proba_result = self.predictor.predict_proba(dummy_data)
81
+ if hasattr(proba_result, 'columns'):
82
+ self.trained_dealers = list(proba_result.columns)
83
+ else:
84
+ self.trained_dealers = ['Model loaded successfully']
85
+ except Exception as e:
86
+ self.trained_dealers = ['Model loaded successfully']
87
+ logger.warning(f"Could not extract dealer list: {e}")
88
+
89
+ def _extract_available_models(self):
90
+ """Extract available models from the predictor"""
91
+ try:
92
+ if hasattr(self.predictor, 'model_names'):
93
+ raw_models = self.predictor.model_names()
94
+ print("my models", raw_models)
95
+ # Add ensemble options with user-friendly names
96
+ self.available_models = ['WeightedEnsemble_L3 (Best)', 'WeightedEnsemble_L2'] + [
97
+ name for name in raw_models if 'WeightedEnsemble' not in name
98
+ ]
99
+
100
+ except Exception as e:
101
+ self.available_models = ['WeightedEnsemble_L3 (Best)']
102
+ logger.warning(f"Could not extract model list: {e}")
103
+
104
+ def _create_dummy_data(self):
105
+ """Create dummy data with all required features"""
106
+ return pd.DataFrame([{
107
+ 'make': 'toyota',
108
+ 'model': 'camry',
109
+ 'year': 2020,
110
+ 'car_age': 4, # Add the missing car_age feature (2024 - 2020 = 4)
111
+ 'vehicle_body_type': 'sedan',
112
+ 'vehicle_fuel_type': 'petrol',
113
+ 'vehicle_transmission_type': 'automatic',
114
+ 'odometer': 50000,
115
+ 'vehicle_doors': 4,
116
+ 'vehicle_seats': 5,
117
+ 'series': 'unknown',
118
+ 'variant': 'unknown',
119
+ 'vehicle_body_type_group': 'Passenger',
120
+ 'vehicle_body_type_style': '4 Door',
121
+ 'vehicle_cylinder_description': '4 Cylinder',
122
+ 'vehicle_cylinders': 4.0,
123
+ 'vehicle_drive_type': 'Front Wheel Drive',
124
+ 'vehicle_engine_size': 2.0,
125
+ 'vehicle_power': 150.0,
126
+ 'vehicle_safety_rating': 5,
127
+ 'vehicle_segment': 'Medium',
128
+ 'condition': 'Used',
129
+ 'vehicle_type': 1
130
+ }])
131
+
132
+ def predict_dealers(self, make=None, model=None, year=None, body_type=None, fuel_type=None, transmission=None,
133
+ odometer=None, doors=None, seats=None, engine_size=None, power=None, cylinders=None,
134
+ safety_rating=None, drive_type=None, segment=None, condition=None, selected_model=None):
135
+ """Predict top dealers for the given car specifications using selected model"""
136
+
137
+ if not self.model_loaded:
138
+ return "❌ AutoGluon model not loaded. Please check model directory availability.", "", ""
139
+
140
+ try:
141
+ # Calculate car age (current year - vehicle year)
142
+ current_year = 2024 # You can use datetime.now().year for dynamic year
143
+ car_age = current_year - int(year) if year else None
144
+
145
+ # Create input dataframe with only non-None values for AutoGluon model
146
+ car_data_dict = {}
147
+
148
+ # Add parameters only if they are not None
149
+ if make is not None:
150
+ car_data_dict['make'] = make.lower()
151
+ if model is not None:
152
+ car_data_dict['model'] = model.lower()
153
+ if year is not None:
154
+ car_data_dict['year'] = int(year)
155
+ if car_age is not None:
156
+ car_data_dict['car_age'] = car_age
157
+ if body_type is not None:
158
+ car_data_dict['vehicle_body_type'] = body_type.lower()
159
+ if fuel_type is not None:
160
+ car_data_dict['vehicle_fuel_type'] = fuel_type.lower()
161
+ if transmission is not None:
162
+ car_data_dict['vehicle_transmission_type'] = transmission.lower()
163
+ if odometer is not None:
164
+ car_data_dict['odometer'] = int(odometer)
165
+ if doors is not None:
166
+ car_data_dict['vehicle_doors'] = float(doors)
167
+ if seats is not None:
168
+ car_data_dict['vehicle_seats'] = float(seats)
169
+ if engine_size is not None:
170
+ car_data_dict['vehicle_engine_size'] = float(engine_size)
171
+ if power is not None:
172
+ car_data_dict['vehicle_power'] = float(power)
173
+ if cylinders is not None:
174
+ car_data_dict['vehicle_cylinders'] = float(cylinders)
175
+ if safety_rating is not None:
176
+ car_data_dict['vehicle_safety_rating'] = float(safety_rating)
177
+ if drive_type is not None:
178
+ car_data_dict['vehicle_drive_type'] = drive_type
179
+ if segment is not None:
180
+ car_data_dict['vehicle_segment'] = segment
181
+ if condition is not None:
182
+ car_data_dict['condition'] = condition
183
+
184
+ # Auto-generated features based on inputs (only if base inputs exist)
185
+ if body_type is not None:
186
+ car_data_dict['vehicle_body_type_group'] = self._map_body_type_group(body_type)
187
+ if doors is not None:
188
+ car_data_dict['vehicle_body_type_style'] = self._map_body_style(doors)
189
+ if cylinders is not None:
190
+ car_data_dict['vehicle_cylinder_description'] = f'{int(cylinders)} Cylinder'
191
+
192
+ # Always include these if not specified (required for model compatibility)
193
+ if 'series' not in car_data_dict:
194
+ car_data_dict['series'] = 'unknown'
195
+ if 'variant' not in car_data_dict:
196
+ car_data_dict['variant'] = 'unknown'
197
+ if 'vehicle_type' not in car_data_dict:
198
+ car_data_dict['vehicle_type'] = 1 # Passenger vehicle
199
+
200
+ car_data = pd.DataFrame([car_data_dict])
201
+
202
+ # Get predictions using AutoGluon predictor with selected model
203
+ if selected_model and selected_model != 'WeightedEnsemble_L3 (Best)':
204
+ # Clean model name
205
+ clean_model = selected_model.replace(' (Best)', '')
206
+ try:
207
+ proba_result = self.predictor.predict_proba(car_data, model=clean_model)
208
+ model_used = selected_model
209
+ except Exception as model_error:
210
+ logger.warning(f"Failed to use specific model {clean_model}: {model_error}")
211
+ proba_result = self.predictor.predict_proba(car_data)
212
+ model_used = "WeightedEnsemble_L3 (fallback)"
213
+ else:
214
+ proba_result = self.predictor.predict_proba(car_data)
215
+ model_used = "WeightedEnsemble_L3 (Best)"
216
+
217
+ # Convert to dict format and get top-k predictions
218
+ if hasattr(proba_result, 'iloc'):
219
+ proba_dict = proba_result.iloc[0].to_dict()
220
+ else:
221
+ proba_dict = dict(zip(self.trained_dealers, proba_result[0]))
222
+
223
+ # Sort by probability and get top 5
224
+ sorted_dealers = sorted(proba_dict.items(), key=lambda x: x[1], reverse=True)[:5]
225
+
226
+ # Format results
227
+ top_dealer = sorted_dealers[0][0]
228
+ confidence = f"{sorted_dealers[0][1]:.2%}"
229
+
230
+ # Create detailed results
231
+ results_text = "πŸ† **Top 5 Recommended Dealers:**\n\n"
232
+ for i, (dealer, prob) in enumerate(sorted_dealers, 1):
233
+ emoji = "πŸ₯‡" if i == 1 else "πŸ₯ˆ" if i == 2 else "πŸ₯‰" if i == 3 else "πŸ”Έ"
234
+ results_text += f"{emoji} **{i}. {dealer}** - {prob:.1%} confidence\n"
235
+
236
+ # Add car summary
237
+ car_summary = f"""
238
+
239
+ **πŸš— Vehicle Specifications:**
240
+ β€’ **Make & Model:** {make} {model} ({year})
241
+ β€’ **Body Type:** {body_type} β€’ **Segment:** {segment}
242
+ β€’ **Engine:** {engine_size}L, {cylinders} cylinders, {power}HP
243
+ β€’ **Drivetrain:** {fuel_type} β€’ {transmission} β€’ {drive_type}
244
+ β€’ **Details:** {doors} doors, {seats} seats β€’ {odometer:,} km
245
+ β€’ **Condition:** {condition} β€’ **Safety:** {safety_rating}β˜…
246
+
247
+ **πŸ€– Model:** {model_used}
248
+ """
249
+
250
+ return f"🎯 **Best Match: {top_dealer}**", confidence, results_text + car_summary
251
+
252
+ except Exception as e:
253
+ logger.error(f"Prediction error: {e}")
254
+ return f"❌ Error making prediction: {str(e)}", "", ""
255
+
256
+ def _map_body_type_group(self, body_type):
257
+ """Map body type to group"""
258
+ if not body_type:
259
+ return 'Passenger'
260
+ body_lower = body_type.lower()
261
+ if body_lower in ['ute', 'truck', 'van']:
262
+ return 'Commercial'
263
+ return 'Passenger'
264
+
265
+ def _map_body_style(self, doors):
266
+ """Map doors to body style"""
267
+ if not doors:
268
+ return '4 Door'
269
+ doors = int(doors)
270
+ if doors == 2:
271
+ return '2 Door'
272
+ elif doors == 3:
273
+ return '3 Door'
274
+ elif doors == 5:
275
+ return '5 Door'
276
+ else:
277
+ return '4 Door'
278
+
279
+ def load_data_files(self):
280
+ """Load available CSV data files"""
281
+ try:
282
+ data_dir = "./data"
283
+ if os.path.exists(data_dir):
284
+ csv_files = glob.glob(os.path.join(data_dir, "*.csv"))
285
+ self.data_files = [os.path.basename(f) for f in csv_files]
286
+ logger.info(f"βœ… Found {len(self.data_files)} CSV files: {self.data_files}")
287
+ else:
288
+ self.data_files = []
289
+ logger.warning("❌ Data directory not found")
290
+ except Exception as e:
291
+ logger.error(f"❌ Failed to load data files: {e}")
292
+ self.data_files = []
293
+
294
+ def search_data_files(self, make=None, model=None, year_min=None, year_max=None,
295
+ body_type=None, fuel_type=None, max_odometer=None,
296
+ max_price=None, selected_file=None, max_results=100, show_dealer_stats=True):
297
+ """Search through CSV data files using pandas filtering"""
298
+ try:
299
+ if not self.data_files:
300
+ return "❌ No CSV data files available", pd.DataFrame()
301
+
302
+ # Use selected file or default to first available
303
+ if selected_file and selected_file in self.data_files:
304
+ file_to_search = selected_file
305
+ else:
306
+ file_to_search = self.data_files[0] if self.data_files else None
307
+
308
+ if not file_to_search:
309
+ return "❌ No valid file selected", pd.DataFrame()
310
+
311
+ file_path = os.path.join("./data", file_to_search)
312
+
313
+ # Load the CSV file
314
+ logger.info(f"πŸ” Loading data from: {file_to_search}")
315
+
316
+ # Read CSV with error handling for different encodings
317
+ try:
318
+ df = pd.read_csv(file_path, encoding='utf-8')
319
+ except UnicodeDecodeError:
320
+ try:
321
+ df = pd.read_csv(file_path, encoding='latin-1')
322
+ except:
323
+ df = pd.read_csv(file_path, encoding='cp1252')
324
+
325
+ # Convert column names to lowercase for easier matching
326
+ df.columns = df.columns.str.lower().str.strip()
327
+
328
+ original_count = len(df)
329
+
330
+ # Apply filters using correct column names from the dataset
331
+ if make and 'make' in df.columns:
332
+ df = df[df['make'].str.contains(make, case=False, na=False)]
333
+
334
+ if model and 'model' in df.columns:
335
+ df = df[df['model'].str.contains(model, case=False, na=False)]
336
+
337
+ if year_min and 'manu_year' in df.columns:
338
+ df = df[pd.to_numeric(df['manu_year'], errors='coerce') >= year_min]
339
+
340
+ if year_max and 'manu_year' in df.columns:
341
+ df = df[pd.to_numeric(df['manu_year'], errors='coerce') <= year_max]
342
+
343
+ if body_type and 'vehicle_body_type' in df.columns:
344
+ df = df[df['vehicle_body_type'].str.contains(body_type, case=False, na=False)]
345
+
346
+ if fuel_type and 'vehicle_fuel_type' in df.columns:
347
+ df = df[df['vehicle_fuel_type'].str.contains(fuel_type, case=False, na=False)]
348
+
349
+ if max_odometer and 'odometer' in df.columns:
350
+ df = df[pd.to_numeric(df['odometer'], errors='coerce') <= max_odometer]
351
+
352
+ if max_price and 'advertised_price' in df.columns:
353
+ df = df[pd.to_numeric(df['advertised_price'], errors='coerce') <= max_price]
354
+
355
+ filtered_count = len(df)
356
+
357
+ if df.empty:
358
+ return f"βœ… Searched {file_to_search} ({original_count:,} records) - No matches found", pd.DataFrame()
359
+
360
+ # Create dealer ranking summary
361
+ if show_dealer_stats and 'dealer_trading_name' in df.columns:
362
+ dealer_summary = self._create_dealer_summary(df)
363
+ return f"βœ… Found {filtered_count:,} matches from {original_count:,} records in {file_to_search}", dealer_summary
364
+ else:
365
+ # Return basic summary without dealer rankings
366
+ summary_df = pd.DataFrame({
367
+ 'Total Matches': [filtered_count],
368
+ 'File Searched': [file_to_search]
369
+ })
370
+ return f"βœ… Found {filtered_count:,} matches from {original_count:,} records in {file_to_search}", summary_df
371
+
372
+ except Exception as e:
373
+ logger.error(f"❌ Search error: {e}")
374
+ return f"❌ Error searching data: {str(e)}", pd.DataFrame()
375
+
376
+ def _create_dealer_summary(self, df):
377
+ """Create dealer ranking summary showing top dealers by car count with expandable car lists"""
378
+ try:
379
+ logger.info(f"πŸ“Š Creating dealer summary from {len(df)} matching cars...")
380
+
381
+ # Count cars per dealer
382
+ dealer_counts = df['dealer_trading_name'].value_counts()
383
+
384
+ # Get top 10 dealers (increased from 5 to show more)
385
+ top_dealers = dealer_counts.head(10)
386
+
387
+ # Create HTML with expandable sections for each dealer
388
+ html_content = ""
389
+
390
+ # Add dealer sections
391
+ for rank, (dealer_name, car_count) in enumerate(top_dealers.items(), 1):
392
+ # Get cars for this dealer
393
+ dealer_cars = df[df['dealer_trading_name'] == dealer_name]
394
+
395
+ # Create rank emoji
396
+ rank_emoji = "πŸ₯‡" if rank == 1 else "πŸ₯ˆ" if rank == 2 else "πŸ₯‰" if rank == 3 else f"#{rank}"
397
+
398
+ html_content += f"""
399
+ <details style="margin-bottom: 10px; border: 1px solid #ddd; padding: 5px; border-radius: 5px;">
400
+ <summary style="font-weight: bold; cursor: pointer; padding: 5px;">
401
+ {rank_emoji} {dealer_name} ({car_count} cars)
402
+ </summary>
403
+ <div style="margin-top: 10px; max-height: 300px; overflow-y: auto;">
404
+ """
405
+
406
+ # Add individual cars
407
+ for idx, (_, car) in enumerate(dealer_cars.head(20).iterrows()): # Limit to 20 cars per dealer
408
+ # Extract car details safely
409
+ make = car.get('make', 'Unknown')
410
+ model = car.get('model', 'Unknown')
411
+ year = car.get('manu_year', 'Unknown')
412
+ odometer = car.get('odometer', 'Unknown')
413
+ price = car.get('advertised_price', 'Unknown')
414
+ body_type = car.get('vehicle_body_type', 'Unknown')
415
+ fuel_type = car.get('vehicle_fuel_type', 'Unknown')
416
+ transmission = car.get('vehicle_transmission_type', 'Unknown')
417
+
418
+ # Format odometer
419
+ if odometer != 'Unknown' and pd.notna(odometer):
420
+ try:
421
+ odometer_str = f"{int(float(odometer)):,} km"
422
+ except:
423
+ odometer_str = str(odometer)
424
+ else:
425
+ odometer_str = "Unknown km"
426
+
427
+ # Format price
428
+ if price != 'Unknown' and pd.notna(price):
429
+ try:
430
+ price_str = f"${int(float(price)):,}"
431
+ except:
432
+ price_str = str(price)
433
+ else:
434
+ price_str = "Price on request"
435
+
436
+ html_content += f"""
437
+ <div style="padding: 8px; margin: 4px 0; background: #f9f9f9; border-radius: 3px; border-left: 3px solid #007bff;">
438
+ <strong>{year} {make} {model}</strong> - {price_str}<br>
439
+ <small style="color: #666;">{body_type} β€’ {fuel_type} β€’ {transmission} β€’ {odometer_str}</small>
440
+ </div>
441
+ """
442
+
443
+ # Add "show more" if there are more than 20 cars
444
+ if len(dealer_cars) > 20:
445
+ html_content += f"""
446
+ <div style="padding: 8px; margin: 4px 0; background: #e9ecef; border-radius: 3px; text-align: center; font-style: italic;">
447
+ ... and {len(dealer_cars) - 20} more cars
448
+ </div>
449
+ """
450
+
451
+ html_content += """
452
+ </div>
453
+ </details>
454
+ """
455
+
456
+ logger.info(f"βœ… Created dealer summary. Top dealer: {top_dealers.index[0]} with {top_dealers.iloc[0]} cars")
457
+ return html_content
458
+
459
+ except Exception as e:
460
+ logger.error(f"❌ Error creating dealer summary: {e}")
461
+ return f"<div style='color: red; padding: 20px;'>❌ Error creating dealer summary: {str(e)}</div>"
462
+
463
+ # Initialize the matcher with local model
464
+ matcher = CarDealerMatcher(model_path="./autogluon_model")
465
+
466
+ # Initialize a separate simple matcher for the simple tab
467
+ simple_matcher = CarDealerMatcher(model_path="./simple_autogluon_models")
468
+
469
+ # Define structured make-model data for dynamic dropdowns
470
+ MAKE_MODEL_DATA = {
471
+ "Toyota": [
472
+ "Camry",
473
+ "Corolla",
474
+ "HiAce",
475
+ "Hilux",
476
+ "Kluger",
477
+ "Landcruiser",
478
+ "Landcruiser Prado",
479
+ "Prius V",
480
+ "RAV4",
481
+ "Yaris",
482
+ "Yaris Cross"
483
+ ],
484
+ "Audi": [
485
+ "Q3",
486
+ "SQ7",
487
+ "TT"
488
+ ],
489
+ "BMW": [
490
+ "125I",
491
+ "5",
492
+ "M2"
493
+ ],
494
+ "Fiat": [
495
+ "500",
496
+ "500C",
497
+ "Ducato"
498
+ ],
499
+ "Ford": [
500
+ "Everest",
501
+ "F150",
502
+ "Falcon",
503
+ "Fiesta",
504
+ "Ranger",
505
+ "Territory"
506
+ ],
507
+ "GWM": [
508
+ "Haval H6"
509
+ ],
510
+ "Great Wall": [
511
+ "Steed"
512
+ ],
513
+ "Holden": [
514
+ "Calais",
515
+ "Captiva",
516
+ "Colorado",
517
+ "Colorado 7",
518
+ "Commodore",
519
+ "Cruze",
520
+ "Trax",
521
+ "UTE"
522
+ ],
523
+ "Honda": [
524
+ "CR-V"
525
+ ],
526
+ "Hyundai": [
527
+ "Accent",
528
+ "Elantra",
529
+ "I30",
530
+ "IX35",
531
+ "Iload",
532
+ "Kona",
533
+ "Santa FE",
534
+ "Tucson",
535
+ "Veloster"
536
+ ],
537
+ "Isuzu": [
538
+ "D-MAX"
539
+ ],
540
+ "Jaguar": [
541
+ "E-Pace"
542
+ ],
543
+ "Jeep": [
544
+ "Grand Cherokee"
545
+ ],
546
+ "Kia": [
547
+ "Cerato",
548
+ "Optima",
549
+ "Sorento",
550
+ "Sportage"
551
+ ],
552
+ "LDV": [
553
+ "D90",
554
+ "Deliver 9"
555
+ ],
556
+ "Land Rover": [
557
+ "Discovery Sport"
558
+ ],
559
+ "MG": [
560
+ "MG3 Auto"
561
+ ],
562
+ "Mazda": [
563
+ "3",
564
+ "6",
565
+ "BT-50",
566
+ "CX-3",
567
+ "CX-30",
568
+ "CX-5",
569
+ "CX-9",
570
+ "MX-5"
571
+ ],
572
+ "Mercedes-Benz": [
573
+ "C180",
574
+ "C250",
575
+ "E350",
576
+ "EQS",
577
+ "GL320",
578
+ "GLC250",
579
+ "SL400",
580
+ "Sprinter"
581
+ ],
582
+ "Mini": [
583
+ "3D Hatch"
584
+ ],
585
+ "Mitsubishi": [
586
+ "ASX",
587
+ "Eclipse Cross",
588
+ "Lancer",
589
+ "Outlander",
590
+ "Pajero Sport",
591
+ "Triton"
592
+ ],
593
+ "Nissan": [
594
+ "Maxima",
595
+ "Navara",
596
+ "Pathfinder",
597
+ "Patrol",
598
+ "Qashqai",
599
+ "Skyline",
600
+ "X-Trail"
601
+ ],
602
+ "Porsche": [
603
+ "Cayenne",
604
+ "Macan"
605
+ ],
606
+ "Renault": [
607
+ "Captur",
608
+ "Megane"
609
+ ],
610
+ "Skoda": [
611
+ "Octavia"
612
+ ],
613
+ "Subaru": [
614
+ "Forester",
615
+ "Impreza",
616
+ "Liberty",
617
+ "XV"
618
+ ],
619
+ "Suzuki": [
620
+ "Jimny",
621
+ "Swift"
622
+ ],
623
+ "Volkswagen": [
624
+ "Amarok",
625
+ "Golf",
626
+ "Polo",
627
+ "T-ROC",
628
+ "Tiguan"
629
+ ],
630
+ "Volvo": [
631
+ "XC40",
632
+ "XC60"
633
+ ]
634
+ }
635
+
636
+ # Extract makes list for dropdown
637
+ CAR_MAKES = list(MAKE_MODEL_DATA.keys())
638
+
639
+ # Define other dropdown options
640
+ BODY_TYPES = ['Sedan', 'Hatchback', 'SUV', 'Wagon', 'Convertible', 'Coupe', 'Ute', 'Van']
641
+ FUEL_TYPES = ['Petrol', 'Diesel', 'Hybrid', 'Electric', 'LPG']
642
+ TRANSMISSION_TYPES = ['Automatic', 'Manual', 'CVT']
643
+ DRIVE_TYPES = ['Front Wheel Drive', 'Rear Wheel Drive', 'All Wheel Drive', '4x4']
644
+ SEGMENTS = ['Light', 'Small', 'Medium', 'Large', 'Upper Large', 'Luxury', 'Sports']
645
+ CONDITIONS = ['New', 'Used', 'Demo']
646
+
647
+ def update_models(make):
648
+ """Update model choices based on selected make"""
649
+ if make in MAKE_MODEL_DATA:
650
+ models = MAKE_MODEL_DATA[make]
651
+ return gr.Dropdown(choices=models, value=models[0] if models else None)
652
+ else:
653
+ return gr.Dropdown(choices=[], value=None)
654
+
655
+ def predict_dealers_interface(make, model, year, body_type, fuel_type, transmission,
656
+ odometer, doors, seats, engine_size, power, cylinders,
657
+ safety_rating, drive_type, segment, condition, selected_model):
658
+ """Interface function for Gradio"""
659
+ return matcher.predict_dealers(make, model, year, body_type, fuel_type, transmission,
660
+ odometer, doors, seats, engine_size, power, cylinders,
661
+ safety_rating, drive_type, segment, condition, selected_model)
662
+
663
+ def search_data_interface(make, model, year_min, year_max, body_type, fuel_type,
664
+ max_odometer, max_price, selected_file, max_results, show_dealer_stats):
665
+ """Interface function for traditional data search"""
666
+ message, result_data = matcher.search_data_files(make, model, year_min, year_max,
667
+ body_type, fuel_type, max_odometer,
668
+ max_price, selected_file, max_results, show_dealer_stats)
669
+
670
+ if isinstance(result_data, pd.DataFrame) and result_data.empty:
671
+ return message, "No results to display", ""
672
+ elif isinstance(result_data, str): # HTML from _create_dealer_summary
673
+ # Extract stats for info display
674
+ if "Top dealer:" in message:
675
+ info_text = f"**πŸ† Dealer Rankings with Expandable Car Lists**\n\n"
676
+ info_text += "Click on any dealer name to see their individual car listings with prices and specifications."
677
+ else:
678
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
679
+
680
+ return message, info_text, result_data
681
+ else:
682
+ # Handle DataFrame case (when show_dealer_stats=False)
683
+ if hasattr(result_data, 'empty') and not result_data.empty:
684
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
685
+ html_table = result_data.to_html(classes='table table-striped',
686
+ table_id='search-results', escape=False, index=False)
687
+ return message, info_text, html_table
688
+ else:
689
+ return message, "No results to display", ""
690
+
691
+ def simple_search_data_interface(make, model, year, max_odometer, selected_file, max_results, show_dealer_stats):
692
+ """Interface function for simple traditional data search"""
693
+ # Convert simple parameters to traditional search format
694
+ # Use the year as both min and max for exact year matching
695
+ year_min = year if year else None
696
+ year_max = year if year else None
697
+
698
+ message, result_data = matcher.search_data_files(
699
+ make=make,
700
+ model=model,
701
+ year_min=year_min,
702
+ year_max=year_max,
703
+ body_type=None, # Not used in simple search
704
+ fuel_type=None, # Not used in simple search
705
+ max_odometer=max_odometer,
706
+ max_price=None, # Not used in simple search
707
+ selected_file=selected_file,
708
+ max_results=max_results,
709
+ show_dealer_stats=show_dealer_stats
710
+ )
711
+
712
+ if isinstance(result_data, pd.DataFrame) and result_data.empty:
713
+ return message, "No results to display", ""
714
+ elif isinstance(result_data, str): # HTML from _create_dealer_summary
715
+ # Extract stats for info display
716
+ if "Top dealer:" in message:
717
+ info_text = f"**πŸ† Dealer Rankings with Expandable Car Lists**\n\n"
718
+ info_text += "Click on any dealer name to see their individual car listings with prices and specifications."
719
+ else:
720
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
721
+
722
+ return message, info_text, result_data
723
+ else:
724
+ # Handle DataFrame case (when show_dealer_stats=False)
725
+ if hasattr(result_data, 'empty') and not result_data.empty:
726
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
727
+ html_table = result_data.to_html(classes='table table-striped',
728
+ table_id='search-results', escape=False, index=False)
729
+ return message, info_text, html_table
730
+ else:
731
+ return message, "No results to display", ""
732
+
733
+ # Create the modern Gradio interface
734
+ with gr.Blocks(
735
+ title="πŸš— Swiper Match - Car Dealer Predictor",
736
+ theme=gr.themes.Default(),
737
+ css="""
738
+ .gradio-container {
739
+ max-width: 1200px !important;
740
+ margin: 0 auto !important;
741
+ }
742
+ """
743
+ ) as demo:
744
+
745
+ gr.Markdown("""
746
+ # πŸš— Swiper Match - Car Dealer Predictor
747
+ ### AI-Powered Dealer Matching Based on Vehicle Specifications
748
+ Find the perfect dealer for your dream car using machine learning
749
+ """)
750
+
751
+ gr.Markdown("""
752
+ **🎯 How it works:** This tool analyzes vehicle specifications to predict which dealers are most likely to have cars matching your preferences. The model focuses on technical specifications, not pricing.
753
+ """)
754
+
755
+ with gr.Tabs():
756
+ # Simple Tab
757
+ with gr.Tab("πŸ” Simple"):
758
+ gr.Markdown("### Quick Car Dealer Search")
759
+ gr.Markdown("Enter just the basic details for a quick recommendation")
760
+
761
+ # Model Selection for Simple Tab
762
+ simple_model_selection = gr.Dropdown(
763
+ choices=simple_matcher.available_models if simple_matcher.model_loaded else ['Model not loaded'],
764
+ label="πŸ€– Select AI Model",
765
+ value=simple_matcher.available_models[0] if simple_matcher.available_models else 'Model not loaded',
766
+ info="WeightedEnsemble_L3 provides the best overall performance"
767
+ )
768
+
769
+ with gr.Row():
770
+ # Basic inputs
771
+ with gr.Column(scale=1):
772
+ simple_make = gr.Dropdown(choices=CAR_MAKES, label="Make", value="Toyota")
773
+ simple_model = gr.Dropdown(choices=MAKE_MODEL_DATA["Toyota"], label="Model", value="Camry")
774
+
775
+ with gr.Column(scale=1):
776
+ simple_year = gr.Number(label="Year", value=2020, minimum=1990, maximum=2025)
777
+ simple_odometer = gr.Number(label="Odometer (km)", value=50000, minimum=0)
778
+
779
+ # Simple prediction button
780
+ simple_predict_btn = gr.Button(
781
+ "🎯 Find Best Dealers (Simple)",
782
+ variant="primary",
783
+ size="lg"
784
+ )
785
+
786
+ # Simple Results Section
787
+ with gr.Row():
788
+ with gr.Column(scale=1):
789
+ simple_top_dealer = gr.Textbox(
790
+ label="πŸ† Top Recommended Dealer",
791
+ interactive=False,
792
+ lines=2
793
+ )
794
+ simple_confidence = gr.Textbox(
795
+ label="🎯 Confidence Score",
796
+ interactive=False,
797
+ lines=1
798
+ )
799
+ with gr.Column(scale=2):
800
+ simple_detailed_results = gr.Markdown(
801
+ label="πŸ“Š Results",
802
+ value="Click 'Find Best Dealers (Simple)' to see recommendations..."
803
+ )
804
+
805
+ # Detailed Tab
806
+ with gr.Tab("βš™οΈ Detailed"):
807
+ gr.Markdown("### Advanced Car Dealer Search")
808
+ gr.Markdown("Specify detailed vehicle characteristics for more precise recommendations")
809
+
810
+ # Model Selection Section
811
+ with gr.Row():
812
+ model_selection = gr.Dropdown(
813
+ choices=matcher.available_models if matcher.model_loaded else ['Model not loaded'],
814
+ label="πŸ€– Select AI Model",
815
+ value=matcher.available_models[0] if matcher.available_models else 'Model not loaded',
816
+ info="WeightedEnsemble_L3 provides the best overall performance",
817
+ scale=2
818
+ )
819
+
820
+ with gr.Column(scale=1):
821
+ gr.Markdown("""
822
+ **Available Models:**
823
+ - **WeightedEnsemble**: Best performance (combines all models)
824
+ - **RandomForest**: Tree-based, interpretable
825
+ - **XGBoost**: Gradient boosting, fast
826
+ - **NeuralNet**: Deep learning, complex patterns
827
+ """)
828
+
829
+ with gr.Row():
830
+ # Basic Vehicle Info
831
+ with gr.Column(scale=1):
832
+ gr.Markdown("### πŸš— Basic Vehicle Information")
833
+
834
+ make = gr.Dropdown(choices=CAR_MAKES, label="Make", value="Toyota")
835
+ model = gr.Dropdown(choices=MAKE_MODEL_DATA["Toyota"], label="Model", value="Camry")
836
+ year = gr.Number(label="Year", value=2020, minimum=1990, maximum=2025)
837
+ condition = gr.Dropdown(choices=CONDITIONS, label="Condition", value="Used")
838
+
839
+ # Body & Style
840
+ with gr.Column(scale=1):
841
+ gr.Markdown("### πŸ—οΈ Body & Style")
842
+
843
+ body_type = gr.Dropdown(choices=BODY_TYPES, label="Body Type", value="Sedan")
844
+ segment = gr.Dropdown(choices=SEGMENTS, label="Vehicle Segment", value="Medium")
845
+ doors = gr.Number(label="Doors", value=4, minimum=2, maximum=6)
846
+ seats = gr.Number(label="Seats", value=5, minimum=2, maximum=9)
847
+
848
+ # Engine & Performance
849
+ with gr.Column(scale=1):
850
+ gr.Markdown("### ⚑ Engine & Performance")
851
+
852
+ fuel_type = gr.Dropdown(choices=FUEL_TYPES, label="Fuel Type", value="Petrol")
853
+ transmission = gr.Dropdown(choices=TRANSMISSION_TYPES, label="Transmission", value="Automatic")
854
+ engine_size = gr.Number(label="Engine Size (L)", value=2.0, minimum=0.5, maximum=8.0)
855
+ cylinders = gr.Number(label="Cylinders", value=4, minimum=2, maximum=12)
856
+
857
+ with gr.Row():
858
+ # Technical Details
859
+ with gr.Column(scale=1):
860
+ gr.Markdown("### πŸ”§ Technical Details")
861
+
862
+ power = gr.Number(label="Power (HP)", value=150, minimum=50, maximum=1000)
863
+ drive_type = gr.Dropdown(choices=DRIVE_TYPES, label="Drive Type", value="Front Wheel Drive")
864
+ safety_rating = gr.Number(label="Safety Rating (1-5)", value=5, minimum=1, maximum=5)
865
+
866
+ # Usage & History
867
+ with gr.Column(scale=1):
868
+ gr.Markdown("### πŸ“Š Usage & History")
869
+
870
+ odometer = gr.Number(label="Odometer (km)", value=50000, minimum=0)
871
+
872
+ # Detailed prediction button
873
+ predict_btn = gr.Button(
874
+ "🎯 Find Best Dealers (Detailed)",
875
+ variant="primary",
876
+ size="lg"
877
+ )
878
+
879
+ # Detailed Results Section
880
+ with gr.Row():
881
+ with gr.Column(scale=1):
882
+ top_dealer = gr.Textbox(
883
+ label="πŸ† Top Recommended Dealer",
884
+ interactive=False,
885
+ lines=2
886
+ )
887
+ confidence = gr.Textbox(
888
+ label="🎯 Confidence Score",
889
+ interactive=False,
890
+ lines=1
891
+ )
892
+ with gr.Column(scale=2):
893
+ detailed_results = gr.Markdown(
894
+ label="πŸ“Š Detailed Results",
895
+ value="Click 'Find Best Dealers (Detailed)' to see AI recommendations..."
896
+ )
897
+
898
+ # Traditional Search Tab
899
+ with gr.Tab("πŸ“Š Traditional Search"):
900
+ gr.Markdown("### CSV Data File Search")
901
+ gr.Markdown("Search through car listing CSV files and rank dealers by inventory size")
902
+
903
+ with gr.Row():
904
+ with gr.Column(scale=1):
905
+ gr.Markdown("### πŸ—‚οΈ File Selection")
906
+ selected_file = gr.Dropdown(
907
+ choices=matcher.data_files,
908
+ label="Select CSV File",
909
+ value=matcher.data_files[0] if matcher.data_files else None,
910
+ info=f"Available files: {len(matcher.data_files)}"
911
+ )
912
+
913
+ max_results = gr.Number(
914
+ label="Max Results",
915
+ value=100,
916
+ minimum=1,
917
+ maximum=1000,
918
+ info="Limit number of results returned"
919
+ )
920
+
921
+ show_dealer_stats = gr.Checkbox(
922
+ label="πŸ“Š Show Dealer Rankings",
923
+ value=True,
924
+ info="Rank dealers by number of matching cars"
925
+ )
926
+
927
+ with gr.Column(scale=2):
928
+ gr.Markdown("### πŸ” Search Filters")
929
+
930
+ with gr.Row():
931
+ search_make = gr.Textbox(
932
+ label="Make (contains)",
933
+ placeholder="e.g., Toyota, Ford",
934
+ info="Search for car manufacturer"
935
+ )
936
+ search_model = gr.Textbox(
937
+ label="Model (contains)",
938
+ placeholder="e.g., Camry, Focus",
939
+ info="Search for car model"
940
+ )
941
+
942
+ with gr.Row():
943
+ year_min = gr.Number(
944
+ label="Year (Min)",
945
+ value=2015,
946
+ minimum=1980,
947
+ maximum=2025,
948
+ info="Minimum manufacturing year"
949
+ )
950
+ year_max = gr.Number(
951
+ label="Year (Max)",
952
+ value=2024,
953
+ minimum=1980,
954
+ maximum=2025,
955
+ info="Maximum manufacturing year"
956
+ )
957
+
958
+ with gr.Row():
959
+ search_body_type = gr.Textbox(
960
+ label="Body Type (contains)",
961
+ placeholder="e.g., sedan, suv, hatch",
962
+ info="Vehicle body style"
963
+ )
964
+ search_fuel_type = gr.Textbox(
965
+ label="Fuel Type (contains)",
966
+ placeholder="e.g., petrol, diesel, electric",
967
+ info="Fuel/energy type"
968
+ )
969
+
970
+ with gr.Row():
971
+ max_odometer = gr.Number(
972
+ label="Max Odometer (km)",
973
+ minimum=0,
974
+ info="Maximum mileage"
975
+ )
976
+ max_price = gr.Number(
977
+ label="Max Price (AUD)",
978
+ minimum=0,
979
+ info="Maximum advertised price"
980
+ )
981
+
982
+ # Traditional search button
983
+ search_btn = gr.Button(
984
+ "πŸ” Search CSV Data",
985
+ variant="primary",
986
+ size="lg"
987
+ )
988
+
989
+ # Traditional Search Results Section
990
+ with gr.Row():
991
+ search_status = gr.Textbox(
992
+ label="πŸ” Search Status",
993
+ interactive=False,
994
+ lines=2
995
+ )
996
+
997
+ with gr.Row():
998
+ search_info = gr.Markdown(
999
+ label="πŸ“Š Results Info",
1000
+ value="Click 'Search CSV Data' to start searching..."
1001
+ )
1002
+
1003
+ with gr.Row():
1004
+ search_results_table = gr.HTML(
1005
+ label="πŸ“‹ Search Results",
1006
+ value="<p>No search performed yet</p>"
1007
+ )
1008
+
1009
+ # Data file info section
1010
+ gr.Markdown(f"""
1011
+ ---
1012
+ ### πŸ“ Available Data Files
1013
+
1014
+ **Files Found:** {len(matcher.data_files)}
1015
+
1016
+ **File List:**
1017
+ {chr(10).join([f'β€’ {file}' for file in matcher.data_files]) if matcher.data_files else 'β€’ No CSV files found in ./data directory'}
1018
+
1019
+ ### πŸ” Search Features
1020
+
1021
+ **πŸ“Š Dealer Ranking:** Dealers ranked by number of cars matching your criteria
1022
+ **πŸ† Inventory Priority:** Dealers with more matching inventory appear first
1023
+ **πŸ“ˆ Column Mapping:** Uses actual dataset columns:
1024
+ - **Make/Model:** `make`, `model`
1025
+ - **Year:** `manu_year` (manufacturing year)
1026
+ - **Body Type:** `vehicle_body_type`
1027
+ - **Fuel Type:** `vehicle_fuel_type`
1028
+ - **Transmission:** `vehicle_transmission_type`
1029
+ - **Mileage:** `odometer`
1030
+ - **Price:** `advertised_price`
1031
+ - **Dealer:** `dealer_trading_name`
1032
+ - **Location:** `dealer_city`, `dealer_state`
1033
+
1034
+ ### πŸ“Š Dealer Ranking System
1035
+
1036
+ **How it works:** Counts how many cars each dealer has that match your search criteria
1037
+ **Ranking Logic:** Dealers with more matching cars get better ranks (Rank 1 = most inventory)
1038
+ **Sorting:** Results sorted by dealer inventory count first, then by price
1039
+ **Performance:** Fast counting using pandas group operations
1040
+ """)
1041
+
1042
+ # Traditional Simple Search Tab
1043
+ with gr.Tab("πŸ” Traditional Simple Search"):
1044
+ gr.Markdown("### Quick CSV Data Search")
1045
+ gr.Markdown("Simple search through car listing CSV files with basic filters")
1046
+
1047
+ with gr.Row():
1048
+ with gr.Column(scale=1):
1049
+ gr.Markdown("### πŸ—‚οΈ File & Options")
1050
+ simple_search_file = gr.Dropdown(
1051
+ choices=matcher.data_files,
1052
+ label="Select CSV File",
1053
+ value=matcher.data_files[0] if matcher.data_files else None,
1054
+ info=f"Available files: {len(matcher.data_files)}"
1055
+ )
1056
+
1057
+ simple_max_results = gr.Number(
1058
+ label="Max Results",
1059
+ value=100,
1060
+ minimum=1,
1061
+ maximum=1000,
1062
+ info="Limit number of results returned"
1063
+ )
1064
+
1065
+ simple_show_dealer_stats = gr.Checkbox(
1066
+ label="πŸ“Š Show Dealer Rankings",
1067
+ value=True,
1068
+ info="Rank dealers by number of matching cars"
1069
+ )
1070
+
1071
+ with gr.Column(scale=2):
1072
+ gr.Markdown("### πŸ” Basic Search Filters")
1073
+
1074
+ with gr.Row():
1075
+ simple_search_make = gr.Dropdown(
1076
+ choices=CAR_MAKES,
1077
+ label="Make",
1078
+ value="Toyota",
1079
+ info="Select car manufacturer"
1080
+ )
1081
+ simple_search_model = gr.Dropdown(
1082
+ choices=MAKE_MODEL_DATA["Toyota"],
1083
+ label="Model",
1084
+ value="Camry",
1085
+ info="Select car model"
1086
+ )
1087
+
1088
+ with gr.Row():
1089
+ simple_search_year = gr.Number(
1090
+ label="Year",
1091
+ value=2020,
1092
+ minimum=1990,
1093
+ maximum=2025,
1094
+ info="Specific year to search for"
1095
+ )
1096
+ simple_search_max_odometer = gr.Number(
1097
+ label="Max Odometer (km)",
1098
+ value=100000,
1099
+ minimum=0,
1100
+ info="Maximum mileage"
1101
+ )
1102
+
1103
+ # Simple traditional search button
1104
+ simple_search_btn = gr.Button(
1105
+ "πŸ” Search CSV Data (Simple)",
1106
+ variant="primary",
1107
+ size="lg"
1108
+ )
1109
+
1110
+ # Simple Traditional Search Results Section
1111
+ with gr.Row():
1112
+ simple_search_status = gr.Textbox(
1113
+ label="πŸ” Search Status",
1114
+ interactive=False,
1115
+ lines=2
1116
+ )
1117
+
1118
+ with gr.Row():
1119
+ simple_search_info = gr.Markdown(
1120
+ label="πŸ“Š Results Info",
1121
+ value="Click 'Search CSV Data (Simple)' to start searching..."
1122
+ )
1123
+
1124
+ with gr.Row():
1125
+ simple_search_results_table = gr.HTML(
1126
+ label="πŸ“‹ Search Results",
1127
+ value="<p>No search performed yet</p>"
1128
+ )
1129
+
1130
+ gr.Markdown("""
1131
+ ---
1132
+ ### 🎯 Simple Search Features
1133
+
1134
+ **Quick Setup:** Just select make, model, year, and max odometer
1135
+ **Smart Defaults:** Pre-filled with popular choices
1136
+ **Same Power:** Uses the same CSV search engine as Traditional Search
1137
+ **Dealer Ranking:** Get dealers ranked by inventory matching your criteria
1138
+ **Fast Results:** Simplified interface for quicker searches
1139
+ """)
1140
+
1141
+ # Model Information Footer
1142
+ detailed_status_emoji = "βœ…" if matcher.model_loaded else "❌"
1143
+ simple_status_emoji = "βœ…" if simple_matcher.model_loaded else "❌"
1144
+
1145
+ gr.Markdown(f"""
1146
+ ---
1147
+ ### πŸ“Š Model Information
1148
+
1149
+ #### πŸ”§ **Detailed Model** (Advanced Search)
1150
+ **Status:** {detailed_status_emoji} {"Model Loaded Successfully" if matcher.model_loaded else "Model Not Available"}
1151
+ **Path:** ./autogluon_model
1152
+ **Trained Dealers:** {len(matcher.trained_dealers) if matcher.model_loaded else "N/A"}
1153
+ **Available Models:** {len(matcher.available_models) if matcher.model_loaded else "N/A"} ({', '.join(matcher.available_models[:3]) + ('...' if len(matcher.available_models) > 3 else '') if matcher.model_loaded and matcher.available_models else "N/A"})
1154
+ **Features:** 23 vehicle specifications (no pricing data)
1155
+
1156
+ #### πŸ” **Simple Model** (Quick Search)
1157
+ **Status:** {simple_status_emoji} {"Model Loaded Successfully" if simple_matcher.model_loaded else "Model Not Available"}
1158
+ **Path:** ./simple_autogluon_models
1159
+ **Trained Dealers:** {len(simple_matcher.trained_dealers) if simple_matcher.model_loaded else "N/A"}
1160
+ **Available Models:** {len(simple_matcher.available_models) if simple_matcher.model_loaded else "N/A"} ({', '.join(simple_matcher.available_models[:3]) + ('...' if len(simple_matcher.available_models) > 3 else '') if simple_matcher.model_loaded and simple_matcher.available_models else "N/A"})
1161
+ **Features:** Subset of vehicle specifications for faster inference
1162
+
1163
+ #### πŸ€– **Architecture**
1164
+ **Framework:** AutoGluon TabularPredictor
1165
+ **Ensemble Learning:** Multiple algorithms combined via weighted voting
1166
+ **Algorithms:** RandomForest, XGBoost, NeuralNetTorch, CatBoost, LightGBM
1167
+ **Task:** Multi-class classification for dealer prediction
1168
+ """)
1169
+
1170
+ # Set up the prediction functions
1171
+
1172
+ # Simple prediction function (with default values for missing inputs)
1173
+ def simple_predict_dealers_interface(simple_make, simple_model, simple_year, simple_odometer, simple_model_selection):
1174
+ """Simple interface function for Gradio with conditional parameters"""
1175
+
1176
+ return simple_matcher.predict_dealers(
1177
+ make=simple_make,
1178
+ model=simple_model,
1179
+ year=simple_year,
1180
+ body_type=None,
1181
+ fuel_type=None,
1182
+ transmission=None,
1183
+ odometer=simple_odometer,
1184
+ doors=None,
1185
+ seats=None,
1186
+ engine_size=None,
1187
+ power=None,
1188
+ cylinders=None,
1189
+ safety_rating=None,
1190
+ drive_type=None,
1191
+ segment=None,
1192
+ condition=None,
1193
+ selected_model=simple_model_selection
1194
+ )
1195
+
1196
+ # Simple tab click event"
1197
+ simple_predict_btn.click(
1198
+ fn=simple_predict_dealers_interface,
1199
+ inputs=[simple_make, simple_model, simple_year, simple_odometer, simple_model_selection],
1200
+ outputs=[simple_top_dealer, simple_confidence, simple_detailed_results]
1201
+ )
1202
+
1203
+ # Detailed tab click event
1204
+ predict_btn.click(
1205
+ fn=predict_dealers_interface,
1206
+ inputs=[make, model, year, body_type, fuel_type, transmission,
1207
+ odometer, doors, seats, engine_size, power, cylinders,
1208
+ safety_rating, drive_type, segment, condition, model_selection],
1209
+ outputs=[top_dealer, confidence, detailed_results]
1210
+ )
1211
+
1212
+ # Traditional search click event
1213
+ search_btn.click(
1214
+ fn=search_data_interface,
1215
+ inputs=[search_make, search_model, year_min, year_max, search_body_type,
1216
+ search_fuel_type, max_odometer, max_price, selected_file, max_results, show_dealer_stats],
1217
+ outputs=[search_status, search_info, search_results_table]
1218
+ )
1219
+
1220
+ # Simple traditional search click event
1221
+ simple_search_btn.click(
1222
+ fn=simple_search_data_interface,
1223
+ inputs=[simple_search_make, simple_search_model, simple_search_year, simple_search_max_odometer,
1224
+ simple_search_file, simple_max_results, simple_show_dealer_stats],
1225
+ outputs=[simple_search_status, simple_search_info, simple_search_results_table]
1226
+ )
1227
+
1228
+ # Set up dynamic model updating based on make selection
1229
+ simple_make.change(
1230
+ fn=update_models,
1231
+ inputs=simple_make,
1232
+ outputs=simple_model
1233
+ )
1234
+
1235
+ make.change(
1236
+ fn=update_models,
1237
+ inputs=make,
1238
+ outputs=model
1239
+ )
1240
+
1241
+ # Simple traditional search make/model update
1242
+ simple_search_make.change(
1243
+ fn=update_models,
1244
+ inputs=simple_search_make,
1245
+ outputs=simple_search_model
1246
+ )
1247
+
1248
+ # Launch the app
1249
+ if __name__ == "__main__":
1250
+ demo.launch()
core/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Core modules for the Swiper Match application
core/config.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration file for Swiper Match application
3
+ Contains all constants, dropdown data, and application settings
4
+ """
5
+
6
+ import os
7
+
8
+ # Hugging Face configuration
9
+ HF_REPO_ID = "mzx/Swiper-Match"
10
+ HF_TOKEN = os.getenv('HF_TOKEN')
11
+
12
+ # Model paths
13
+ DETAILED_MODEL_PATH = "./autogluon_model"
14
+ SIMPLE_MODEL_PATH = "./simple_autogluon_models"
15
+ DATA_DIR = "./data"
16
+
17
+ # Define structured make-model data for dynamic dropdowns
18
+ MAKE_MODEL_DATA = {
19
+ "Toyota": [
20
+ "Camry",
21
+ "Corolla",
22
+ "HiAce",
23
+ "Hilux",
24
+ "Kluger",
25
+ "Landcruiser",
26
+ "Landcruiser Prado",
27
+ "Prius V",
28
+ "RAV4",
29
+ "Yaris",
30
+ "Yaris Cross"
31
+ ],
32
+ "Audi": [
33
+ "Q3",
34
+ "SQ7",
35
+ "TT"
36
+ ],
37
+ "BMW": [
38
+ "125I",
39
+ "5",
40
+ "M2"
41
+ ],
42
+ "Fiat": [
43
+ "500",
44
+ "500C",
45
+ "Ducato"
46
+ ],
47
+ "Ford": [
48
+ "Everest",
49
+ "F150",
50
+ "Falcon",
51
+ "Fiesta",
52
+ "Ranger",
53
+ "Territory"
54
+ ],
55
+ "GWM": [
56
+ "Haval H6"
57
+ ],
58
+ "Great Wall": [
59
+ "Steed"
60
+ ],
61
+ "Holden": [
62
+ "Calais",
63
+ "Captiva",
64
+ "Colorado",
65
+ "Colorado 7",
66
+ "Commodore",
67
+ "Cruze",
68
+ "Trax",
69
+ "UTE"
70
+ ],
71
+ "Honda": [
72
+ "CR-V"
73
+ ],
74
+ "Hyundai": [
75
+ "Accent",
76
+ "Elantra",
77
+ "I30",
78
+ "IX35",
79
+ "Iload",
80
+ "Kona",
81
+ "Santa FE",
82
+ "Tucson",
83
+ "Veloster"
84
+ ],
85
+ "Isuzu": [
86
+ "D-MAX"
87
+ ],
88
+ "Jaguar": [
89
+ "E-Pace"
90
+ ],
91
+ "Jeep": [
92
+ "Grand Cherokee"
93
+ ],
94
+ "Kia": [
95
+ "Cerato",
96
+ "Optima",
97
+ "Sorento",
98
+ "Sportage"
99
+ ],
100
+ "LDV": [
101
+ "D90",
102
+ "Deliver 9"
103
+ ],
104
+ "Land Rover": [
105
+ "Discovery Sport"
106
+ ],
107
+ "MG": [
108
+ "MG3 Auto"
109
+ ],
110
+ "Mazda": [
111
+ "3",
112
+ "6",
113
+ "BT-50",
114
+ "CX-3",
115
+ "CX-30",
116
+ "CX-5",
117
+ "CX-9",
118
+ "MX-5"
119
+ ],
120
+ "Mercedes-Benz": [
121
+ "C180",
122
+ "C250",
123
+ "E350",
124
+ "EQS",
125
+ "GL320",
126
+ "GLC250",
127
+ "SL400",
128
+ "Sprinter"
129
+ ],
130
+ "Mini": [
131
+ "3D Hatch"
132
+ ],
133
+ "Mitsubishi": [
134
+ "ASX",
135
+ "Eclipse Cross",
136
+ "Lancer",
137
+ "Outlander",
138
+ "Pajero Sport",
139
+ "Triton"
140
+ ],
141
+ "Nissan": [
142
+ "Maxima",
143
+ "Navara",
144
+ "Pathfinder",
145
+ "Patrol",
146
+ "Qashqai",
147
+ "Skyline",
148
+ "X-Trail"
149
+ ],
150
+ "Porsche": [
151
+ "Cayenne",
152
+ "Macan"
153
+ ],
154
+ "Renault": [
155
+ "Captur",
156
+ "Megane"
157
+ ],
158
+ "Skoda": [
159
+ "Octavia"
160
+ ],
161
+ "Subaru": [
162
+ "Forester",
163
+ "Impreza",
164
+ "Liberty",
165
+ "XV"
166
+ ],
167
+ "Suzuki": [
168
+ "Jimny",
169
+ "Swift"
170
+ ],
171
+ "Volkswagen": [
172
+ "Amarok",
173
+ "Golf",
174
+ "Polo",
175
+ "T-ROC",
176
+ "Tiguan"
177
+ ],
178
+ "Volvo": [
179
+ "XC40",
180
+ "XC60"
181
+ ]
182
+ }
183
+
184
+ # Extract makes list for dropdown
185
+ CAR_MAKES = list(MAKE_MODEL_DATA.keys())
186
+
187
+ # Define other dropdown options
188
+ BODY_TYPES = ['Sedan', 'Hatchback', 'SUV', 'Wagon', 'Convertible', 'Coupe', 'Ute', 'Van']
189
+ FUEL_TYPES = ['Petrol', 'Diesel', 'Hybrid', 'Electric', 'LPG']
190
+ TRANSMISSION_TYPES = ['Automatic', 'Manual', 'CVT']
191
+ DRIVE_TYPES = ['Front Wheel Drive', 'Rear Wheel Drive', 'All Wheel Drive', '4x4']
192
+ SEGMENTS = ['Light', 'Small', 'Medium', 'Large', 'Upper Large', 'Luxury', 'Sports']
193
+ CONDITIONS = ['New', 'Used', 'Demo']
194
+
195
+ # Default values
196
+ DEFAULT_VALUES = {
197
+ 'make': 'Toyota',
198
+ 'model': 'Camry',
199
+ 'year': 2020,
200
+ 'body_type': 'Sedan',
201
+ 'fuel_type': 'Petrol',
202
+ 'transmission': 'Automatic',
203
+ 'odometer': 50000,
204
+ 'doors': 4,
205
+ 'seats': 5,
206
+ 'engine_size': 2.0,
207
+ 'power': 150,
208
+ 'cylinders': 4,
209
+ 'safety_rating': 5,
210
+ 'drive_type': 'Front Wheel Drive',
211
+ 'segment': 'Medium',
212
+ 'condition': 'Used',
213
+ 'year_min': 2015,
214
+ 'year_max': 2024,
215
+ 'max_results': 100
216
+ }
217
+
218
+ # UI Configuration
219
+ UI_CONFIG = {
220
+ 'title': 'πŸš— Swiper Match - Car Dealer Predictor',
221
+ 'max_width': '1200px',
222
+ 'theme': 'light'
223
+ }
224
+
225
+ # Gradio CSS
226
+ GRADIO_CSS = """
227
+ .gradio-container {
228
+ max-width: 1200px !important;
229
+ margin: 0 auto !important;
230
+ }
231
+ """
core/matcher.py ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Car Dealer Matcher class for predicting best dealers based on vehicle specifications
3
+ """
4
+
5
+ import pandas as pd
6
+ import numpy as np
7
+ import os
8
+ import logging
9
+ import glob
10
+ from huggingface_hub import snapshot_download
11
+ from autogluon.tabular import TabularPredictor
12
+
13
+ from .config import HF_REPO_ID, HF_TOKEN, DATA_DIR
14
+
15
+ # Set up logging
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class CarDealerMatcher:
20
+ def __init__(self, model_path: str = "./autogluon_model"):
21
+ self.model_path = model_path
22
+ self.predictor = None
23
+ self.trained_dealers = []
24
+ self.available_models = []
25
+ self.model_loaded = False
26
+ self.data_files = []
27
+ self.load_model()
28
+ self.load_data_files()
29
+
30
+ def load_model(self):
31
+ """Load AutoGluon model from local directory or download from Hugging Face"""
32
+ try:
33
+ logger.info(f"πŸ€– Loading AutoGluon model from: {self.model_path}")
34
+
35
+ # Check if model exists locally
36
+ if not os.path.exists(self.model_path):
37
+ logger.info(f"πŸ“₯ Model not found locally. Downloading from Hugging Face: {HF_REPO_ID}")
38
+ try:
39
+ # Download the model from Hugging Face
40
+ downloaded_path = snapshot_download(
41
+ repo_id=HF_REPO_ID,
42
+ cache_dir="./hf_cache",
43
+ token=HF_TOKEN,
44
+ local_dir="./",
45
+ local_dir_use_symlinks=False
46
+ )
47
+ logger.info(f"βœ… Model downloaded successfully to: {downloaded_path}")
48
+ except Exception as download_error:
49
+ logger.error(f"❌ Failed to download model from Hugging Face: {download_error}")
50
+ self.model_loaded = False
51
+ return
52
+
53
+ # Load the model
54
+ if os.path.exists(self.model_path):
55
+ self.predictor = TabularPredictor.load(self.model_path)
56
+ self._extract_trained_dealers()
57
+ self._extract_available_models()
58
+ self.model_loaded = True
59
+ logger.info(f"βœ… Model loaded successfully! Can predict for {len(self.trained_dealers)} dealers")
60
+ logger.info(f"🎯 Available models: {self.available_models}")
61
+ else:
62
+ logger.error(f"❌ Model directory still not found after download attempt: {self.model_path}")
63
+ self.model_loaded = False
64
+
65
+ except Exception as e:
66
+ logger.error(f"❌ Failed to load model: {e}")
67
+ self.model_loaded = False
68
+
69
+ def _extract_trained_dealers(self):
70
+ """Extract trained dealers from the predictor"""
71
+ try:
72
+ if hasattr(self.predictor, 'class_labels'):
73
+ self.trained_dealers = list(self.predictor.class_labels)
74
+ else:
75
+ # Use a dummy prediction to extract dealer names
76
+ dummy_data = self._create_dummy_data()
77
+ proba_result = self.predictor.predict_proba(dummy_data)
78
+ if hasattr(proba_result, 'columns'):
79
+ self.trained_dealers = list(proba_result.columns)
80
+ else:
81
+ self.trained_dealers = ['Model loaded successfully']
82
+ except Exception as e:
83
+ self.trained_dealers = ['Model loaded successfully']
84
+ logger.warning(f"Could not extract dealer list: {e}")
85
+
86
+ def _extract_available_models(self):
87
+ """Extract available models from the predictor"""
88
+ try:
89
+ if hasattr(self.predictor, 'model_names'):
90
+ raw_models = self.predictor.model_names()
91
+ print("my models", raw_models)
92
+ # Add ensemble options with user-friendly names
93
+ self.available_models = ['WeightedEnsemble_L3 (Best)', 'WeightedEnsemble_L2'] + [
94
+ name for name in raw_models if 'WeightedEnsemble' not in name
95
+ ]
96
+
97
+ except Exception as e:
98
+ self.available_models = ['WeightedEnsemble_L3 (Best)']
99
+ logger.warning(f"Could not extract model list: {e}")
100
+
101
+ def _create_dummy_data(self):
102
+ """Create dummy data with all required features"""
103
+ return pd.DataFrame([{
104
+ 'make': 'toyota',
105
+ 'model': 'camry',
106
+ 'year': 2020,
107
+ 'car_age': 4, # Add the missing car_age feature (2024 - 2020 = 4)
108
+ 'vehicle_body_type': 'sedan',
109
+ 'vehicle_fuel_type': 'petrol',
110
+ 'vehicle_transmission_type': 'automatic',
111
+ 'odometer': 50000,
112
+ 'vehicle_doors': 4,
113
+ 'vehicle_seats': 5,
114
+ 'series': 'unknown',
115
+ 'variant': 'unknown',
116
+ 'vehicle_body_type_group': 'Passenger',
117
+ 'vehicle_body_type_style': '4 Door',
118
+ 'vehicle_cylinder_description': '4 Cylinder',
119
+ 'vehicle_cylinders': 4.0,
120
+ 'vehicle_drive_type': 'Front Wheel Drive',
121
+ 'vehicle_engine_size': 2.0,
122
+ 'vehicle_power': 150.0,
123
+ 'vehicle_safety_rating': 5,
124
+ 'vehicle_segment': 'Medium',
125
+ 'condition': 'Used',
126
+ 'vehicle_type': 1
127
+ }])
128
+
129
+ def predict_dealers(self, make=None, model=None, year=None, body_type=None, fuel_type=None, transmission=None,
130
+ odometer=None, doors=None, seats=None, engine_size=None, power=None, cylinders=None,
131
+ safety_rating=None, drive_type=None, segment=None, condition=None, selected_model=None):
132
+ """Predict top dealers for the given car specifications using selected model"""
133
+
134
+ if not self.model_loaded:
135
+ return "❌ AutoGluon model not loaded. Please check model directory availability.", "", ""
136
+
137
+ try:
138
+ # Calculate car age (current year - vehicle year)
139
+ current_year = 2024 # You can use datetime.now().year for dynamic year
140
+ car_age = current_year - int(year) if year else None
141
+
142
+ # Create input dataframe with only non-None values for AutoGluon model
143
+ car_data_dict = {}
144
+
145
+ # Add parameters only if they are not None
146
+ if make is not None:
147
+ car_data_dict['make'] = make.lower()
148
+ if model is not None:
149
+ car_data_dict['model'] = model.lower()
150
+ if year is not None:
151
+ car_data_dict['year'] = int(year)
152
+ if car_age is not None:
153
+ car_data_dict['car_age'] = car_age
154
+ if body_type is not None:
155
+ car_data_dict['vehicle_body_type'] = body_type.lower()
156
+ if fuel_type is not None:
157
+ car_data_dict['vehicle_fuel_type'] = fuel_type.lower()
158
+ if transmission is not None:
159
+ car_data_dict['vehicle_transmission_type'] = transmission.lower()
160
+ if odometer is not None:
161
+ car_data_dict['odometer'] = int(odometer)
162
+ if doors is not None:
163
+ car_data_dict['vehicle_doors'] = float(doors)
164
+ if seats is not None:
165
+ car_data_dict['vehicle_seats'] = float(seats)
166
+ if engine_size is not None:
167
+ car_data_dict['vehicle_engine_size'] = float(engine_size)
168
+ if power is not None:
169
+ car_data_dict['vehicle_power'] = float(power)
170
+ if cylinders is not None:
171
+ car_data_dict['vehicle_cylinders'] = float(cylinders)
172
+ if safety_rating is not None:
173
+ car_data_dict['vehicle_safety_rating'] = float(safety_rating)
174
+ if drive_type is not None:
175
+ car_data_dict['vehicle_drive_type'] = drive_type
176
+ if segment is not None:
177
+ car_data_dict['vehicle_segment'] = segment
178
+ if condition is not None:
179
+ car_data_dict['condition'] = condition
180
+
181
+ # Auto-generated features based on inputs (only if base inputs exist)
182
+ if body_type is not None:
183
+ car_data_dict['vehicle_body_type_group'] = self._map_body_type_group(body_type)
184
+ if doors is not None:
185
+ car_data_dict['vehicle_body_type_style'] = self._map_body_style(doors)
186
+ if cylinders is not None:
187
+ car_data_dict['vehicle_cylinder_description'] = f'{int(cylinders)} Cylinder'
188
+
189
+ # Always include these if not specified (required for model compatibility)
190
+ if 'series' not in car_data_dict:
191
+ car_data_dict['series'] = 'unknown'
192
+ if 'variant' not in car_data_dict:
193
+ car_data_dict['variant'] = 'unknown'
194
+ if 'vehicle_type' not in car_data_dict:
195
+ car_data_dict['vehicle_type'] = 1 # Passenger vehicle
196
+
197
+ car_data = pd.DataFrame([car_data_dict])
198
+
199
+ # Get predictions using AutoGluon predictor with selected model
200
+ if selected_model and selected_model != 'WeightedEnsemble_L3 (Best)':
201
+ # Clean model name
202
+ clean_model = selected_model.replace(' (Best)', '')
203
+ try:
204
+ proba_result = self.predictor.predict_proba(car_data, model=clean_model)
205
+ model_used = selected_model
206
+ except Exception as model_error:
207
+ logger.warning(f"Failed to use specific model {clean_model}: {model_error}")
208
+ proba_result = self.predictor.predict_proba(car_data)
209
+ model_used = "WeightedEnsemble_L3 (fallback)"
210
+ else:
211
+ proba_result = self.predictor.predict_proba(car_data)
212
+ model_used = "WeightedEnsemble_L3 (Best)"
213
+
214
+ # Convert to dict format and get top-k predictions
215
+ if hasattr(proba_result, 'iloc'):
216
+ proba_dict = proba_result.iloc[0].to_dict()
217
+ else:
218
+ proba_dict = dict(zip(self.trained_dealers, proba_result[0]))
219
+
220
+ # Sort by probability and get top 5
221
+ sorted_dealers = sorted(proba_dict.items(), key=lambda x: x[1], reverse=True)[:5]
222
+
223
+ # Format results
224
+ top_dealer = sorted_dealers[0][0]
225
+ confidence = f"{sorted_dealers[0][1]:.2%}"
226
+
227
+ # Create detailed results
228
+ results_text = "πŸ† **Top 5 Recommended Dealers:**\n\n"
229
+ for i, (dealer, prob) in enumerate(sorted_dealers, 1):
230
+ emoji = "πŸ₯‡" if i == 1 else "πŸ₯ˆ" if i == 2 else "πŸ₯‰" if i == 3 else "πŸ”Έ"
231
+ results_text += f"{emoji} **{i}. {dealer}** - {prob:.1%} confidence\n"
232
+
233
+ # Add car summary
234
+ car_summary = f"""
235
+
236
+ **πŸš— Vehicle Specifications:**
237
+ β€’ **Make & Model:** {make} {model} ({year})
238
+ β€’ **Body Type:** {body_type} β€’ **Segment:** {segment}
239
+ β€’ **Engine:** {engine_size}L, {cylinders} cylinders, {power}HP
240
+ β€’ **Drivetrain:** {fuel_type} β€’ {transmission} β€’ {drive_type}
241
+ β€’ **Details:** {doors} doors, {seats} seats β€’ {odometer:,} km
242
+ β€’ **Condition:** {condition} β€’ **Safety:** {safety_rating}β˜…
243
+
244
+ **πŸ€– Model:** {model_used}
245
+ """
246
+
247
+ return f"🎯 **Best Match: {top_dealer}**", confidence, results_text + car_summary
248
+
249
+ except Exception as e:
250
+ logger.error(f"Prediction error: {e}")
251
+ return f"❌ Error making prediction: {str(e)}", "", ""
252
+
253
+ def _map_body_type_group(self, body_type):
254
+ """Map body type to group"""
255
+ if not body_type:
256
+ return 'Passenger'
257
+ body_lower = body_type.lower()
258
+ if body_lower in ['ute', 'truck', 'van']:
259
+ return 'Commercial'
260
+ return 'Passenger'
261
+
262
+ def _map_body_style(self, doors):
263
+ """Map doors to body style"""
264
+ if not doors:
265
+ return '4 Door'
266
+ doors = int(doors)
267
+ if doors == 2:
268
+ return '2 Door'
269
+ elif doors == 3:
270
+ return '3 Door'
271
+ elif doors == 5:
272
+ return '5 Door'
273
+ else:
274
+ return '4 Door'
275
+
276
+ def load_data_files(self):
277
+ """Load available CSV data files"""
278
+ try:
279
+ if os.path.exists(DATA_DIR):
280
+ csv_files = glob.glob(os.path.join(DATA_DIR, "*.csv"))
281
+ self.data_files = [os.path.basename(f) for f in csv_files]
282
+ logger.info(f"βœ… Found {len(self.data_files)} CSV files: {self.data_files}")
283
+ else:
284
+ self.data_files = []
285
+ logger.warning("❌ Data directory not found")
286
+ except Exception as e:
287
+ logger.error(f"❌ Failed to load data files: {e}")
288
+ self.data_files = []
289
+
290
+ def search_data_files(self, make=None, model=None, year_min=None, year_max=None,
291
+ year_value=None, year_range=None,
292
+ body_type=None, fuel_type=None, max_odometer=None,
293
+ odometer_value=None, odometer_range=None,
294
+ max_price=None, selected_file=None, max_results=100, show_dealer_stats=True):
295
+ """Search through CSV data files using pandas filtering"""
296
+ try:
297
+ if not self.data_files:
298
+ return "❌ No CSV data files available", pd.DataFrame()
299
+
300
+ # Use selected file or default to first available
301
+ if selected_file and selected_file in self.data_files:
302
+ file_to_search = selected_file
303
+ else:
304
+ file_to_search = self.data_files[0] if self.data_files else None
305
+
306
+ if not file_to_search:
307
+ return "❌ No valid file selected", pd.DataFrame()
308
+
309
+ file_path = os.path.join(DATA_DIR, file_to_search)
310
+
311
+ # Load the CSV file
312
+ logger.info(f"πŸ” Loading data from: {file_to_search}")
313
+
314
+ # Read CSV with error handling for different encodings
315
+ try:
316
+ df = pd.read_csv(file_path, encoding='utf-8')
317
+ except UnicodeDecodeError:
318
+ try:
319
+ df = pd.read_csv(file_path, encoding='latin-1')
320
+ except:
321
+ df = pd.read_csv(file_path, encoding='cp1252')
322
+
323
+ # Convert column names to lowercase for easier matching
324
+ df.columns = df.columns.str.lower().str.strip()
325
+
326
+ original_count = len(df)
327
+
328
+ # Apply filters using correct column names from the dataset
329
+ if make and 'make' in df.columns:
330
+ df = df[df['make'].str.contains(make, case=False, na=False)]
331
+
332
+ if model and 'model' in df.columns:
333
+ df = df[df['model'].str.contains(model, case=False, na=False)]
334
+
335
+ # Handle year filtering - prioritize range-based search over min/max search
336
+ if year_value is not None and year_range is not None and 'manu_year' in df.columns:
337
+ # Symmetric range search: year_value Β± year_range
338
+ min_year = year_value - year_range
339
+ max_year_range = year_value + year_range
340
+ year_numeric = pd.to_numeric(df['manu_year'], errors='coerce')
341
+ df = df[(year_numeric >= min_year) & (year_numeric <= max_year_range)]
342
+ elif year_min and 'manu_year' in df.columns:
343
+ df = df[pd.to_numeric(df['manu_year'], errors='coerce') >= year_min]
344
+ elif year_max and 'manu_year' in df.columns:
345
+ df = df[pd.to_numeric(df['manu_year'], errors='coerce') <= year_max]
346
+
347
+ if body_type and 'vehicle_body_type' in df.columns:
348
+ df = df[df['vehicle_body_type'].str.contains(body_type, case=False, na=False)]
349
+
350
+ if fuel_type and 'vehicle_fuel_type' in df.columns:
351
+ df = df[df['vehicle_fuel_type'].str.contains(fuel_type, case=False, na=False)]
352
+
353
+ # Handle odometer filtering - prioritize range-based search over max search
354
+ if odometer_value is not None and odometer_range is not None and 'odometer' in df.columns:
355
+ # Symmetric range search: odometer_value Β± odometer_range
356
+ min_odometer = odometer_value - odometer_range
357
+ max_odometer_range = odometer_value + odometer_range
358
+ odometer_numeric = pd.to_numeric(df['odometer'], errors='coerce')
359
+ df = df[(odometer_numeric >= min_odometer) & (odometer_numeric <= max_odometer_range)]
360
+ elif max_odometer and 'odometer' in df.columns:
361
+ # Fallback to max odometer filter if range search not specified
362
+ df = df[pd.to_numeric(df['odometer'], errors='coerce') <= max_odometer]
363
+
364
+ if max_price and 'advertised_price' in df.columns:
365
+ df = df[pd.to_numeric(df['advertised_price'], errors='coerce') <= max_price]
366
+
367
+ filtered_count = len(df)
368
+
369
+ if df.empty:
370
+ return f"βœ… Searched {file_to_search} ({original_count:,} records) - No matches found", pd.DataFrame()
371
+
372
+ # Create dealer ranking summary
373
+ if show_dealer_stats and 'dealer_trading_name' in df.columns:
374
+ dealer_summary = self._create_dealer_summary(df)
375
+ return f"βœ… Found {filtered_count:,} matches from {original_count:,} records in {file_to_search}", dealer_summary
376
+ else:
377
+ # Return basic summary without dealer rankings
378
+ summary_df = pd.DataFrame({
379
+ 'Total Matches': [filtered_count],
380
+ 'File Searched': [file_to_search]
381
+ })
382
+ return f"βœ… Found {filtered_count:,} matches from {original_count:,} records in {file_to_search}", summary_df
383
+
384
+ except Exception as e:
385
+ logger.error(f"❌ Search error: {e}")
386
+ return f"❌ Error searching data: {str(e)}", pd.DataFrame()
387
+
388
+ def _create_dealer_summary(self, df):
389
+ """Create dealer ranking summary showing top dealers by car count with expandable car lists"""
390
+ try:
391
+ logger.info(f"πŸ“Š Creating dealer summary from {len(df)} matching cars...")
392
+
393
+ # Count cars per dealer
394
+ dealer_counts = df['dealer_trading_name'].value_counts()
395
+
396
+ # Get top 10 dealers (increased from 5 to show more)
397
+ top_dealers = dealer_counts.head(10)
398
+
399
+ # Create HTML with expandable sections for each dealer
400
+ html_content = ""
401
+
402
+ # Add dealer sections
403
+ for rank, (dealer_name, car_count) in enumerate(top_dealers.items(), 1):
404
+ # Get cars for this dealer
405
+ dealer_cars = df[df['dealer_trading_name'] == dealer_name]
406
+
407
+ # Create rank emoji
408
+ rank_emoji = "πŸ₯‡" if rank == 1 else "πŸ₯ˆ" if rank == 2 else "πŸ₯‰" if rank == 3 else f"#{rank}"
409
+
410
+ html_content += f"""
411
+ <details style="margin-bottom: 10px; border: 1px solid #ddd; padding: 5px; border-radius: 5px;">
412
+ <summary style="font-weight: bold; cursor: pointer; padding: 5px;">
413
+ {rank_emoji} {dealer_name} ({car_count} cars)
414
+ </summary>
415
+ <div style="margin-top: 10px; max-height: 300px; overflow-y: auto;">
416
+ """
417
+
418
+ # Add individual cars
419
+ for idx, (_, car) in enumerate(dealer_cars.head(20).iterrows()): # Limit to 20 cars per dealer
420
+ # Extract car details safely
421
+ make = car.get('make', 'Unknown')
422
+ model = car.get('model', 'Unknown')
423
+ year = car.get('manu_year', 'Unknown')
424
+ odometer = car.get('odometer', 'Unknown')
425
+ price = car.get('advertised_price', 'Unknown')
426
+ body_type = car.get('vehicle_body_type', 'Unknown')
427
+ fuel_type = car.get('vehicle_fuel_type', 'Unknown')
428
+ transmission = car.get('vehicle_transmission_type', 'Unknown')
429
+
430
+ # Format odometer
431
+ if odometer != 'Unknown' and pd.notna(odometer):
432
+ try:
433
+ odometer_str = f"{int(float(odometer)):,} km"
434
+ except:
435
+ odometer_str = str(odometer)
436
+ else:
437
+ odometer_str = "Unknown km"
438
+
439
+ # Format price
440
+ if price != 'Unknown' and pd.notna(price):
441
+ try:
442
+ price_str = f"${int(float(price)):,}"
443
+ except:
444
+ price_str = str(price)
445
+ else:
446
+ price_str = "Price on request"
447
+
448
+ html_content += f"""
449
+ <div style="padding: 8px; margin: 4px 0; background: #f9f9f9; border-radius: 3px; border-left: 3px solid #007bff;">
450
+ <strong>{year} {make} {model}</strong> - {price_str}<br>
451
+ <small style="color: #666;">{body_type} β€’ {fuel_type} β€’ {transmission} β€’ {odometer_str}</small>
452
+ </div>
453
+ """
454
+
455
+ # Add "show more" if there are more than 20 cars
456
+ if len(dealer_cars) > 20:
457
+ html_content += f"""
458
+ <div style="padding: 8px; margin: 4px 0; background: #e9ecef; border-radius: 3px; text-align: center; font-style: italic;">
459
+ ... and {len(dealer_cars) - 20} more cars
460
+ </div>
461
+ """
462
+
463
+ html_content += """
464
+ </div>
465
+ </details>
466
+ """
467
+
468
+ logger.info(f"βœ… Created dealer summary. Top dealer: {top_dealers.index[0]} with {top_dealers.iloc[0]} cars")
469
+ return html_content
470
+
471
+ except Exception as e:
472
+ logger.error(f"❌ Error creating dealer summary: {e}")
473
+ return f"<div style='color: red; padding: 20px;'>❌ Error creating dealer summary: {str(e)}</div>"
ui/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # UI components for the Swiper Match Gradio application
ui/interface.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Interface functions for the Gradio application
3
+ Contains all the functions that connect the UI to the business logic
4
+ """
5
+
6
+ import gradio as gr
7
+ import pandas as pd
8
+ from core.config import MAKE_MODEL_DATA
9
+
10
+
11
+ def update_models(make):
12
+ """Update model choices based on selected make"""
13
+ if make in MAKE_MODEL_DATA:
14
+ models = MAKE_MODEL_DATA[make]
15
+ return gr.Dropdown(choices=models, value=models[0] if models else None)
16
+ else:
17
+ return gr.Dropdown(choices=[], value=None)
18
+
19
+
20
+ def predict_dealers_interface(matcher, make, model, year, body_type, fuel_type, transmission,
21
+ odometer, doors, seats, engine_size, power, cylinders,
22
+ safety_rating, drive_type, segment, condition, selected_model):
23
+ """Interface function for detailed Gradio prediction"""
24
+ return matcher.predict_dealers(make, model, year, body_type, fuel_type, transmission,
25
+ odometer, doors, seats, engine_size, power, cylinders,
26
+ safety_rating, drive_type, segment, condition, selected_model)
27
+
28
+
29
+ def simple_predict_dealers_interface(matcher, simple_make, simple_model, simple_year,
30
+ simple_odometer, simple_model_selection):
31
+ """Simple interface function for Gradio with conditional parameters"""
32
+
33
+ return matcher.predict_dealers(
34
+ make=simple_make,
35
+ model=simple_model,
36
+ year=simple_year,
37
+ body_type=None,
38
+ fuel_type=None,
39
+ transmission=None,
40
+ odometer=simple_odometer,
41
+ doors=None,
42
+ seats=None,
43
+ engine_size=None,
44
+ power=None,
45
+ cylinders=None,
46
+ safety_rating=None,
47
+ drive_type=None,
48
+ segment=None,
49
+ condition=None,
50
+ selected_model=simple_model_selection
51
+ )
52
+
53
+
54
+ def search_data_interface(matcher, make, model, year_value, year_range, body_type, fuel_type,
55
+ odometer_value, odometer_range, max_price, selected_file, max_results, show_dealer_stats):
56
+ """Interface function for traditional data search"""
57
+ message, result_data = matcher.search_data_files(make, model, year_min=None, year_max=None,
58
+ year_value=year_value, year_range=year_range,
59
+ body_type=body_type, fuel_type=fuel_type, max_odometer=None,
60
+ odometer_value=odometer_value, odometer_range=odometer_range,
61
+ max_price=max_price, selected_file=selected_file,
62
+ max_results=max_results, show_dealer_stats=show_dealer_stats)
63
+
64
+ if isinstance(result_data, pd.DataFrame) and result_data.empty:
65
+ return message, "No results to display", ""
66
+ elif isinstance(result_data, str): # HTML from _create_dealer_summary
67
+ # Extract stats for info display
68
+ if "Top dealer:" in message:
69
+ info_text = f"**πŸ† Dealer Rankings with Expandable Car Lists**\n\n"
70
+ info_text += "Click on any dealer name to see their individual car listings with prices and specifications."
71
+ else:
72
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
73
+
74
+ return message, info_text, result_data
75
+ else:
76
+ # Handle DataFrame case (when show_dealer_stats=False)
77
+ if hasattr(result_data, 'empty') and not result_data.empty:
78
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
79
+ html_table = result_data.to_html(classes='table table-striped',
80
+ table_id='search-results', escape=False, index=False)
81
+ return message, info_text, html_table
82
+ else:
83
+ return message, "No results to display", ""
84
+
85
+
86
+ def simple_search_data_interface(matcher, make, model, year_value, year_range, odometer_value, odometer_range, selected_file,
87
+ max_results, show_dealer_stats):
88
+ """Interface function for simple traditional data search"""
89
+
90
+ message, result_data = matcher.search_data_files(
91
+ make=make,
92
+ model=model,
93
+ year_min=None,
94
+ year_max=None,
95
+ year_value=year_value,
96
+ year_range=year_range,
97
+ body_type=None, # Not used in simple search
98
+ fuel_type=None, # Not used in simple search
99
+ max_odometer=None, # Not using max_odometer anymore
100
+ odometer_value=odometer_value,
101
+ odometer_range=odometer_range,
102
+ max_price=None, # Not used in simple search
103
+ selected_file=selected_file,
104
+ max_results=max_results,
105
+ show_dealer_stats=show_dealer_stats
106
+ )
107
+
108
+ if isinstance(result_data, pd.DataFrame) and result_data.empty:
109
+ return message, "No results to display", ""
110
+ elif isinstance(result_data, str): # HTML from _create_dealer_summary
111
+ # Extract stats for info display
112
+ if "Top dealer:" in message:
113
+ info_text = f"**πŸ† Dealer Rankings with Expandable Car Lists**\n\n"
114
+ info_text += "Click on any dealer name to see their individual car listings with prices and specifications."
115
+ else:
116
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
117
+
118
+ return message, info_text, result_data
119
+ else:
120
+ # Handle DataFrame case (when show_dealer_stats=False)
121
+ if hasattr(result_data, 'empty') and not result_data.empty:
122
+ info_text = f"**πŸ“Š Search completed successfully**\n\n"
123
+ html_table = result_data.to_html(classes='table table-striped',
124
+ table_id='search-results', escape=False, index=False)
125
+ return message, info_text, html_table
126
+ else:
127
+ return message, "No results to display", ""
ui/tabs/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Tab components for the Gradio interface
ui/tabs/detailed_tab.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Detailed tab component for advanced car dealer search
3
+ """
4
+
5
+ import gradio as gr
6
+ from core.config import (
7
+ CAR_MAKES, MAKE_MODEL_DATA, BODY_TYPES, FUEL_TYPES, TRANSMISSION_TYPES,
8
+ DRIVE_TYPES, SEGMENTS, CONDITIONS, DEFAULT_VALUES
9
+ )
10
+
11
+
12
+ def create_detailed_tab(matcher):
13
+ """Create the detailed prediction tab"""
14
+
15
+ with gr.Tab("βš™οΈ Detailed ML Search"):
16
+ gr.Markdown("### Advanced Car Dealer Search")
17
+ gr.Markdown("Specify detailed vehicle characteristics for more precise recommendations")
18
+
19
+ # Model Selection Section
20
+ with gr.Row():
21
+ model_selection = gr.Dropdown(
22
+ choices=matcher.available_models if matcher.model_loaded else ['Model not loaded'],
23
+ label="πŸ€– Select AI Model",
24
+ value=matcher.available_models[0] if matcher.available_models else 'Model not loaded',
25
+ info="WeightedEnsemble_L3 provides the best overall performance",
26
+ scale=2
27
+ )
28
+
29
+ with gr.Column(scale=1):
30
+ gr.Markdown("""
31
+ **Available Models:**
32
+ - **WeightedEnsemble**: Best performance (combines all models)
33
+ - **RandomForest**: Tree-based, interpretable
34
+ - **XGBoost**: Gradient boosting, fast
35
+ - **NeuralNet**: Deep learning, complex patterns
36
+ """)
37
+
38
+ with gr.Row():
39
+ # Basic Vehicle Info
40
+ with gr.Column(scale=1):
41
+ gr.Markdown("### πŸš— Basic Vehicle Information")
42
+
43
+ make = gr.Dropdown(
44
+ choices=CAR_MAKES,
45
+ label="Make",
46
+ value=DEFAULT_VALUES['make']
47
+ )
48
+ model = gr.Dropdown(
49
+ choices=MAKE_MODEL_DATA[DEFAULT_VALUES['make']],
50
+ label="Model",
51
+ value=DEFAULT_VALUES['model']
52
+ )
53
+ year = gr.Number(
54
+ label="Year",
55
+ value=DEFAULT_VALUES['year'],
56
+ minimum=1990,
57
+ maximum=2025
58
+ )
59
+ condition = gr.Dropdown(
60
+ choices=CONDITIONS,
61
+ label="Condition",
62
+ value=DEFAULT_VALUES['condition']
63
+ )
64
+
65
+ # Body & Style
66
+ with gr.Column(scale=1):
67
+ gr.Markdown("### πŸ—οΈ Body & Style")
68
+
69
+ body_type = gr.Dropdown(
70
+ choices=BODY_TYPES,
71
+ label="Body Type",
72
+ value=DEFAULT_VALUES['body_type']
73
+ )
74
+ segment = gr.Dropdown(
75
+ choices=SEGMENTS,
76
+ label="Vehicle Segment",
77
+ value=DEFAULT_VALUES['segment']
78
+ )
79
+ doors = gr.Number(
80
+ label="Doors",
81
+ value=DEFAULT_VALUES['doors'],
82
+ minimum=2,
83
+ maximum=6
84
+ )
85
+ seats = gr.Number(
86
+ label="Seats",
87
+ value=DEFAULT_VALUES['seats'],
88
+ minimum=2,
89
+ maximum=9
90
+ )
91
+
92
+ # Engine & Performance
93
+ with gr.Column(scale=1):
94
+ gr.Markdown("### ⚑ Engine & Performance")
95
+
96
+ fuel_type = gr.Dropdown(
97
+ choices=FUEL_TYPES,
98
+ label="Fuel Type",
99
+ value=DEFAULT_VALUES['fuel_type']
100
+ )
101
+ transmission = gr.Dropdown(
102
+ choices=TRANSMISSION_TYPES,
103
+ label="Transmission",
104
+ value=DEFAULT_VALUES['transmission']
105
+ )
106
+ engine_size = gr.Number(
107
+ label="Engine Size (L)",
108
+ value=DEFAULT_VALUES['engine_size'],
109
+ minimum=0.5,
110
+ maximum=8.0
111
+ )
112
+ cylinders = gr.Number(
113
+ label="Cylinders",
114
+ value=DEFAULT_VALUES['cylinders'],
115
+ minimum=2,
116
+ maximum=12
117
+ )
118
+
119
+ with gr.Row():
120
+ # Technical Details
121
+ with gr.Column(scale=1):
122
+ gr.Markdown("### πŸ”§ Technical Details")
123
+
124
+ power = gr.Number(
125
+ label="Power (HP)",
126
+ value=DEFAULT_VALUES['power'],
127
+ minimum=50,
128
+ maximum=1000
129
+ )
130
+ drive_type = gr.Dropdown(
131
+ choices=DRIVE_TYPES,
132
+ label="Drive Type",
133
+ value=DEFAULT_VALUES['drive_type']
134
+ )
135
+ safety_rating = gr.Number(
136
+ label="Safety Rating (1-5)",
137
+ value=DEFAULT_VALUES['safety_rating'],
138
+ minimum=1,
139
+ maximum=5
140
+ )
141
+
142
+ # Usage & History
143
+ with gr.Column(scale=1):
144
+ gr.Markdown("### πŸ“Š Usage & History")
145
+
146
+ odometer = gr.Number(
147
+ label="Odometer (km)",
148
+ value=DEFAULT_VALUES['odometer'],
149
+ minimum=0
150
+ )
151
+
152
+ # Detailed prediction button
153
+ predict_btn = gr.Button(
154
+ "🎯 Find Best Dealers (Detailed)",
155
+ variant="primary",
156
+ size="lg"
157
+ )
158
+
159
+ # Detailed Results Section
160
+ with gr.Row():
161
+ with gr.Column(scale=1):
162
+ top_dealer = gr.Textbox(
163
+ label="πŸ† Top Recommended Dealer",
164
+ interactive=False,
165
+ lines=2
166
+ )
167
+ confidence = gr.Textbox(
168
+ label="🎯 Confidence Score",
169
+ interactive=False,
170
+ lines=1
171
+ )
172
+ with gr.Column(scale=2):
173
+ detailed_results = gr.Markdown(
174
+ label="πŸ“Š Detailed Results",
175
+ value="Click 'Find Best Dealers (Detailed)' to see AI recommendations..."
176
+ )
177
+
178
+ return {
179
+ 'inputs': [make, model, year, body_type, fuel_type, transmission,
180
+ odometer, doors, seats, engine_size, power, cylinders,
181
+ safety_rating, drive_type, segment, condition, model_selection],
182
+ 'outputs': [top_dealer, confidence, detailed_results],
183
+ 'button': predict_btn,
184
+ 'make_dropdown': make,
185
+ 'model_dropdown': model
186
+ }
ui/tabs/simple_search_tab.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple search tab component for quick CSV data search
3
+ """
4
+
5
+ import gradio as gr
6
+ from core.config import CAR_MAKES, MAKE_MODEL_DATA, DEFAULT_VALUES
7
+
8
+
9
+ def create_simple_search_tab(matcher):
10
+ """Create the simple traditional search tab"""
11
+
12
+ with gr.Tab("πŸ” Traditional Simple Search"):
13
+ gr.Markdown("### Quick CSV Data Search")
14
+ gr.Markdown("Simple search through car listing CSV files with basic filters")
15
+
16
+ with gr.Row():
17
+ with gr.Column(scale=1):
18
+ gr.Markdown("### πŸ—‚οΈ File & Options")
19
+ simple_search_file = gr.Dropdown(
20
+ choices=matcher.data_files,
21
+ label="Select CSV File",
22
+ value=matcher.data_files[0] if matcher.data_files else None,
23
+ info=f"Available files: {len(matcher.data_files)}"
24
+ )
25
+
26
+ simple_max_results = gr.Number(
27
+ label="Max Results",
28
+ value=DEFAULT_VALUES['max_results'],
29
+ minimum=1,
30
+ maximum=1000,
31
+ info="Limit number of results returned"
32
+ )
33
+
34
+ simple_show_dealer_stats = gr.Checkbox(
35
+ label="πŸ“Š Show Dealer Rankings",
36
+ value=True,
37
+ info="Rank dealers by number of matching cars"
38
+ )
39
+
40
+ with gr.Column(scale=2):
41
+ gr.Markdown("### πŸ” Basic Search Filters")
42
+
43
+ with gr.Row():
44
+ simple_search_make = gr.Dropdown(
45
+ choices=CAR_MAKES,
46
+ label="Make",
47
+ value=DEFAULT_VALUES['make'],
48
+ info="Select car manufacturer"
49
+ )
50
+ simple_search_model = gr.Dropdown(
51
+ choices=MAKE_MODEL_DATA[DEFAULT_VALUES['make']],
52
+ label="Model",
53
+ value=DEFAULT_VALUES['model'],
54
+ info="Select car model"
55
+ )
56
+
57
+ with gr.Row():
58
+ simple_year_value = gr.Number(
59
+ label="Target Year",
60
+ value=DEFAULT_VALUES['year'],
61
+ minimum=1990,
62
+ maximum=2025,
63
+ info="Target manufacturing year"
64
+ )
65
+ simple_year_range = gr.Slider(
66
+ minimum=0,
67
+ maximum=8,
68
+ value=1,
69
+ step=1,
70
+ label="Year Range (Β±years)",
71
+ info="Range around target year"
72
+ )
73
+
74
+ with gr.Row():
75
+ simple_odometer_value = gr.Number(
76
+ label="Target Odometer (km)",
77
+ value=65000,
78
+ minimum=0,
79
+ info="Target odometer reading"
80
+ )
81
+ simple_odometer_range = gr.Slider(
82
+ minimum=0,
83
+ maximum=100000,
84
+ value=20000,
85
+ step=1000,
86
+ label="Odometer Range (Β±km)",
87
+ info="Range around target (Β±km)"
88
+ )
89
+
90
+ # Simple traditional search button
91
+ simple_search_btn = gr.Button(
92
+ "πŸ” Search CSV Data (Simple)",
93
+ variant="primary",
94
+ size="lg"
95
+ )
96
+
97
+ # Simple Traditional Search Results Section
98
+ with gr.Row():
99
+ simple_search_status = gr.Textbox(
100
+ label="πŸ” Search Status",
101
+ interactive=False,
102
+ lines=2
103
+ )
104
+
105
+ with gr.Row():
106
+ simple_search_info = gr.Markdown(
107
+ label="πŸ“Š Results Info",
108
+ value="Click 'Search CSV Data (Simple)' to start searching..."
109
+ )
110
+
111
+ with gr.Row():
112
+ simple_search_results_table = gr.HTML(
113
+ label="πŸ“‹ Search Results",
114
+ value="<p>No search performed yet</p>"
115
+ )
116
+
117
+ gr.Markdown("""
118
+ ---
119
+ ### 🎯 Simple Search Features
120
+
121
+ **Quick Setup:** Just select make, model, target year Β± range, and odometer target Β± range
122
+ **Smart Defaults:** Pre-filled with popular choices
123
+ **🎯 Year Range:** Set target year with ± range (e.g., 2020 ± 1 year = 2019-2021)
124
+ **🎯 Odometer Range:** Set target odometer reading with ± range (e.g., 65,000 ± 20,000 km)
125
+ **Same Power:** Uses the same CSV search engine as Traditional Search
126
+ **Dealer Ranking:** Get dealers ranked by inventory matching your criteria
127
+ **Fast Results:** Simplified interface for quicker searches
128
+ """)
129
+
130
+ return {
131
+ 'inputs': [simple_search_make, simple_search_model, simple_year_value, simple_year_range,
132
+ simple_odometer_value, simple_odometer_range, simple_search_file, simple_max_results,
133
+ simple_show_dealer_stats],
134
+ 'outputs': [simple_search_status, simple_search_info, simple_search_results_table],
135
+ 'button': simple_search_btn,
136
+ 'make_dropdown': simple_search_make,
137
+ 'model_dropdown': simple_search_model
138
+ }
ui/tabs/simple_tab.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple tab component for quick car dealer search
3
+ """
4
+
5
+ import gradio as gr
6
+ from core.config import CAR_MAKES, MAKE_MODEL_DATA, DEFAULT_VALUES
7
+
8
+
9
+ def create_simple_tab(simple_matcher):
10
+ """Create the simple prediction tab"""
11
+
12
+ with gr.Tab("πŸ” Simple ML Search"):
13
+ gr.Markdown("### Quick Car Dealer Search")
14
+ gr.Markdown("Enter just the basic details for a quick recommendation")
15
+
16
+ # Model Selection for Simple Tab
17
+ simple_model_selection = gr.Dropdown(
18
+ choices=simple_matcher.available_models if simple_matcher.model_loaded else ['Model not loaded'],
19
+ label="πŸ€– Select AI Model",
20
+ value=simple_matcher.available_models[0] if simple_matcher.available_models else 'Model not loaded',
21
+ info="WeightedEnsemble_L3 provides the best overall performance"
22
+ )
23
+
24
+ with gr.Row():
25
+ # Basic inputs
26
+ with gr.Column(scale=1):
27
+ simple_make = gr.Dropdown(
28
+ choices=CAR_MAKES,
29
+ label="Make",
30
+ value=DEFAULT_VALUES['make']
31
+ )
32
+ simple_model = gr.Dropdown(
33
+ choices=MAKE_MODEL_DATA[DEFAULT_VALUES['make']],
34
+ label="Model",
35
+ value=DEFAULT_VALUES['model']
36
+ )
37
+
38
+ with gr.Column(scale=1):
39
+ simple_year = gr.Number(
40
+ label="Year",
41
+ value=DEFAULT_VALUES['year'],
42
+ minimum=1990,
43
+ maximum=2025
44
+ )
45
+ simple_odometer = gr.Number(
46
+ label="Odometer (km)",
47
+ value=DEFAULT_VALUES['odometer'],
48
+ minimum=0
49
+ )
50
+
51
+ # Simple prediction button
52
+ simple_predict_btn = gr.Button(
53
+ "🎯 Find Best Dealers (Simple)",
54
+ variant="primary",
55
+ size="lg"
56
+ )
57
+
58
+ # Simple Results Section
59
+ with gr.Row():
60
+ with gr.Column(scale=1):
61
+ simple_top_dealer = gr.Textbox(
62
+ label="πŸ† Top Recommended Dealer",
63
+ interactive=False,
64
+ lines=2
65
+ )
66
+ simple_confidence = gr.Textbox(
67
+ label="🎯 Confidence Score",
68
+ interactive=False,
69
+ lines=1
70
+ )
71
+ with gr.Column(scale=2):
72
+ simple_detailed_results = gr.Markdown(
73
+ label="πŸ“Š Results",
74
+ value="Click 'Find Best Dealers (Simple)' to see recommendations..."
75
+ )
76
+
77
+ return {
78
+ 'inputs': [simple_make, simple_model, simple_year, simple_odometer, simple_model_selection],
79
+ 'outputs': [simple_top_dealer, simple_confidence, simple_detailed_results],
80
+ 'button': simple_predict_btn,
81
+ 'make_dropdown': simple_make,
82
+ 'model_dropdown': simple_model
83
+ }
ui/tabs/traditional_tab.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Traditional search tab component for CSV data file search
3
+ """
4
+
5
+ import gradio as gr
6
+ from core.config import DEFAULT_VALUES
7
+
8
+
9
+ def create_traditional_tab(matcher):
10
+ """Create the traditional search tab"""
11
+
12
+ with gr.Tab("πŸ“Š Traditional Search"):
13
+ gr.Markdown("### CSV Data File Search")
14
+ gr.Markdown("Search through car listing CSV files and rank dealers by inventory size")
15
+
16
+ with gr.Row():
17
+ with gr.Column(scale=1):
18
+ gr.Markdown("### πŸ—‚οΈ File Selection")
19
+ selected_file = gr.Dropdown(
20
+ choices=matcher.data_files,
21
+ label="Select CSV File",
22
+ value=matcher.data_files[0] if matcher.data_files else None,
23
+ info=f"Available files: {len(matcher.data_files)}"
24
+ )
25
+
26
+ max_results = gr.Number(
27
+ label="Max Results",
28
+ value=DEFAULT_VALUES['max_results'],
29
+ minimum=1,
30
+ maximum=1000,
31
+ info="Limit number of results returned"
32
+ )
33
+
34
+ show_dealer_stats = gr.Checkbox(
35
+ label="πŸ“Š Show Dealer Rankings",
36
+ value=True,
37
+ info="Rank dealers by number of matching cars"
38
+ )
39
+
40
+ with gr.Column(scale=2):
41
+ gr.Markdown("### πŸ” Search Filters")
42
+
43
+ with gr.Row():
44
+ search_make = gr.Textbox(
45
+ label="Make (contains)",
46
+ placeholder="e.g., Toyota, Ford",
47
+ info="Search for car manufacturer"
48
+ )
49
+ search_model = gr.Textbox(
50
+ label="Model (contains)",
51
+ placeholder="e.g., Camry, Focus",
52
+ info="Search for car model"
53
+ )
54
+
55
+ with gr.Row():
56
+ year_value = gr.Number(
57
+ label="Target Year",
58
+ value=DEFAULT_VALUES['year'],
59
+ minimum=1980,
60
+ maximum=2025,
61
+ info="Target manufacturing year"
62
+ )
63
+ year_range = gr.Slider(
64
+ minimum=0,
65
+ maximum=10,
66
+ value=2,
67
+ step=1,
68
+ label="Year Range (Β±years)",
69
+ info="Range around target year (e.g., Β±2 years)"
70
+ )
71
+
72
+ with gr.Row():
73
+ search_body_type = gr.Textbox(
74
+ label="Body Type (contains)",
75
+ placeholder="e.g., sedan, suv, hatch",
76
+ info="Vehicle body style"
77
+ )
78
+ search_fuel_type = gr.Textbox(
79
+ label="Fuel Type (contains)",
80
+ placeholder="e.g., petrol, diesel, electric",
81
+ info="Fuel/energy type"
82
+ )
83
+
84
+ with gr.Row():
85
+ odometer_value = gr.Number(
86
+ label="Target Odometer (km)",
87
+ value=75000,
88
+ minimum=0,
89
+ info="Target odometer reading (center value)"
90
+ )
91
+ odometer_range = gr.Slider(
92
+ minimum=0,
93
+ maximum=100000,
94
+ value=25000,
95
+ step=1000,
96
+ label="Odometer Range (Β±km)",
97
+ info="Range around target (e.g., Β±25,000 km)"
98
+ )
99
+
100
+ with gr.Row():
101
+ max_price = gr.Number(
102
+ label="Max Price (AUD)",
103
+ minimum=0,
104
+ info="Maximum advertised price"
105
+ )
106
+
107
+ # Traditional search button
108
+ search_btn = gr.Button(
109
+ "πŸ” Search CSV Data",
110
+ variant="primary",
111
+ size="lg"
112
+ )
113
+
114
+ # Traditional Search Results Section
115
+ with gr.Row():
116
+ search_status = gr.Textbox(
117
+ label="πŸ” Search Status",
118
+ interactive=False,
119
+ lines=2
120
+ )
121
+
122
+ with gr.Row():
123
+ search_info = gr.Markdown(
124
+ label="πŸ“Š Results Info",
125
+ value="Click 'Search CSV Data' to start searching..."
126
+ )
127
+
128
+ with gr.Row():
129
+ search_results_table = gr.HTML(
130
+ label="πŸ“‹ Search Results",
131
+ value="<p>No search performed yet</p>"
132
+ )
133
+
134
+ # Data file info section
135
+ gr.Markdown(f"""
136
+ ---
137
+ ### πŸ“ Available Data Files
138
+
139
+ **Files Found:** {len(matcher.data_files)}
140
+
141
+ **File List:**
142
+ {chr(10).join([f'β€’ {file}' for file in matcher.data_files]) if matcher.data_files else 'β€’ No CSV files found in ./data directory'}
143
+
144
+ ### πŸ” Search Features
145
+
146
+ **πŸ“Š Dealer Ranking:** Dealers ranked by number of cars matching your criteria
147
+ **πŸ† Inventory Priority:** Dealers with more matching inventory appear first
148
+ **🎯 Year Range Search:** Set target year ± range (e.g., 2020 ± 2 years = 2018-2022)
149
+ **🎯 Odometer Range Search:** Set target odometer value ± range (e.g., 75,000 ± 25,000 km = 50,000-100,000 km)
150
+ **πŸ“ˆ Column Mapping:** Uses actual dataset columns:
151
+ - **Make/Model:** `make`, `model`
152
+ - **Year:** `manu_year` (searchable by target Β± range)
153
+ - **Body Type:** `vehicle_body_type`
154
+ - **Fuel Type:** `vehicle_fuel_type`
155
+ - **Transmission:** `vehicle_transmission_type`
156
+ - **Mileage:** `odometer` (searchable by target Β± range)
157
+ - **Price:** `advertised_price`
158
+ - **Dealer:** `dealer_trading_name`
159
+ - **Location:** `dealer_city`, `dealer_state`
160
+
161
+ ### πŸ“Š Dealer Ranking System
162
+
163
+ **How it works:** Counts how many cars each dealer has that match your search criteria
164
+ **Ranking Logic:** Dealers with more matching cars get better ranks (Rank 1 = most inventory)
165
+ **Sorting:** Results sorted by dealer inventory count first, then by price
166
+ **Performance:** Fast counting using pandas group operations
167
+ """)
168
+
169
+ return {
170
+ 'inputs': [search_make, search_model, year_value, year_range, search_body_type,
171
+ search_fuel_type, odometer_value, odometer_range, max_price, selected_file, max_results, show_dealer_stats],
172
+ 'outputs': [search_status, search_info, search_results_table],
173
+ 'button': search_btn
174
+ }
utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Utility functions for the Swiper Match application
utils/helpers.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Helper utility functions for the Swiper Match application
3
+ """
4
+
5
+ import logging
6
+
7
+ # Set up logging
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def get_model_status_info(matcher, simple_matcher):
13
+ """Generate model status information for the footer"""
14
+ detailed_status_emoji = "βœ…" if matcher.model_loaded else "❌"
15
+ simple_status_emoji = "βœ…" if simple_matcher.model_loaded else "❌"
16
+
17
+ return f"""
18
+ ### πŸ“Š Model Information
19
+
20
+ #### πŸ”§ **Detailed Model** (Advanced Search)
21
+ **Status:** {detailed_status_emoji} {"Model Loaded Successfully" if matcher.model_loaded else "Model Not Available"}
22
+ **Path:** ./autogluon_model
23
+ **Trained Dealers:** {len(matcher.trained_dealers) if matcher.model_loaded else "N/A"}
24
+ **Available Models:** {len(matcher.available_models) if matcher.model_loaded else "N/A"} ({', '.join(matcher.available_models[:3]) + ('...' if len(matcher.available_models) > 3 else '') if matcher.model_loaded and matcher.available_models else "N/A"})
25
+ **Features:** 23 vehicle specifications (no pricing data)
26
+
27
+ #### πŸ” **Simple Model** (Quick Search)
28
+ **Status:** {simple_status_emoji} {"Model Loaded Successfully" if simple_matcher.model_loaded else "Model Not Available"}
29
+ **Path:** ./simple_autogluon_models
30
+ **Trained Dealers:** {len(simple_matcher.trained_dealers) if simple_matcher.model_loaded else "N/A"}
31
+ **Available Models:** {len(simple_matcher.available_models) if simple_matcher.model_loaded else "N/A"} ({', '.join(simple_matcher.available_models[:3]) + ('...' if len(simple_matcher.available_models) > 3 else '') if simple_matcher.model_loaded and simple_matcher.available_models else "N/A"})
32
+ **Features:** Subset of vehicle specifications for faster inference
33
+
34
+ #### πŸ€– **Architecture**
35
+ **Framework:** AutoGluon TabularPredictor
36
+ **Ensemble Learning:** Multiple algorithms combined via weighted voting
37
+ **Algorithms:** RandomForest, XGBoost, NeuralNetTorch, CatBoost, LightGBM
38
+ **Task:** Multi-class classification for dealer prediction
39
+ """
40
+
41
+
42
+ def get_app_header():
43
+ """Generate the main application header"""
44
+ return """
45
+ # πŸš— Swiper Match - Car Dealer Predictor
46
+ ### AI-Powered Dealer Matching Based on Vehicle Specifications
47
+ Find the perfect dealer for your dream car using machine learning
48
+ """
49
+
50
+
51
+ def get_app_description():
52
+ """Generate the application description"""
53
+ return """
54
+ **🎯 How it works:** This tool analyzes vehicle specifications to predict which dealers are most likely to have cars matching your preferences. The model focuses on technical specifications, not pricing.
55
+ """
56
+
57
+
58
+ def setup_event_handlers(tabs_data, interface_functions, matchers):
59
+ """Setup event handlers for all tabs"""
60
+ simple_tab, detailed_tab, traditional_tab, simple_search_tab = tabs_data
61
+ matcher, simple_matcher = matchers
62
+
63
+ # Simple tab click event
64
+ simple_tab['button'].click(
65
+ fn=lambda *args: interface_functions['simple_predict'](simple_matcher, *args),
66
+ inputs=simple_tab['inputs'],
67
+ outputs=simple_tab['outputs']
68
+ )
69
+
70
+ # Detailed tab click event
71
+ detailed_tab['button'].click(
72
+ fn=lambda *args: interface_functions['predict'](matcher, *args),
73
+ inputs=detailed_tab['inputs'],
74
+ outputs=detailed_tab['outputs']
75
+ )
76
+
77
+ # Traditional search click event
78
+ traditional_tab['button'].click(
79
+ fn=lambda *args: interface_functions['search'](matcher, *args),
80
+ inputs=traditional_tab['inputs'],
81
+ outputs=traditional_tab['outputs']
82
+ )
83
+
84
+ # Simple traditional search click event
85
+ simple_search_tab['button'].click(
86
+ fn=lambda *args: interface_functions['simple_search'](matcher, *args),
87
+ inputs=simple_search_tab['inputs'],
88
+ outputs=simple_search_tab['outputs']
89
+ )
90
+
91
+ # Set up dynamic model updating based on make selection
92
+ simple_tab['make_dropdown'].change(
93
+ fn=interface_functions['update_models'],
94
+ inputs=simple_tab['make_dropdown'],
95
+ outputs=simple_tab['model_dropdown']
96
+ )
97
+
98
+ detailed_tab['make_dropdown'].change(
99
+ fn=interface_functions['update_models'],
100
+ inputs=detailed_tab['make_dropdown'],
101
+ outputs=detailed_tab['model_dropdown']
102
+ )
103
+
104
+ # Simple traditional search make/model update
105
+ simple_search_tab['make_dropdown'].change(
106
+ fn=interface_functions['update_models'],
107
+ inputs=simple_search_tab['make_dropdown'],
108
+ outputs=simple_search_tab['model_dropdown']
109
+ )