import pandas as pd import numpy as np import plotly.express as px import plotly.graph_objects as go from dash import Dash, dcc, html, Input, Output, State, callback, no_update import tensorflow as tf import joblib from sklearn.preprocessing import StandardScaler import dash_bootstrap_components as dbc from datetime import datetime, timedelta import re import os import json DEV_MODE = os.getenv('DEV_MODE', 'True').lower() == 'true' CONFIG_FILE = 'olive_config.json' def load_config(): default_config = { 'oliveto': { 'hectares': 1, 'varieties': [ { 'variety': olive_varieties['Varietà di Olive'].iloc[0], 'technique': 'Tradizionale', 'percentage': 100 } ] }, 'costs': { 'fixed': { 'ammortamento': 2000, 'assicurazione': 500, 'manutenzione': 800 }, 'variable': { 'raccolta': 0.35, 'potatura': 600, 'fertilizzanti': 400 }, 'transformation': { 'molitura': 0.15, 'stoccaggio': 0.20, 'bottiglia': 1.20, 'etichettatura': 0.30 }, 'selling_price': 12.00 } } try: if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r') as f: return json.load(f) return default_config except Exception as e: print(f"Errore nel caricamento della configurazione: {e}") return default_config # Funzione per salvare la configurazione def save_config(config): try: with open(CONFIG_FILE, 'w') as f: json.dump(config, f, indent=4) return True except Exception as e: print(f"Errore nel salvataggio della configurazione: {e}") return False # Caricamento dati print("Inizializzazione della dashboard...") try: # Caricamento dati e modello print("Caricamento dati...") simulated_data = pd.read_parquet("./data/simulated_data.parquet") weather_data = pd.read_parquet("./data/weather_data_complete.parquet") olive_varieties = pd.read_parquet("./data/olive_varieties.parquet") if not DEV_MODE: print("Caricamento modello e scaler...") model = tf.keras.models.load_model('./models/oli_transformer/olive_transformer.keras') scaler_temporal = joblib.load('./models/oli_transformer/scaler_temporal.joblib') scaler_static = joblib.load('./models/oli_transformer/scaler_static.joblib') scaler_y = joblib.load('./models/oli_transformer/scaler_y.joblib') else: print("Modalità sviluppo attiva - Modelli non caricati") except Exception as e: print(f"Errore nel caricamento: {str(e)}") raise e def clean_column_name(name): # Rimuove caratteri speciali e spazi, converte in snake_case e abbrevia name = re.sub(r'[^a-zA-Z0-9\s]', '', name) # Rimuove caratteri speciali name = name.lower().replace(' ', '_') # Converte in snake_case # Abbreviazioni comuni abbreviations = { 'production': 'prod', 'percentage': 'pct', 'hectare': 'ha', 'tonnes': 't', 'litres': 'l', 'minimum': 'min', 'maximum': 'max', 'average': 'avg' } for full, abbr in abbreviations.items(): name = name.replace(full, abbr) return name # Funzioni di supporto per la dashboard def prepare_static_features_multiple(varieties_info, percentages, hectares, all_varieties): """ Prepara le feature statiche per multiple varietà seguendo la struttura esatta della simulazione. Args: varieties_info (list): Lista di dizionari contenenti le informazioni sulle varietà selezionate percentages (list): Lista delle percentuali corrispondenti a ciascuna varietà selezionata hectares (float): Numero di ettari totali all_varieties (list): Lista di tutte le possibili varietà nel dataset originale Returns: np.array: Array numpy contenente tutte le feature statiche """ # Inizializza un dizionario per tutte le varietà possibili variety_data = {variety: { 'pct': 0, 'prod_t_ha': 0, 'oil_prod_t_ha': 0, 'oil_prod_l_ha': 0, 'min_yield_pct': 0, 'max_yield_pct': 0, 'min_oil_prod_l_ha': 0, 'max_oil_prod_l_ha': 0, 'avg_oil_prod_l_ha': 0, 'l_per_t': 0, 'min_l_per_t': 0, 'max_l_per_t': 0, 'avg_l_per_t': 0, 'tech': '' } for variety in all_varieties} # Aggiorna i dati per le varietà selezionate for variety_info, percentage in zip(varieties_info, percentages): variety_name = clean_column_name(variety_info['Varietà di Olive']) technique = clean_column_name(variety_info['Tecnica di Coltivazione']) # Base production calculations annual_prod = variety_info['Produzione (tonnellate/ettaro)'] * 1000 * percentage / 100 * hectares min_oil_prod = annual_prod * variety_info['Min Litri per Tonnellata'] / 1000 max_oil_prod = annual_prod * variety_info['Max Litri per Tonnellata'] / 1000 avg_oil_prod = annual_prod * variety_info['Media Litri per Tonnellata'] / 1000 # Water need calculation base_water_need = ( variety_info['Fabbisogno Acqua Primavera (m³/ettaro)'] + variety_info['Fabbisogno Acqua Estate (m³/ettaro)'] + variety_info['Fabbisogno Acqua Autunno (m³/ettaro)'] + variety_info['Fabbisogno Acqua Inverno (m³/ettaro)'] ) / 4 * percentage / 100 * hectares variety_data[variety_name].update({ 'pct': percentage / 100, 'prod_t_ha': variety_info['Produzione (tonnellate/ettaro)'], 'oil_prod_t_ha': variety_info['Produzione Olio (tonnellate/ettaro)'], 'oil_prod_l_ha': variety_info['Produzione Olio (litri/ettaro)'], 'min_yield_pct': variety_info['Min % Resa'], 'max_yield_pct': variety_info['Max % Resa'], 'min_oil_prod_l_ha': variety_info['Min Produzione Olio (litri/ettaro)'], 'max_oil_prod_l_ha': variety_info['Max Produzione Olio (litri/ettaro)'], 'avg_oil_prod_l_ha': variety_info['Media Produzione Olio (litri/ettaro)'], 'l_per_t': variety_info['Litri per Tonnellata'], 'min_l_per_t': variety_info['Min Litri per Tonnellata'], 'max_l_per_t': variety_info['Max Litri per Tonnellata'], 'avg_l_per_t': variety_info['Media Litri per Tonnellata'], 'tech': technique }) # Crea il vettore delle feature nell'ordine esatto static_features = [hectares] # Inizia con gli ettari # Lista delle feature per ogni varietà variety_features = ['pct', 'prod_t_ha', 'oil_prod_t_ha', 'oil_prod_l_ha', 'min_yield_pct', 'max_yield_pct', 'min_oil_prod_l_ha', 'max_oil_prod_l_ha', 'avg_oil_prod_l_ha', 'l_per_t', 'min_l_per_t', 'max_l_per_t', 'avg_l_per_t'] # Appiattisci i dati delle varietà mantenendo l'ordine esatto for variety in all_varieties: # Feature esistenti for feature in variety_features: static_features.append(variety_data[variety][feature]) # Feature binarie per le tecniche di coltivazione for technique in ['tradizionale', 'intensiva', 'superintensiva']: static_features.append(1 if variety_data[variety]['tech'] == technique else 0) print(f"lunghezza features {len(static_features)} ") return np.array(static_features).reshape(1, -1) def mock_make_prediction(weather_data, varieties_info, percentages, hectares): """ Versione mock della funzione make_prediction che simula risultati realistici basati sui dati reali delle varietà e tiene conto degli ettari """ try: # Calcola la produzione di olive basata sui dati reali delle varietà per ettaro olive_production_per_ha = sum( variety_info['Produzione (tonnellate/ettaro)'] * 1000 * (percentage / 100) for variety_info, percentage in zip(varieties_info, percentages) ) # Applica il fattore ettari olive_production = olive_production_per_ha * hectares # Aggiungi una variabilità realistica basata sulle condizioni meteorologiche recenti recent_weather = weather_data.tail(3) weather_factor = 1.0 # Temperatura influenza la produzione avg_temp = recent_weather['temp'].mean() if avg_temp < 15: weather_factor *= 0.9 elif avg_temp > 25: weather_factor *= 0.95 # Precipitazioni influenzano la produzione total_precip = recent_weather['precip'].sum() if total_precip < 30: # Siccità weather_factor *= 0.85 elif total_precip > 200: # Troppa pioggia weather_factor *= 0.9 # Radiazione solare influenza la produzione avg_solar = recent_weather['solarradiation'].mean() if avg_solar < 150: weather_factor *= 0.95 # Applica il fattore meteorologico alla produzione totale olive_production = olive_production * weather_factor # Calcola la produzione di olio basata sulle rese delle varietà oil_per_ha = 0 oil_total = 0 for variety_info, percentage in zip(varieties_info, percentages): # Calcolo della produzione di olio per ettaro per questa varietà variety_oil_per_ha = variety_info['Produzione Olio (litri/ettaro)'] * (percentage / 100) oil_per_ha += variety_oil_per_ha # Calcolo della produzione totale di olio per questa varietà variety_oil_total = variety_oil_per_ha * hectares oil_total += variety_oil_total # Applica il fattore meteorologico anche alla produzione di olio oil_total = oil_total * weather_factor oil_per_ha = oil_per_ha * weather_factor # Calcola il fabbisogno idrico considerando la stagione attuale e gli ettari current_month = datetime.now().month seasons = { 'Primavera': [3, 4, 5], 'Estate': [6, 7, 8], 'Autunno': [9, 10, 11], 'Inverno': [12, 1, 2] } current_season = next(season for season, months in seasons.items() if current_month in months) season_water_need = { 'Primavera': 'Fabbisogno Acqua Primavera (m³/ettaro)', 'Estate': 'Fabbisogno Acqua Estate (m³/ettaro)', 'Autunno': 'Fabbisogno Acqua Autunno (m³/ettaro)', 'Inverno': 'Fabbisogno Acqua Inverno (m³/ettaro)' } # Calcolo del fabbisogno idrico per ettaro water_need_per_ha = sum( variety_info[season_water_need[current_season]] * (percentage / 100) for variety_info, percentage in zip(varieties_info, percentages) ) # Applica il fattore ettari al fabbisogno idrico total_water_need = water_need_per_ha * hectares # Prepara i dettagli per varietà variety_details = [] for variety_info, percentage in zip(varieties_info, percentages): variety_prod_per_ha = variety_info['Produzione (tonnellate/ettaro)'] * 1000 * (percentage / 100) variety_prod_total = variety_prod_per_ha * hectares * weather_factor # Calcolo corretto dell'olio per varietà variety_oil_per_ha = variety_info['Produzione Olio (litri/ettaro)'] * (percentage / 100) variety_oil_total = variety_oil_per_ha * hectares * weather_factor variety_details.append({ 'variety': variety_info['Varietà di Olive'], 'percentage': percentage, 'production_per_ha': variety_prod_per_ha, 'production_total': variety_prod_total, 'oil_per_ha': variety_oil_per_ha, 'oil_total': variety_oil_total, 'water_need': variety_info[season_water_need[current_season]] * hectares }) return { 'olive_production': olive_production_per_ha, 'olive_production_total': olive_production, 'min_oil_production': oil_per_ha * 0.9, 'max_oil_production': oil_per_ha * 1.1, 'avg_oil_production': oil_per_ha, 'avg_oil_production_total': oil_total, 'water_need': water_need_per_ha, 'water_need_total': total_water_need, 'variety_details': variety_details, 'hectares': hectares } except Exception as e: print(f"Errore nella funzione mock_make_prediction: {str(e)}") import traceback print("Traceback completo:") print(traceback.format_exc()) raise e def make_prediction(weather_data, varieties_info, percentages, hectares): if DEV_MODE: return mock_make_prediction(weather_data, varieties_info, percentages, hectares) else: """Effettua una predizione usando il modello.""" try: print("Inizio della funzione make_prediction") # Prepara i dati meteorologici mensili monthly_stats = weather_data.groupby(['year', 'month']).agg({ 'temp': 'mean', 'precip': 'sum', 'solarradiation': 'sum' }).reset_index() monthly_stats = monthly_stats.rename(columns={ 'temp': 'temp_mean', 'precip': 'precip_sum', 'solarradiation': 'solar_energy_sum' }) print(f"Shape dei dati meteorologici mensili: {monthly_stats.shape}") # Definisci la dimensione della finestra temporale window_size = 41 # Prendi gli ultimi window_size mesi di dati if len(monthly_stats) >= window_size: temporal_data = monthly_stats[['temp_mean', 'precip_sum', 'solar_energy_sum']].values[-window_size:] else: raise ValueError(f"Non ci sono abbastanza dati meteorologici. Necessari almeno {window_size} mesi.") print(f"Shape dei dati temporali prima della trasformazione: {temporal_data.shape}") temporal_data = scaler_temporal.transform(temporal_data) print(f"Shape dei dati temporali dopo la trasformazione: {temporal_data.shape}") temporal_data = np.expand_dims(temporal_data, axis=0) print(f"Shape finale dei dati temporali: {temporal_data.shape}") all_varieties = olive_varieties['Varietà di Olive'].unique() varieties = [clean_column_name(variety) for variety in all_varieties] # Prepara i dati statici print("Preparazione dei dati statici") static_data = prepare_static_features_multiple(varieties_info, percentages, hectares, varieties) # Verifica che il numero di feature statiche sia corretto if static_data.shape[1] != scaler_static.n_features_in_: print("ATTENZIONE: Il numero di feature statiche non corrisponde a quello atteso dallo scaler!") print(f"Feature generate: {static_data.shape[1]}, Feature attese: {scaler_static.n_features_in_}") static_data = scaler_static.transform(static_data) print(f"Shape dei dati statici dopo la trasformazione: {static_data.shape}") # Effettua la predizione print("Effettuazione della predizione") prediction = model.predict({'temporal': temporal_data, 'static': static_data}) prediction = scaler_y.inverse_transform(prediction)[0] # Calcola i dettagli per varietà variety_details = [] for variety_info, percentage in zip(varieties_info, percentages): # Calcoli specifici per varietà prod_per_ha = variety_info['Produzione (tonnellate/ettaro)'] * 1000 oil_per_ha = variety_info['Produzione Olio (litri/ettaro)'] water_need = ( variety_info['Fabbisogno Acqua Primavera (m³/ettaro)'] + variety_info['Fabbisogno Acqua Estate (m³/ettaro)'] + variety_info['Fabbisogno Acqua Autunno (m³/ettaro)'] + variety_info['Fabbisogno Acqua Inverno (m³/ettaro)'] ) / 4 variety_details.append({ 'variety': variety_info['Varietà di Olive'], 'percentage': percentage, 'production_per_ha': prod_per_ha, 'oil_per_ha': oil_per_ha, 'water_need': water_need }) return { 'olive_production': prediction[0], 'min_oil_production': prediction[1], 'max_oil_production': prediction[2], 'avg_oil_production': prediction[3], 'water_need': prediction[4], 'variety_details': variety_details } except Exception as e: print(f"Errore durante la preparazione dei dati o la predizione: {str(e)}") print(f"Tipo di errore: {type(e).__name__}") import traceback print("Traceback completo:") print(traceback.format_exc()) raise e app = Dash( __name__, external_stylesheets=[dbc.themes.FLATLY], meta_tags=[ {"name": "viewport", "content": "width=device-width, initial-scale=1"} ] ) # Stili comuni CARD_STYLE = { "height": "100%", "margin-bottom": "15px" } CARD_BODY_STYLE = { "padding": "15px" } # Modifiche al layout - aggiungi tooltips per chiarire la funzionalità variety2_tooltip = dbc.Tooltip( "Seleziona una seconda varietà per creare un mix", target="variety-2-dropdown", placement="top" ) variety3_tooltip = dbc.Tooltip( "Seleziona una terza varietà per completare il mix", target="variety-3-dropdown", placement="top" ) # Layout della dashboard modernizzato e responsive app.layout = dbc.Container([ variety2_tooltip, variety3_tooltip, # Header dbc.Row([ dbc.Col([ html.Div([ html.H1("Dashboard Produzione Olio d'Oliva", className="text-primary text-center mb-3"), html.P("Analisi e previsioni della produzione olivicola", className="text-muted text-center") ], className="mt-4 mb-4") ]) ]), # Main content dbc.Row([ dbc.Col([ dbc.Tabs([ dbc.Tab([ # Metriche principali dbc.Row([ dbc.Col([ dbc.Card([ dbc.CardHeader("Produzione Olive/ha"), dbc.CardBody([ html.H3( id='olive-production_ha', className="text-center text-primary" ) ]) ], style=CARD_STYLE) ], md=4), dbc.Col([ dbc.Card([ dbc.CardHeader("Produzione Olio/ha"), dbc.CardBody([ html.H3( id='oil-production_ha', className="text-center text-success" ) ]) ], style=CARD_STYLE) ], md=4), dbc.Col([ dbc.Card([ dbc.CardHeader("Fabbisogno Idrico/ha"), dbc.CardBody([ html.H3( id='water-need_ha', className="text-center text-info" ) ]) ], style=CARD_STYLE) ], md=4), ]), dbc.Row([ dbc.Col([ dbc.Card([ dbc.CardHeader("Produzione Olive"), dbc.CardBody([ html.H3( id='olive-production', className="text-center text-primary" ) ]) ], style=CARD_STYLE) ], md=4), dbc.Col([ dbc.Card([ dbc.CardHeader("Produzione Olio"), dbc.CardBody([ html.H3( id='oil-production', className="text-center text-success" ) ]) ], style=CARD_STYLE) ], md=4), dbc.Col([ dbc.Card([ dbc.CardHeader("Fabbisogno Idrico"), dbc.CardBody([ html.H3( id='water-need', className="text-center text-info" ) ]) ], style=CARD_STYLE) ], md=4), ]), # Grafici dbc.Row([ dbc.Col([ dbc.Card([ dbc.CardHeader("Dettagli Produzione"), dbc.CardBody([ dcc.Graph( id='production-details', config={'displayModeBar': False} ) ]) ], style=CARD_STYLE) ], md=12) ]), dbc.Row([ dbc.Col([ dbc.Card([ dbc.CardHeader("Analisi Meteorologica"), dbc.CardBody([ dcc.Graph( id='weather-impact', config={'displayModeBar': False} ) ]) ], style=CARD_STYLE) ], md=6), dbc.Col([ dbc.Card([ dbc.CardHeader("Fabbisogno Idrico Mensile"), dbc.CardBody([ dcc.Graph( id='water-needs', config={'displayModeBar': False} ) ]) ], style=CARD_STYLE) ], md=6) ]), # Info dettagliate dbc.Row([ dbc.Col([ html.Div( id='extra-info', className="mt-3" ) ]) ]) ], label="Produzione", tab_id="tab-production"), dbc.Tab([ # Sezione Costi dbc.Row([ dbc.Col([ create_costs_config_section() ], md=12) ]), # Sezione Costi di Trasformazione dbc.Row([ dbc.Col([ dbc.Card([ dbc.CardHeader("Costi di Trasformazione"), dbc.CardBody([ dbc.Row([ dbc.Col([ html.H5("Frantoio", className="mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Molitura: "), "€0.15/kg olive" ]), dbc.ListGroupItem([ html.Strong("Stoccaggio: "), "€0.20/L olio" ]) ], flush=True) ], md=6), dbc.Col([ html.H5("Imbottigliamento", className="mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Bottiglia (1L): "), "€1.20/unità" ]), dbc.ListGroupItem([ html.Strong("Etichettatura: "), "€0.30/bottiglia" ]) ], flush=True) ], md=6) ]) ]) ], style=CARD_STYLE) ], md=12) ]), # Sezione Ricavi e Guadagni dbc.Row([ dbc.Col([ dbc.Card([ dbc.CardHeader("Analisi Economica"), dbc.CardBody([ dbc.Row([ dbc.Col([ html.H5("Ricavi", className="mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Prezzo vendita olio: "), "€12.00/L" ]), dbc.ListGroupItem([ html.Strong("Ricavo totale: "), "€48,000.00" ]) ], flush=True) ], md=4), dbc.Col([ html.H5("Costi Totali", className="mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Costi produzione: "), "€25,000.00" ]), dbc.ListGroupItem([ html.Strong("Costi trasformazione: "), "€8,000.00" ]) ], flush=True) ], md=4), dbc.Col([ html.H5("Margini", className="mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Margine lordo: "), "€15,000.00" ]), dbc.ListGroupItem([ html.Strong("Margine per litro: "), "€3.75/L" ]) ], flush=True) ], md=4) ]) ]) ], style=CARD_STYLE) ], md=12) ]), # Grafici Finanziari dbc.Row([ dbc.Col([ dbc.Card([ dbc.CardHeader("Distribuzione Costi"), dbc.CardBody([ dcc.Graph( figure=px.pie( values=[2000, 500, 800, 1500, 600, 400], names=['Ammortamento', 'Assicurazione', 'Manutenzione', 'Raccolta', 'Potatura', 'Fertilizzanti'], title='Distribuzione Costi per Ettaro' ), config={'displayModeBar': False} ) ]) ], style=CARD_STYLE) ], md=6), dbc.Col([ dbc.Card([ dbc.CardHeader("Analisi Break-Even"), dbc.CardBody([ dcc.Graph( figure=px.line( x=[0, 1000, 2000, 3000, 4000], y=[[0, 12000, 24000, 36000, 48000], [5000, 15000, 25000, 35000, 45000]], title='Analisi Break-Even', labels={'x': 'Litri di olio', 'y': 'Euro'} ), config={'displayModeBar': False} ) ]) ], style=CARD_STYLE) ], md=6) ]) ], label="Analisi Economica", tab_id="tab-financial"), # Tab Configurazione dbc.Tab([ dbc.Row([ # Configurazione Oliveto dbc.Col([ dbc.Card([ dbc.CardHeader([ html.H4("Configurazione Oliveto", className="text-primary mb-0"), ], className="bg-light"), dbc.CardBody([ # Hectares input dbc.Row([ dbc.Col([ dbc.Label("Ettari totali:", className="fw-bold"), dbc.Input( id='hectares-input', type='number', value=1, min=1, className="mb-3" ) ]) ]), # Variety sections html.Div([ # Variety 1 html.Div([ html.H6("Varietà 1", className="text-primary mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Varietà:", className="fw-bold"), dcc.Dropdown( id='variety-1-dropdown', options=[{'label': v, 'value': v} for v in olive_varieties['Varietà di Olive'].unique()], value=olive_varieties['Varietà di Olive'].iloc[0], className="mb-2" ), ], md=4), dbc.Col([ dbc.Label("Tecnica:", className="fw-bold"), dcc.Dropdown( id='technique-1-dropdown', options=[ {'label': 'Tradizionale', 'value': 'Tradizionale'}, {'label': 'Intensiva', 'value': 'Intensiva'}, {'label': 'Superintensiva', 'value': 'Superintensiva'} ], value='Tradizionale', className="mb-2" ), ], md=4), dbc.Col([ dbc.Label("Percentuale:", className="fw-bold"), dbc.Input( id='percentage-1-input', type='number', min=1, max=100, value=100, className="mb-2" ) ], md=4) ]) ], className="mb-4"), # Variety 2 html.Div([ html.H6("Varietà 2 (opzionale)", className="text-primary mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Varietà:", className="fw-bold"), dcc.Dropdown( id='variety-2-dropdown', options=[{'label': v, 'value': v} for v in olive_varieties['Varietà di Olive'].unique()], value=None, className="mb-2" ), ], md=4), dbc.Col([ dbc.Label("Tecnica:", className="fw-bold"), dcc.Dropdown( id='technique-2-dropdown', options=[ {'label': 'Tradizionale', 'value': 'Tradizionale'}, {'label': 'Intensiva', 'value': 'Intensiva'}, {'label': 'Superintensiva', 'value': 'Superintensiva'} ], value=None, disabled=True, className="mb-2" ), ], md=4), dbc.Col([ dbc.Label("Percentuale:", className="fw-bold"), dbc.Input( id='percentage-2-input', type='number', min=0, max=99, value=0, disabled=True, className="mb-2" ) ], md=4) ]) ], className="mb-4"), # Variety 3 html.Div([ html.H6("Varietà 3 (opzionale)", className="text-primary mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Varietà:", className="fw-bold"), dcc.Dropdown( id='variety-3-dropdown', options=[{'label': v, 'value': v} for v in olive_varieties['Varietà di Olive'].unique()], value=None, className="mb-2" ), ], md=4), dbc.Col([ dbc.Label("Tecnica:", className="fw-bold"), dcc.Dropdown( id='technique-3-dropdown', options=[ {'label': 'Tradizionale', 'value': 'Tradizionale'}, {'label': 'Intensiva', 'value': 'Intensiva'}, {'label': 'Superintensiva', 'value': 'Superintensiva'} ], value=None, disabled=True, className="mb-2" ), ], md=4), dbc.Col([ dbc.Label("Percentuale:", className="fw-bold"), dbc.Input( id='percentage-3-input', type='number', min=0, max=99, value=0, disabled=True, className="mb-2" ) ], md=4) ]) ], className="mb-4"), ]), # Warning message html.Div( id='percentage-warning', className="text-danger mt-3" ) ]) ], className="mb-4") ], md=6), # Configurazione Costi dbc.Col([ dbc.Card([ dbc.CardHeader([ html.H4("Configurazione Costi", className="text-primary mb-0") ], className="bg-light"), dbc.CardBody([ # Costi Fissi html.H5("Costi Fissi Annuali", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Ammortamento impianto (€/ha):", className="fw-bold"), dbc.Input( id='cost-ammortamento', type='number', value=2000, min=0, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Assicurazione (€/ha):", className="fw-bold"), dbc.Input( id='cost-assicurazione', type='number', value=500, min=0, className="mb-2" ) ], md=6) ]), dbc.Row([ dbc.Col([ dbc.Label("Manutenzione (€/ha):", className="fw-bold"), dbc.Input( id='cost-manutenzione', type='number', value=800, min=0, className="mb-2" ) ], md=6) ], className="mb-4"), # Costi Variabili html.H5("Costi Variabili", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Manodopera raccolta (€/kg):", className="fw-bold"), dbc.Input( id='cost-raccolta', type='number', value=0.35, min=0, step=0.01, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Potatura (€/ha):", className="fw-bold"), dbc.Input( id='cost-potatura', type='number', value=600, min=0, className="mb-2" ) ], md=6) ]), dbc.Row([ dbc.Col([ dbc.Label("Fertilizzanti (€/ha):", className="fw-bold"), dbc.Input( id='cost-fertilizzanti', type='number', value=400, min=0, className="mb-2" ) ], md=6) ], className="mb-4"), # Costi Trasformazione html.H5("Costi Trasformazione", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Molitura (€/kg olive):", className="fw-bold"), dbc.Input( id='cost-molitura', type='number', value=0.15, min=0, step=0.01, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Stoccaggio (€/L olio):", className="fw-bold"), dbc.Input( id='cost-stoccaggio', type='number', value=0.20, min=0, step=0.01, className="mb-2" ) ], md=6) ]), dbc.Row([ dbc.Col([ dbc.Label("Bottiglia 1L (€/unità):", className="fw-bold"), dbc.Input( id='cost-bottiglia', type='number', value=1.20, min=0, step=0.01, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Etichettatura (€/bottiglia):", className="fw-bold"), dbc.Input( id='cost-etichettatura', type='number', value=0.30, min=0, step=0.01, className="mb-2" ) ], md=6) ], className="mb-4"), # Prezzo Vendita html.H5("Prezzo di Vendita", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Prezzo vendita olio (€/L):", className="fw-bold"), dbc.Input( id='price-olio', type='number', value=12.00, min=0, step=0.01, className="mb-2" ) ], md=6) ]) ]) ]) ], md=6), html.Div([ dbc.Button( "Salva Configurazione", id="save-config-button", color="primary", className="mt-3" ), html.Div( id="save-config-message", className="mt-2" ) ], className="text-center") ]) ], label="Configurazione", tab_id="tab-config"), ], id="tabs", active_tab="tab-production") ], md=12, lg=12) ]) ], fluid=True, className="px-4 py-3") def create_costs_config_section(): return dbc.Card([ dbc.CardHeader([ html.H4("Configurazione Costi e Marketing", className="text-primary mb-0") ], className="bg-light"), dbc.CardBody([ # Costi Fissi Annuali (totali) html.H5("Costi Fissi Annuali Totali", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Ammortamento impianto (€):", className="fw-bold"), dbc.Input( id='cost-ammortamento', type='number', value=10000, min=0, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Assicurazioni (€):", className="fw-bold"), dbc.Input( id='cost-assicurazioni', type='number', value=2500, min=0, className="mb-2" ) ], md=6) ]), dbc.Row([ dbc.Col([ dbc.Label("Manutenzione attrezzature (€):", className="fw-bold"), dbc.Input( id='cost-manutenzione', type='number', value=4000, min=0, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Certificazioni e licenze (€):", className="fw-bold"), dbc.Input( id='cost-certificazioni', type='number', value=3000, min=0, className="mb-2" ) ], md=6) ], className="mb-4"), # Costi Variabili (per ettaro) html.H5("Costi Variabili per Ettaro", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Manodopera raccolta (€/ha):", className="fw-bold"), dbc.Input( id='cost-raccolta', type='number', value=1200, min=0, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Potatura (€/ha):", className="fw-bold"), dbc.Input( id='cost-potatura', type='number', value=600, min=0, className="mb-2" ) ], md=6) ]), dbc.Row([ dbc.Col([ dbc.Label("Fertilizzanti (€/ha):", className="fw-bold"), dbc.Input( id='cost-fertilizzanti', type='number', value=400, min=0, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Irrigazione (€/ha):", className="fw-bold"), dbc.Input( id='cost-irrigazione', type='number', value=300, min=0, className="mb-2" ) ], md=6) ], className="mb-4"), # Costi di Trasformazione html.H5("Costi di Trasformazione", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Molitura (€/kg olive):", className="fw-bold"), dbc.Input( id='cost-molitura', type='number', value=0.15, min=0, step=0.01, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Stoccaggio (€/L olio):", className="fw-bold"), dbc.Input( id='cost-stoccaggio', type='number', value=0.20, min=0, step=0.01, className="mb-2" ) ], md=6) ]), dbc.Row([ dbc.Col([ dbc.Label("Bottiglia 1L (€/unità):", className="fw-bold"), dbc.Input( id='cost-bottiglia', type='number', value=1.20, min=0, step=0.01, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Etichettatura (€/bottiglia):", className="fw-bold"), dbc.Input( id='cost-etichettatura', type='number', value=0.30, min=0, step=0.01, className="mb-2" ) ], md=6) ], className="mb-4"), # Marketing e Vendita html.H5("Marketing e Vendita", className="mb-3"), dbc.Row([ dbc.Col([ dbc.Label("Budget Marketing Annuale (€):", className="fw-bold"), dbc.Input( id='cost-marketing', type='number', value=15000, min=0, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("Costi commerciali (€/L):", className="fw-bold"), dbc.Input( id='cost-commerciali', type='number', value=0.50, min=0, step=0.01, className="mb-2" ) ], md=6) ]), dbc.Row([ dbc.Col([ dbc.Label("Prezzo vendita olio (€/L):", className="fw-bold"), dbc.Input( id='price-olio', type='number', value=12.00, min=0, step=0.01, className="mb-2" ) ], md=6), dbc.Col([ dbc.Label("% Vendita diretta:", className="fw-bold"), dbc.Input( id='perc-vendita-diretta', type='number', value=30, min=0, max=100, className="mb-2" ) ], md=6) ], className="mb-4"), ]) ]) @app.callback( Output("save-config-message", "children"), [Input("save-config-button", "n_clicks")], [State("hectares-input", "value"), State("variety-1-dropdown", "value"), State("technique-1-dropdown", "value"), State("percentage-1-input", "value"), State("variety-2-dropdown", "value"), State("technique-2-dropdown", "value"), State("percentage-2-input", "value"), State("variety-3-dropdown", "value"), State("technique-3-dropdown", "value"), State("percentage-3-input", "value"), State("cost-ammortamento", "value"), State("cost-assicurazione", "value"), State("cost-manutenzione", "value"), State("cost-raccolta", "value"), State("cost-potatura", "value"), State("cost-fertilizzanti", "value"), State("cost-molitura", "value"), State("cost-stoccaggio", "value"), State("cost-bottiglia", "value"), State("cost-etichettatura", "value"), State("price-olio", "value")] ) def save_configuration(n_clicks, hectares, var1, tech1, perc1, var2, tech2, perc2, var3, tech3, perc3, amm, ass, man, rac, pot, fer, mol, sto, bot, eti, price): if n_clicks is None: return no_update # Prepara la configurazione varieties = [{"variety": var1, "technique": tech1, "percentage": perc1}] if var2: varieties.append({"variety": var2, "technique": tech2, "percentage": perc2}) if var3: varieties.append({"variety": var3, "technique": tech3, "percentage": perc3}) config = { 'oliveto': { 'hectares': hectares, 'varieties': varieties }, 'costs': { 'fixed': { 'ammortamento': amm, 'assicurazione': ass, 'manutenzione': man }, 'variable': { 'raccolta': rac, 'potatura': pot, 'fertilizzanti': fer }, 'transformation': { 'molitura': mol, 'stoccaggio': sto, 'bottiglia': bot, 'etichettatura': eti }, 'selling_price': price } } if save_config(config): return dbc.Alert( "Configurazione salvata con successo!", color="success", duration=4000, is_open=True ) else: return dbc.Alert( "Errore nel salvataggio della configurazione", color="danger", duration=4000, is_open=True ) @app.callback( [ Output("hectares-input", "value"), Output("variety-1-dropdown", "value"), Output("technique-1-dropdown", "value"), Output("percentage-1-input", "value"), Output("variety-2-dropdown", "value"), Output("cost-ammortamento", "value"), Output("cost-assicurazione", "value"), Output("cost-manutenzione", "value"), Output("cost-raccolta", "value"), Output("cost-potatura", "value"), Output("cost-fertilizzanti", "value"), Output("cost-molitura", "value"), Output("cost-stoccaggio", "value"), Output("cost-bottiglia", "value"), Output("cost-etichettatura", "value"), Output("price-olio", "value") ], [Input("tabs", "active_tab")] ) def load_saved_config(tab): if tab != "tab-config": return [no_update] * 16 config = load_config() varieties = config['oliveto']['varieties'] var1 = varieties[0] if len(varieties) > 0 else {"variety": None, "technique": None, "percentage": 0} var2 = varieties[1] if len(varieties) > 1 else {"variety": None, "technique": None, "percentage": 0} costs = config['costs'] return [ config['oliveto']['hectares'], var1["variety"], var1["technique"], var1["percentage"], var2["variety"], costs['fixed']['ammortamento'], costs['fixed']['assicurazione'], costs['fixed']['manutenzione'], costs['variable']['raccolta'], costs['variable']['potatura'], costs['variable']['fertilizzanti'], costs['transformation']['molitura'], costs['transformation']['stoccaggio'], costs['transformation']['bottiglia'], costs['transformation']['etichettatura'], costs['selling_price'] ] # Aggiorna la configurazione dei grafici per essere più responsive def create_figure_layout(fig, title): fig.update_layout( title=title, title_x=0.5, margin=dict(l=20, r=20, t=40, b=20), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', height=350, font=dict(family="Helvetica, Arial, sans-serif"), showlegend=True, legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ) ) return fig @app.callback( [ Output('technique-2-dropdown', 'disabled'), Output('percentage-2-input', 'disabled'), Output('technique-3-dropdown', 'disabled'), Output('percentage-3-input', 'disabled'), Output('percentage-warning', 'children'), Output('technique-2-dropdown', 'value'), Output('technique-3-dropdown', 'value'), Output('percentage-2-input', 'value'), Output('percentage-3-input', 'value') ], [ Input('variety-2-dropdown', 'value'), Input('variety-3-dropdown', 'value'), Input('percentage-1-input', 'value'), Input('percentage-2-input', 'value'), Input('percentage-3-input', 'value') ] ) def manage_percentages_and_techniques(variety2, variety3, perc1, perc2, perc3): perc1 = perc1 or 0 perc2 = perc2 or 0 perc3 = perc3 or 0 total = perc1 + perc2 + perc3 # Gestione varietà 2 disable_2 = variety2 is None technique2_value = None if disable_2 else 'Tradizionale' percentage2_value = 0 if disable_2 else (perc2 if perc2 else 0) # Gestione varietà 3 disable_3 = variety3 is None or variety2 is None technique3_value = None if disable_3 else 'Tradizionale' percentage3_value = 0 if disable_3 else (perc3 if perc3 else 0) # Gestione warning percentuali - ora mostra sempre il warning se non è 100% warning = "" if total != 100: if total > 100: warning = f"La somma delle percentuali è {total}% (dovrebbe essere 100%)" else: warning = f"La somma delle percentuali è {total}% (dovrebbe essere 100%)" return ( disable_2, disable_2, disable_3, disable_3, warning, technique2_value, technique3_value, percentage2_value, percentage3_value ) # Aggiunta callback per resettare i valori della varietà 3 quando la varietà 2 viene deselezionata @app.callback( [ Output('variety-3-dropdown', 'value'), Output('variety-3-dropdown', 'disabled') ], [ Input('variety-2-dropdown', 'value') ] ) def manage_variety3_availability(variety2): if variety2 is None: return None, True return no_update, False # Modifica il callback update_dashboard per utilizzare il nuovo layout dei grafici @app.callback( [Output('olive-production_ha', 'children'), Output('oil-production_ha', 'children'), Output('water-need_ha', 'children'), Output('olive-production', 'children'), Output('oil-production', 'children'), Output('water-need', 'children'), Output('production-details', 'figure'), Output('weather-impact', 'figure'), Output('water-needs', 'figure'), Output('extra-info', 'children')], [Input('variety-1-dropdown', 'value'), Input('technique-1-dropdown', 'value'), Input('percentage-1-input', 'value'), Input('variety-2-dropdown', 'value'), Input('technique-2-dropdown', 'value'), Input('percentage-2-input', 'value'), Input('variety-3-dropdown', 'value'), Input('technique-3-dropdown', 'value'), Input('percentage-3-input', 'value'), Input('hectares-input', 'value')] ) def update_dashboard(variety1, tech1, perc1, variety2, tech2, perc2, variety3, tech3, perc3, hectares): if not variety1 or not tech1 or perc1 is None or hectares is None or hectares <= 0: return "N/A", "N/A", "N/A", {}, {}, {}, "" # Raccogli le informazioni delle varietà varieties_info = [] percentages = [] # Prima varietà variety_data = olive_varieties[ (olive_varieties['Varietà di Olive'] == variety1) & (olive_varieties['Tecnica di Coltivazione'] == tech1) ] if not variety_data.empty: varieties_info.append(variety_data.iloc[0]) percentages.append(perc1) # Seconda varietà if variety2 and tech2 and perc2: variety_data = olive_varieties[ (olive_varieties['Varietà di Olive'] == variety2) & (olive_varieties['Tecnica di Coltivazione'] == tech2) ] if not variety_data.empty: varieties_info.append(variety_data.iloc[0]) percentages.append(perc2) # Terza varietà if variety3 and tech3 and perc3: variety_data = olive_varieties[ (olive_varieties['Varietà di Olive'] == variety3) & (olive_varieties['Tecnica di Coltivazione'] == tech3) ] if not variety_data.empty: varieties_info.append(variety_data.iloc[0]) percentages.append(perc3) try: prediction = make_prediction(weather_data, varieties_info, percentages, hectares) # Formattazione output principale # Formattazione output con valori per ettaro e totali olive_prod_text_ha = f"{prediction['olive_production']:.0f} kg/ha\n" olive_prod_text = f"Totale: {prediction['olive_production_total']:.0f} kg" oil_prod_text_ha = f"{prediction['avg_oil_production']:.0f} L/ha\n" oil_prod_text = f"Totale: {(prediction['avg_oil_production_total'] * hectares):.0f} L" water_need_text_ha = f"{prediction['water_need']:.0f} m³/ha\n" water_need_text = f"Totale: {prediction['water_need_total']:.0f} m³" # Creazione grafici con il nuovo stile details_fig = create_production_details_figure(prediction) weather_fig = create_weather_impact_figure(weather_data) water_fig = create_water_needs_figure(prediction) # Creazione info extra con il nuovo stile extra_info = create_extra_info_component(prediction, varieties_info) return ( olive_prod_text_ha, oil_prod_text_ha, water_need_text_ha, olive_prod_text, oil_prod_text, water_need_text, details_fig, weather_fig, water_fig, extra_info) except Exception as e: print(f"Errore nell'aggiornamento dashboard: {str(e)}") return "Errore", "Errore", "Errore", {}, {}, {}, "Errore nella generazione dei dati" def create_production_details_figure(prediction): """Crea il grafico dei dettagli produzione con il nuovo stile""" details_data = prepare_details_data(prediction) fig = px.bar( details_data, x='Varietà', y='Produzione', color='Tipo', barmode='group', color_discrete_map={'Olive': '#2185d0', 'Olio': '#21ba45'} ) return create_figure_layout(fig, 'Dettagli Produzione per Varietà') def create_weather_impact_figure(weather_data): """Crea il grafico dell'impatto meteorologico con il nuovo stile""" recent_weather = weather_data.tail(41).copy() fig = px.scatter( recent_weather, x='temp', y='solarradiation', size='precip', color_discrete_sequence=['#2185d0'] ) return create_figure_layout(fig, 'Condizioni Meteorologiche') def create_water_needs_figure(prediction): """Crea il grafico del fabbisogno idrico con il nuovo stile""" months = ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'] water_data = [] for detail in prediction['variety_details']: for month in months: season = get_season_from_month(month) variety_info = olive_varieties[ olive_varieties['Varietà di Olive'] == detail['variety'] ].iloc[0] water_need = variety_info[f'Fabbisogno Acqua {season} (m³/ettaro)'] water_data.append({ 'Mese': month, 'Varietà': detail['variety'], 'Fabbisogno': water_need * (detail['percentage'] / 100) }) water_df = pd.DataFrame(water_data) fig = px.bar( water_df, x='Mese', y='Fabbisogno', color='Varietà', barmode='stack', color_discrete_sequence=['#2185d0', '#21ba45', '#6435c9'] ) return create_figure_layout(fig, 'Fabbisogno Idrico Mensile') def create_extra_info_component(prediction, varieties_info): """Crea il componente delle informazioni dettagliate con il nuovo stile""" cards = [] # Card per ogni varietà for detail, variety_info in zip(prediction['variety_details'], varieties_info): variety_card = dbc.Card([ dbc.CardHeader( html.H5(f"{detail['variety']} - {detail['percentage']}%", className="mb-0 text-primary") ), dbc.CardBody([ dbc.Row([ dbc.Col([ html.Div([ html.H6("Produzione", className="text-muted mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Olive: "), f"{detail['production_total']:.0f} kg" ], className="px-2 py-1"), dbc.ListGroupItem([ html.Strong("Olio: "), f"{detail['oil_total']:.0f} L" ], className="px-2 py-1"), dbc.ListGroupItem([ html.Strong("Resa: "), f"{detail['oil_total'] / detail['production_total']:.3f} %" ], className="px-2 py-1") ], flush=True) ]) ], md=4), # Colonna Produzione/ha dbc.Col([ html.Div([ html.H6("Produzione/ha", className="text-muted mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Olive: "), f"{detail['production_per_ha']:.0f} kg/ha" ], className="px-2 py-1"), dbc.ListGroupItem([ html.Strong("Olio: "), f"{detail['oil_per_ha']:.0f} L/ha" ], className="px-2 py-1") ], flush=True) ]) ], md=4), # Colonna Rese dbc.Col([ html.Div([ html.H6("Caratteristiche", className="text-muted mb-3"), dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Resa: "), f"{variety_info['Min % Resa']:.1f}% - {variety_info['Max % Resa']:.1f}%" ], className="px-2 py-1"), dbc.ListGroupItem([ html.Strong("L/t: "), f"{variety_info['Min Litri per Tonnellata']:.0f} - {variety_info['Max Litri per Tonnellata']:.0f}" ], className="px-2 py-1") ], flush=True) ]) ], md=4) ]), # Fabbisogno Idrico dbc.Row([ dbc.Col([ html.H6("Fabbisogno Idrico Stagionale", className="text-muted mb-3 mt-3"), dbc.Table([ html.Thead([ html.Tr([ html.Th("Stagione"), html.Th("m³/ha") ]) ]), html.Tbody([ html.Tr([ html.Td("Primavera"), html.Td(f"{variety_info['Fabbisogno Acqua Primavera (m³/ettaro)']:.0f}") ]), html.Tr([ html.Td("Estate"), html.Td(f"{variety_info['Fabbisogno Acqua Estate (m³/ettaro)']:.0f}") ]), html.Tr([ html.Td("Autunno"), html.Td(f"{variety_info['Fabbisogno Acqua Autunno (m³/ettaro)']:.0f}") ]), html.Tr([ html.Td("Inverno"), html.Td(f"{variety_info['Fabbisogno Acqua Inverno (m³/ettaro)']:.0f}") ]) ]) ], size="sm", bordered=True) ]) ]) ]) ], className="mb-3", style=CARD_STYLE) cards.append(variety_card) # Card riepilogo totali summary_card = dbc.Card([ dbc.CardHeader( html.H5("Riepilogo Totali", className="mb-0 text-primary") ), dbc.CardBody([ dbc.Row([ dbc.Col([ dbc.ListGroup([ dbc.ListGroupItem([ html.Strong("Produzione Olive: "), f"{prediction['olive_production']:.0f} kg/ha" ], className="px-2 py-1"), dbc.ListGroupItem([ html.Strong("Produzione Olio: "), f"{prediction['avg_oil_production']:.0f} L/ha" ], className="px-2 py-1"), dbc.ListGroupItem([ html.Strong("Resa Media: "), f"{(prediction['avg_oil_production_total'] / prediction['olive_production_total']):.3f}%" ], className="px-2 py-1"), dbc.ListGroupItem([ html.Strong("Fabbisogno Idrico: "), f"{prediction['water_need']:.0f} m³/ha" ], className="px-2 py-1") ], flush=True) ]) ]) ]) ], className="mb-3", style=CARD_STYLE) return html.Div([ dbc.Row([ dbc.Col(card, md=12 if len(cards) == 1 else 6 if len(cards) == 2 else 4) for card in cards ]), dbc.Row([ dbc.Col(summary_card) ]) ]) def prepare_details_data(prediction): """Prepara i dati per il grafico dei dettagli di produzione""" details_data = [] # Dati per ogni varietà for detail in prediction['variety_details']: details_data.extend([ { 'Varietà': f"{detail['variety']} ({detail['percentage']}%)", 'Tipo': 'Olive', 'Produzione': detail['production_per_ha'] }, { 'Varietà': f"{detail['variety']} ({detail['percentage']}%)", 'Tipo': 'Olio', 'Produzione': detail['oil_per_ha'] } ]) # Aggiungi totali details_data.extend([ { 'Varietà': 'Totale', 'Tipo': 'Olive', 'Produzione': prediction['olive_production'] }, { 'Varietà': 'Totale', 'Tipo': 'Olio', 'Produzione': prediction['avg_oil_production'] } ]) return pd.DataFrame(details_data) def get_season_from_month(month): """Helper function per determinare la stagione dal mese.""" seasons = { 'Gen': 'Inverno', 'Feb': 'Inverno', 'Mar': 'Primavera', 'Apr': 'Primavera', 'Mag': 'Primavera', 'Giu': 'Estate', 'Lug': 'Estate', 'Ago': 'Estate', 'Set': 'Autunno', 'Ott': 'Autunno', 'Nov': 'Autunno', 'Dic': 'Inverno' } return seasons[month] if __name__ == '__main__': app.run_server(debug=True)