From f1d9bdc04d112e90c803b50c2c38156dcab80992 Mon Sep 17 00:00:00 2001 From: Giuseppe Nucifora Date: Mon, 23 Dec 2024 10:37:37 +0100 Subject: [PATCH] Remove Python cache files --- src/__pycache__/__init__.cpython-39.pyc | Bin 166 -> 0 bytes src/components/ids.py | 1 + .../environmental_simulator.cpython-311.pyc | Bin 8205 -> 0 bytes .../environmental_simulator.cpython-39.pyc | Bin 4757 -> 0 bytes src/olive-oil-dashboard.py | 927 +++++++++--------- .../create_train_dataset.cpython-312.pyc | Bin 21049 -> 0 bytes .../create_train_dataset.cpython-39.pyc | Bin 12540 -> 0 bytes src/requirements.txt | 2 +- .../__pycache__/__init__.cpython-311.pyc | Bin 202 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 190 -> 0 bytes src/utils/__pycache__/__init__.cpython-39.pyc | Bin 172 -> 0 bytes src/utils/__pycache__/helpers.cpython-311.pyc | Bin 19745 -> 0 bytes src/utils/__pycache__/helpers.cpython-312.pyc | Bin 17279 -> 0 bytes src/utils/__pycache__/helpers.cpython-39.pyc | Bin 12313 -> 0 bytes 14 files changed, 466 insertions(+), 464 deletions(-) delete mode 100755 src/__pycache__/__init__.cpython-39.pyc delete mode 100644 src/dashboard/__pycache__/environmental_simulator.cpython-311.pyc delete mode 100755 src/dashboard/__pycache__/environmental_simulator.cpython-39.pyc delete mode 100644 src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-312.pyc delete mode 100755 src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-39.pyc delete mode 100644 src/utils/__pycache__/__init__.cpython-311.pyc delete mode 100644 src/utils/__pycache__/__init__.cpython-312.pyc delete mode 100755 src/utils/__pycache__/__init__.cpython-39.pyc delete mode 100644 src/utils/__pycache__/helpers.cpython-311.pyc delete mode 100644 src/utils/__pycache__/helpers.cpython-312.pyc delete mode 100755 src/utils/__pycache__/helpers.cpython-39.pyc diff --git a/src/__pycache__/__init__.cpython-39.pyc b/src/__pycache__/__init__.cpython-39.pyc deleted file mode 100755 index e129e36bbe3f80fceb53ed920f8af4df8ad37fb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmYe~<>g`kf+7aJbP)X*L?8o3AjbiSi&=m~3PUi1CZpdK3G?Cl=@H7Z)Y#$H!;p bWtPOp>lIYq;;_lhPbtkwwF6o58HgDGXU8dJ diff --git a/src/components/ids.py b/src/components/ids.py index 8f6d43a..c4ba840 100644 --- a/src/components/ids.py +++ b/src/components/ids.py @@ -4,6 +4,7 @@ from dataclasses import dataclass @dataclass class Ids: # Auth Container + USE_MODEL = 'use-model' AUTH_CONTAINER = 'auth-container' DASHBOARD_CONTAINER = 'dashboard-container' diff --git a/src/dashboard/__pycache__/environmental_simulator.cpython-311.pyc b/src/dashboard/__pycache__/environmental_simulator.cpython-311.pyc deleted file mode 100644 index 8a61725504d27e2569ad256bc1556355f9de1ddd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8205 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 diff --git a/src/dashboard/__pycache__/environmental_simulator.cpython-39.pyc b/src/dashboard/__pycache__/environmental_simulator.cpython-39.pyc deleted file mode 100755 index 14fbf3bae089c86fa2da63e01a4bc8e2ce0b0458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4757 zcmai2OOG4J5uP`PkL9l9)kd6=*>ZcX=+vl`A& zJufAv*0 z+0s(Yz_s<|x$f>s!}yvii98xzDT6c)Z5zC@Z|dmr(k=iMgy4YgdzsddoI}z5 z^PlLNY)(2E7QBG7YuqV6B#h#WFp@B!jJ&C@sMx)Dibo>2zwEe zY#_-VLb8pIWV$Uvm2G@93VTfU=XE8r=-dDc$%)it($j|XM!o4I*l?z+m# zU0pM_?E~|aao^(ymLgYT*!G3V)6e ze~ygm91$H~gNp5RJ9Yh3POP&$>lMrtv(7Wc%6yEerf8H+vAbN{(}*g?DVoe$2rCdL zfRXgt%Vy-F*|QLaT{BPF$IVrH)dm*6vn=nwmhJt=amVMLf`ccIIO&6CLXU==USz#0 zZVin;;Gs*K4|p7uTkhUBuNa@IT|ciV8=GnFnJ0~X=Kuh}RvY_XUImP>soVa3H3vbi z8<6(8abWG%@xGRu0Fs?M2hMH-^?F{*8+f;rd;5*WF`=*LjeLnapSuU9Y1~;=OZ&~- zJ0LoD$7f2`Yym1E&=E$8wZP@^nKX`e(pDnk7Hv^VZp+yylf63yddUA?=C7Em`!&)_K_L{w7H&AbwR{8sP%^$z|6dXP>U`WQDT|`L^ZjHTP;DMXF zL^>LMK;79-tX+H80e&39)kOnufvq(ow{DxVncJ3OBBF)pBSx05~WZ~Vb;v?nWq@> zQJ95<$k{~}q`I~u#b>fbZmz?c>&C8~BVov`Q^u-6w5%<2c1F4poQ7o?mcC#ZZ4DMj zLZ*^tD9@5O4be8WAz%-p&=OKV#Z#wBmPGJ{y^jJ)zKPeA%w#l6!)lu2*PgP*Cwj*b zxExt1(IUZR9?9(7!a{9YC>CjtUz{xxk)=OiN~JA$3Xm|6DIeGC?wDHZpy-KiXPmh~ca?~oFs&eDvlF?^^VS$T2#a6yNC0-8#wKE`J<&BEAy?E8iP zYm>)@_z;_+!C9r8ApvCsK~W2WJ{A|#b0Y}u3|U;hkrYZa4M|G*Gz9hD$V*f`+%)|} zF-ekhR&Z;SBJnuppQt_WSAAE%p$rkOO>D!!cLHRZ97$(svj{yJm0#gOVY|2$nO*$2 zy=U$s0ogW{wT+~LJBS)c^MP4UIh5S<>Yl}Gdk#|C#-3#Y&yTcSLfad~L5X zo5P(Q^sMlc{1jhDYW*w{@zeady$a0^kb8c7?y?lD1*hstwiSgXqp(&x&!o3@D z)oVM2d9N@>g+seeM3snZz{z_w;>!@@mG|dK)(xF#V8E*v-@N?B<H_m2!NQdKC?lgOttkh~u+2 zbgo;PVz#E1Xq9tmU79C+g+q~WeBvI-wOyy z)ODH^Oams&7N9qBl+Q3y_G<{kI&M3rqfY~tS^L)UtZ%&fS^mYQkYHqpN(dl>nM>ryb8pF}@F1!)_c_rTnT14CL!4NYZJ5(wwA_FZLfd)hVN zPzca)V;ydAHs=1GL&;ojZNsH;I`+t!a6WDBd8CEgEI-+8o=)22ABB1Ski1K1uYEM< zb>Im{Ld6IymXQU1;2Te;Osq&a-H7IBMZs7dQdZPTZ8gfoyy7 zw%K3tgfkv{4mBR@lf!n>w-SO!$Ff6m~@<@rER zce?~pqn{?K7mrHtY~jt*5cF7(m?G$p=#x>fFzi*!*BByn00rMV-#N7-n0V+_CH-fE ze=Xc(*dL6hDeV9$Ez9Ivc%>8MGAUlAe^27kc@W;97o@lJNw~m4dZAh_b$Qxy_K%Bp sF&#?eMH(|9K~U6RUs&NhA{G8SWpI0!SSq7~3!R5oD0#A)AFsasKO?yVLI3~& diff --git a/src/olive-oil-dashboard.py b/src/olive-oil-dashboard.py index a639547..f57dbe2 100755 --- a/src/olive-oil-dashboard.py +++ b/src/olive-oil-dashboard.py @@ -1,6 +1,8 @@ +import warnings + import flask import plotly.express as px -from dash import Dash, dcc, html, Input, Output, State, callback_context +from dash import Dash, dcc, html, Input, Output, State import tensorflow as tf import keras import joblib @@ -9,13 +11,14 @@ import os import argparse import json from dash.exceptions import PreventUpdate +from dash_bootstrap_components import Card from auth import utils from utils.helpers import clean_column_name from dashboard.environmental_simulator import * from dash import no_update from auth.utils import ( - init_directory_structure, verify_user, create_token, + verify_user, create_token, verify_token, create_user, get_user_config_path, get_default_config ) from auth.login import create_login_layout, create_register_layout @@ -27,13 +30,6 @@ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # Set global precision policy tf.keras.mixed_precision.set_global_policy('float32') -DEV_MODE = True -model = None -scaler_temporal = None -scaler_static = None -scaler_y = None -MODEL_LOADING = False - def load_config(): try: @@ -106,20 +102,6 @@ def save_config(config): return False, f"Errore nel salvataggio: {str(e)}" -try: - print(f"Caricamento dataset e scaler...") - - simulated_data = pd.read_parquet("./sources/olive_training_dataset.parquet") - weather_data = pd.read_parquet("./sources/weather_data_solarenergy.parquet") - olive_varieties = pd.read_parquet("./sources/olive_varieties.parquet") - scaler_temporal = joblib.load('./sources/olive_oil_transformer/olive_oil_transformer_scaler_temporal.joblib') - scaler_static = joblib.load('./sources/olive_oil_transformer/olive_oil_transformer_scaler_static.joblib') - scaler_y = joblib.load('./sources/olive_oil_transformer/olive_oil_transformer_scaler_y.joblib') -except Exception as e: - print(f"Errore nel caricamento: {str(e)}") - raise e - - def prepare_static_features_multiple(varieties_info, percentages, hectares, all_varieties): """ Prepara le feature statiche per multiple varietà seguendo la struttura esatta della simulazione. @@ -348,233 +330,216 @@ def mock_make_prediction(weather_data, varieties_info, percentages, hectares, si def make_prediction(weather_data, varieties_info, percentages, hectares, simulation_data=None): - print(f"DEV_MODE: {DEV_MODE}") - if DEV_MODE: - return mock_make_prediction(weather_data, varieties_info, percentages, hectares, simulation_data) - try: - if MODEL_LOADING: - return { - 'olive_production': 0, # kg/ha - 'olive_production_total': 0 * hectares, # kg totali - 'min_oil_production': 0, # L/ha - 'max_oil_production': 0, # L/ha - 'avg_oil_production': 0, # L/ha - 'avg_oil_production_total': 0 * hectares, # L totali - 'water_need': 0, # m³/ha - 'water_need_total': 0, # m³ totali - 'variety_details': 0, - 'hectares': hectares, - 'stress_factor': 0 if simulation_data is not None else 1.0 + if app_state.use_model: + try: + print("Inizio della funzione make_prediction") + + # Prepara i dati temporali (meteorologici) + temporal_features = ['temp_mean', 'precip_sum', 'solar_energy_sum'] + + if simulation_data is not None: + # Usa i dati della simulazione + print("Usando dati dalla simulazione ambientale") + # Calcola le medie dai dati simulati + temporal_data = np.array([[ + simulation_data['temperature'].mean(), + simulation_data['rainfall'].sum(), + simulation_data['radiation'].mean() + ]]) + else: + # Usa i dati meteorologici storici + 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' + }) + + # Prendi gli ultimi dati meteorologici + temporal_data = monthly_stats[temporal_features].tail(1).values + + # Calcola il fattore di stress dalla simulazione + stress_factor = 1.0 + if simulation_data is not None: + avg_stress = simulation_data['stress_index'].mean() + # Applica una penalità basata sullo stress + stress_factor = 1.0 - (avg_stress * 0.5) # Riduce fino al 50% basato sullo stress + print(f"Fattore di stress dalla simulazione: {stress_factor:.2f}") + + # Prepara i dati statici + static_data = [] + + # Aggiungi hectares come prima feature statica + static_data.append(hectares) + + # Ottieni tutte le possibili varietà dal dataset di training + all_varieties = app_state.olive_varieties['Varietà di Olive'].unique() + varieties = [clean_column_name(variety) for variety in all_varieties] + + # Per ogni varietà possibile nel dataset + for variety in varieties: + # Trova se questa varietà è tra quelle selezionate + selected_variety = None + selected_idx = None + + for idx, info in enumerate(varieties_info): + if clean_column_name(info['Varietà di Olive']) == variety: + selected_variety = info + selected_idx = idx + break + + if selected_variety is not None: + percentage = percentages[selected_idx] / 100 # Converti in decimale + + # Aggiungi tutte le feature numeriche della varietà + static_data.extend([ + percentage, # pct + float(selected_variety['Produzione (tonnellate/ettaro)']), # prod_t_ha + float(selected_variety['Produzione Olio (tonnellate/ettaro)']), # oil_prod_t_ha + float(selected_variety['Produzione Olio (litri/ettaro)']), # oil_prod_l_ha + float(selected_variety['Min % Resa']), # min_yield_pct + float(selected_variety['Max % Resa']), # max_yield_pct + float(selected_variety['Min Produzione Olio (litri/ettaro)']), # min_oil_prod_l_ha + float(selected_variety['Max Produzione Olio (litri/ettaro)']), # max_oil_prod_l_ha + float(selected_variety['Media Produzione Olio (litri/ettaro)']), # avg_oil_prod_l_ha + float(selected_variety['Litri per Tonnellata']), # l_per_t + float(selected_variety['Min Litri per Tonnellata']), # min_l_per_t + float(selected_variety['Max Litri per Tonnellata']), # max_l_per_t + float(selected_variety['Media Litri per Tonnellata']) # avg_l_per_t + ]) + + # Aggiungi le feature binarie per la tecnica + tech = str(selected_variety['Tecnica di Coltivazione']).lower() + static_data.extend([ + 1 if tech == 'tradizionale' else 0, + 1 if tech == 'intensiva' else 0, + 1 if tech == 'superintensiva' else 0 + ]) + else: + # Se la varietà non è selezionata, aggiungi zeri per tutte le sue feature + static_data.extend([0] * 13) # Feature numeriche + static_data.extend([0] * 3) # Feature tecniche binarie + + # Converti in array numpy e reshape + temporal_data = np.array(temporal_data).reshape(1, 1, -1) # (1, 1, n_temporal_features) + static_data = np.array(static_data).reshape(1, -1) # (1, n_static_features) + + print(f"Shape dei dati temporali: {temporal_data.shape}") + print(f"Shape dei dati statici: {static_data.shape}") + + # Standardizza i dati + temporal_data = app_state.scaler_temporal.transform(temporal_data.reshape(1, -1)).reshape(1, 1, -1) + static_data = app_state.scaler_static.transform(static_data) + + # Prepara il dizionario di input per il modello + input_data = { + 'temporal': temporal_data, + 'static': static_data } - print("Inizio della funzione make_prediction") + # Effettua la predizione + prediction = app_state.model.predict(input_data) - # Prepara i dati temporali (meteorologici) - temporal_features = ['temp_mean', 'precip_sum', 'solar_energy_sum'] + print("\nRaw prediction:", prediction) - if simulation_data is not None: - # Usa i dati della simulazione - print("Usando dati dalla simulazione ambientale") - # Calcola le medie dai dati simulati - temporal_data = np.array([[ - simulation_data['temperature'].mean(), - simulation_data['rainfall'].sum(), - simulation_data['radiation'].mean() - ]]) - else: - # Usa i dati meteorologici storici - monthly_stats = weather_data.groupby(['year', 'month']).agg({ - 'temp': 'mean', - 'precip': 'sum', - 'solarradiation': 'sum' - }).reset_index() + target_features = [ + 'olive_prod', # Produzione olive kg/ha + 'min_oil_prod', # Produzione minima olio L/ha + 'max_oil_prod', # Produzione massima olio L/ha + 'avg_oil_prod', # Produzione media olio L/ha + 'total_water_need' # Fabbisogno idrico totale m³/ha + ] - monthly_stats = monthly_stats.rename(columns={ - 'temp': 'temp_mean', - 'precip': 'precip_sum', - 'solarradiation': 'solar_energy_sum' - }) + prediction = app_state.scaler_y.inverse_transform(prediction)[0] + print("\nInverse transformed prediction:") + for feature, value in zip(target_features, prediction): + print(f"{feature}: {value:.2f}") - # Prendi gli ultimi dati meteorologici - temporal_data = monthly_stats[temporal_features].tail(1).values - - # Calcola il fattore di stress dalla simulazione - stress_factor = 1.0 - if simulation_data is not None: - avg_stress = simulation_data['stress_index'].mean() - # Applica una penalità basata sullo stress - stress_factor = 1.0 - (avg_stress * 0.5) # Riduce fino al 50% basato sullo stress - print(f"Fattore di stress dalla simulazione: {stress_factor:.2f}") - - # Prepara i dati statici - static_data = [] - - # Aggiungi hectares come prima feature statica - static_data.append(hectares) - - # Ottieni tutte le possibili varietà dal dataset di training - all_varieties = olive_varieties['Varietà di Olive'].unique() - varieties = [clean_column_name(variety) for variety in all_varieties] - - # Per ogni varietà possibile nel dataset - for variety in varieties: - # Trova se questa varietà è tra quelle selezionate - selected_variety = None - selected_idx = None - - for idx, info in enumerate(varieties_info): - if clean_column_name(info['Varietà di Olive']) == variety: - selected_variety = info - selected_idx = idx - break - - if selected_variety is not None: - percentage = percentages[selected_idx] / 100 # Converti in decimale - - # Aggiungi tutte le feature numeriche della varietà - static_data.extend([ - percentage, # pct - float(selected_variety['Produzione (tonnellate/ettaro)']), # prod_t_ha - float(selected_variety['Produzione Olio (tonnellate/ettaro)']), # oil_prod_t_ha - float(selected_variety['Produzione Olio (litri/ettaro)']), # oil_prod_l_ha - float(selected_variety['Min % Resa']), # min_yield_pct - float(selected_variety['Max % Resa']), # max_yield_pct - float(selected_variety['Min Produzione Olio (litri/ettaro)']), # min_oil_prod_l_ha - float(selected_variety['Max Produzione Olio (litri/ettaro)']), # max_oil_prod_l_ha - float(selected_variety['Media Produzione Olio (litri/ettaro)']), # avg_oil_prod_l_ha - float(selected_variety['Litri per Tonnellata']), # l_per_t - float(selected_variety['Min Litri per Tonnellata']), # min_l_per_t - float(selected_variety['Max Litri per Tonnellata']), # max_l_per_t - float(selected_variety['Media Litri per Tonnellata']) # avg_l_per_t - ]) - - # Aggiungi le feature binarie per la tecnica - tech = str(selected_variety['Tecnica di Coltivazione']).lower() - static_data.extend([ - 1 if tech == 'tradizionale' else 0, - 1 if tech == 'intensiva' else 0, - 1 if tech == 'superintensiva' else 0 - ]) - else: - # Se la varietà non è selezionata, aggiungi zeri per tutte le sue feature - static_data.extend([0] * 13) # Feature numeriche - static_data.extend([0] * 3) # Feature tecniche binarie - - # Converti in array numpy e reshape - temporal_data = np.array(temporal_data).reshape(1, 1, -1) # (1, 1, n_temporal_features) - static_data = np.array(static_data).reshape(1, -1) # (1, n_static_features) - - print(f"Shape dei dati temporali: {temporal_data.shape}") - print(f"Shape dei dati statici: {static_data.shape}") - - # Standardizza i dati - temporal_data = scaler_temporal.transform(temporal_data.reshape(1, -1)).reshape(1, 1, -1) - static_data = scaler_static.transform(static_data) - - # Prepara il dizionario di input per il modello - input_data = { - 'temporal': temporal_data, - 'static': static_data - } - - # Effettua la predizione - prediction = model.predict(input_data) - - print("\nRaw prediction:", prediction) - - target_features = [ - 'olive_prod', # Produzione olive kg/ha - 'min_oil_prod', # Produzione minima olio L/ha - 'max_oil_prod', # Produzione massima olio L/ha - 'avg_oil_prod', # Produzione media olio L/ha - 'total_water_need' # Fabbisogno idrico totale m³/ha - ] - - prediction = scaler_y.inverse_transform(prediction)[0] - print("\nInverse transformed prediction:") - for feature, value in zip(target_features, prediction): - print(f"{feature}: {value:.2f}") - - # Applica il fattore di stress se disponibile - if simulation_data is not None: - prediction = prediction * stress_factor - print(f"Applied stress factor: {stress_factor}") - print(f"Prediction after stress:", prediction) - - prediction[4] = prediction[4] / 4 # correggo il bias creato dai dati di simulazione errati @todo nel prossimo modello addestrato con i dati corretti sarà dovrà essere rimosso - - # Calcola i valori per ettaro dividendo per il numero di ettari - olive_prod_ha = prediction[0] / hectares - min_oil_prod_ha = prediction[1] / hectares - max_oil_prod_ha = prediction[2] / hectares - avg_oil_prod_ha = prediction[3] / hectares - water_need_ha = prediction[4] / hectares - - print("\nValori per ettaro:") - print(f"Olive production per ha: {olive_prod_ha:.2f} kg/ha") - print(f"Min oil production per ha: {min_oil_prod_ha:.2f} L/ha") - print(f"Max oil production per ha: {max_oil_prod_ha:.2f} L/ha") - print(f"Avg oil production per ha: {avg_oil_prod_ha:.2f} L/ha") - print(f"Water need per ha: {water_need_ha:.2f} m³/ha") - - # Calcola i dettagli per varietà - variety_details = [] - total_water_need = prediction[4] # Usa il valore predetto totale - - for variety_info, percentage in zip(varieties_info, percentages): - # Calcoli specifici per varietà - base_prod_per_ha = float(variety_info['Produzione (tonnellate/ettaro)']) * 1000 * (percentage / 100) - prod_per_ha = base_prod_per_ha + # Applica il fattore di stress se disponibile if simulation_data is not None: - prod_per_ha *= stress_factor - prod_total = prod_per_ha * hectares + prediction = prediction * stress_factor + print(f"Applied stress factor: {stress_factor}") + print(f"Prediction after stress:", prediction) - base_oil_per_ha = float(variety_info['Produzione Olio (litri/ettaro)']) * (percentage / 100) - oil_per_ha = base_oil_per_ha - if simulation_data is not None: - oil_per_ha *= stress_factor - oil_total = oil_per_ha * hectares + # Calcola i valori per ettaro dividendo per il numero di ettari + olive_prod_ha = prediction[0] / hectares + min_oil_prod_ha = prediction[1] / hectares + max_oil_prod_ha = prediction[2] / hectares + avg_oil_prod_ha = prediction[3] / hectares + water_need_ha = prediction[4] / hectares - print(f"\nVariety: {variety_info['Varietà di Olive']}") - print(f"Base production: {base_prod_per_ha:.2f} kg/ha") - print(f"Final production: {prod_per_ha:.2f} kg/ha") - print(f"Base oil: {base_oil_per_ha:.2f} L/ha") - print(f"Final oil: {oil_per_ha:.2f} L/ha") + print("\nValori per ettaro:") + print(f"Olive production per ha: {olive_prod_ha:.2f} kg/ha") + print(f"Min oil production per ha: {min_oil_prod_ha:.2f} L/ha") + print(f"Max oil production per ha: {max_oil_prod_ha:.2f} L/ha") + print(f"Avg oil production per ha: {avg_oil_prod_ha:.2f} L/ha") + print(f"Water need per ha: {water_need_ha:.2f} m³/ha") - variety_details.append({ - 'variety': variety_info['Varietà di Olive'], - 'percentage': percentage, - 'production_per_ha': prod_per_ha, - 'production_total': prod_total, - 'oil_per_ha': oil_per_ha, - 'oil_total': oil_total, - 'water_need': water_need_ha * (percentage / 100), # Distribuisci il fabbisogno idrico in base alla percentuale - 'base_production': base_prod_per_ha, # Produzione senza stress - 'base_oil': base_oil_per_ha, # Produzione olio senza stress + # Calcola i dettagli per varietà + variety_details = [] + total_water_need = prediction[4] # Usa il valore predetto totale + + for variety_info, percentage in zip(varieties_info, percentages): + # Calcoli specifici per varietà + base_prod_per_ha = float(variety_info['Produzione (tonnellate/ettaro)']) * 1000 * (percentage / 100) + prod_per_ha = base_prod_per_ha + if simulation_data is not None: + prod_per_ha *= stress_factor + prod_total = prod_per_ha * hectares + + base_oil_per_ha = float(variety_info['Produzione Olio (litri/ettaro)']) * (percentage / 100) + oil_per_ha = base_oil_per_ha + if simulation_data is not None: + oil_per_ha *= stress_factor + oil_total = oil_per_ha * hectares + + print(f"\nVariety: {variety_info['Varietà di Olive']}") + print(f"Base production: {base_prod_per_ha:.2f} kg/ha") + print(f"Final production: {prod_per_ha:.2f} kg/ha") + print(f"Base oil: {base_oil_per_ha:.2f} L/ha") + print(f"Final oil: {oil_per_ha:.2f} L/ha") + + variety_details.append({ + 'variety': variety_info['Varietà di Olive'], + 'percentage': percentage, + 'production_per_ha': prod_per_ha, + 'production_total': prod_total, + 'oil_per_ha': oil_per_ha, + 'oil_total': oil_total, + 'water_need': water_need_ha * (percentage / 100), # Distribuisci il fabbisogno idrico in base alla percentuale + 'base_production': base_prod_per_ha, # Produzione senza stress + 'base_oil': base_oil_per_ha, # Produzione olio senza stress + 'stress_factor': stress_factor if simulation_data is not None else 1.0 + }) + + return { + 'olive_production': olive_prod_ha, # kg/ha + 'olive_production_total': prediction[0], # kg totali + 'min_oil_production': min_oil_prod_ha, # L/ha + 'max_oil_production': max_oil_prod_ha, # L/ha + 'avg_oil_production': avg_oil_prod_ha, # L/ha + 'avg_oil_production_total': prediction[3], # L totali + 'water_need': water_need_ha, # m³/ha + 'water_need_total': prediction[4], # m³ totali + 'variety_details': variety_details, + 'hectares': hectares, 'stress_factor': stress_factor if simulation_data is not None else 1.0 - }) + } - return { - 'olive_production': olive_prod_ha, # kg/ha - 'olive_production_total': prediction[0], # kg totali - 'min_oil_production': min_oil_prod_ha, # L/ha - 'max_oil_production': max_oil_prod_ha, # L/ha - 'avg_oil_production': avg_oil_prod_ha, # L/ha - 'avg_oil_production_total': prediction[3], # L totali - 'water_need': water_need_ha, # m³/ha - 'water_need_total': prediction[4], # m³ totali - 'variety_details': variety_details, - 'hectares': hectares, - 'stress_factor': stress_factor if simulation_data is not None else 1.0 - } - - 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 + 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 + else: + return mock_make_prediction(weather_data, varieties_info, percentages, hectares, simulation_data) def create_phase_card(phase: str, data: dict) -> dbc.Card: @@ -639,7 +604,7 @@ def calculate_kpis(sim_data: pd.DataFrame) -> dict: return kpis -def create_kpi_indicators(kpis: dict) -> html.Div: +def create_kpi_indicators(kpis: dict) -> Card: """Crea gli indicatori visivi per i KPI""" def get_stress_color(value): @@ -720,6 +685,213 @@ def create_kpi_indicators(kpis: dict) -> html.Div: return indicators +class AppState: + _instance = None + _initialized = False + + def __new__(cls): + if cls._instance is None: + print("Creating new AppState instance...") + cls._instance = super(AppState, cls).__new__(cls) + return cls._instance + + def __init__(self): + # Assicurati che l'inizializzazione avvenga solo una volta + if not AppState._initialized: + print("Inizializzazione AppState...") + self.simulated_data = None + self.weather_data = None + self.olive_varieties = None + self.scaler_temporal = None + self.scaler_static = None + self.scaler_y = None + self.model = None + self.use_model = None + self.initialize_app() + AppState._initialized = True + + def initialize_app(self): + """Inizializza l'applicazione caricando dati e modello""" + try: + print("Inizializzazione applicazione...") + print("Caricamento dataset e scaler...") + + # Ignora warning sulla versione di scikit-learn + warnings.filterwarnings("ignore", category=UserWarning) + + self.simulated_data = pd.read_parquet("./sources/olive_training_dataset.parquet") + self.weather_data = pd.read_parquet("./sources/weather_data_solarenergy.parquet") + self.olive_varieties = pd.read_parquet("./sources/olive_varieties.parquet") + self.scaler_temporal = joblib.load('./sources/olive_oil_transformer/olive_oil_transformer_scaler_temporal.joblib') + self.scaler_static = joblib.load('./sources/olive_oil_transformer/olive_oil_transformer_scaler_static.joblib') + self.scaler_y = joblib.load('./sources/olive_oil_transformer/olive_oil_transformer_scaler_y.joblib') + + print("Caricamento modello...") + try: + self.load_model() + print("Modello caricato con successo") + except Exception as e: + print(f"Errore nel caricamento del modello: {e}") + raise e + + print("Inizializzazione completata con successo") + + except Exception as e: + print(f"Errore nell'inizializzazione: {str(e)}") + import traceback + traceback.print_exc() + raise e + + def load_model(self): + + try: + print(f"Keras version: {keras.__version__}") + print(f"TensorFlow version: {tf.__version__}") + print(f"CUDA available: {tf.test.is_built_with_cuda()}") + print(f"GPU devices: {tf.config.list_physical_devices('GPU')}") + + # GPU memory configuration + gpus = tf.config.experimental.list_physical_devices('GPU') + if gpus: + try: + for gpu in gpus: + tf.config.experimental.set_memory_growth(gpu, True) + + logical_gpus = tf.config.experimental.list_logical_devices('GPU') + print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs") + except RuntimeError as e: + print(e) + + @keras.saving.register_keras_serializable() + class DataAugmentation(tf.keras.layers.Layer): + """Custom layer per l'augmentation dei dati""" + + def __init__(self, noise_stddev=0.03, **kwargs): + super().__init__(**kwargs) + self.noise_stddev = noise_stddev + + def call(self, inputs, training=None): + if training: + return inputs + tf.random.normal( + shape=tf.shape(inputs), + mean=0.0, + stddev=self.noise_stddev + ) + return inputs + + def get_config(self): + config = super().get_config() + config.update({"noise_stddev": self.noise_stddev}) + return config + + @keras.saving.register_keras_serializable() + class PositionalEncoding(tf.keras.layers.Layer): + """Custom layer per l'encoding posizionale""" + + def __init__(self, d_model, **kwargs): + super().__init__(**kwargs) + self.d_model = d_model + + def build(self, input_shape): + _, seq_length, _ = input_shape + + # Crea la matrice di encoding posizionale + position = tf.range(seq_length, dtype=tf.float32)[:, tf.newaxis] + div_term = tf.exp( + tf.range(0, self.d_model, 2, dtype=tf.float32) * + (-tf.math.log(10000.0) / self.d_model) + ) + + # Calcola sin e cos + pos_encoding = tf.zeros((1, seq_length, self.d_model)) + pos_encoding_even = tf.sin(position * div_term) + pos_encoding_odd = tf.cos(position * div_term) + + # Assegna i valori alle posizioni pari e dispari + pos_encoding = tf.concat( + [tf.expand_dims(pos_encoding_even, -1), + tf.expand_dims(pos_encoding_odd, -1)], + axis=-1 + ) + pos_encoding = tf.reshape(pos_encoding, (1, seq_length, -1)) + pos_encoding = pos_encoding[:, :, :self.d_model] + + # Salva l'encoding come peso non trainabile + self.pos_encoding = self.add_weight( + shape=(1, seq_length, self.d_model), + initializer=tf.keras.initializers.Constant(pos_encoding), + trainable=False, + name='positional_encoding' + ) + + super().build(input_shape) + + def call(self, inputs): + # Broadcast l'encoding posizionale sul batch + batch_size = tf.shape(inputs)[0] + pos_encoding_tiled = tf.tile(self.pos_encoding, [batch_size, 1, 1]) + return inputs + pos_encoding_tiled + + def get_config(self): + config = super().get_config() + config.update({"d_model": self.d_model}) + return config + + @keras.saving.register_keras_serializable() + class WarmUpLearningRateSchedule(tf.keras.optimizers.schedules.LearningRateSchedule): + """Custom learning rate schedule with linear warmup and exponential decay.""" + + def __init__(self, initial_learning_rate=1e-3, warmup_steps=500, decay_steps=5000): + super().__init__() + self.initial_learning_rate = initial_learning_rate + self.warmup_steps = warmup_steps + self.decay_steps = decay_steps + + def __call__(self, step): + warmup_pct = tf.cast(step, tf.float32) / self.warmup_steps + warmup_lr = self.initial_learning_rate * warmup_pct + decay_factor = tf.pow(0.1, tf.cast(step, tf.float32) / self.decay_steps) + decayed_lr = self.initial_learning_rate * decay_factor + return tf.where(step < self.warmup_steps, warmup_lr, decayed_lr) + + def get_config(self): + return { + 'initial_learning_rate': self.initial_learning_rate, + 'warmup_steps': self.warmup_steps, + 'decay_steps': self.decay_steps + } + + @keras.saving.register_keras_serializable() + def weighted_huber_loss(y_true, y_pred): + # Pesi per diversi output + weights = tf.constant([1.0, 0.8, 0.8, 1.0, 0.6], dtype=tf.float32) + huber = tf.keras.losses.Huber(delta=1.0) + loss = huber(y_true, y_pred) + weighted_loss = tf.reduce_mean(loss * weights) + return weighted_loss + + print("Caricamento modello...") + + model_path = './sources/olive_oil_transformer/olive_oil_transformer_model.keras' + if not os.path.exists(model_path): + raise FileNotFoundError(f"Modello non trovato in: {model_path}") + + model = tf.keras.models.load_model(model_path, custom_objects={ + 'DataAugmentation': DataAugmentation, + 'PositionalEncoding': PositionalEncoding, + 'WarmUpLearningRateSchedule': WarmUpLearningRateSchedule, + 'weighted_huber_loss': weighted_huber_loss + }) + self.model = model + self.use_model = True + except Exception as e: + print(f"Errore nel caricamento del modello: {str(e)}") + self.model = None + self.use_model = False + + +app_state = AppState() + server = flask.Flask(__name__) server.secret_key = utils.SECRET_KEY @@ -1202,8 +1374,8 @@ def create_configuration_tab(): 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], + for v in app_state.olive_varieties['Varietà di Olive'].unique()], + value=app_state.olive_varieties['Varietà di Olive'].iloc[0], className="mb-2" ), ], md=4), @@ -1243,7 +1415,7 @@ def create_configuration_tab(): dcc.Dropdown( id='variety-2-dropdown', options=[{'label': v, 'value': v} - for v in olive_varieties['Varietà di Olive'].unique()], + for v in app_state.olive_varieties['Varietà di Olive'].unique()], value=None, className="mb-2" ), @@ -1286,7 +1458,7 @@ def create_configuration_tab(): dcc.Dropdown( id='variety-3-dropdown', options=[{'label': v, 'value': v} - for v in olive_varieties['Varietà di Olive'].unique()], + for v in app_state.olive_varieties['Varietà di Olive'].unique()], value=None, className="mb-2" ), @@ -1783,7 +1955,7 @@ app.layout = html.Div([ dcc.Store(id='session', storage_type='local'), dcc.Store(id='user-data', storage_type='local'), dcc.Store(id=Ids.INFERENCE_COUNTER, storage_type='session', data={'count': 0}), - dcc.Store(id=Ids.DEV_MODE, storage_type='session', data={'count': 0}), + dcc.Store(id=Ids.USE_MODEL, storage_type='session', data={'count': 0}), html.Div(id=Ids.AUTH_CONTAINER), html.Div(id=Ids.DASHBOARD_CONTAINER), ]) @@ -2057,8 +2229,8 @@ def create_water_needs_figure(prediction): 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'] + variety_info = app_state.olive_varieties[ + app_state.olive_varieties['Varietà di Olive'] == detail['variety'] ].iloc[0] water_need = variety_info[f'Fabbisogno Acqua {season} (m³/ettaro)'] @@ -2128,34 +2300,6 @@ def get_season_from_month(month): return seasons[month] -@app.callback( - Output('loading-alert', 'children'), - [Input('simulate-btn', 'n_clicks'), - Input('debug-switch', 'value')], - running=[ - (Output(Ids.DASHBOARD_CONTAINER, 'children'), - [Input('url', 'pathname')], - lambda x: x == '/') - ] -) -def update_loading_status(n_clicks, debug_mode): - global DEV_MODE - - config = load_config() - - DEV_MODE = config['inference']['debug_mode'] - if not DEV_MODE and MODEL_LOADING: - return dbc.Alert( - [ - html.I(className="fas fa-spinner fa-spin me-2"), - "Caricamento del modello in corso..." - ], - color="warning", - is_open=True - ) - return None - - @app.callback( [ Output(Ids.PRODUCTION_INFERENCE_MODE, 'children'), @@ -2195,7 +2339,6 @@ def update_inference_status(debug_mode, counter_data): prevent_initial_call=True ) def toggle_inference_mode(debug_mode): - global DEV_MODE, model, MODEL_LOADING, scaler_temporal, scaler_static, scaler_y new_counter_data = {'count': 0} try: config = load_config() @@ -2204,12 +2347,11 @@ def toggle_inference_mode(debug_mode): config['inference'] = config.get('inference', {}) # Crea la sezione se non esiste config['inference']['debug_mode'] = debug_mode - DEV_MODE = debug_mode - print(f"DEV_MODE: {DEV_MODE}") + use_model = not debug_mode + print(f"use_model: {use_model}") dcc.Store(id=Ids.INFERENCE_COUNTER, data=new_counter_data) - if debug_mode: - - MODEL_LOADING = False + app_state.use_model = use_model + if not use_model: return ( dbc.Alert("Modalità Debug attiva - Using mock predictions", color="info"), "Debug (Mock)", @@ -2218,174 +2360,15 @@ def toggle_inference_mode(debug_mode): new_counter_data ) else: - if model is None: - try: - MODEL_LOADING = True - print(f"Keras version: {keras.__version__}") - print(f"TensorFlow version: {tf.__version__}") - print(f"CUDA available: {tf.test.is_built_with_cuda()}") - print(f"GPU devices: {tf.config.list_physical_devices('GPU')}") - - # GPU memory configuration - gpus = tf.config.experimental.list_physical_devices('GPU') - if gpus: - try: - for gpu in gpus: - tf.config.experimental.set_memory_growth(gpu, True) - - logical_gpus = tf.config.experimental.list_logical_devices('GPU') - print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs") - except RuntimeError as e: - print(e) - - @keras.saving.register_keras_serializable() - class DataAugmentation(tf.keras.layers.Layer): - """Custom layer per l'augmentation dei dati""" - - def __init__(self, noise_stddev=0.03, **kwargs): - super().__init__(**kwargs) - self.noise_stddev = noise_stddev - - def call(self, inputs, training=None): - if training: - return inputs + tf.random.normal( - shape=tf.shape(inputs), - mean=0.0, - stddev=self.noise_stddev - ) - return inputs - - def get_config(self): - config = super().get_config() - config.update({"noise_stddev": self.noise_stddev}) - return config - - @keras.saving.register_keras_serializable() - class PositionalEncoding(tf.keras.layers.Layer): - """Custom layer per l'encoding posizionale""" - - def __init__(self, d_model, **kwargs): - super().__init__(**kwargs) - self.d_model = d_model - - def build(self, input_shape): - _, seq_length, _ = input_shape - - # Crea la matrice di encoding posizionale - position = tf.range(seq_length, dtype=tf.float32)[:, tf.newaxis] - div_term = tf.exp( - tf.range(0, self.d_model, 2, dtype=tf.float32) * - (-tf.math.log(10000.0) / self.d_model) - ) - - # Calcola sin e cos - pos_encoding = tf.zeros((1, seq_length, self.d_model)) - pos_encoding_even = tf.sin(position * div_term) - pos_encoding_odd = tf.cos(position * div_term) - - # Assegna i valori alle posizioni pari e dispari - pos_encoding = tf.concat( - [tf.expand_dims(pos_encoding_even, -1), - tf.expand_dims(pos_encoding_odd, -1)], - axis=-1 - ) - pos_encoding = tf.reshape(pos_encoding, (1, seq_length, -1)) - pos_encoding = pos_encoding[:, :, :self.d_model] - - # Salva l'encoding come peso non trainabile - self.pos_encoding = self.add_weight( - shape=(1, seq_length, self.d_model), - initializer=tf.keras.initializers.Constant(pos_encoding), - trainable=False, - name='positional_encoding' - ) - - super().build(input_shape) - - def call(self, inputs): - # Broadcast l'encoding posizionale sul batch - batch_size = tf.shape(inputs)[0] - pos_encoding_tiled = tf.tile(self.pos_encoding, [batch_size, 1, 1]) - return inputs + pos_encoding_tiled - - def get_config(self): - config = super().get_config() - config.update({"d_model": self.d_model}) - return config - - @keras.saving.register_keras_serializable() - class WarmUpLearningRateSchedule(tf.keras.optimizers.schedules.LearningRateSchedule): - """Custom learning rate schedule with linear warmup and exponential decay.""" - - def __init__(self, initial_learning_rate=1e-3, warmup_steps=500, decay_steps=5000): - super().__init__() - self.initial_learning_rate = initial_learning_rate - self.warmup_steps = warmup_steps - self.decay_steps = decay_steps - - def __call__(self, step): - warmup_pct = tf.cast(step, tf.float32) / self.warmup_steps - warmup_lr = self.initial_learning_rate * warmup_pct - decay_factor = tf.pow(0.1, tf.cast(step, tf.float32) / self.decay_steps) - decayed_lr = self.initial_learning_rate * decay_factor - return tf.where(step < self.warmup_steps, warmup_lr, decayed_lr) - - def get_config(self): - return { - 'initial_learning_rate': self.initial_learning_rate, - 'warmup_steps': self.warmup_steps, - 'decay_steps': self.decay_steps - } - - @keras.saving.register_keras_serializable() - def weighted_huber_loss(y_true, y_pred): - # Pesi per diversi output - weights = tf.constant([1.0, 0.8, 0.8, 1.0, 0.6], dtype=tf.float32) - huber = tf.keras.losses.Huber(delta=1.0) - loss = huber(y_true, y_pred) - weighted_loss = tf.reduce_mean(loss * weights) - return weighted_loss - - print("Caricamento modello...") - - # Verifica che il modello sia disponibile - model_path = './sources/olive_oil_transformer/olive_oil_transformer_model.keras' - if not os.path.exists(model_path): - raise FileNotFoundError(f"Modello non trovato in: {model_path}") - - # Prova a caricare il modello - model = tf.keras.models.load_model(model_path, custom_objects={ - 'DataAugmentation': DataAugmentation, - 'PositionalEncoding': PositionalEncoding, - 'WarmUpLearningRateSchedule': WarmUpLearningRateSchedule, - 'weighted_huber_loss': weighted_huber_loss - }) - MODEL_LOADING = False - return ( - dbc.Alert("Modello caricato correttamente", color="success"), - "Produzione (Local Model)", - "~ 100ms", - "0", - new_counter_data - ) - except Exception as e: - print(f"Errore nel caricamento del modello: {str(e)}") - # Se c'è un errore nel caricamento del modello, torna in modalità debug - DEV_MODE = True - MODEL_LOADING = False - return ( - dbc.Alert(f"Errore nel caricamento del modello: {str(e)}", color="danger"), - "Debug (Mock) - Fallback", - "N/A", - "N/A", - new_counter_data - ) - - else: - MODEL_LOADING = False + return ( + dbc.Alert("Modello caricato correttamente", color="success"), + "Produzione (Local Model)", + "~ 100ms", + "0", + new_counter_data + ) except Exception as e: print(f"Errore nella configurazione inferenza: {str(e)}") - MODEL_LOADING = False return ( dbc.Alert(f"Errore: {str(e)}", color="danger"), "Errore", @@ -2440,7 +2423,6 @@ def display_page(pathname, session_data): is_valid, username = verify_token(session_data['token']) if not is_valid: - print("Invalid token") # Debug print return create_login_layout(), html.Div() # print(f"Valid session for user: {username}") # Debug print @@ -2821,7 +2803,8 @@ def check_percentages(perc1, perc2, perc3): (Output(Ids.DASHBOARD_CONTAINER, 'children'), [Input('url', 'pathname')], lambda x: x == '/') - ] + ], + prevent_initial_call=True ) def load_configuration(active_tab, var2_current, var3_current, pathname): try: @@ -2831,10 +2814,7 @@ def load_configuration(active_tab, var2_current, var3_current, pathname): # Carica dati varietà 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} - var3 = varieties[2] if len(varieties) > 2 else {"variety": None, "technique": None, "percentage": 0} - print(var2_current) if var2_current is not None: var2 = next((v for v in varieties if v["variety"] == var2_current), {"variety": var2_current, "technique": None, "percentage": 0}) @@ -2847,7 +2827,6 @@ def load_configuration(active_tab, var2_current, var3_current, pathname): else: var3 = {"variety": None, "technique": None, "percentage": 0} - # Carica costi e marketing costs = config['costs'] fixed = costs.get('fixed', {}) @@ -2937,7 +2916,7 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count """ Callback principale per aggiornare tutti i componenti della simulazione """ - if n_clicks is None or MODEL_LOADING: + if n_clicks is None: # Crea grafici vuoti per l'inizializzazione empty_growth_fig = go.Figure() empty_production_fig = go.Figure() @@ -2993,9 +2972,9 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count # Estrai le informazioni dalle varietà configurate for variety_config in config['oliveto']['varieties']: - variety_data = olive_varieties[ - (olive_varieties['Varietà di Olive'] == variety_config['variety']) & - (olive_varieties['Tecnica di Coltivazione'].str.lower() == variety_config['technique'].lower()) + variety_data = app_state.olive_varieties[ + (app_state.olive_varieties['Varietà di Olive'] == variety_config['variety']) & + (app_state.olive_varieties['Tecnica di Coltivazione'].str.lower() == variety_config['technique'].lower()) ] if not variety_data.empty: varieties_info.append(variety_data.iloc[0]) @@ -3003,7 +2982,7 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count current_count = counter_data.get('count', 0) + 1 - prediction = make_prediction(weather_data, varieties_info, percentages, hectares, sim_data) + prediction = make_prediction(app_state.weather_data, varieties_info, percentages, hectares, sim_data) dcc.Store(id=Ids.INFERENCE_COUNTER, data={'count': current_count}) @@ -3019,7 +2998,7 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count # Creazione grafici con il nuovo stile details_fig = create_production_details_figure(prediction) - weather_fig = create_weather_impact_figure(weather_data) + weather_fig = create_weather_impact_figure(app_state.weather_data) water_fig = {} # create_water_needs_figure(prediction) # Creazione info extra con il nuovo stile @@ -3160,18 +3139,40 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--port', type=int, help='Port to run the server on') parser.add_argument('--debug', action='store_true', help='Debug mode') + parser.add_argument('--host', type=str, default='0.0.0.0', help='Host to run the server on') args = parser.parse_args() env_port = int(os.environ.get('DASH_PORT', 8888)) env_debug = os.environ.get('DASH_DEBUG', '').lower() == 'true' + env_host = os.environ.get('DASH_HOST', '0.0.0.0') port = args.port if args.port is not None else env_port debug = args.debug if args.debug else env_debug + host = args.host if args.host else env_host - print(f"Starting server on port {port} with debug={'on' if debug else 'off'}") + print(f"Starting server on {host}:{port} with debug={'on' if debug else 'off'}") - app.run_server( - host='0.0.0.0', - port=port, - debug=debug + # Configurazione del server + server.config.update( + SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_HTTPONLY=True, + SESSION_COOKIE_SAMESITE='Lax', ) + + # Aggiunta middleware per la sicurezza + from werkzeug.middleware.proxy_fix import ProxyFix + + server.wsgi_app = ProxyFix( + server.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 + ) + + try: + app.run_server( + host=host, + port=port, + debug=debug, + ssl_context=None, # Disabilita SSL a livello applicativo + threaded=True + ) + except Exception as e: + print(f"Error starting server: {e}") diff --git a/src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-312.pyc b/src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-312.pyc deleted file mode 100644 index d3d7c06963fbaf95082f4e16e96dc74ffdea615a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21049 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 kQ+rsi`3GV-^CMgH;cc29ZNvEg1LTvvfdBvi diff --git a/src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-39.pyc b/src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-39.pyc deleted file mode 100755 index ac54765c6092373f72c37638df134e27d6821188..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12540 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 JUxfnI_+NOhsS^MI diff --git a/src/requirements.txt b/src/requirements.txt index 53cf16a..3ab6813 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,7 +1,7 @@ # Data handling and analysis pandas>=1.5.0 numpy>=1.21.0 -scikit-learn==1.5.2 +scikit-learn==1.6.0 # Dashboard and visualization dash>=2.9.0 diff --git a/src/utils/__pycache__/__init__.cpython-311.pyc b/src/utils/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 916b44ce18d0a3b20e3f88651abb943dcc9426ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 202 zcmZ3^%ge<81UZZJ(n0iN5CH>>P{wCAAY(d13PUi1CZpdH&etz4O4cte$;>I%kB`sH%PfhH*DI*}#bJ}1pHiBWYFESxv<>8*Vtyd; Rftit!@dE>lC}IYR0RWG3H2eSn diff --git a/src/utils/__pycache__/__init__.cpython-312.pyc b/src/utils/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 5461059fe78f52ff0154d7fac9522ac2c79897e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190 zcmX@j%ge<81UZZJ(n0iN5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!iq#Lx&neAKE!KCf zC`m2KOU%&^D9TSyEh)*=ch4*>PAw=%^(#%zOv^7y)OYs`(GRFB$;i*sFG(%V1QWUi zsp*Nu`TE60$@--wnK{M!@$s2?nI-Y@dIgogIBatBQ%ZAE?TT1|wlM;6F^KVznURsP Ih#ANN0B77X@Bjb+ diff --git a/src/utils/__pycache__/__init__.cpython-39.pyc b/src/utils/__pycache__/__init__.cpython-39.pyc deleted file mode 100755 index f493464e1ffc2da8c8732540ab5fc480f8982bd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmYe~<>g`kg4{)V=^*+sh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10SKeRZts8~Na zBQvomv7jikEVCrFC{aJ4C_gJTxujS>pfWilu_zbLFG(%V)GbI&Pb|*YFD^>fFD=Q; iDb|mV&& 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 DA!}Uc diff --git a/src/utils/__pycache__/helpers.cpython-39.pyc b/src/utils/__pycache__/helpers.cpython-39.pyc deleted file mode 100755 index 597d4c031b4fc15c8bc0690b75056681a1c4c3f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12313 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 W0@x8D!m6mo37&VOsW1*T~P&X{`0pvcmV~ujf zgD7EL(Y~O{3*vaF3A<-2vPmJs^_#LQE3Fk;Rg3d<$LUr=pxJAm^ zV8&yex!lxC9O!xaBL7i$YqaRO4aFT#LV+tDkOoAyEDeZp z`6LaVVc!8XdbkdWkn{JwL+!@lFR|5kNIj?B;@e(%&>|#uE54&@wa}+m)pNnv39$OmvEtgVY_9Xr$D^gyMkaZqJ5E6}h zU>{@Dc%nzP!wDGrA(>SeNes&MY)}R)D$*0kpe8aO5)JrD6u10d;w|k7RCbQB&cls2 z+5dv7iN{16IcO3tn>-V$Ms%>mcy%s8z8jd%p!vut5z1&YTttD8JbIiJh>3>Bs~xAZ zlRb|Yc&OtE-@Rv%IWC`LmApzcE7w_|uJ4jV2&S=5);XGYcE`MjSKKe-)7O}Dkt zqcOn&O-?E(p!p&kKTrmiDVBIxz&=obNFYR$gC-7^@X3uhR^p+tHhdxd7RJNGGU{fI zNCX3eU~`)TN4aw(Cl$cUZGtbm&Dd|X$o&RvH0GG|$W_GU8U;QD>l9p}AfTW_K}bPF z0hy(8g91rGOhH0HkAh7Ku2OJ~f-MBMb*BoCL0|L*o>L-f4`Wb(Q;T~T^6*=>sS??x z%pUjN#RH8~--e~VOsQ;L7l_TkuiooII(YJFWMo>{%?ix;$x#@uxm~YOlPgrGMFEil zYh_tACzY+-QXYP>x$y`}Im0A!5t6adS2UgJ2@a6e2TculLawGx4U+qg%YN=8cOr7W zCu8{+j3#~&fwI6{YjGAve;rC|UDXhp>?f!2J$^qZFPgelf-;L*N1)I?rpaGNs|{^T zPbG0Qc#4S7pY_z&5D_XgI29V4MlJ%pb-dv!hi-NbjsenG7hveCyGm+63pL0@?|U;J zW_@n~?tnoqjQL^ixs9VKoj#epx6loX-sj1HgG*}MyE%~2MCK>pIPqa}lQIJeyg&cZ z|3@Pkvecn!a&Ie_^}ai4Ippyid20J};khy_8&mI}3RA2eJE)J)AP5U>C({1-I& zd8GcwSH4Wo-=2kYs*D3V@hIt&i+a2ngFnCWK`Ghb0{x z-k*bfG=T$Z@a0ic&{geDBz)NAP#1VaBj(DZ z!drFWGPIvM{b=Lf2;8rs_lE|BfVG2ubMTA@7+{jC_qmrqm%oT*hHsLbRXB}-flr8{YhqjEaCTq>^ z8bGu~6XgQ}Pyl%p_^-*YpmJ**+T?W|GFB6{0AxN2O4S8S_z)nyqk(;5!Z4uCK^mFx zJG!ut4)c9Z5oLT+DH!mginD>sVrr)#YCEQ|c8cM-e$F&Ea|k(~ zM`{77rkF!c6|n+ZF~#AXVhSJc9C=3dIb~iUCj-4 z)H)6faD)I&VdAY(s$oEbcvsWRA8YSw#j5&%T2mj?ZHNb-(NT!uk0gjT9@J4T*L>6K*eDL-;7~TmnLx7_d1UB z5@<}<;YQ|T6+J1eoA?)ZEl4-MdGv;+DoJSt6`biOGB?>jOC6RxzS@K4G86Z9aD<9`{n<{8iS?90;r^KTdo-g^QN}f!ix3Tpmz&%c6j=l2mU&UZe(2qMqTKDdxp@ z4!@9N5-Mh5uj0ayu-(Moessxx5t`23=4G#9X_KXW$RR^43F`K)&1U#}5w45J( z+*akMQTjIRY$)QZezJC3V*uUB3ol*WOo0+SA5QHwS1;oh~ z>`fBYjRM~dkbg`?zee?|1ew7h#GU4m%^Vo{w+J7(_`!v7XW_DqmyRm!)DOrJ!v-(y$L>d{a#5AQ&0dp^A zS0tJE5^XLK$%UGgE?}wnav)z|ljo_7OF0GhA7sV;2^#wd*_*?*J6p4C6R^6>YLFy3 z$9#$DfM++hR=l4i95U++bm?B;eGChf+a=^M%71lw>$OMgj~+QzKLykI-RfELXliQt YN7X9}$L9`NW$T1xmTTpDdA3~sKVF96(f|Me