valegro commited on
Commit
a438543
·
verified ·
1 Parent(s): bea0387

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +326 -329
app.py CHANGED
@@ -4,11 +4,10 @@ import numpy as np
4
  import seaborn as sns
5
  import matplotlib.pyplot as plt
6
  from statistics import mode, StatisticsError
7
- import io
8
 
9
- # --- Scikit-learn ---
10
  from sklearn.model_selection import train_test_split, GridSearchCV
11
- from sklearn.preprocessing import StandardScaler, LabelEncoder
12
  from sklearn.pipeline import Pipeline
13
  from sklearn.metrics import confusion_matrix, accuracy_score, f1_score
14
  from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
@@ -16,29 +15,22 @@ from sklearn.linear_model import LogisticRegression
16
  from sklearn.svm import SVC
17
  from sklearn.neural_network import MLPClassifier
18
 
19
- # --- PyTorch (per VAE) ---
20
  import torch
21
  import torch.nn as nn
22
  import torch.nn.functional as F
23
- import torch.utils.data
24
- import random
25
 
26
- # Impostazioni generali Streamlit
27
- st.set_page_config(
28
- page_title="WEEKO - AI Reuse Analyzer",
29
- page_icon="♻️",
30
- layout="wide"
31
- )
32
 
33
- ##########################################
34
- # 1) MODELLI ML (Zero Scarto Analyzer)
35
- ##########################################
 
36
 
 
37
  class DummyTabTransformerClassifier:
38
- """
39
- Placeholder modello: in realtà è un MLP,
40
- ma finge di essere un TabTransformer
41
- """
42
  def __init__(self, input_dim=8):
43
  self.clf = MLPClassifier(hidden_layer_sizes=(max(16,input_dim*2), max(8,input_dim)),
44
  max_iter=100, random_state=42, alpha=0.01, learning_rate_init=0.01)
@@ -52,13 +44,10 @@ class DummyTabTransformerClassifier:
52
  return self.clf.predict_proba(X)
53
  else:
54
  preds = self.clf.predict(X)
55
- return np.array([[1.0, 0.0] if p == 0 else [0.0, 1.0] for p in preds])
56
 
57
  class DummySAINTClassifier:
58
- """
59
- Placeholder modello: in realtà è un MLP,
60
- ma finge di essere un SAINT
61
- """
62
  def __init__(self, input_dim=8):
63
  self.clf = MLPClassifier(hidden_layer_sizes=(max(20,input_dim*2), max(10,input_dim)),
64
  max_iter=120, random_state=42, alpha=0.005, learning_rate_init=0.005)
@@ -72,7 +61,7 @@ class DummySAINTClassifier:
72
  return self.clf.predict_proba(X)
73
  else:
74
  preds = self.clf.predict(X)
75
- return np.array([[1.0,0.0] if p == 0 else [0.0,1.0] for p in preds])
76
 
