|
|
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: |
|
|
|
|
|
print("Loading data...") |
|
|
df = pd.read_csv(file.name, nrows=10000) |
|
|
print(f"Loaded {len(df)} data rows") |
|
|
|
|
|
|
|
|
if len(df) == 0: |
|
|
return "Error: Empty file", None, None |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
if len(df) > 5000: |
|
|
df = df.sample(n=5000, random_state=42) |
|
|
print(f"Sampled down to {len(df)} rows for HF Spaces") |
|
|
|
|
|
|
|
|
print("Processing distances...") |
|
|
df['distance'] = 100.0 |
|
|
|
|
|
|
|
|
print("Creating spatial grid...") |
|
|
lat_min, lat_max = df['lat'].min(), df['lat'].max() |
|
|
lng_min, lng_max = df['lng'].min(), df['lng'].max() |
|
|
|
|
|
|
|
|
df['lat_bin'] = ((df['lat'] - lat_min) // 0.01).astype(int) |
|
|
df['lng_bin'] = ((df['lng'] - lng_min) // 0.01).astype(int) |
|
|
|
|
|
|
|
|
df['lat_grid'] = df['lat_bin'].astype(str) |
|
|
df['lng_grid'] = df['lng_bin'].astype(str) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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']) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
df_ml['predicted_demand'] = 0.0 |
|
|
|
|
|
|
|
|
X = df_ml.drop(['lat_grid', 'lng_grid', 'zone_density', 'target'], axis=1) |
|
|
|
|
|
|
|
|
predictions = model.predict(X) |
|
|
|
|
|
|
|
|
df_ml['predicted_demand'] = np.expm1(predictions) |
|
|
|
|
|
|
|
|
predictions_df = df_ml[['lat_grid', 'lng_grid', 'zone_avg_alt', 'zone_avg_azm', |
|
|
'zone_point_count', 'target', 'predicted_demand']].copy() |
|
|
|
|
|
|
|
|
zone_centers = df.groupby(['lat_grid', 'lng_grid']).agg({ |
|
|
'lat': 'mean', |
|
|
'lng': 'mean' |
|
|
}).reset_index() |
|
|
|
|
|
|
|
|
predictions_df = pd.merge(predictions_df, zone_centers, on=['lat_grid', 'lng_grid'], how='left') |
|
|
|
|
|
|
|
|
predictions_df['actual_demand'] = np.expm1(predictions_df['target']) |
|
|
predictions_df['priority_score'] = predictions_df['predicted_demand'] |
|
|
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'] |
|
|
|
|
|
|
|
|
if len(predictions_df) > 100: |
|
|
predictions_df = predictions_df.head(100) |
|
|
print(f"Limited to top 100 zones for HF Spaces") |
|
|
|
|
|
|
|
|
predictions_df = predictions_df.sort_values(by='priority_score', ascending=False) |
|
|
|
|
|
|
|
|
top_n = min(5, len(predictions_df)) |
|
|
top_zones = predictions_df.head(top_n) |
|
|
|
|
|
if len(top_zones) == 0: |
|
|
return "No valid zones found", None, None |
|
|
|
|
|
|
|
|
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, |
|
|
tiles='OpenStreetMap', |
|
|
width=600, |
|
|
height=400 |
|
|
) |
|
|
|
|
|
|
|
|
for index, row in top_zones.iterrows(): |
|
|
folium.Marker( |
|
|
location=[row['lat'], row['lng']], |
|
|
popup=f"Demand: {row['priority_score']:.1f}", |
|
|
icon=folium.Icon(color='red', icon='star') |
|
|
).add_to(m) |
|
|
|
|
|
|
|
|
try: |
|
|
markers_html = m._repr_html_() |
|
|
except: |
|
|
markers_html = "<p>Map generation failed - please try with smaller file</p>" |
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
heat_zones = predictions_df.head(min(50, len(predictions_df))) |
|
|
|
|
|
|
|
|
heat_data = [] |
|
|
for index, row in heat_zones.iterrows(): |
|
|
value = max(0.1, abs(row['demand_supply_difference'])) |
|
|
heat_data.append([row['lat'], row['lng'], value]) |
|
|
|
|
|
|
|
|
m_heatmap = folium.Map( |
|
|
location=[map_center_lat, map_center_lng], |
|
|
zoom_start=10, |
|
|
tiles='OpenStreetMap', |
|
|
width=600, |
|
|
height=400 |
|
|
) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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) |