File size: 11,238 Bytes
cd5feed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6070490
cd5feed
6070490
cd5feed
 
94899de
 
 
 
cd5feed
 
94899de
 
 
 
6070490
 
 
 
cd5feed
6070490
94899de
6070490
cd5feed
6070490
94899de
cd5feed
 
 
6070490
 
 
cd5feed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6070490
 
 
 
94899de
cd5feed
 
 
6070490
 
cd5feed
 
94899de
 
 
6070490
cd5feed
 
6070490
 
 
 
 
 
 
 
 
cd5feed
 
 
6070490
 
cd5feed
 
6070490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd5feed
6070490
cd5feed
 
 
6070490
 
 
 
cd5feed
6070490
 
 
 
cd5feed
 
 
 
 
 
 
6070490
cd5feed
6070490
cd5feed
 
 
6070490
cd5feed
6070490
cd5feed
6070490
cd5feed
 
 
6070490
 
 
cd5feed
 
6070490
cd5feed
 
 
 
6070490
 
 
 
 
cd5feed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6070490
cd5feed
6070490
cd5feed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94899de
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
import gradio as gr
import pandas as pd
import numpy as np
import joblib
import folium
from folium.plugins import HeatMap

# Загружаем модель
MODEL_PATH = "model/optimization_model.joblib"
try:
    model = joblib.load(MODEL_PATH)
    print("Модель загружена успешно")
except:
    model = None
    print("Не удалось загрузить модель")