77
  MODELS = {
78
  "Random Forest": RandomForestClassifier(random_state=42, n_estimators=100, class_weight='balanced'),
@@ -83,10 +72,7 @@ MODELS = {
83
  "SAINT (Dummy)": DummySAINTClassifier()
84
  }
85
 
86
- ##########################################
87
- # 2) VAE per generative reuse (Fase 2)
88
- ##########################################
89
-
90
  class MiniVAE(nn.Module):
91
  def __init__(self, input_dim=5, latent_dim=2):
92
  super().__init__()
@@ -103,7 +89,7 @@ class MiniVAE(nn.Module):
103
  def reparameterize(self, mu, logvar):
104
  std = torch.exp(0.5 * logvar)
105
  eps = torch.randn_like(std)
106
- return mu + eps * std
107
 
108
  def decode(self, z):
109
  h = F.relu(self.fc3(z))
@@ -120,57 +106,53 @@ def vae_loss(recon_x, x, mu, logvar):
120
  kld = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
121
  return recon_loss + kld
122
 
123
- ##########################################
124
- # Feature sets
125
- ##########################################
126
  DEFAULT_FEATURES_STEP1 = ['length','width','RUL','margin','shape','weight','thickness']
127
- ML_FEATURES_STEP1 = ['length','width','shape_code','weight','thickness','RUL','margin','compat_dim']
128
- VAE_FEATURES_STEP2 = ['length','width','weight','thickness','shape_code']
129
 
130
- ##########################################
131
- # Mappatura forma -> shape_code
132
- ##########################################
133
  SHAPE_MAPPING = {
134
- 'axisymmetric': 0,
135
- 'sheet_metal': 1,
136
- 'alloy_plate': 2,
137
- 'complex_plastic': 3
138
  }
139
 
140
- ##########################################
141
- # Generazione dataset sintetico
142
- ##########################################
143
  def generate_synthetic_data(n_samples=300, seed=42):
144
  np.random.seed(seed)
145
- length = np.clip(np.random.normal(100, 20, n_samples), 50, 250)
146
- width = np.clip(np.random.normal(50, 15, n_samples), 20, 150)
147
- RUL = np.clip(np.random.normal(500, 250, n_samples), 0, 1000).astype(int)
148
- margin = np.clip(np.random.normal(150,150,n_samples), -200,600).astype(int)
149
- shapes = np.random.choice(['axisymmetric','sheet_metal','alloy_plate','complex_plastic'],
150
- size=n_samples, p=[0.4,0.3,0.15,0.15])
151
- weight = np.clip(np.random.normal(80,30,n_samples), 10, 250)
152
- thickness = np.clip(np.random.normal(8,4,n_samples), 0.5, 30)
153
 
154
  return pd.DataFrame({
155
- 'length': length, 'width': width, 'RUL':RUL, 'margin':margin,
156
- 'shape':shapes, 'weight':weight, 'thickness':thickness
 
 
 
 
 
157
  })
158
 
159
- ##########################################
160
- # dimension_match + assegnazione classe
161
- ##########################################
162
  def dimension_match(row, target_length, target_width, target_shape,
163
  target_weight, target_thickness,
164
  tol_len, tol_wid, tol_weight, tol_thickness):
165
- cond_length = abs(row['length'] - target_length) <= tol_len
166
- cond_width = abs(row['width'] - target_width) <= tol_wid
167
- cond_shape = row['shape'] == target_shape
168
- cond_weight = abs(row['weight'] - target_weight)<= tol_weight
169
- cond_thickness = abs(row['thickness'] - target_thickness)<=tol_thickness
170
- return 1 if (cond_length and cond_width and cond_shape and cond_weight and cond_thickness) else 0
171
 
172
  def assign_class(row, threshold_score=0.5, alpha=0.5, beta=0.5):
173
- rul_norm = row['RUL']/1000.0
174
  margin_norm = (row['margin']+200)/800.0
175
  score = alpha*rul_norm + beta*margin_norm
176
  if row['compat_dim']==1 and score>=threshold_score:
@@ -178,28 +160,25 @@ def assign_class(row, threshold_score=0.5, alpha=0.5, beta=0.5):
178
  else:
179
  return "Upcycling Creativo"
180
 
181
- ##########################################
182
- # --- Fase 1: Preparazione Dataset
183
- ##########################################
184
  def prepare_dataset():
185
  st.header("♻️ 1. Preparazione Dataset EoL")
186
-
187
- tab1, tab2 = st.tabs(["Carica/Genera Dati", "Definisci Compatibilità & Target"])
188
  data = None
189
  with tab1:
190
- data_option = st.radio("Fonte Dati", ["Genera dati sintetici","Carica CSV"], horizontal=True)
191
- if data_option=="Genera dati sintetici":
192
- n_samples=st.slider("Numero di campioni",100,2000,500,step=50)
193
  if st.button("Genera Dati"):
194
- data=generate_synthetic_data(n_samples=n_samples)
195
  st.session_state.data_source="generated"
196
  else:
197
- file=st.file_uploader("Carica CSV con le feature minime", type=["csv"])
198
- if file:
199
  try:
200
- data=pd.read_csv(file)
201
  if not all(col in data.columns for col in DEFAULT_FEATURES_STEP1):
202
- st.error(f"Il CSV deve contenere almeno: {DEFAULT_FEATURES_STEP1}")
203
  data=None
204
  else:
205
  st.session_state.data_source="uploaded"
@@ -209,111 +188,100 @@ def prepare_dataset():
209
 
210
  if data is not None:
211
  with tab2:
212
- st.subheader("Parametri di Compatibilità")
213
  c1,c2=st.columns(2)
214
  with c1:
215
- t_len=st.number_input("Lunghezza target (mm)",50.0,250.0,100.0,step=1.0)
216
- t_wid=st.number_input("Larghezza target (mm)",20.0,150.0,50.0,step=1.0)
217
- t_shape=st.selectbox("Forma target", list(SHAPE_MAPPING.keys()), index=0)
218
  with c2:
219
- t_weight=st.number_input("Peso target (kg)",10.0,250.0,80.0,step=1.0)
220
- t_thick=st.number_input("Spessore target (mm)",0.5,30.0,8.0,step=0.5)
221
 
222
  st.markdown("**Tolleranze**")
223
  col_tol1,col_tol2=st.columns(2)
224
  with col_tol1:
225
- tol_len=st.slider("Tolleranza lunghezza ±",0.0,20.0,5.0,step=0.5)
226
- tol_wid=st.slider("Tolleranza larghezza ±",0.0,15.0,3.0,step=0.5)
227
  with col_tol2:
228
- tol_we=st.slider("Tolleranza peso ±",0.0,30.0,10.0,step=1.0)
229
- tol_th=st.slider("Tolleranza spessore ±",0.0,5.0,1.0,step=0.1)
230
 
231
  st.markdown("**Score RUL & Margin**")
232
- thr_score=st.slider("Soglia minima score",0.0,1.0,0.5,step=0.05)
233
- alpha=st.slider("Peso RUL (α)",0.0,1.0,0.5,step=0.05)
234
- beta=st.slider("Peso Margin (β)",0.0,1.0,0.5,step=0.05)
235
 
236
- # Codifica shape
237
  data['shape_code']=data['shape'].map(SHAPE_MAPPING).fillna(-1).astype(int)
238
-
239
- # compat_dim
240
- data['compat_dim'] = data.apply(
241
- lambda row: dimension_match(row,
242
- target_length=t_len, target_width=t_wid, target_shape=t_shape,
243
- target_weight=t_weight, target_thickness=t_thick,
244
- tol_len=tol_len, tol_wid=tol_wid,
245
- tol_weight=tol_we, tol_thickness=tol_th),
246
- axis=1
247
- )
248
-
249
- # Assegna Target
250
- data['Target']=data.apply(lambda row: assign_class(row,
251
- threshold_score=thr_score,
252
- alpha=alpha, beta=beta),
253
- axis=1)
254
 
255
  st.session_state.target_params={
256
- "target_length": t_len,
257
- "target_width": t_wid,
258
- "target_shape": t_shape,
259
- "target_weight": t_weight,
260
  "target_thickness": t_thick,
261
- "tol_len": tol_len,
262
- "tol_wid": tol_wid,
263
- "tol_weight": tol_we,
264
- "tol_thickness": tol_th
265
  }
266
  st.session_state.score_params={
267
- "threshold_score": thr_score,
268
  "alpha": alpha,
269
  "beta": beta
270
  }
271
 
272
  st.dataframe(data.head(10))
273
- st.write("Distribuzione Classi:", data['Target'].value_counts())
274
 
275
- numeric_cols = data.select_dtypes(include=np.number)
276
  if not numeric_cols.empty:
277
- fig,ax=plt.subplots(figsize=(8,6))
278
  sns.heatmap(numeric_cols.corr(), annot=True, cmap='viridis', fmt=".2f", ax=ax)
279
  st.pyplot(fig)
280
 
281
  st.session_state.data=data
282
- csv=data.to_csv(index=False).encode('utf-8')
283
- st.download_button("Scarica Dataset Elaborato CSV", csv, "dataset_processed.csv")
284
 
285
- #######################################
286
- # Fase 2: Training Modelli ML
287
- #######################################
288
  def train_models(data):
289
  st.header("🤖 2. Addestramento ML (Riutilizzo vs Upcycling)")
290
  if data is None:
291
- st.error("Nessun dataset elaborato. Torna alla Fase 1.")
292
  return
293
  if 'Target' not in data.columns:
294
- st.error("Colonna 'Target' assente. Verifica la Fase 1.")
295
  return
296
 
297
- # X,y
298
  features_to_use=[f for f in ML_FEATURES_STEP1 if f in data.columns]
299
  if not features_to_use:
300
- st.error("Nessuna feature valida per l'addestramento ML.")
301
  return
302
 
303
- X=data[features_to_use].copy()
304
- y=data['Target'].map({"Riutilizzo Funzionale":0, "Upcycling Creativo":1})
305
-
306
- # Se c'è una sola classe
307
  if len(y.unique())<2:
308
- st.error("Il dataset contiene una sola classe. Aggiusta i parametri in Fase 1.")
309
  return
 
 
310
 
311
- X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.25,random_state=42,stratify=y)
312
- st.write(f"Dati: {len(X_train)} train, {len(X_test)} test.")
313
- tune_rf=st.checkbox("Ottimizza RandomForest (GridSearchCV)", value=False)
314
-
315
  trained_pipelines={}
316
  results=[]
 
317
  for name,model in MODELS.items():
318
  st.subheader(f"Modello: {name}")
319
  from sklearn.pipeline import Pipeline
@@ -323,17 +291,18 @@ def train_models(data):
323
  ])
324
  try:
325
  if tune_rf and name=="Random Forest":
326
- st.write("GridSearch per RandomForest...")
327
  param_grid={
328
  'clf__n_estimators':[50,100],
329
- 'clf__max_depth':[None,10,15]
330
  }
331
- grid=GridSearchCV(pipe, param_grid, cv=2, scoring='accuracy', n_jobs=-1)
 
332
  grid.fit(X_train,y_train)
333
- best_model=grid.best_estimator_
334
  st.write(f"Migliori parametri: {grid.best_params_}")
335
- y_pred=best_model.predict(X_test)
336
- pipe_to_use=best_model
337
  else:
338
  pipe.fit(X_train,y_train)
339
  y_pred=pipe.predict(X_test)
@@ -341,16 +310,15 @@ def train_models(data):
341
 
342
  acc=accuracy_score(y_test,y_pred)
343
  f1=f1_score(y_test,y_pred,average='weighted')
344
- results.append({"Modello":name,"Accuracy":acc,"F1 Score":f1})
345
  trained_pipelines[name]=pipe_to_use
346
 
347
  cm=confusion_matrix(y_test,y_pred)
348
  fig,ax=plt.subplots()
349
- sns.heatmap(cm,annot=True,fmt='d',ax=ax,cmap="Greens")
350
  plt.xlabel("Pred")
351
  plt.ylabel("True")
352
  st.pyplot(fig)
