Spaces:
Sleeping
Sleeping
File size: 6,328 Bytes
4bdde62 | 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 | import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
import plotly.graph_objects as go
import io
def generate_mock_export():
"""
Genera un CSV con 4 settimane di dati (28 giorni) per simulare una pianificazione.
Include pattern orari (picchi 10:00/16:00), settimanali (Lunedì alto) e festivi.
"""
# Generiamo dati partendo da un Lunedì recente
start_date = pd.Timestamp("2025-05-05")
periods = 28 * 24 * 4
dates = pd.date_range(start=start_date, periods=periods, freq='15min')
df = pd.DataFrame({'Timestamp': dates})
df['Hour'] = df['Timestamp'].dt.hour
df['DayOfWeek'] = df['Timestamp'].dt.dayofweek
base_volume = 10
df['Volume'] = base_volume
mask_work = (df['Hour'] >= 8) & (df['Hour'] <= 21)
df.loc[mask_work, 'Volume'] += 50
mask_peak1 = (df['Hour'] >= 10) & (df['Hour'] <= 11)
mask_peak2 = (df['Hour'] >= 15) & (df['Hour'] <= 16)
df.loc[mask_peak1, 'Volume'] += 40
df.loc[mask_peak2, 'Volume'] += 30
df.loc[df['Hour'] == 13, 'Volume'] -= 15
df.loc[df['DayOfWeek'] <= 1, 'Volume'] *= 1.2
df.loc[df['DayOfWeek'] >= 5, 'Volume'] *= 0.2
df.loc[df['Hour'] < 7, 'Volume'] = np.random.randint(0, 5, size=sum(df['Hour'] < 7))
noise = np.random.randint(-5, 10, size=len(df))
df['Volume'] += noise
df['Volume'] = df['Volume'].clip(lower=0).astype(int)
csv_buffer = io.StringIO()
df[['Timestamp', 'Volume']].to_csv(csv_buffer, index=False)
csv_buffer.seek(0)
return csv_buffer.getvalue()
def predict_workload(file_obj):
"""
Logica Backtesting con Output PLOTLY (Interattivo).
"""
if file_obj is None: return None, "⚠️ Seleziona un file CSV dalla sidebar laterale."
try:
# Caricamento file cvs
if hasattr(file_obj, 'name'):
df = pd.read_csv(file_obj.name)
else:
df = pd.read_csv(file_obj)
df['Timestamp'] = pd.to_datetime(df['Timestamp'])
df = df.sort_values('Timestamp')
# --- LOGICA SPLIT settimana n da settimane n-1 ---
last_timestamp = df['Timestamp'].max()
days_to_subtract = last_timestamp.dayofweek
split_date = (last_timestamp - pd.Timedelta(days=days_to_subtract)).normalize()
train_df = df[df['Timestamp'] < split_date].copy()
test_df = df[df['Timestamp'] >= split_date].copy()
if len(train_df) == 0 or len(test_df) == 0:
return None, "⚠️ Dati insufficienti per il backtesting."
# --- TRAINING ---
for d in [train_df, test_df]:
d['Hour'] = d['Timestamp'].dt.hour
d['Minute'] = d['Timestamp'].dt.minute
d['DayOfWeek'] = d['Timestamp'].dt.dayofweek
X_train = train_df[['Hour', 'Minute', 'DayOfWeek']]
y_train = train_df['Volume']
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# --- PREDICTION ---
X_test = test_df[['Hour', 'Minute', 'DayOfWeek']]
test_df['Predicted_Volume'] = model.predict(X_test).astype(int)
# --- METRICS ---
total_actual = test_df['Volume'].sum()
total_pred = test_df['Predicted_Volume'].sum()
mae = mean_absolute_error(test_df['Volume'], test_df['Predicted_Volume'])
diff_perc = ((total_pred - total_actual) / total_actual) * 100
# ==========================================
# CREAZIONE GRAFICO INTERATTIVO (PLOTLY)
# ==========================================
# Creiamo un oggetto Figure
fig = go.Figure()
# 1. Serie: Dati Reali
fig.add_trace(go.Scatter(
x=test_df['Timestamp'],
y=test_df['Volume'],
mode='lines',
name='Reale (Consuntivo)',
line=dict(color='#3b82f6', width=2),
fill='tozeroy', # Riempie l'area sotto
fillcolor='rgba(59, 130, 246, 0.2)' # Blu trasparente
))
# 2. Serie: Forecast AI (Previsione)
fig.add_trace(go.Scatter(
x=test_df['Timestamp'],
y=test_df['Predicted_Volume'],
mode='lines+markers', # Linea con pallini sui punti
name='Forecast AI (Pianificato)',
line=dict(color='#f97316', width=3, dash='solid'),
marker=dict(size=4)
))
# 3. Layout e Stile
fig.update_layout(
title=f"<b>Analisi Comparativa:</b> Reale vs AI (Settimana corrente)",
xaxis_title="Fascia Oraria (15min)",
yaxis_title="Volume Chiamate",
template="plotly_white", # Sfondo bianco pulito
hovermode="x unified", # mostra entrambi i valori al passaggio del mouse
legend=dict(
orientation="h", # Legenda orizzontale in alto
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
),
height=500, # Altezza fissa
margin=dict(l=20, r=20, t=60, b=20)
)
# 4. Aggiunta Range Slider (Barra sotto per scorrere i giorni)
fig.update_xaxes(
rangeslider_visible=True,
rangeselector=dict(
buttons=list([
dict(count=1, label="1gg", step="day", stepmode="backward"),
dict(count=3, label="3gg", step="day", stepmode="backward"),
dict(step="all", label="Settimana")
])
)
)
# Statistiche testuali
msg = (
f"✅ **Analisi Interattiva Completata**\n"
f"📊 **Risultati Backtesting:**\n"
f"• Volume Reale: {total_actual}\n"
f"• Volume Previsto AI: {total_pred} ({diff_perc:+.1f}%)\n"
f"• Scostamento Medio (MAE): {mae:.1f} chiamate/slot\n\n"
f"💡 *Usa lo slider in basso per zoomare su un giorno specifico.*"
)
# Gradio gr.Plot accetta l'oggetto fig direttamente
return fig, msg
except Exception as e:
# In caso di errore restituiamo un grafico vuoto e il messaggio
return go.Figure(), f"Errore Tecnico: {str(e)}" |