- add login, logout, registration

- various fix
This commit is contained in:
Giuseppe Nucifora 2024-12-20 02:26:52 +01:00
parent e587a4cea6
commit 0a1bc21035
11 changed files with 2046 additions and 1131 deletions

14
.idea/csv-editor.xml generated
View File

@ -17,6 +17,20 @@
</Attribute>
</value>
</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>
</option>
</component>

0
src/auth/__init__.py Normal file
View File

91
src/auth/login.py Normal file
View 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
View 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
View 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

View File

@ -1,6 +1,6 @@
{
"oliveto": {
"hectares": 4.35,
"hectares": 1,
"varieties": [
{
"variety": "Nocellara dell'Etna",
@ -10,12 +10,12 @@
{
"variety": "Frantoio",
"technique": "tradizionale",
"percentage": 10
"percentage": 20
},
{
"variety": "Coratina",
"technique": "tradizionale",
"percentage": 40
"percentage": 30
}
]
},
@ -46,6 +46,7 @@
}
},
"inference": {
"debug_mode": false
"debug_mode": false,
"model_path": "./sources/olive_oil_transformer/olive_oil_transformer_model.keras"
}
}

View 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

View File

@ -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 Autunno (m³/ettaro)'] +
variety_info['Fabbisogno Acqua Inverno (m³/ettaro)']
) / 4
) / 12
monthly_water_need = zone_weather.apply(
lambda row: calculate_water_need(row, base_water_need, variety_info['Temperatura Ottimale']),
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))
#print(f'Monthly 2 - {variety} - hectares {hectares} - {monthly_water_need}')
annual_variety_water_need = monthly_water_need.sum() * percentage * hectares
#print(f'Annual Variety - {variety} - hectares {hectares} - {annual_variety_water_need}')
# Aggiorna totali annuali
annual_production += annual_variety_production
annual_min_oil += min_oil_production
annual_max_oil += max_oil_production
annual_avg_oil += avg_oil_production
annual_water_need += annual_variety_water_need
#print(f'Total Annual {annual_water_need}')
# Aggiorna dati varietà
clean_variety = clean_column_name(variety)
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")
# 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"Totale record attesi: {num_simulations * num_zones:,}")
print(f"Totale record attesi: {num_simulations * num_zones}")
# Lista per contenere tutti i DataFrame dei batch
all_batches = []
@ -368,11 +370,11 @@ def calculate_production(variety_info, weather, percentage, hectares, seed):
variety_info['Fabbisogno Acqua Inverno (m³/ettaro)']
) / 4 * percentage * hectares
water_need = (
base_water_need *
(1 + max(0, (weather['temp_mean'] - 20) / 50)) *
max(0.6, 1 - (weather['precip_sum'] / 1000))
)
temp_factor = 1 + max(0, (weather["temp_mean"] - 20) / 50)
rain_factor = max(0.6, 1 - (weather["precip_sum"] / 1000))
water_need = base_water_need * temp_factor * rain_factor
print(f'temp factor: {temp_factor} rainfall factor: {rain_factor} water_need: {water_need}')
return {
'variety': variety_info['Varietà di Olive'],

View File

@ -18,4 +18,7 @@ python-dotenv>=0.21.0
psutil>=5.9.0
# 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

View File

@ -424,10 +424,20 @@ def calculate_weather_effect(row, optimal_temp):
return combined_effect
def calculate_water_need(weather_data, base_need, optimal_temp):
# Calcola il fabbisogno idrico basato su temperatura e precipitazioni
temp_factor = 1 + 0.05 * (weather_data['temp_mean'] - optimal_temp) # Aumenta del 5% per ogni grado sopra l'ottimale
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 temperatura (minimo 80% del fabbisogno)
temp_factor = max(0.8, 1 + 0.05 * (weather_data['temp_mean'] - optimal_temp))
# 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'):
# Estrai tutte le tecniche uniche dal dataset e convertile in lowercase