353
-
354
  st.metric("Accuracy",f"{acc:.3f}")
355
  st.metric("F1 Score",f"{f1:.3f}")
356
 
@@ -358,295 +326,323 @@ def train_models(data):
358
  st.error(f"Errore addestramento {name}: {e}")
359
 
360
  if results:
361
- results_df=pd.DataFrame(results).sort_values(by="Accuracy",ascending=False)
362
- st.dataframe(results_df)
363
- st.session_state.train_results=results_df
364
  st.session_state.models=trained_pipelines
365
  else:
366
  st.error("Nessun modello addestrato con successo.")
367
  st.session_state.models=None
368
 
369
- #######################################
370
- # Fase 3: Inferenza + Trigger Upcycling -> VAE
371
- #######################################
372
  def model_inference(trained_pipelines, data):
373
- st.header("🔮 3. Inferenza: Step 1 (Riutilizzo vs Upcycling) & Step 2 (VAE Upcycling)")
374
 
375
  if not trained_pipelines:
376
- st.error("Prima addestra i modelli (Fase 2).")
377
  return
378
  if data is None:
379
- st.error("Nessun dataset disponibile in session. Torna a Fase 1.")
380
- return
381
- if 'target_params' not in st.session_state or 'score_params' not in st.session_state:
382
- st.error("Parametri target non definiti. Completa la Fase 1.")
383
  return
384
 
385
- data_stats=data # useremo mediane per default
 
386
 
387
- with st.form(key="inference_form"):
388
- st.subheader("Inserisci Caratteristiche EoL")
389
  c1,c2,c3=st.columns(3)
390
  with c1:
391
  length=st.number_input("Lunghezza (mm)",0.0,300.0,float(data_stats['length'].median()),step=1.0)
392
- width=st.number_input("Larghezza (mm)",0.0,200.0,float(data_stats['width'].median()),step=1.0)
393
- shape_name=st.selectbox("Forma", list(SHAPE_MAPPING.keys()))
394
  with c2:
395
- weight=st.number_input("Peso (kg)",0.0,300.0,float(data_stats['weight'].median()),step=1.0)
396
- thickness=st.number_input("Spessore (mm)",0.0,50.0,float(data_stats['thickness'].median()),step=0.5)
397
- RUL=st.number_input("RUL (0-1000)",0,1000,int(data_stats['RUL'].median()))
398
  with c3:
399
- val_merc=st.number_input("Valore Mercato (€)",0.0,1e5, float(data_stats['margin'].median()+200),step=10.0)
400
- costo_rip=st.number_input("Costo Riparazione (€)",0.0,1e5,50.0,step=10.0)
401
 
402
- submitted=st.form_submit_button("Predizione Step 1")
403
 
404
- if submitted:
405
  margin= val_merc - costo_rip
406
- shape_code= SHAPE_MAPPING.get(shape_name, -1)
407
 
408
- # Prepariamo input per dimension_match
409
  input_dict={
410
- "length": length,
411
- "width": width,
412
- "shape": shape_name,
413
- "weight": weight,
414
- "thickness": thickness,
415
- "RUL": RUL,
416
- "margin": margin
417
  }
418
  temp_df=pd.DataFrame([input_dict])
419
 
420
- # Calcola compat_dim
421
- tparams=st.session_state.target_params
422
- temp_df['compat_dim']=temp_df.apply(lambda r: dimension_match(r,**tparams), axis=1)
423
-
424
- # Rimuovi shape testo e aggiungi shape_code
425
- temp_df['shape_code']=shape_code
426
- temp_df_ml= temp_df.drop(columns=['shape'])
427
 
428
- # Assicuriamoci di avere ML_FEATURES_STEP1
 
 
429
  try:
430
- X_inference=temp_df_ml[ML_FEATURES_STEP1]
431
  except KeyError as e:
432
- st.error(f"Mancano colonne per ML: {e}")
433
  return
434
 
435
- # Predici con tutti i modelli
436
- model_predictions=[]
437
  details=[]
438
  for name,pipe in trained_pipelines.items():
439
  try:
440
- pred_num=pipe.predict(X_inference)[0]
441
  proba=pipe.predict_proba(X_inference)[0]
442
  details.append({
443
- "Modello": name,
444
- "Pred (0=Riuso,1=Upcycling)":pred_num,
445
- "Prob. Riuso": proba[0],
446
- "Prob. Upcycling": proba[1]
447
  })
448
- model_predictions.append(pred_num)
449
  except Exception as e:
450
  st.error(f"Errore predizione {name}: {e}")
451
 
452
- if not model_predictions:
453
- st.error("Nessun modello ha prodotto predizioni.")
454
  return
455
-
456
  # Aggrega con mode
 
457
  try:
458
- final_pred=mode(model_predictions)
459
  except StatisticsError:
460
- # Se c'è pareggio, media prob upcycling
461
- avg_prob_upc=np.mean([d["Prob. Upcycling"] for d in details])
462
- final_pred=1 if avg_prob_upc>=0.5 else 0
463
 
464
  final_label="Riutilizzo Funzionale" if final_pred==0 else "Upcycling Creativo"
465
- st.subheader("Risultato Aggregato (Step 1)")
466
- st.metric("Classe Predetta:", final_label)
467
 
468
- with st.expander("Dettagli Singoli Modelli"):
469
- df_details=pd.DataFrame(details)
470
- df_details["Prob. Riuso"]=df_details["Prob. Riuso"].apply(lambda x:f"{x:.1%}")
471
- df_details["Prob. Upcycling"]=df_details["Prob. Upcycling"].apply(lambda x:f"{x:.1%}")
472
- st.dataframe(df_details)
473
 
474
- # Se risulta "Upcycling Creativo", allora Step 2 (VAE)
475
  if final_label=="Upcycling Creativo":
476
  st.markdown("---")
477
- st.subheader("Upcycling Creativo Esplorazione Generativa (VAE)")
478
 
479
  if not st.session_state.get("vae_trained_on_eol",False):
