Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a9b381bd0 | |||
| f1d9bdc04d | |||
| 84bfdaed48 |
14
src/.gitignore
vendored
14
src/.gitignore
vendored
@ -1 +1,15 @@
|
|||||||
/sources
|
/sources
|
||||||
|
|
||||||
|
|
||||||
|
/auth/config/users/*.json
|
||||||
|
users/*.json
|
||||||
|
# Python cache files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
Binary file not shown.
@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Ids:
|
class Ids:
|
||||||
# Auth Container
|
# Auth Container
|
||||||
|
USE_MODEL = 'use-model'
|
||||||
AUTH_CONTAINER = 'auth-container'
|
AUTH_CONTAINER = 'auth-container'
|
||||||
DASHBOARD_CONTAINER = 'dashboard-container'
|
DASHBOARD_CONTAINER = 'dashboard-container'
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -1,6 +1,8 @@
|
|||||||
|
import warnings
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import plotly.express as px
|
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 tensorflow as tf
|
||||||
import keras
|
import keras
|
||||||
import joblib
|
import joblib
|
||||||
@ -9,13 +11,14 @@ import os
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
from dash.exceptions import PreventUpdate
|
from dash.exceptions import PreventUpdate
|
||||||
|
from dash_bootstrap_components import Card
|
||||||
|
|
||||||
from auth import utils
|
from auth import utils
|
||||||
from utils.helpers import clean_column_name
|
from utils.helpers import clean_column_name
|
||||||
from dashboard.environmental_simulator import *
|
from dashboard.environmental_simulator import *
|
||||||
from dash import no_update
|
from dash import no_update
|
||||||
from auth.utils import (
|
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
|
verify_token, create_user, get_user_config_path, get_default_config
|
||||||
)
|
)
|
||||||
from auth.login import create_login_layout, create_register_layout
|
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
|
# Set global precision policy
|
||||||
tf.keras.mixed_precision.set_global_policy('float32')
|
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():
|
def load_config():
|
||||||
try:
|
try:
|
||||||
@ -106,20 +102,6 @@ def save_config(config):
|
|||||||
return False, f"Errore nel salvataggio: {str(e)}"
|
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):
|
def prepare_static_features_multiple(varieties_info, percentages, hectares, all_varieties):
|
||||||
"""
|
"""
|
||||||
Prepara le feature statiche per multiple varietà seguendo la struttura esatta della simulazione.
|
Prepara le feature statiche per multiple varietà seguendo la struttura esatta della simulazione.
|
||||||
@ -348,25 +330,8 @@ def mock_make_prediction(weather_data, varieties_info, percentages, hectares, si
|
|||||||
|
|
||||||
|
|
||||||
def make_prediction(weather_data, varieties_info, percentages, hectares, simulation_data=None):
|
def make_prediction(weather_data, varieties_info, percentages, hectares, simulation_data=None):
|
||||||
print(f"DEV_MODE: {DEV_MODE}")
|
if app_state.use_model:
|
||||||
if DEV_MODE:
|
|
||||||
return mock_make_prediction(weather_data, varieties_info, percentages, hectares, simulation_data)
|
|
||||||
try:
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Inizio della funzione make_prediction")
|
print("Inizio della funzione make_prediction")
|
||||||
|
|
||||||
# Prepara i dati temporali (meteorologici)
|
# Prepara i dati temporali (meteorologici)
|
||||||
@ -413,7 +378,7 @@ def make_prediction(weather_data, varieties_info, percentages, hectares, simulat
|
|||||||
static_data.append(hectares)
|
static_data.append(hectares)
|
||||||
|
|
||||||
# Ottieni tutte le possibili varietà dal dataset di training
|
# Ottieni tutte le possibili varietà dal dataset di training
|
||||||
all_varieties = olive_varieties['Varietà di Olive'].unique()
|
all_varieties = app_state.olive_varieties['Varietà di Olive'].unique()
|
||||||
varieties = [clean_column_name(variety) for variety in all_varieties]
|
varieties = [clean_column_name(variety) for variety in all_varieties]
|
||||||
|
|
||||||
# Per ogni varietà possibile nel dataset
|
# Per ogni varietà possibile nel dataset
|
||||||
@ -468,8 +433,8 @@ def make_prediction(weather_data, varieties_info, percentages, hectares, simulat
|
|||||||
print(f"Shape dei dati statici: {static_data.shape}")
|
print(f"Shape dei dati statici: {static_data.shape}")
|
||||||
|
|
||||||
# Standardizza i dati
|
# Standardizza i dati
|
||||||
temporal_data = scaler_temporal.transform(temporal_data.reshape(1, -1)).reshape(1, 1, -1)
|
temporal_data = app_state.scaler_temporal.transform(temporal_data.reshape(1, -1)).reshape(1, 1, -1)
|
||||||
static_data = scaler_static.transform(static_data)
|
static_data = app_state.scaler_static.transform(static_data)
|
||||||
|
|
||||||
# Prepara il dizionario di input per il modello
|
# Prepara il dizionario di input per il modello
|
||||||
input_data = {
|
input_data = {
|
||||||
@ -478,7 +443,7 @@ def make_prediction(weather_data, varieties_info, percentages, hectares, simulat
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Effettua la predizione
|
# Effettua la predizione
|
||||||
prediction = model.predict(input_data)
|
prediction = app_state.model.predict(input_data)
|
||||||
|
|
||||||
print("\nRaw prediction:", prediction)
|
print("\nRaw prediction:", prediction)
|
||||||
|
|
||||||
@ -490,7 +455,7 @@ def make_prediction(weather_data, varieties_info, percentages, hectares, simulat
|
|||||||
'total_water_need' # Fabbisogno idrico totale m³/ha
|
'total_water_need' # Fabbisogno idrico totale m³/ha
|
||||||
]
|
]
|
||||||
|
|
||||||
prediction = scaler_y.inverse_transform(prediction)[0]
|
prediction = app_state.scaler_y.inverse_transform(prediction)[0]
|
||||||
print("\nInverse transformed prediction:")
|
print("\nInverse transformed prediction:")
|
||||||
for feature, value in zip(target_features, prediction):
|
for feature, value in zip(target_features, prediction):
|
||||||
print(f"{feature}: {value:.2f}")
|
print(f"{feature}: {value:.2f}")
|
||||||
@ -501,8 +466,6 @@ def make_prediction(weather_data, varieties_info, percentages, hectares, simulat
|
|||||||
print(f"Applied stress factor: {stress_factor}")
|
print(f"Applied stress factor: {stress_factor}")
|
||||||
print(f"Prediction after stress:", prediction)
|
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
|
# Calcola i valori per ettaro dividendo per il numero di ettari
|
||||||
olive_prod_ha = prediction[0] / hectares
|
olive_prod_ha = prediction[0] / hectares
|
||||||
min_oil_prod_ha = prediction[1] / hectares
|
min_oil_prod_ha = prediction[1] / hectares
|
||||||
@ -575,6 +538,8 @@ def make_prediction(weather_data, varieties_info, percentages, hectares, simulat
|
|||||||
print("Traceback completo:")
|
print("Traceback completo:")
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
raise e
|
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:
|
def create_phase_card(phase: str, data: dict) -> dbc.Card:
|
||||||
@ -639,7 +604,7 @@ def calculate_kpis(sim_data: pd.DataFrame) -> dict:
|
|||||||
return kpis
|
return kpis
|
||||||
|
|
||||||
|
|
||||||
def create_kpi_indicators(kpis: dict) -> html.Div:
|
def create_kpi_indicators(kpis: dict) -> Card:
|
||||||
"""Crea gli indicatori visivi per i KPI"""
|
"""Crea gli indicatori visivi per i KPI"""
|
||||||
|
|
||||||
def get_stress_color(value):
|
def get_stress_color(value):
|
||||||
@ -720,6 +685,213 @@ def create_kpi_indicators(kpis: dict) -> html.Div:
|
|||||||
return indicators
|
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 = flask.Flask(__name__)
|
||||||
server.secret_key = utils.SECRET_KEY
|
server.secret_key = utils.SECRET_KEY
|
||||||
|
|
||||||
@ -1202,8 +1374,8 @@ def create_configuration_tab():
|
|||||||
dcc.Dropdown(
|
dcc.Dropdown(
|
||||||
id='variety-1-dropdown',
|
id='variety-1-dropdown',
|
||||||
options=[{'label': v, 'value': v}
|
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=olive_varieties['Varietà di Olive'].iloc[0],
|
value=app_state.olive_varieties['Varietà di Olive'].iloc[0],
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
),
|
),
|
||||||
], md=4),
|
], md=4),
|
||||||
@ -1243,7 +1415,7 @@ def create_configuration_tab():
|
|||||||
dcc.Dropdown(
|
dcc.Dropdown(
|
||||||
id='variety-2-dropdown',
|
id='variety-2-dropdown',
|
||||||
options=[{'label': v, 'value': v}
|
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,
|
value=None,
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
),
|
),
|
||||||
@ -1286,7 +1458,7 @@ def create_configuration_tab():
|
|||||||
dcc.Dropdown(
|
dcc.Dropdown(
|
||||||
id='variety-3-dropdown',
|
id='variety-3-dropdown',
|
||||||
options=[{'label': v, 'value': v}
|
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,
|
value=None,
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
),
|
),
|
||||||
@ -1783,7 +1955,7 @@ app.layout = html.Div([
|
|||||||
dcc.Store(id='session', storage_type='local'),
|
dcc.Store(id='session', storage_type='local'),
|
||||||
dcc.Store(id='user-data', 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.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.AUTH_CONTAINER),
|
||||||
html.Div(id=Ids.DASHBOARD_CONTAINER),
|
html.Div(id=Ids.DASHBOARD_CONTAINER),
|
||||||
])
|
])
|
||||||
@ -2057,8 +2229,8 @@ def create_water_needs_figure(prediction):
|
|||||||
for detail in prediction['variety_details']:
|
for detail in prediction['variety_details']:
|
||||||
for month in months:
|
for month in months:
|
||||||
season = get_season_from_month(month)
|
season = get_season_from_month(month)
|
||||||
variety_info = olive_varieties[
|
variety_info = app_state.olive_varieties[
|
||||||
olive_varieties['Varietà di Olive'] == detail['variety']
|
app_state.olive_varieties['Varietà di Olive'] == detail['variety']
|
||||||
].iloc[0]
|
].iloc[0]
|
||||||
|
|
||||||
water_need = variety_info[f'Fabbisogno Acqua {season} (m³/ettaro)']
|
water_need = variety_info[f'Fabbisogno Acqua {season} (m³/ettaro)']
|
||||||
@ -2128,35 +2300,6 @@ def get_season_from_month(month):
|
|||||||
return seasons[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()
|
|
||||||
|
|
||||||
print(config)
|
|
||||||
DEV_MODE = config['inference']['debug_mode']
|
|
||||||
if 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(
|
@app.callback(
|
||||||
[
|
[
|
||||||
Output(Ids.PRODUCTION_INFERENCE_MODE, 'children'),
|
Output(Ids.PRODUCTION_INFERENCE_MODE, 'children'),
|
||||||
@ -2196,7 +2339,6 @@ def update_inference_status(debug_mode, counter_data):
|
|||||||
prevent_initial_call=True
|
prevent_initial_call=True
|
||||||
)
|
)
|
||||||
def toggle_inference_mode(debug_mode):
|
def toggle_inference_mode(debug_mode):
|
||||||
global DEV_MODE, model, MODEL_LOADING, scaler_temporal, scaler_static, scaler_y
|
|
||||||
new_counter_data = {'count': 0}
|
new_counter_data = {'count': 0}
|
||||||
try:
|
try:
|
||||||
config = load_config()
|
config = load_config()
|
||||||
@ -2205,12 +2347,11 @@ def toggle_inference_mode(debug_mode):
|
|||||||
config['inference'] = config.get('inference', {}) # Crea la sezione se non esiste
|
config['inference'] = config.get('inference', {}) # Crea la sezione se non esiste
|
||||||
config['inference']['debug_mode'] = debug_mode
|
config['inference']['debug_mode'] = debug_mode
|
||||||
|
|
||||||
DEV_MODE = debug_mode
|
use_model = not debug_mode
|
||||||
print(f"DEV_MODE: {DEV_MODE}")
|
print(f"use_model: {use_model}")
|
||||||
dcc.Store(id=Ids.INFERENCE_COUNTER, data=new_counter_data)
|
dcc.Store(id=Ids.INFERENCE_COUNTER, data=new_counter_data)
|
||||||
if debug_mode:
|
app_state.use_model = use_model
|
||||||
|
if not use_model:
|
||||||
MODEL_LOADING = False
|
|
||||||
return (
|
return (
|
||||||
dbc.Alert("Modalità Debug attiva - Using mock predictions", color="info"),
|
dbc.Alert("Modalità Debug attiva - Using mock predictions", color="info"),
|
||||||
"Debug (Mock)",
|
"Debug (Mock)",
|
||||||
@ -2219,149 +2360,6 @@ def toggle_inference_mode(debug_mode):
|
|||||||
new_counter_data
|
new_counter_data
|
||||||
)
|
)
|
||||||
else:
|
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 (
|
return (
|
||||||
dbc.Alert("Modello caricato correttamente", color="success"),
|
dbc.Alert("Modello caricato correttamente", color="success"),
|
||||||
"Produzione (Local Model)",
|
"Produzione (Local Model)",
|
||||||
@ -2369,24 +2367,8 @@ def toggle_inference_mode(debug_mode):
|
|||||||
"0",
|
"0",
|
||||||
new_counter_data
|
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
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Errore nella configurazione inferenza: {str(e)}")
|
print(f"Errore nella configurazione inferenza: {str(e)}")
|
||||||
MODEL_LOADING = False
|
|
||||||
return (
|
return (
|
||||||
dbc.Alert(f"Errore: {str(e)}", color="danger"),
|
dbc.Alert(f"Errore: {str(e)}", color="danger"),
|
||||||
"Errore",
|
"Errore",
|
||||||
@ -2441,7 +2423,6 @@ def display_page(pathname, session_data):
|
|||||||
|
|
||||||
is_valid, username = verify_token(session_data['token'])
|
is_valid, username = verify_token(session_data['token'])
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
print("Invalid token") # Debug print
|
|
||||||
return create_login_layout(), html.Div()
|
return create_login_layout(), html.Div()
|
||||||
|
|
||||||
# print(f"Valid session for user: {username}") # Debug print
|
# print(f"Valid session for user: {username}") # Debug print
|
||||||
@ -2822,9 +2803,10 @@ def check_percentages(perc1, perc2, perc3):
|
|||||||
(Output(Ids.DASHBOARD_CONTAINER, 'children'),
|
(Output(Ids.DASHBOARD_CONTAINER, 'children'),
|
||||||
[Input('url', 'pathname')],
|
[Input('url', 'pathname')],
|
||||||
lambda x: x == '/')
|
lambda x: x == '/')
|
||||||
]
|
],
|
||||||
|
prevent_initial_call=True
|
||||||
)
|
)
|
||||||
def load_configuration(active_tab, variety2, variety3, pathname):
|
def load_configuration(active_tab, var2_current, var3_current, pathname):
|
||||||
try:
|
try:
|
||||||
# Carica la configurazione
|
# Carica la configurazione
|
||||||
config = load_config()
|
config = load_config()
|
||||||
@ -2832,8 +2814,18 @@ def load_configuration(active_tab, variety2, variety3, pathname):
|
|||||||
# Carica dati varietà
|
# Carica dati varietà
|
||||||
varieties = config['oliveto']['varieties']
|
varieties = config['oliveto']['varieties']
|
||||||
var1 = varieties[0] if len(varieties) > 0 else {"variety": None, "technique": None, "percentage": 0}
|
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}
|
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})
|
||||||
|
else:
|
||||||
|
var2 = {"variety": None, "technique": None, "percentage": 0}
|
||||||
|
|
||||||
|
if var3_current is not None:
|
||||||
|
var3 = next((v for v in varieties if v["variety"] == var3_current),
|
||||||
|
{"variety": var3_current, "technique": None, "percentage": 0})
|
||||||
|
else:
|
||||||
|
var3 = {"variety": None, "technique": None, "percentage": 0}
|
||||||
|
|
||||||
# Carica costi e marketing
|
# Carica costi e marketing
|
||||||
costs = config['costs']
|
costs = config['costs']
|
||||||
@ -2842,8 +2834,8 @@ def load_configuration(active_tab, variety2, variety3, pathname):
|
|||||||
transformation = costs.get('transformation', {})
|
transformation = costs.get('transformation', {})
|
||||||
marketing = costs.get('marketing', {})
|
marketing = costs.get('marketing', {})
|
||||||
|
|
||||||
var2_exists = var2["variety"] is not None
|
var2_exists = True
|
||||||
var3_exists = var3["variety"] is not None
|
var3_exists = True
|
||||||
|
|
||||||
return [
|
return [
|
||||||
# Configurazioni base (15 valori)
|
# Configurazioni base (15 valori)
|
||||||
@ -2924,7 +2916,7 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count
|
|||||||
"""
|
"""
|
||||||
Callback principale per aggiornare tutti i componenti della simulazione
|
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
|
# Crea grafici vuoti per l'inizializzazione
|
||||||
empty_growth_fig = go.Figure()
|
empty_growth_fig = go.Figure()
|
||||||
empty_production_fig = go.Figure()
|
empty_production_fig = go.Figure()
|
||||||
@ -2980,9 +2972,9 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count
|
|||||||
|
|
||||||
# Estrai le informazioni dalle varietà configurate
|
# Estrai le informazioni dalle varietà configurate
|
||||||
for variety_config in config['oliveto']['varieties']:
|
for variety_config in config['oliveto']['varieties']:
|
||||||
variety_data = olive_varieties[
|
variety_data = app_state.olive_varieties[
|
||||||
(olive_varieties['Varietà di Olive'] == variety_config['variety']) &
|
(app_state.olive_varieties['Varietà di Olive'] == variety_config['variety']) &
|
||||||
(olive_varieties['Tecnica di Coltivazione'].str.lower() == variety_config['technique'].lower())
|
(app_state.olive_varieties['Tecnica di Coltivazione'].str.lower() == variety_config['technique'].lower())
|
||||||
]
|
]
|
||||||
if not variety_data.empty:
|
if not variety_data.empty:
|
||||||
varieties_info.append(variety_data.iloc[0])
|
varieties_info.append(variety_data.iloc[0])
|
||||||
@ -2990,7 +2982,7 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count
|
|||||||
|
|
||||||
current_count = counter_data.get('count', 0) + 1
|
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})
|
dcc.Store(id=Ids.INFERENCE_COUNTER, data={'count': current_count})
|
||||||
|
|
||||||
@ -3006,7 +2998,7 @@ def update_simulation(n_clicks, temp_range, humidity, rainfall, radiation, count
|
|||||||
|
|
||||||
# Creazione grafici con il nuovo stile
|
# Creazione grafici con il nuovo stile
|
||||||
details_fig = create_production_details_figure(prediction)
|
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)
|
water_fig = {} # create_water_needs_figure(prediction)
|
||||||
|
|
||||||
# Creazione info extra con il nuovo stile
|
# Creazione info extra con il nuovo stile
|
||||||
@ -3147,18 +3139,40 @@ if __name__ == '__main__':
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--port', type=int, help='Port to run the server on')
|
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('--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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
env_port = int(os.environ.get('DASH_PORT', 8888))
|
env_port = int(os.environ.get('DASH_PORT', 8888))
|
||||||
env_debug = os.environ.get('DASH_DEBUG', '').lower() == 'true'
|
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
|
port = args.port if args.port is not None else env_port
|
||||||
debug = args.debug if args.debug else env_debug
|
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(
|
# Configurazione del server
|
||||||
host='0.0.0.0',
|
server.config.update(
|
||||||
port=port,
|
SESSION_COOKIE_SECURE=True,
|
||||||
debug=debug
|
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}")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
# Data handling and analysis
|
# Data handling and analysis
|
||||||
pandas>=1.5.0
|
pandas>=1.5.0
|
||||||
numpy>=1.21.0
|
numpy>=1.21.0
|
||||||
scikit-learn==1.5.2
|
scikit-learn==1.6.0
|
||||||
|
|
||||||
# Dashboard and visualization
|
# Dashboard and visualization
|
||||||
dash>=2.9.0
|
dash>=2.9.0
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user