def create_maps(file):
    """Creating two maps from notebook: markers map and heatmap"""
    if file is None:
        return "Please upload geodata file", None, None
    
    try:
        # Very aggressive data limits for HF Spaces
        print("Loading data...")
        df = pd.read_csv(file.name, nrows=10000)  # Drastically reduced to 10k rows
        print(f"Loaded {len(df)} data rows")
        
        # Check file size
        if len(df) == 0:
            return "Error: Empty file", None, None
        
        # Check columns
        required_cols = ['lat', 'lng', 'spd', 'alt', 'azm', 'randomized_id']
        missing_cols = [col for col in required_cols if col not in df.columns]
        if missing_cols:
            return f"Missing columns: {missing_cols}. Available: {list(df.columns)}", None, None
        
        # Aggressive sampling for HF Spaces memory limits
        if len(df) > 5000:
            df = df.sample(n=5000, random_state=42)
            print(f"Sampled down to {len(df)} rows for HF Spaces")
        
        # Simplified distance calculation
        print("Processing distances...")
        df['distance'] = 100.0  # Constant distance
        
        # Create coarser grid for fewer zones
        print("Creating spatial grid...")
        lat_min, lat_max = df['lat'].min(), df['lat'].max()
        lng_min, lng_max = df['lng'].min(), df['lng'].max()
        
        # Use larger grid cells (0.01 instead of 0.005) for fewer zones
        df['lat_bin'] = ((df['lat'] - lat_min) // 0.01).astype(int)
        df['lng_bin'] = ((df['lng'] - lng_min) // 0.01).astype(int)
        
        # Create string identifiers for grouping
        df['lat_grid'] = df['lat_bin'].astype(str)
        df['lng_grid'] = df['lng_bin'].astype(str)
        
        # Aggregate by zones as in notebook
        df_zone_stats = df.groupby(['lat_grid', 'lng_grid']).agg(
            zone_avg_spd=('spd', 'mean'),
            zone_spd_std=('spd', 'std'),
            zone_min_spd=('spd', 'min'),
            zone_max_spd=('spd', 'max'),
            zone_avg_alt=('alt', 'mean'),
            zone_alt_std=('alt', 'std'),
            zone_min_alt=('alt', 'min'),
            zone_max_alt=('alt', 'max'),
            zone_avg_azm=('azm', 'mean'),
            zone_azm_std=('azm', 'std'),
            zone_point_count=('randomized_id', 'count'),
            zone_total_distance=('distance', 'sum')
        ).reset_index().fillna(0)
        
        # Create target variable
        zone_counts = df.groupby(['lat_grid', 'lng_grid'])['randomized_id'].nunique().reset_index(name='zone_density')
        zone_counts['target'] = np.log1p(zone_counts['zone_density'])
        
        # Merge data
        df_ml = pd.merge(df_zone_stats, zone_counts, on=['lat_grid', 'lng_grid'], how='inner')
        
        if model is None:
            return "Model not loaded", None, None
        
        # FIX: model expects predicted_demand in data
        # Add dummy column with value 0
        df_ml['predicted_demand'] = 0.0
        
        # Use all columns except identifiers and target variable
        X = df_ml.drop(['lat_grid', 'lng_grid', 'zone_density', 'target'], axis=1)
        
        # Predict
        predictions = model.predict(X)
        
        # Replace dummy values with real predictions (convert from log-scale)
        df_ml['predicted_demand'] = np.expm1(predictions)
        
        # Create predictions_df as in notebook
        predictions_df = df_ml[['lat_grid', 'lng_grid', 'zone_avg_alt', 'zone_avg_azm', 
                               'zone_point_count', 'target', 'predicted_demand']].copy()
        
        # Calculate zone center coordinates - use grouping of original data
        zone_centers = df.groupby(['lat_grid', 'lng_grid']).agg({
            'lat': 'mean',
            'lng': 'mean'
        }).reset_index()
        
        # Merge with predictions
        predictions_df = pd.merge(predictions_df, zone_centers, on=['lat_grid', 'lng_grid'], how='left')
        
        # Add calculations as in notebook
        predictions_df['actual_demand'] = np.expm1(predictions_df['target'])
        predictions_df['priority_score'] = predictions_df['predicted_demand']  # Already converted
        predictions_df['supply'] = predictions_df['zone_point_count'] / predictions_df['zone_point_count'].mean()
        predictions_df['demand_supply_ratio'] = predictions_df['priority_score'] / predictions_df['supply']
        predictions_df['demand_supply_difference'] = predictions_df['priority_score'] - predictions_df['supply']
        
        # Aggressive limits for HF Spaces
        if len(predictions_df) > 100:  # Drastically reduced from 1000
            predictions_df = predictions_df.head(100)
            print(f"Limited to top 100 zones for HF Spaces")
        
        # Sort by priority
        predictions_df = predictions_df.sort_values(by='priority_score', ascending=False)
        
        # === MAP 1: Minimal markers map for HF Spaces ===
        top_n = min(5, len(predictions_df))  # Only 5 markers instead of 10
        top_zones = predictions_df.head(top_n)
        
        if len(top_zones) == 0:
            return "No valid zones found", None, None
        
        # Create minimal map
        map_center_lat = top_zones['lat'].mean()
        map_center_lng = top_zones['lng'].mean()
        m = folium.Map(
            location=[map_center_lat, map_center_lng], 
            zoom_start=10,  # Reduced zoom
            tiles='OpenStreetMap',
            width=600,  # Fixed width
            height=400  # Fixed height
        )
        
        # Minimal markers with simple tooltips
        for index, row in top_zones.iterrows():
            folium.Marker(
                location=[row['lat'], row['lng']],
                popup=f"Demand: {row['priority_score']:.1f}",  # Use popup instead of tooltip
                icon=folium.Icon(color='red', icon='star')
            ).add_to(m)
        
        # Get HTML for first map (with error handling)
        try:
            markers_html = m._repr_html_()
        except:
            markers_html = "<p>Map generation failed - please try with smaller file</p>"
        
        # === MAP 2: Ultra-simplified heatmap for HF Spaces ===
        try:
            # Minimal heatmap data (only top 50 zones)
            heat_zones = predictions_df.head(min(50, len(predictions_df)))
            
            # Simple heatmap data with positive values only
            heat_data = []
            for index, row in heat_zones.iterrows():
                value = max(0.1, abs(row['demand_supply_difference']))  # Ensure positive values
                heat_data.append([row['lat'], row['lng'], value])
            
            # Minimal heatmap
            m_heatmap = folium.Map(
                location=[map_center_lat, map_center_lng], 
                zoom_start=10,
                tiles='OpenStreetMap',
                width=600,
                height=400
            )
            
            # Simple heatmap with minimal settings
            if heat_data:
                HeatMap(heat_data, radius=10, blur=10).add_to(m_heatmap)
            
            heatmap_html = m_heatmap._repr_html_()
        except Exception as e:
            heatmap_html = f"<p>Heatmap generation failed: {str(e)}</p>"
        
        status = f"✅ Processed {len(predictions_df)} zones from {len(df)} data points (HF Spaces optimized)"
        
        return status, markers_html, heatmap_html
        
    except MemoryError:
        return "❌ File too large for HF Spaces. Please use a smaller dataset (< 1MB)", None, None
    except pd.errors.EmptyDataError:
        return "❌ Empty or invalid file", None, None
    except Exception as e:
        error_msg = str(e)
        if "BodyStreamBuffer" in error_msg:
            return "❌ Processing timeout. Please use a smaller file (< 5000 rows)", None, None
        return f"❌ Error: {error_msg}", None, None

# Create beautiful Gradio interface
with gr.Blocks(
    title="Driver Placement Optimization System",
    theme=gr.themes.Soft(),
    css="""
    .main-container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 15px;
    }
    .header {
        text-align: center;
        margin-bottom: 20px;
        color: white;
        padding: 15px;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        border-radius: 10px;
    }
    .upload-section {
        background: #f8f9fa;
        padding: 15px;
        border-radius: 8px;
        margin-bottom: 15px;
    }
    .maps-container {
        gap: 15px;
    }
    .map-card {
        background: white;
        border: 1px solid #e0e0e0;
        border-radius: 8px;
        padding: 10px;
        box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        max-height: 500px;
        overflow: hidden;
    }
    """
) as interface:
    
    with gr.Column(elem_classes="main-container"):
        with gr.Row(elem_classes="header"):
            gr.Markdown(
                """
                # Driver Placement Optimization System
                ### Geodata analysis for optimal placement zones
                """,
                elem_classes="header-text"
            )
        
        with gr.Row(elem_classes="upload-section"):
            with gr.Column():
                gr.Markdown("### Data Upload")
                gr.Markdown("⚠️ **HF Spaces Limits**: Max 10,000 rows, 5MB file size")
                file_input = gr.File(
                    label="Select file with geodata (CSV format)",
                    elem_id="file-upload"
                )
                status_output = gr.Textbox(
                    label="Processing Status",
                    interactive=False,
                    lines=2
                )
        
        gr.Markdown("### Analysis Results")
        
        with gr.Row(elem_classes="maps-container"):
            with gr.Column(elem_classes="map-card"):
                gr.Markdown("#### Priority Zones Map")
                gr.Markdown("*Displays top-10 zones with highest demand*")
                map1_output = gr.HTML(
                    label="Top Zones Map for Driver Placement",
                    elem_id="map1"
                )
            
            with gr.Column(elem_classes="map-card"):
                gr.Markdown("#### Imbalance Heatmap")
                gr.Markdown("*Shows difference between demand and supply*")
                map2_output = gr.HTML(
                    label="Demand-Supply Imbalance Heatmap",
                    elem_id="map2"
                )
    
    file_input.change(
        fn=create_maps,
        inputs=file_input,
        outputs=[status_output, map1_output, map2_output]
    )

if __name__ == "__main__":
    interface.launch(server_name="0.0.0.0", debug=False, show_error=True)