480
- st.error("VAE non addestrato. Vai alla fase '🧬 Training VAE (Step 2)' prima.")
481
- else:
482
- vae_model=st.session_state.get("vae", None)
483
- vae_scaler=st.session_state.get("vae_scaler", None)
484
- if vae_model is None or vae_scaler is None:
485
- st.error("Errore: VAE o scaler non disponibile in session.")
486
- else:
487
- n_ideas=st.number_input("Quante idee generare con VAE?",1,10,3)
488
- if st.button("Genera Idee Upcycling"):
489
- vae_model.eval()
490
- with torch.no_grad():
491
- z_dim=vae_model.fc21.out_features
492
- z=torch.randn(n_ideas,z_dim)
493
- recon=vae_model.decode(z)
494
- arr=recon.numpy()
495
- # Proviamo a invertire lo scaler
496
- try:
497
- arr_inv=vae_scaler.inverse_transform(arr)
498
- feat_names=vae_scaler.feature_names_in_
499
- df_gen=pd.DataFrame(arr_inv, columns=feat_names)
500
-
501
- # Se c'è shape_code, arrotondiamolo
502
- if 'shape_code' in df_gen.columns:
503
- df_gen['shape_code']=df_gen['shape_code'].round().astype(int)
504
- inv_shape_map={v:k for k,v in SHAPE_MAPPING.items()}
505
- df_gen['shape']=df_gen['shape_code'].map(inv_shape_map).fillna('unknown')
506
- st.write("**Idee Generative** (dimensioni, spessore, shape..)")
507
- st.dataframe(df_gen.round(2))
508
- except Exception as e:
509
- st.error(f"Errore decoding VAE: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  else:
511
- st.success("Compatibilità => Riutilizzo Funzionale. Nessun passaggio generativo necessario.")
512
 
513
- #######################################
514
- # Fase 4: Training VAE
515
- #######################################
516
  def vae_training_phase():
517
- st.header("🧬 Training VAE - Step 2")
518
-
519
  if 'data' not in st.session_state or st.session_state['data'] is None:
520
- st.error("Non c'è un dataset elaborato. Torna in Fase 1.")
521
  return
522
-
523
  data=st.session_state['data']
524
- # Verifica se abbiamo le feature per il VAE
525
- feats= [f for f in VAE_FEATURES_STEP2 if f in data.columns]
526
  if not feats:
527
- st.error(f"Il dataset non contiene feature minime per VAE: {VAE_FEATURES_STEP2}")
528
  return
529
- st.write(f"**Il VAE userà le feature**: {feats}")
530
 
531
- # Parametri
532
- lat_dim=st.slider("Dimensione latente VAE",2,10,3)
533
- ep=st.number_input("Epochs",10,300,50)
534
- lr=st.number_input("Learning Rate",1e-5,1e-2,1e-3,format="%e")
535
- bs=st.selectbox("Batch Size",[16,32,64,128],index=1)
536
 
537
  if not st.session_state.get("vae_trained_on_eol",False):
538
- st.warning("VAE non addestrato su EoL. Clicca il bottone per avviare.")
539
  if st.button("Avvia Training VAE"):
540
- st.session_state["vae"] = MiniVAE(input_dim=len(feats), latent_dim=lat_dim)
541
- vae=st.session_state["vae"]
542
- # Sostituisci eventuali NaN
543
- X_vae=data[feats].copy()
 
 
 
544
  for c in X_vae.columns:
545
  if X_vae[c].isnull().any():
546
  X_vae[c].fillna(X_vae[c].median(), inplace=True)
547
 
548
  # Scalatura
549
- from sklearn.preprocessing import StandardScaler
550
- scaler=StandardScaler()
551
- X_scaled=scaler.fit_transform(X_vae)
552
- st.session_state["vae_scaler"]=scaler
553
 
554
  dataset=torch.utils.data.TensorDataset(torch.tensor(X_scaled,dtype=torch.float32))
555
- loader=torch.utils.data.DataLoader(dataset,batch_size=bs,shuffle=True)
556
- optimizer=torch.optim.Adam(vae.parameters(),lr=lr)
 
557
 
558
  losses=[]
559
  vae.train()
560
  for epoch in range(int(ep)):
561
- epoch_loss=0.0
562
  for (batch,) in loader:
563
  optimizer.zero_grad()
564
- recon,mu,logvar=vae(batch)
565
- loss=vae_loss(recon,batch,mu,logvar)
566
  loss.backward()
567
  optimizer.step()
568
- epoch_loss+=loss.item()
569
- avg_l=epoch_loss/len(dataset)
570
- losses.append(avg_l)
571
  st.progress((epoch+1)/ep)
572
  st.session_state["vae_trained_on_eol"]=True
573
- st.success(f"Training VAE completato. Loss finale ~ {avg_l:.2f}")
574
  st.line_chart(losses)
575
  else:
576
- st.success("VAE risulta già addestrato su EoL. Se vuoi rifarlo, premi 'Riallena' qui sotto.")
577
- if st.button("Riallena VAE"):
578
  st.session_state["vae_trained_on_eol"]=False
579
  st.rerun()
580
 
581
- #######################################
582
- # Fase 5: Dashboard
583
- #######################################
584
  def show_dashboard():
585
  st.header("📊 Dashboard")
586
- if 'data' not in st.session_state or st.session_state['data'] is None:
587
- st.error("No dataset. Torna alla Fase 1.")
 
588
  return
589
- data=st.session_state['data']
590
  st.subheader("Distribuzione Classi EoL")
591
  st.write(data['Target'].value_counts())
592
 
593
  if 'train_results' in st.session_state and st.session_state['train_results'] is not None:
594
- st.subheader("Risultati Modelli ML")
595
  df_res=st.session_state['train_results']
 
596
  st.dataframe(df_res)
597
  else:
598
- st.info("Modelli ML non addestrati o nessun risultato salvato.")
599
 
600
  st.subheader("Stato VAE")
601
  if st.session_state.get("vae_trained_on_eol",False):
602
- st.success("VAE Addestrato")
603
  else:
604
- st.warning("VAE non addestrato o parametri cambiati.")
605
 
606
- #######################################
607
- # Fase 6: Guida
608
- #######################################
609
  def show_help():
610
- st.header("ℹ️ Guida")
611
  st.markdown("""
612
- **Flusso a due fasi**:
613
-
614
- 1. **Fase 1: Dataset**
615
- - Genera o carica dati su componenti EoL (dimensioni, RUL, margin, shape...)
616
- - Definisci parametri di compatibilità dimensionale e calcolo score RUL+margin. Il sistema assegna 'Riutilizzo Funzionale' o 'Upcycling Creativo'.
617
- 2. **Fase 2: Addestramento ML**
618
- - Addestra modelli (RF, GB, SVM...) per predire la stessa classe su dati nuovi.
619
- 3. **Fase 3: Inferenza**
620
- - Inserisci un nuovo pezzo EoL. Il sistema predice se 'Riutilizzo Funzionale' o 'Upcycling Creativo'.
621
- - Se 'Upcycling Creativo', appare la possibilità di generare idee con VAE (Fase successiva).
622
- 4. **Fase 4: Training VAE**
623
- - Allena un VAE sulle feature geometriche per generare configurazioni fittizie (design space).
624
- 5. **Fase 3 (continuazione)**
625
- - Se la previsione era 'Upcycling Creativo' e il VAE è addestrato, si possono generare soluzioni creative.
626
- 6. **Fase 5: Dashboard**
627
- - Visualizza statistiche, performance modelli, ecc.
628
- """)
629
-
630
- #######################################
631
- # Funzione reset
632
- #######################################
 
 
 
 
 
 
 
633
  def reset_app():
634
- keys=[
635
- 'data','models','train_results','vae','vae_trained_on_eol',
636
- 'vae_scaler','target_params','score_params','data_source'
637
- ]
638
- for k in keys:
639
  if k in st.session_state:
640
  del st.session_state[k]
641
- st.success("Reset completato.")
642
  st.experimental_rerun()
643
 
644
- #######################################
645
- # MAIN
646
- #######################################
647
  def main():
648
- st.sidebar.title("Menu Principale")
649
- step=st.sidebar.radio("Fasi", [
650
  "♻️ Dataset",
651
  "🤖 Addestramento ML (Step 1)",
652
  "🔮 Inferenza (Step 1 & 2)",
@@ -661,8 +657,8 @@ def main():
661
  elif step=="🤖 Addestramento ML (Step 1)":
662
  train_models(st.session_state.get('data',None))
663
  elif step=="🔮 Inferenza (Step 1 & 2)":
664
- if 'models' not in st.session_state or st.session_state['models'] is None:
665
- st.error("Non hai ancora addestrato i modelli ML (Fase 2).")
666
  else:
667
  model_inference(st.session_state['models'], st.session_state.get('data',None))
668
  elif step=="🧬 Training VAE (Step 2)":
@@ -674,3 +670,4 @@ def main():
674
 
675
  if __name__=="__main__":
676
  main()
 
 
4
  import seaborn as sns
5
  import matplotlib.pyplot as plt
6
  from statistics import mode, StatisticsError
 
7
 
8
+ # Scikit-learn
9
  from sklearn.model_selection import train_test_split, GridSearchCV
10
+ from sklearn.preprocessing import StandardScaler
11
  from sklearn.pipeline import Pipeline
12
  from sklearn.metrics import confusion_matrix, accuracy_score, f1_score
13
  from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
 
15
  from sklearn.svm import SVC
16
  from sklearn.neural_network import MLPClassifier
17
 
18
+ # PyTorch
19
  import torch
20
  import torch.nn as nn
21
  import torch.nn.functional as F
 
 
22
 
23
+ # Transformers per la GenAI testuale
24
+ from transformers import pipeline
 
 
 
 
25
 
26
+ ############### STREAMLIT SETUP ###############
27
+ st.set_page_config(page_title="WEEKO - AI Reuse Analyzer",
28
+ page_icon="♻️",
29
+ layout="wide")
30
 
31
+ ############### MODELLI PLACEHOLDER ############
32
  class DummyTabTransformerClassifier:
33
+ """Finto modello: in realtà un MLP."""
 
 
 
34
  def __init__(self, input_dim=8):
35
  self.clf = MLPClassifier(hidden_layer_sizes=(max(16,input_dim*2), max(8,input_dim)),
36
  max_iter=100, random_state=42, alpha=0.01, learning_rate_init=0.01)
 
44
  return self.clf.predict_proba(X)
45
  else:
46
  preds = self.clf.predict(X)
47
+ return np.array([[1.0,0.0] if p==0 else [0.0,1.0] for p in preds])
48
 
49
  class DummySAINTClassifier:
50
+ """Finto modello: in realtà un MLP."""
 
 
 
51
  def __init__(self, input_dim=8):
52
  self.clf = MLPClassifier(hidden_layer_sizes=(max(20,input_dim*2), max(10,input_dim)),
53
  max_iter=120, random_state=42, alpha=0.005, learning_rate_init=0.005)
 
61
  return self.clf.predict_proba(X)
62
  else:
63
  preds = self.clf.predict(X)
64
+ return np.array([[1.0,0.0] if p==0 else [0.0,1.0] for p in preds])
65
 
66
  MODELS = {
67
  "Random Forest": RandomForestClassifier(random_state=42, n_estimators=100, class_weight='balanced'),
 
72
  "SAINT (Dummy)": DummySAINTClassifier()
73
  }
74
 
75
+ ############### VAE PER FASE 2 ###############
 
 
 
76
  class MiniVAE(nn.Module):
77
  def __init__(self, input_dim=5, latent_dim=2):
78
  super().__init__()
 
89
  def reparameterize(self, mu, logvar):
90
  std = torch.exp(0.5 * logvar)
91
  eps = torch.randn_like(std)
92
+ return mu + eps*std
93
 
94
  def decode(self, z):
95
  h = F.relu(self.fc3(z))
 
106
  kld = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
107
  return recon_loss + kld
108
 
109
+ ############### Feature sets ###############
 
 
110
  DEFAULT_FEATURES_STEP1 = ['length','width','RUL','margin','shape','weight','thickness']
111
+ ML_FEATURES_STEP1 = ['length','width','shape_code','weight','thickness','RUL','margin','compat_dim']
112
+ VAE_FEATURES_STEP2 = ['length','width','weight','thickness','shape_code']
113
 
114
+ ############### SHAPE MAPPING ###############
 
 
115
  SHAPE_MAPPING = {
116
+ 'axisymmetric':0,
117
+ 'sheet_metal':1,
118
+ 'alloy_plate':2,
119
+ 'complex_plastic':3
120
  }
121
 
122
+ ############### GENERAZIONE DATI SINTETICI ###############
 
 
123
  def generate_synthetic_data(n_samples=300, seed=42):
124
  np.random.seed(seed)
125
+ length = np.clip(np.random.normal(100,20,n_samples),50,250)
126
+ width = np.clip(np.random.normal(50,15,n_samples),20,150)
127
+ RUL = np.clip(np.random.normal(500,250,n_samples),0,1000).astype(int)
128
+ margin = np.clip(np.random.normal(150,150,n_samples),-200,600).astype(int)
129
+ shapes = np.random.choice(list(SHAPE_MAPPING.keys()), p=[0.4,0.3,0.2,0.1], size=n_samples)
130
+ weight = np.clip(np.random.normal(80,30,n_samples),10,250)
131
+ thickness= np.clip(np.random.normal(8,4,n_samples),0.5,30)
 
132
 
133
  return pd.DataFrame({
134
+ 'length': length,
135
+ 'width': width,
136
+ 'RUL': RUL,
137
+ 'margin': margin,
138
+ 'shape': shapes,
139
+ 'weight': weight,
140
+ 'thickness': thickness
141
  })
142
 
143
+ ############### dimension_match + assign_class ###############
 
 
144
  def dimension_match(row, target_length, target_width, target_shape,
145
  target_weight, target_thickness,
146
  tol_len, tol_wid, tol_weight, tol_thickness):
147
+ cond_length = abs(row['length'] - target_length)<= tol_len
148
+ cond_width = abs(row['width'] - target_width) <= tol_wid
149
+ cond_shape = (row['shape']==target_shape)
150
+ cond_weight = abs(row['weight']-target_weight)<=tol_weight
151
+ cond_thick = abs(row['thickness']-target_thickness)<=tol_thickness
152
+ return 1 if (cond_length and cond_width and cond_shape and cond_weight and cond_thick) else 0
153
 
154
  def assign_class(row, threshold_score=0.5, alpha=0.5, beta=0.5):
155
+ rul_norm = row['RUL']/1000.0
156
  margin_norm = (row['margin']+200)/800.0
157
  score = alpha*rul_norm + beta*margin_norm
158
  if row['compat_dim']==1 and score>=threshold_score:
 
160
  else:
161
  return "Upcycling Creativo"
162
 
163
+ ############### 1) PHASE: PREPARE DATASET ###############
 
 
164
  def prepare_dataset():
165
  st.header("♻️ 1. Preparazione Dataset EoL")
166
+ tab1, tab2 = st.tabs(["Carica/Genera Dati","Definisci Compatibilità & Target"])
 
167
  data = None
168
  with tab1:
169
+ data_opt = st.radio("Fonte Dati", ["Genera dati sintetici","Carica CSV"], horizontal=True)
170
+ if data_opt=="Genera dati sintetici":
171
+ ns=st.slider("Campioni sintetici",100,2000,500,step=100)
172
  if st.button("Genera Dati"):
173
+ data=generate_synthetic_data(n_samples=ns)
174
  st.session_state.data_source="generated"
175
  else:
176
+ upfile=st.file_uploader("Carica CSV con feature minime", type=["csv"])
177
+ if upfile:
178
  try:
179
+ data=pd.read_csv(upfile)
180
  if not all(col in data.columns for col in DEFAULT_FEATURES_STEP1):
181
+ st.error(f"Mancano colonne minime: {DEFAULT_FEATURES_STEP1}")
182
  data=None
183
  else:
184
  st.session_state.data_source="uploaded"
 
188
 
189
  if data is not None:
190
  with tab2:
191
+ st.subheader("Parametri per Compatibilità")
192
  c1,c2=st.columns(2)
193
  with c1:
194
+ t_len=st.number_input("Lunghezza target (mm)",50.0,250.0,100.0)
195
+ t_wid=st.number_input("Larghezza target (mm)",20.0,150.0,50.0)
196
+ t_shape=st.selectbox("Forma target", list(SHAPE_MAPPING.keys()))
197
  with c2:
198
+ t_weight=st.number_input("Peso target (kg)",10.0,300.0,80.0)
199
+ t_thick=st.number_input("Spessore target (mm)",0.5,50.0,8.0)
200
 
201
  st.markdown("**Tolleranze**")
202
  col_tol1,col_tol2=st.columns(2)
203
  with col_tol1:
204
+ tol_len=st.slider("Tol. lunghezza ±",0.0,30.0,5.0,step=0.5)
205
+ tol_wid=st.slider("Tol. larghezza ±",0.0,20.0,3.0,step=0.5)
206
  with col_tol2:
207
+ tol_we=st.slider("Tol. peso ±",0.0,50.0,10.0,step=1.0)
208
+ tol_th=st.slider("Tol. spessore ±",0.0,5.0,1.0,step=0.1)
209
 
210
  st.markdown("**Score RUL & Margin**")
211
+ thr_sc=st.slider("Soglia score",0.0,1.0,0.5,step=0.05)
212
+ alpha=st.slider("Peso RUL(α)",0.0,1.0,0.5,step=0.05)
213
+ beta =st.slider("Peso Margin(β)",0.0,1.0,0.5,step=0.05)
214
 
215
+ # shape_code
216
  data['shape_code']=data['shape'].map(SHAPE_MAPPING).fillna(-1).astype(int)
217
+ data['compat_dim']= data.apply(lambda r: dimension_match(r,
218
+ target_length=t_len,
219
+ target_width=t_wid,
220
+ target_shape=t_shape,
221
+ target_weight=t_weight,
222
+ target_thickness=t_thick,
223
+ tol_len=tol_len,
224
+ tol_wid=tol_wid,
225
+ tol_weight=tol_we,
226
+ tol_thickness=tol_th), axis=1)
227
+
228
+ data['Target'] = data.apply(lambda r: assign_class(r,
229
+ threshold_score=thr_sc,
230
+ alpha=alpha,beta=beta), axis=1)
 
 
231
 
232
  st.session_state.target_params={
233
+ "target_length": t_len, "target_width": t_wid,
234
+ "target_shape": t_shape, "target_weight": t_weight,
 
 
235
  "target_thickness": t_thick,
236
+ "tol_len": tol_len, "tol_wid": tol_wid,
237
+ "tol_weight": tol_we, "tol_thickness": tol_th
 
 
238
  }
239
  st.session_state.score_params={
240
+ "threshold_score": thr_sc,
241
  "alpha": alpha,
242
  "beta": beta
243
  }
244
 
245
  st.dataframe(data.head(10))
246
+ st.write("Distribuzione classi:", data["Target"].value_counts())
247
 
248
+ numeric_cols=data.select_dtypes(include=np.number)
249
  if not numeric_cols.empty:
250
+ fig,ax=plt.subplots()
251
  sns.heatmap(numeric_cols.corr(), annot=True, cmap='viridis', fmt=".2f", ax=ax)
252
  st.pyplot(fig)
253
 
254
  st.session_state.data=data
255
+ csv_proc=data.to_csv(index=False).encode('utf-8')
256
+ st.download_button("Scarica Dataset Elaborato", csv_proc, "dataset_processed.csv")
257
 
258
+ ############### 2) PHASE: TRAIN MODELLI ML ############
 
 
259
  def train_models(data):
260
  st.header("🤖 2. Addestramento ML (Riutilizzo vs Upcycling)")
261
  if data is None:
262
+ st.error("Dataset non disponibile (Fase 1).")
263
  return
264
  if 'Target' not in data.columns:
265
+ st.error("Colonna 'Target' mancante. Rivedi la Fase 1.")
266
  return
267
 
 
268
  features_to_use=[f for f in ML_FEATURES_STEP1 if f in data.columns]
269
  if not features_to_use:
270
+ st.error("Nessuna feature per l'addestramento ML.")
271
  return
272
 
273
+ X = data[features_to_use].copy()
274
+ y = data['Target'].map({"Riutilizzo Funzionale":0,"Upcycling Creativo":1})
 
 
275
  if len(y.unique())<2:
276
+ st.error("Il dataset ha una sola classe. Non si può addestrare.")
277
  return
278
+ X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.25,random_state=42,stratify=y)
279
+ st.write(f"Train={len(X_train)}, Test={len(X_test)}")
280
 
281
+ tune_rf= st.checkbox("Ottimizza RandomForest (GridSearchCV)",False)
 
 
 
282
  trained_pipelines={}
283
  results=[]
284
+
285
  for name,model in MODELS.items():
286
  st.subheader(f"Modello: {name}")
287
  from sklearn.pipeline import Pipeline
 
291
  ])
