- add login, logout, registration
- various fix
This commit is contained in:
parent
e587a4cea6
commit
0a1bc21035
14
.idea/csv-editor.xml
generated
14
.idea/csv-editor.xml
generated
@ -17,6 +17,20 @@
|
|||||||
</Attribute>
|
</Attribute>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry key="/dumpDataPreview">
|
||||||
|
<value>
|
||||||
|
<Attribute>
|
||||||
|
<option name="separator" value="," />
|
||||||
|
</Attribute>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="/textView">
|
||||||
|
<value>
|
||||||
|
<Attribute>
|
||||||
|
<option name="separator" value="," />
|
||||||
|
</Attribute>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
0
src/auth/__init__.py
Normal file
0
src/auth/__init__.py
Normal file
91
src/auth/login.py
Normal file
91
src/auth/login.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash import html, dcc
|
||||||
|
|
||||||
|
from components.ids import Ids
|
||||||
|
|
||||||
|
def create_login_layout():
|
||||||
|
return dbc.Container([
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col([
|
||||||
|
html.H2("Login Dashboard Olio d'Oliva", className="text-center mb-4"),
|
||||||
|
dbc.Card([
|
||||||
|
dbc.CardBody([
|
||||||
|
dbc.Input(
|
||||||
|
id=Ids.LOGIN_USERNAME,
|
||||||
|
type="text",
|
||||||
|
placeholder="Username",
|
||||||
|
className="mb-3"
|
||||||
|
),
|
||||||
|
dbc.Input(
|
||||||
|
id=Ids.LOGIN_PASSWORD,
|
||||||
|
type="password",
|
||||||
|
placeholder="Password",
|
||||||
|
className="mb-3"
|
||||||
|
),
|
||||||
|
dbc.Button(
|
||||||
|
"Login",
|
||||||
|
id=Ids.LOGIN_BUTTON,
|
||||||
|
color="primary",
|
||||||
|
className="w-100 mb-3"
|
||||||
|
),
|
||||||
|
html.Div(id=Ids.LOGIN_ERROR),
|
||||||
|
html.Hr(),
|
||||||
|
html.P("Non hai un account?", className="text-center"),
|
||||||
|
dbc.Button(
|
||||||
|
"Registrati",
|
||||||
|
id=Ids.SHOW_REGISTER_BUTTON,
|
||||||
|
color="secondary",
|
||||||
|
className="w-100"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
], md=6, className="mx-auto")
|
||||||
|
], className="vh-100 align-items-center")
|
||||||
|
])
|
||||||
|
|
||||||
|
def create_register_layout():
|
||||||
|
return dbc.Container([
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col([
|
||||||
|
html.H2("Registrazione", className="text-center mb-4"),
|
||||||
|
dbc.Card([
|
||||||
|
dbc.CardBody([
|
||||||
|
dbc.Input(
|
||||||
|
id=Ids.REGISTER_USERNAME,
|
||||||
|
type="text",
|
||||||
|
placeholder="Username",
|
||||||
|
className="mb-3"
|
||||||
|
),
|
||||||
|
dbc.Input(
|
||||||
|
id=Ids.REGISTER_PASSWORD,
|
||||||
|
type="password",
|
||||||
|
placeholder="Password",
|
||||||
|
className="mb-3"
|
||||||
|
),
|
||||||
|
dbc.Input(
|
||||||
|
id=Ids.REGISTER_CONFIRM,
|
||||||
|
type="password",
|
||||||
|
placeholder="Conferma Password",
|
||||||
|
className="mb-3"
|
||||||
|
),
|
||||||
|
dbc.Button(
|
||||||
|
"Registrati",
|
||||||
|
id=Ids.REGISTER_BUTTON,
|
||||||
|
color="primary",
|
||||||
|
className="w-100 mb-3"
|
||||||
|
),
|
||||||
|
html.Div(id=Ids.REGISTER_ERROR),
|
||||||
|
html.Div(id=Ids.REGISTER_SUCCESS),
|
||||||
|
html.Hr(),
|
||||||
|
html.P("Hai già un account?", className="text-center"),
|
||||||
|
dbc.Button(
|
||||||
|
"Torna al Login",
|
||||||
|
id=Ids.SHOW_LOGIN_BUTTON,
|
||||||
|
color="secondary",
|
||||||
|
className="w-100"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
], md=6, className="mx-auto")
|
||||||
|
], className="vh-100 align-items-center")
|
||||||
|
])
|
||||||
236
src/auth/utils.py
Normal file
236
src/auth/utils.py
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import jwt as pyjwt
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
# Costanti
|
||||||
|
SECRET_KEY = 'M!3EmyJ@P$yqt$dYRQ#73QtxFy$aTn8M98P8i5T9x9Fd5LHMcHgdfEEt#?H9EPg&9Qhokh$#pTyYLHxL' # In produzione, usare una variabile d'ambiente
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
USERS_FILE = os.path.join(BASE_DIR, "users", "users.json")
|
||||||
|
CONFIGS_DIR = os.path.join(BASE_DIR, "users", "configs")
|
||||||
|
|
||||||
|
def init_directory_structure():
|
||||||
|
"""
|
||||||
|
Inizializza la struttura delle directory necessarie per l'applicazione
|
||||||
|
"""
|
||||||
|
# Crea directory per utenti e configurazioni
|
||||||
|
os.makedirs(os.path.dirname(USERS_FILE), exist_ok=True)
|
||||||
|
os.makedirs(CONFIGS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# Se il file users.json non esiste, crealo vuoto
|
||||||
|
if not os.path.exists(USERS_FILE):
|
||||||
|
with open(USERS_FILE, 'w') as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
|
||||||
|
print(f"Initialized directory structure:")
|
||||||
|
print(f"Users file: {USERS_FILE}")
|
||||||
|
print(f"Configs directory: {CONFIGS_DIR}")
|
||||||
|
|
||||||
|
def hash_password(password):
|
||||||
|
"""Hash la password usando SHA-256"""
|
||||||
|
return hashlib.sha256(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
# In auth/utils.py
|
||||||
|
def create_user(username, password):
|
||||||
|
"""
|
||||||
|
Crea un nuovo utente
|
||||||
|
Args:
|
||||||
|
username: nome utente
|
||||||
|
password: password in chiaro
|
||||||
|
Returns:
|
||||||
|
tuple: (success: bool, message: str)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
print(f"Tentativo di creazione utente: {username}")
|
||||||
|
|
||||||
|
# Inizializza la struttura delle directory
|
||||||
|
init_directory_structure()
|
||||||
|
|
||||||
|
# Inizializza o carica il dizionario degli utenti
|
||||||
|
users = {}
|
||||||
|
if os.path.exists(USERS_FILE):
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
if content.strip(): # Verifica che il file non sia vuoto
|
||||||
|
users = json.loads(content)
|
||||||
|
print(f"Utenti esistenti: {len(users)}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"Errore nel parsing del file utenti: {e}")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Validazioni
|
||||||
|
if not username or len(username) < 3:
|
||||||
|
return False, "Username deve essere almeno 3 caratteri"
|
||||||
|
if not password or len(password) < 6:
|
||||||
|
return False, "Password deve essere almeno 6 caratteri"
|
||||||
|
if username in users:
|
||||||
|
return False, "Username già esistente"
|
||||||
|
|
||||||
|
# Genera il salt e hash della password
|
||||||
|
salt = secrets.token_hex(8)
|
||||||
|
password_hash = hashlib.sha256((password + salt).encode()).hexdigest()
|
||||||
|
|
||||||
|
# Ottieni il percorso della configurazione utente
|
||||||
|
user_config = get_user_config_path(username)
|
||||||
|
print(f"Percorso configurazione utente: {user_config}")
|
||||||
|
|
||||||
|
# Crea la configurazione dell'utente
|
||||||
|
os.makedirs(os.path.dirname(user_config), exist_ok=True)
|
||||||
|
|
||||||
|
# Salva la configurazione di default
|
||||||
|
default_config = get_default_config()
|
||||||
|
with open(user_config, 'w') as f:
|
||||||
|
json.dump(default_config, f, indent=4)
|
||||||
|
print("Configurazione default salvata")
|
||||||
|
|
||||||
|
# Aggiungi nuovo utente con struttura semplificata
|
||||||
|
users[username] = {
|
||||||
|
"salt": salt,
|
||||||
|
"password_hash": password_hash
|
||||||
|
}
|
||||||
|
|
||||||
|
# Salva il file utenti
|
||||||
|
with open(USERS_FILE, 'w') as f:
|
||||||
|
json.dump(users, f, indent=4)
|
||||||
|
print("File utenti aggiornato con successo")
|
||||||
|
|
||||||
|
return True, "Utente creato con successo"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Errore nella creazione dell'utente: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False, f"Errore nella creazione dell'utente: {str(e)}"
|
||||||
|
|
||||||
|
def verify_user(username, password):
|
||||||
|
"""
|
||||||
|
Verifica le credenziali utente usando salt e hash
|
||||||
|
Args:
|
||||||
|
username: nome utente
|
||||||
|
password: password in chiaro
|
||||||
|
Returns:
|
||||||
|
bool: True se le credenziali sono valide, False altrimenti
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(USERS_FILE):
|
||||||
|
print("File utenti non trovato")
|
||||||
|
return False
|
||||||
|
|
||||||
|
with open(USERS_FILE, 'r') as f:
|
||||||
|
users = json.load(f)
|
||||||
|
|
||||||
|
if username not in users:
|
||||||
|
print("Username non trovato")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Ottieni il salt e l'hash salvati
|
||||||
|
user_data = users[username]
|
||||||
|
stored_salt = user_data['salt']
|
||||||
|
stored_hash = user_data['password_hash']
|
||||||
|
|
||||||
|
# Calcola l'hash della password fornita con il salt salvato
|
||||||
|
password_hash = hashlib.sha256((password + stored_salt).encode()).hexdigest()
|
||||||
|
|
||||||
|
# Confronta gli hash
|
||||||
|
return stored_hash == password_hash
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Errore nella verifica dell'utente: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_token(username):
|
||||||
|
"""Crea JWT token"""
|
||||||
|
expiration = datetime.utcnow() + timedelta(hours=24)
|
||||||
|
return pyjwt.encode(
|
||||||
|
{"user": username, "exp": expiration},
|
||||||
|
SECRET_KEY,
|
||||||
|
algorithm="HS256"
|
||||||
|
)
|
||||||
|
|
||||||
|
def verify_token(token):
|
||||||
|
"""Verifica JWT token"""
|
||||||
|
try:
|
||||||
|
payload = pyjwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
||||||
|
return True, payload["user"]
|
||||||
|
except pyjwt.ExpiredSignatureError:
|
||||||
|
return False, "Token scaduto"
|
||||||
|
except pyjwt.InvalidTokenError:
|
||||||
|
return False, "Token non valido"
|
||||||
|
|
||||||
|
def get_user_config_path(username):
|
||||||
|
"""
|
||||||
|
Restituisce il percorso del file di configurazione per l'utente specificato
|
||||||
|
Args:
|
||||||
|
username: nome utente
|
||||||
|
Returns:
|
||||||
|
str: percorso assoluto del file di configurazione
|
||||||
|
"""
|
||||||
|
# Sostituisci caratteri non validi nel nome utente
|
||||||
|
safe_username = "".join(c for c in username if c.isalnum() or c in ('-', '_'))
|
||||||
|
|
||||||
|
# Costruisci il percorso assoluto
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
config_dir = os.path.join(base_dir, 'config', 'users')
|
||||||
|
config_path = os.path.join(config_dir, f"{safe_username}_config.json")
|
||||||
|
|
||||||
|
# Assicurati che la directory esista
|
||||||
|
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||||
|
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
def get_default_config():
|
||||||
|
return {
|
||||||
|
"oliveto": {
|
||||||
|
"hectares": 1,
|
||||||
|
"varieties": [
|
||||||
|
{
|
||||||
|
"variety": "Nocellara dell'Etna",
|
||||||
|
"technique": "tradizionale",
|
||||||
|
"percentage": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variety": "Frantoio",
|
||||||
|
"technique": "tradizionale",
|
||||||
|
"percentage": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variety": "Coratina",
|
||||||
|
"technique": "tradizionale",
|
||||||
|
"percentage": 40
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"costs": {
|
||||||
|
"fixed": {
|
||||||
|
"ammortamento": 2000,
|
||||||
|
"assicurazione": 500,
|
||||||
|
"manutenzione": 800,
|
||||||
|
"certificazioni": 3000
|
||||||
|
},
|
||||||
|
"variable": {
|
||||||
|
"raccolta": 0.35,
|
||||||
|
"potatura": 600,
|
||||||
|
"fertilizzanti": 400,
|
||||||
|
"irrigazione": 300
|
||||||
|
},
|
||||||
|
"transformation": {
|
||||||
|
"molitura": 0.15,
|
||||||
|
"stoccaggio": 0.2,
|
||||||
|
"bottiglia": 1.2,
|
||||||
|
"etichettatura": 0.3
|
||||||
|
},
|
||||||
|
"marketing": {
|
||||||
|
"budget_annuale": 15000,
|
||||||
|
"costi_commerciali": 0.5,
|
||||||
|
"prezzo_vendita": 12,
|
||||||
|
"perc_vendita_diretta": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inference": {
|
||||||
|
"debug_mode": True,
|
||||||
|
'model_path': './sources/olive_oil_transformer/olive_oil_transformer_model.keras'
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/components/ids.py
Normal file
104
src/components/ids.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# components/ids.py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Ids:
|
||||||
|
# Auth Container
|
||||||
|
AUTH_CONTAINER = 'auth-container'
|
||||||
|
DASHBOARD_CONTAINER = 'dashboard-container'
|
||||||
|
|
||||||
|
# Login Form
|
||||||
|
LOGIN_FORM = 'login-form'
|
||||||
|
LOGIN_USERNAME = 'login-username'
|
||||||
|
LOGIN_PASSWORD = 'login-password'
|
||||||
|
LOGIN_BUTTON = 'login-button'
|
||||||
|
LOGIN_ERROR = 'login-error'
|
||||||
|
|
||||||
|
# Register Form
|
||||||
|
REGISTER_FORM = 'register-form'
|
||||||
|
REGISTER_USERNAME = 'register-username'
|
||||||
|
REGISTER_PASSWORD = 'register-password'
|
||||||
|
REGISTER_CONFIRM = 'register-confirm'
|
||||||
|
REGISTER_BUTTON = 'register-button'
|
||||||
|
REGISTER_ERROR = 'register-error'
|
||||||
|
REGISTER_SUCCESS = 'register-success'
|
||||||
|
|
||||||
|
# Navigation
|
||||||
|
SHOW_REGISTER_BUTTON = 'show-register-button'
|
||||||
|
SHOW_LOGIN_BUTTON = 'show-login-button'
|
||||||
|
|
||||||
|
# Inference
|
||||||
|
INFERENCE_CONTAINER = 'inference-container'
|
||||||
|
INFERENCE_STATUS = 'inference-status'
|
||||||
|
INFERENCE_MODE = 'inference-mode'
|
||||||
|
INFERENCE_LATENCY = 'inference-latency'
|
||||||
|
INFERENCE_REQUESTS = 'inference-requests'
|
||||||
|
INFERENCE_COUNTER = 'inference-counter'
|
||||||
|
DEBUG_SWITCH = 'debug-switch'
|
||||||
|
|
||||||
|
# Simulation
|
||||||
|
SIMULATE_BUTTON = 'simulate-btn'
|
||||||
|
GROWTH_CHART = 'growth-simulation-chart'
|
||||||
|
PRODUCTION_CHART = 'production-simulation-chart'
|
||||||
|
SIMULATION_SUMMARY = 'simulation-summary'
|
||||||
|
KPI_CONTAINER = 'kpi-container'
|
||||||
|
PRODUCTION_DEBUG_SWITCH = 'production-debug-switch'
|
||||||
|
PRODUCTION_INFERENCE_REQUESTS = 'production-inference-requests'
|
||||||
|
PRODUCTION_INFERENCE_MODE = 'production-inference-mode'
|
||||||
|
|
||||||
|
# Environment Controls
|
||||||
|
TEMP_SLIDER = 'temp-slider'
|
||||||
|
HUMIDITY_SLIDER = 'humidity-slider'
|
||||||
|
RAINFALL_INPUT = 'rainfall-input'
|
||||||
|
RADIATION_INPUT = 'radiation-input'
|
||||||
|
|
||||||
|
# Production Views
|
||||||
|
OLIVE_PRODUCTION_HA = 'olive-production_ha'
|
||||||
|
OIL_PRODUCTION_HA = 'oil-production_ha'
|
||||||
|
WATER_NEED_HA = 'water-need_ha'
|
||||||
|
OLIVE_PRODUCTION = 'olive-production'
|
||||||
|
OIL_PRODUCTION = 'oil-production'
|
||||||
|
WATER_NEED = 'water-need'
|
||||||
|
PRODUCTION_DETAILS = 'production-details'
|
||||||
|
WEATHER_IMPACT = 'weather-impact'
|
||||||
|
WATER_NEEDS = 'water-needs'
|
||||||
|
EXTRA_INFO = 'extra-info'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
HECTARES_INPUT = 'hectares-input'
|
||||||
|
VARIETY_1_DROPDOWN = 'variety-1-dropdown'
|
||||||
|
TECHNIQUE_1_DROPDOWN = 'technique-1-dropdown'
|
||||||
|
PERCENTAGE_1_INPUT = 'percentage-1-input'
|
||||||
|
VARIETY_2_DROPDOWN = 'variety-2-dropdown'
|
||||||
|
TECHNIQUE_2_DROPDOWN = 'technique-2-dropdown'
|
||||||
|
PERCENTAGE_2_INPUT = 'percentage-2-input'
|
||||||
|
VARIETY_3_DROPDOWN = 'variety-3-dropdown'
|
||||||
|
TECHNIQUE_3_DROPDOWN = 'technique-3-dropdown'
|
||||||
|
PERCENTAGE_3_INPUT = 'percentage-3-input'
|
||||||
|
PERCENTAGE_WARNING = 'percentage-warning'
|
||||||
|
|
||||||
|
# Cost Inputs
|
||||||
|
COST_AMMORTAMENTO = 'cost-ammortamento'
|
||||||
|
COST_ASSICURAZIONE = 'cost-assicurazione'
|
||||||
|
COST_MANUTENZIONE = 'cost-manutenzione'
|
||||||
|
COST_CERTIFICAZIONI = 'cost-certificazioni'
|
||||||
|
COST_RACCOLTA = 'cost-raccolta'
|
||||||
|
COST_POTATURA = 'cost-potatura'
|
||||||
|
COST_FERTILIZZANTI = 'cost-fertilizzanti'
|
||||||
|
COST_IRRIGAZIONE = 'cost-irrigazione'
|
||||||
|
COST_MOLITURA = 'cost-molitura'
|
||||||
|
COST_STOCCAGGIO = 'cost-stoccaggio'
|
||||||
|
COST_BOTTIGLIA = 'cost-bottiglia'
|
||||||
|
COST_ETICHETTATURA = 'cost-etichettatura'
|
||||||
|
COST_MARKETING = 'cost-marketing'
|
||||||
|
COST_COMMERCIALI = 'cost-commerciali'
|
||||||
|
PRICE_OLIO = 'price-olio'
|
||||||
|
PERC_VENDITA_DIRETTA = 'perc-vendita-diretta'
|
||||||
|
|
||||||
|
# Other
|
||||||
|
LOADING_ALERT = 'loading-alert'
|
||||||
|
TABS = 'tabs'
|
||||||
|
SAVE_CONFIG_BUTTON = 'save-config-button'
|
||||||
|
SAVE_CONFIG_MESSAGE = 'save-config-message'
|
||||||
|
LOGOUT_BUTTON = 'logout-button'
|
||||||
|
DEV_MODE = 'dev-mode'
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"oliveto": {
|
"oliveto": {
|
||||||
"hectares": 4.35,
|
"hectares": 1,
|
||||||
"varieties": [
|
"varieties": [
|
||||||
{
|
{
|
||||||
"variety": "Nocellara dell'Etna",
|
"variety": "Nocellara dell'Etna",
|
||||||
@ -10,12 +10,12 @@
|
|||||||
{
|
{
|
||||||
"variety": "Frantoio",
|
"variety": "Frantoio",
|
||||||
"technique": "tradizionale",
|
"technique": "tradizionale",
|
||||||
"percentage": 10
|
"percentage": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"variety": "Coratina",
|
"variety": "Coratina",
|
||||||
"technique": "tradizionale",
|
"technique": "tradizionale",
|
||||||
"percentage": 40
|
"percentage": 30
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -46,6 +46,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inference": {
|
"inference": {
|
||||||
"debug_mode": false
|
"debug_mode": false,
|
||||||
|
"model_path": "./sources/olive_oil_transformer/olive_oil_transformer_model.keras"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
65
src/olive_oil_train_dataset/README.MD
Normal file
65
src/olive_oil_train_dataset/README.MD
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Olive Oil Production Training Dataset Generator
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This Python script generates a synthetic training dataset for olive oil production simulation. It simulates various factors affecting olive production including weather conditions, olive varieties, cultivation techniques, and geographical zones.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Parallel processing for efficient dataset generation
|
||||||
|
- Batch processing to manage memory usage
|
||||||
|
- Configurable simulation parameters
|
||||||
|
- Weather effect calculations
|
||||||
|
- Multiple olive varieties and cultivation techniques support
|
||||||
|
- Zone-based production simulation
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
Required Python packages:
|
||||||
|
- pandas
|
||||||
|
- numpy
|
||||||
|
- psutil
|
||||||
|
- tqdm
|
||||||
|
- concurrent.futures (part of Python standard library)
|
||||||
|
- multiprocessing (part of Python standard library)
|
||||||
|
|
||||||
|
Required input data files:
|
||||||
|
- `./sources/weather_data_solarenergy.parquet`: Weather data including solar energy measurements
|
||||||
|
- `./sources/olive_varieties.parquet`: Olive varieties and their characteristics
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Command Line Arguments
|
||||||
|
```bash
|
||||||
|
python olive_oil_train_dataset.create_train_dataset [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--random-seed`: Seed for reproducible results (optional)
|
||||||
|
- `--num-simulations`: Total number of simulations to run (default: 100000)
|
||||||
|
- `--num-zones`: Number of zones per simulation (default: same as num-simulations)
|
||||||
|
- `--batch-size`: Size of each simulation batch (default: 10000)
|
||||||
|
- `--output-path`: Output file path (default: './sources/olive_training_dataset.parquet')
|
||||||
|
- `--max-workers`: Number of parallel workers (default: automatically optimized)
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```bash
|
||||||
|
python olive_oil_train_dataset.create_train_dataset --num-simulations 50 --num-zones 10 --batch-size 50 --output-path "./output/olive_dataset.parquet"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
The script generates a Parquet file containing simulated olive production data with the following key features:
|
||||||
|
- Simulation and zone identifiers
|
||||||
|
- Weather conditions (temperature, precipitation, solar energy)
|
||||||
|
- Production metrics per olive variety
|
||||||
|
- Oil yield calculations
|
||||||
|
- Water requirements
|
||||||
|
- Cultivation techniques
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Simulation Parameters
|
||||||
|
The simulation takes into account:
|
||||||
|
- Temperature effects on production
|
||||||
|
- Water availability and drought resistance
|
||||||
|
- Solar radiation impact
|
||||||
|
- Variety-specific characteristics
|
||||||
|
- Cultivation technique influence
|
||||||
|
- Zone-specific variations
|
||||||
@ -133,22 +133,24 @@ def simulate_zone(base_weather, olive_varieties, year, zone, all_varieties, vari
|
|||||||
variety_info['Fabbisogno Acqua Estate (m³/ettaro)'] +
|
variety_info['Fabbisogno Acqua Estate (m³/ettaro)'] +
|
||||||
variety_info['Fabbisogno Acqua Autunno (m³/ettaro)'] +
|
variety_info['Fabbisogno Acqua Autunno (m³/ettaro)'] +
|
||||||
variety_info['Fabbisogno Acqua Inverno (m³/ettaro)']
|
variety_info['Fabbisogno Acqua Inverno (m³/ettaro)']
|
||||||
) / 4
|
) / 12
|
||||||
|
|
||||||
monthly_water_need = zone_weather.apply(
|
monthly_water_need = zone_weather.apply(
|
||||||
lambda row: calculate_water_need(row, base_water_need, variety_info['Temperatura Ottimale']),
|
lambda row: calculate_water_need(row, base_water_need, variety_info['Temperatura Ottimale']),
|
||||||
axis=1
|
axis=1
|
||||||
)
|
)
|
||||||
|
#print(f'Monthly - {variety} - hectares {hectares} - {monthly_water_need}')
|
||||||
monthly_water_need *= np.random.uniform(0.95, 1.05, len(monthly_water_need))
|
monthly_water_need *= np.random.uniform(0.95, 1.05, len(monthly_water_need))
|
||||||
|
#print(f'Monthly 2 - {variety} - hectares {hectares} - {monthly_water_need}')
|
||||||
annual_variety_water_need = monthly_water_need.sum() * percentage * hectares
|
annual_variety_water_need = monthly_water_need.sum() * percentage * hectares
|
||||||
|
#print(f'Annual Variety - {variety} - hectares {hectares} - {annual_variety_water_need}')
|
||||||
# Aggiorna totali annuali
|
# Aggiorna totali annuali
|
||||||
annual_production += annual_variety_production
|
annual_production += annual_variety_production
|
||||||
annual_min_oil += min_oil_production
|
annual_min_oil += min_oil_production
|
||||||
annual_max_oil += max_oil_production
|
annual_max_oil += max_oil_production
|
||||||
annual_avg_oil += avg_oil_production
|
annual_avg_oil += avg_oil_production
|
||||||
annual_water_need += annual_variety_water_need
|
annual_water_need += annual_variety_water_need
|
||||||
|
#print(f'Total Annual {annual_water_need}')
|
||||||
# Aggiorna dati varietà
|
# Aggiorna dati varietà
|
||||||
clean_variety = clean_column_name(variety)
|
clean_variety = clean_column_name(variety)
|
||||||
variety_data[clean_variety].update({
|
variety_data[clean_variety].update({
|
||||||
@ -240,9 +242,9 @@ def simulate_olive_production_parallel(weather_data, olive_varieties, num_simula
|
|||||||
print(f"Utilizzando {max_workers} workers ottimali basati sulle risorse del sistema")
|
print(f"Utilizzando {max_workers} workers ottimali basati sulle risorse del sistema")
|
||||||
|
|
||||||
# Calcolo numero di batch
|
# Calcolo numero di batch
|
||||||
num_batches = (num_simulations + batch_size - 1) // batch_size
|
num_batches = (num_simulations * num_zones - 1) // batch_size
|
||||||
print(f"Elaborazione di {num_simulations} simulazioni con {num_zones} zone in {num_batches} batch")
|
print(f"Elaborazione di {num_simulations} simulazioni con {num_zones} zone in {num_batches} batch")
|
||||||
print(f"Totale record attesi: {num_simulations * num_zones:,}")
|
print(f"Totale record attesi: {num_simulations * num_zones}")
|
||||||
|
|
||||||
# Lista per contenere tutti i DataFrame dei batch
|
# Lista per contenere tutti i DataFrame dei batch
|
||||||
all_batches = []
|
all_batches = []
|
||||||
@ -368,11 +370,11 @@ def calculate_production(variety_info, weather, percentage, hectares, seed):
|
|||||||
variety_info['Fabbisogno Acqua Inverno (m³/ettaro)']
|
variety_info['Fabbisogno Acqua Inverno (m³/ettaro)']
|
||||||
) / 4 * percentage * hectares
|
) / 4 * percentage * hectares
|
||||||
|
|
||||||
water_need = (
|
temp_factor = 1 + max(0, (weather["temp_mean"] - 20) / 50)
|
||||||
base_water_need *
|
rain_factor = max(0.6, 1 - (weather["precip_sum"] / 1000))
|
||||||
(1 + max(0, (weather['temp_mean'] - 20) / 50)) *
|
water_need = base_water_need * temp_factor * rain_factor
|
||||||
max(0.6, 1 - (weather['precip_sum'] / 1000))
|
|
||||||
)
|
print(f'temp factor: {temp_factor} rainfall factor: {rain_factor} water_need: {water_need}')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'variety': variety_info['Varietà di Olive'],
|
'variety': variety_info['Varietà di Olive'],
|
||||||
|
|||||||
@ -18,4 +18,7 @@ python-dotenv>=0.21.0
|
|||||||
psutil>=5.9.0
|
psutil>=5.9.0
|
||||||
|
|
||||||
# File handling
|
# File handling
|
||||||
dvc>=2.0.0 # Per la gestione dei file sources.dvc
|
dvc>=2.0.0 # Per la gestione dei file sources.dvc
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
PyJWT==2.7.0
|
||||||
@ -424,10 +424,20 @@ def calculate_weather_effect(row, optimal_temp):
|
|||||||
return combined_effect
|
return combined_effect
|
||||||
|
|
||||||
def calculate_water_need(weather_data, base_need, optimal_temp):
|
def calculate_water_need(weather_data, base_need, optimal_temp):
|
||||||
# Calcola il fabbisogno idrico basato su temperatura e precipitazioni
|
# Calcola il fattore temperatura (minimo 80% del fabbisogno)
|
||||||
temp_factor = 1 + 0.05 * (weather_data['temp_mean'] - optimal_temp) # Aumenta del 5% per ogni grado sopra l'ottimale
|
temp_factor = max(0.8, 1 + 0.05 * (weather_data['temp_mean'] - optimal_temp))
|
||||||
rain_factor = 1 - 0.001 * weather_data['precip_sum'] # Diminuisce leggermente con l'aumentare delle precipitazioni
|
|
||||||
return base_need * temp_factor * rain_factor
|
# Calcola il fattore precipitazioni (minimo 50% del fabbisogno)
|
||||||
|
rain_factor = max(0.5, 1 - 0.001 * weather_data['precip_sum'])
|
||||||
|
|
||||||
|
# Calcola il fabbisogno idrico totale
|
||||||
|
water_need = base_need * temp_factor * rain_factor
|
||||||
|
|
||||||
|
# Debug: controlla che il fabbisogno idrico sia positivo
|
||||||
|
assert water_need >= 0, "Il fabbisogno idrico calcolato è negativo!"
|
||||||
|
|
||||||
|
return water_need
|
||||||
|
|
||||||
|
|
||||||
def create_technique_mapping(olive_varieties, mapping_path='./sources/technique_mapping.joblib'):
|
def create_technique_mapping(olive_varieties, mapping_path='./sources/technique_mapping.joblib'):
|
||||||
# Estrai tutte le tecniche uniche dal dataset e convertile in lowercase
|
# Estrai tutte le tecniche uniche dal dataset e convertile in lowercase
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user