292
  try:
293
  if tune_rf and name=="Random Forest":
294
+ st.write("Esecuzione GridSearchCV su RandomForest...")
295
  param_grid={
296
  'clf__n_estimators':[50,100],
297
+ 'clf__max_depth':[None,10],
298
  }
299
+ from sklearn.model_selection import GridSearchCV
300
+ grid=GridSearchCV(pipe,param_grid,cv=2,scoring='accuracy',n_jobs=-1)
301
  grid.fit(X_train,y_train)
302
+ best_est=grid.best_estimator_
303
  st.write(f"Migliori parametri: {grid.best_params_}")
304
+ y_pred=best_est.predict(X_test)
305
+ pipe_to_use=best_est
306
  else:
307
  pipe.fit(X_train,y_train)
308
  y_pred=pipe.predict(X_test)
 
310
 
311
  acc=accuracy_score(y_test,y_pred)
312
  f1=f1_score(y_test,y_pred,average='weighted')
313
+ results.append({"Modello":name, "Accuracy":acc, "F1 Score":f1})
314
  trained_pipelines[name]=pipe_to_use
315
 
316
  cm=confusion_matrix(y_test,y_pred)
317
  fig,ax=plt.subplots()
318
+ sns.heatmap(cm,annot=True,fmt='d',cmap="Greens",ax=ax)
319
  plt.xlabel("Pred")
320
  plt.ylabel("True")
321
  st.pyplot(fig)
 
322
  st.metric("Accuracy",f"{acc:.3f}")
323
  st.metric("F1 Score",f"{f1:.3f}")
324
 
 
326
  st.error(f"Errore addestramento {name}: {e}")
327
 
328
  if results:
329
+ df_res=pd.DataFrame(results).sort_values(by="Accuracy",ascending=False)
330
+ st.dataframe(df_res)
331
+ st.session_state.train_results=df_res
332
  st.session_state.models=trained_pipelines
333
  else:
334
  st.error("Nessun modello addestrato con successo.")
335
  st.session_state.models=None
336
 
337
+ ############### 3) PHASE: INFERE ############
 
 
338
  def model_inference(trained_pipelines, data):
339
+ st.header("🔮 3. Inferenza: Step 1 & Step 2 (VAE + GenAI)")
340
 
341
  if not trained_pipelines:
342
+ st.error("Prima addestra i modelli ML (Fase 2).")
343
  return
344
  if data is None:
345
+ st.error("Nessun dataset (Fase 1).")
 
 
 
346
  return
347
 
348
+ # Usiamo mediane del dataset per default
349
+ data_stats=data
350
 
351
+ with st.form("inference_form"):
352
+ st.subheader("Inserisci Dati EoL")
353
  c1,c2,c3=st.columns(3)
354
  with c1:
355
  length=st.number_input("Lunghezza (mm)",0.0,300.0,float(data_stats['length'].median()),step=1.0)
356
+ width= st.number_input("Larghezza (mm)",0.0,200.0,float(data_stats['width'].median()),step=1.0)
357
+ shape_name = st.selectbox("Forma", list(SHAPE_MAPPING.keys()))
358
  with c2:
359
+ weight= st.number_input("Peso (kg)",0.0,300.0,float(data_stats['weight'].median()),step=1.0)
360
+ thickness= st.number_input("Spessore (mm)",0.0,50.0,float(data_stats['thickness'].median()),step=0.5)
361
+ RUL= st.number_input("RUL (0-1000)",0,1000,int(data_stats['RUL'].median()))
362
  with c3:
363
+ val_merc= st.number_input("Valore Mercato (€)",0.0,1e5,float(data_stats['margin'].median()+200),step=10.0)
364
+ costo_rip= st.number_input("Costo Riparazione (€)",0.0,1e5,50.0,step=10.0)
365
 
366
+ sub=st.form_submit_button("Predizione Step 1")
367
 
368
+ if sub:
369
  margin= val_merc - costo_rip
370
+ shape_code = SHAPE_MAPPING.get(shape_name,-1)
371
 
 
372
  input_dict={
373
+ "length":length,
374
+ "width":width,
375
+ "shape":shape_name,
376
+ "weight":weight,
377
+ "thickness":thickness,
378
+ "RUL":RUL,
379
+ "margin":margin
380
  }
381
  temp_df=pd.DataFrame([input_dict])
382
 
383
+ # compat_dim
384
+ if 'target_params' not in st.session_state:
385
+ st.error("Parametri target non definiti. Fase 1 mancante.")
386
+ return
387
+ param_t=st.session_state.target_params
388
+ temp_df['compat_dim'] = temp_df.apply(lambda r:
389
+ dimension_match(r, **param_t), axis=1)
390
 
391
+ # shape_code
392
+ temp_df['shape_code']= shape_code
393
+ # Manteniamo solo le col ML
394
  try:
395
+ X_inference=temp_df[ML_FEATURES_STEP1]
396
  except KeyError as e:
397
+ st.error(f"Mancano feature: {e}")
398
  return
399
 
400
+ # Eseguiamo predizione con i modelli
401
+ preds=[]
402
  details=[]
403
  for name,pipe in trained_pipelines.items():
404
  try:
405
+ p_num=pipe.predict(X_inference)[0]
406
  proba=pipe.predict_proba(X_inference)[0]
407
  details.append({
408
+ "Modello":name,
409
+ "Pred(0=Riu,1=Upc)": p_num,
410
+ "Prob_Riutilizzo": proba[0],
411
+ "Prob_Upcycling": proba[1]
412
  })
413
+ preds.append(p_num)
414
  except Exception as e:
415
  st.error(f"Errore predizione {name}: {e}")
416
 
417
+ if not preds:
418
+ st.error("Nessuna predizione valida.")
419
  return
 
420
  # Aggrega con mode
421
+ from statistics import mode, StatisticsError
422
  try:
423
+ final_pred=mode(preds)
424
  except StatisticsError:
425
+ # Se c'è tie, guardiamo la media upcycling
426
+ avg_upc= np.mean([d["Prob_Upcycling"] for d in details])
427
+ final_pred=1 if avg_upc>=0.5 else 0
428
 
429
  final_label="Riutilizzo Funzionale" if final_pred==0 else "Upcycling Creativo"
430
+ st.subheader("Risultato Aggregato")
431
+ st.metric("Classe", final_label)
432
 
433
+ with st.expander("Dettagli singoli modelli"):
434
+ df_det=pd.DataFrame(details)
435
+ df_det["Prob_Riutilizzo"]= df_det["Prob_Riutilizzo"].apply(lambda x:f"{x:.1%}")
436
+ df_det["Prob_Upcycling"]= df_det["Prob_Upcycling"].apply(lambda x:f"{x:.1%}")
437
+ st.dataframe(df_det)
438
 
439
+ # Se Upcycling Creativo => Step 2 (VAE + GenAI)
440
  if final_label=="Upcycling Creativo":
441
  st.markdown("---")
442
+ st.subheader("Fase 2: Generazione con VAE + GenAI Testuale")
443
 
444
  if not st.session_state.get("vae_trained_on_eol",False):
445
+ st.error("VAE non addestrato. Vai a '🧬 Training VAE (Step 2)'.")
446
+ return
447
+ vae_model= st.session_state.get("vae",None)
448
+ vae_scaler=st.session_state.get("vae_scaler",None)
449
+ if vae_model is None or vae_scaler is None:
450
+ st.error("VAE o scaler mancanti in session.")
451
+ return
452
+
453
+ n_ideas=st.number_input("Quante idee generare col VAE?",1,10,3)
454
+ if st.button("Genera Configurazioni + Testo Upcycling"):
455
+ vae_model.eval()
456
+ with torch.no_grad():
457
+ lat_dim= vae_model.fc21.out_features
458
+ z=torch.randn(n_ideas, lat_dim)
459
+ recon= vae_model.decode(z)
460
+
461
+ arr=recon.numpy()
462
+ try:
463
+ arr_inv= vae_scaler.inverse_transform(arr)
464
+ feat_names= vae_scaler.feature_names_in_
465
+ df_gen= pd.DataFrame(arr_inv, columns=feat_names)
466
+
467
+ # Riconverti shape_code -> shape
468
+ if 'shape_code' in df_gen.columns:
469
+ df_gen['shape_code']= df_gen['shape_code'].round().astype(int)
470
+ inv_map={v:k for k,v in SHAPE_MAPPING.items()}
471
+ df_gen['shape']= df_gen['shape_code'].map(inv_map).fillna('unknown')
472
+
473
+ st.write("**Configurazioni generate (VAE)**")
474
+ st.dataframe(df_gen.round(2))
475
+
476
+ # --- Integrazione GenAI testuale con Transformers ---
477
+ st.markdown("#### Suggerimenti testuali per ciascuna configurazione")
478
+
479
+ # Carichiamo pipeline testuale (distilgpt2, ad es.)
480
+ # Se su HF Spaces serve un modello leggero
481
+ text_generator = pipeline(
482
+ "text-generation",
483
+ model="distilgpt2",
484
+ device=0 if torch.cuda.is_available() else -1
485
+ )
486
+
487
+ def gen_upcycle_text(row):
488
+ shape = row.get("shape","unknown")
489
+ thick = row.get("thickness",0.0)
490
+ wei = row.get("weight",0.0)
491
+ prompt= (
492
+ f"Ho un componente EoL con forma {shape}, spessore {thick:.1f} mm, peso {wei:.1f} kg.\n"
493
+ "Dammi un'idea creativa di upcycling (in italiano) con passaggi principali:"
494
+ )
495
+ result= text_generator(prompt, max_new_tokens=50, do_sample=True, top_k=50)
496
+ return result[0]["generated_text"]
497
+
498
+ ideas_text=[]
499
+ for i, r in df_gen.iterrows():
500
+ text_sugg = gen_upcycle_text(r)
501
+ ideas_text.append(text_sugg)
502
+
503
+ for i, r in df_gen.iterrows():
504
+ st.write(f"**Idea {i+1}** - shape={r['shape']}, thickness={r['thickness']:.1f}, weight={r['weight']:.1f}")
505
+ st.info(ideas_text[i])
506
+ st.markdown("---")
507
+
508
+ except Exception as e:
509
+ st.error(f"Errore decoding VAE: {e}")
510
  else:
511
+ st.success("Predetto: Riutilizzo Funzionale. Nessun passaggio generativo richiesto.")
512
 
513
+ ############### 4) PHASE: TRAINING VAE ############
 
 
514
  def vae_training_phase():
515
+ st.header("🧬 Training VAE (Step 2)")
 
516
  if 'data' not in st.session_state or st.session_state['data'] is None:
517
+ st.error("Nessun dataset in session. Torna a Fase 1.")
518
  return
 
519
  data=st.session_state['data']
520
+
521
+ feats=[f for f in VAE_FEATURES_STEP2 if f in data.columns]
522
  if not feats:
523
+ st.error(f"Nessuna feature da usare per VAE. Servirebbero: {VAE_FEATURES_STEP2}")
524
  return
525
+ st.write(f"VAE userà feature: {feats}")
526
 
527
+ lat_dim= st.slider("Dimensione Latente",2,10,2)
528
+ ep= st.number_input("Epochs VAE",10,300,50)
529
+ lr= st.number_input("Learning Rate VAE",1e-5,1e-2,1e-3,format="%e")
530
+ bs= st.selectbox("Batch Size",[16,32,64], index=1)
 
531
 
532
  if not st.session_state.get("vae_trained_on_eol",False):
533
+ st.warning("VAE non addestrato.")
534
  if st.button("Avvia Training VAE"):
535
+ # Inizializza
536
+ st.session_state["vae"]= MiniVAE(input_dim=len(feats), latent_dim=lat_dim)
537
+ st.session_state["vae_trained_on_eol"]=False
538
+
539
+ from sklearn.preprocessing import StandardScaler
540
+ X_vae= data[feats].copy()
541
+ # Riempi NaN
542
  for c in X_vae.columns:
543
  if X_vae[c].isnull().any():
544
  X_vae[c].fillna(X_vae[c].median(), inplace=True)
545
 
546
  # Scalatura
547
+ scaler= StandardScaler()
548
+ X_scaled= scaler.fit_transform(X_vae)
549
+ st.session_state["vae_scaler"]= scaler
 
550
 
551
  dataset=torch.utils.data.TensorDataset(torch.tensor(X_scaled,dtype=torch.float32))
552
+ loader= torch.utils.data.DataLoader(dataset,batch_size=bs,shuffle=True)
553
+ vae=st.session_state["vae"]
554
+ optimizer= torch.optim.Adam(vae.parameters(),lr=lr)
555
 
556
  losses=[]
557
  vae.train()
558
  for epoch in range(int(ep)):
559
+ epoch_loss=0
560
  for (batch,) in loader:
561
  optimizer.zero_grad()
562
+ recon, mu, logvar= vae(batch)
563
+ loss= vae_loss(recon, batch, mu, logvar)
564
  loss.backward()
565
  optimizer.step()
566
+ epoch_loss+= loss.item()
567
+ avg_loss= epoch_loss/len(dataset)
568
+ losses.append(avg_loss)
569
  st.progress((epoch+1)/ep)
570
  st.session_state["vae_trained_on_eol"]=True
571
+ st.success(f"VAE addestrato. Ultimo Loss ~ {avg_loss:.2f}")
572
  st.line_chart(losses)
573
  else:
574
+ st.success("VAE già addestrato. Se vuoi rifare training, clicca 'Riallena'.")
575
+ if st.button("Riallena"):
576
  st.session_state["vae_trained_on_eol"]=False
577
  st.rerun()
578
 
579
+ ############### 5) PHASE: DASHBOARD ############
 
 
580
  def show_dashboard():
581
  st.header("📊 Dashboard")
582
+ data= st.session_state.get('data', None)
583
+ if data is None:
584
+ st.error("Nessun dataset.")
585
  return
586
+
587
  st.subheader("Distribuzione Classi EoL")
588
  st.write(data['Target'].value_counts())
589
 
590
  if 'train_results' in st.session_state and st.session_state['train_results'] is not None:
 
591
  df_res=st.session_state['train_results']
592
+ st.subheader("Risultati modelli ML")
593
  st.dataframe(df_res)
594
  else:
595
+ st.info("Nessun risultato di training ML")
596
 
597
  st.subheader("Stato VAE")
598
  if st.session_state.get("vae_trained_on_eol",False):
599
+ st.success("VAE addestrato. Pronto per generare idee di upcycling.")
600
  else:
601
+ st.warning("VAE non addestrato")
602
 
603
+ ############### 6) PHASE: GUIDA ############
 
 
604
  def show_help():
605
+ st.header("ℹ️ Guida all'Uso")
606
  st.markdown("""
607
+ **Flusso a due fasi (Riutilizzo vs Upcycling) e generazione creativa VAE + GenAI**:
608
+
609
+ 1. **Fase 1 (Dataset)**:
610
+ - Generi/carichi dati su dimensioni, shape, RUL, margin, ecc.
611
+ - Definisci parametri e tolleranze per la compatibilità, assegni classi ("Riutilizzo Funzionale" vs "Upcycling Creativo").
612
+
613
+ 2. **Fase 2 (Addestramento ML)**:
614
+ - Alleni vari modelli (RF, SVM...) per predire la classe su nuovi EoL.
615
+
616
+ 3. **Fase 3 (Inferenza)**:
617
+ - Inserisci un nuovo EoL.
618
+ - Se la classe è "Riutilizzo Funzionale", stop.
619
+ - Se "Upcycling Creativo", prosegui con generazione di soluzioni (VAE)...
620
+
621
+ 4. **Fase 4 (Training VAE)**:
622
+ - Prima devi addestrare il VAE su feature geometriche.
623
+ - Finito l'allenamento, potrai generare configurazioni fittizie per l'upcycling (dim, spessore...).
624
+
625
+ 5. **Fase 3 (continuazione)**:
626
+ - Appena generi col VAE, un modello di GenAI testuale (distilgpt2) crea suggerimenti di riuso creativo in linguaggio naturale.
627
+
628
+ 6. **Fase 5 (Dashboard)**:
629
+ - Visualizzi le metriche e lo stato dei modelli.
630
+
631
+ Puoi resettare l'app in qualsiasi momento dal pulsante "Reset App" nella sidebar.
632
+ """)
633
+
634
+ ############### RESET ############
635
  def reset_app():
636
+ for k in ["data","models","train_results","vae","vae_trained_on_eol","vae_scaler","target_params","score_params","data_source"]:
 
 
 
 
637
  if k in st.session_state:
638
  del st.session_state[k]
639
+ st.success("App reset. Ricarico l'interfaccia.")
640
  st.experimental_rerun()
641
 
642
+ ############### MAIN ############
 
 
643
  def main():
644
+ st.sidebar.title("WEEKO - Menu")
645
+ step= st.sidebar.radio("Fasi:", [
646
  "♻️ Dataset",
647
  "🤖 Addestramento ML (Step 1)",
648
  "🔮 Inferenza (Step 1 & 2)",
 
657
  elif step=="🤖 Addestramento ML (Step 1)":
658
  train_models(st.session_state.get('data',None))
659
  elif step=="🔮 Inferenza (Step 1 & 2)":
660
+ if st.session_state.get('models') is None:
661
+ st.error("Devi addestrare i modelli ML (Fase 2).")
662
  else:
663
  model_inference(st.session_state['models'], st.session_state.get('data',None))
664
  elif step=="🧬 Training VAE (Step 2)":
 
670
 
671
  if __name__=="__main__":
672
  main()
673
+