251 lines
12 KiB
Python
251 lines
12 KiB
Python
import re
|
|
|
|
import extra_streamlit_components as stx
|
|
import streamlit as st
|
|
|
|
from functions import check_auth, do_logout
|
|
from functions_ui import search_cas_inci, search_cir, show_login_page
|
|
|
|
# Configure page
|
|
st.set_page_config(
|
|
page_title="LMB App",
|
|
page_icon="🔬",
|
|
layout="wide"
|
|
)
|
|
|
|
st.session_state["_cm"] = stx.CookieManager(key="pif_cookies") # una sola volta per rerun
|
|
|
|
if not check_auth():
|
|
show_login_page()
|
|
st.stop()
|
|
|
|
|
|
# Define home page function
|
|
def home():
|
|
name = st.session_state.get("user_name") or st.session_state.get("user_email") or ""
|
|
greeting = f"Benvenuto, {name}" if name else "Benvenuto"
|
|
st.title("LMB App: PIF & Database Tossicologico")
|
|
st.caption(greeting)
|
|
|
|
if "selected_cas" not in st.session_state:
|
|
st.session_state.selected_cas = None
|
|
if "selected_inci" not in st.session_state:
|
|
st.session_state.selected_inci = None
|
|
|
|
with st.container(border=True):
|
|
col_left, col_right = st.columns([2, 1])
|
|
with col_left:
|
|
search_type = st.radio("Cerca per:", ("CAS", "INCI"), index=0, key="search_mode", horizontal=True)
|
|
search_input = st.text_input("Inserisci:", "")
|
|
with col_right:
|
|
real_time = st.checkbox("Ricerca online", value=False, key="real_time_search")
|
|
force_refresh = st.checkbox("Aggiorna Ingrediente", value=False)
|
|
search_cir_enabled = st.checkbox("Cerca in CIR", value=False, key="search_cir")
|
|
|
|
if search_input:
|
|
st.caption(f"Ricerca per {search_input}: trovati i seguenti ingredienti.")
|
|
results = search_cas_inci(search_input, type="cas" if search_type == "CAS" else "inci")
|
|
|
|
if results:
|
|
display_options = [f"{cas} - {inci}" for cas, inci in results]
|
|
selected_display = st.selectbox("Risultati", options=[""] + display_options, key="cas_selectbox")
|
|
|
|
if selected_display:
|
|
found_cas = re.findall(r"\b\d{2,7}-\d{2}-\d\b", selected_display)
|
|
unique_cas = list(dict.fromkeys(found_cas))
|
|
|
|
if not unique_cas:
|
|
st.warning("Nessun pattern CAS valido trovato nella stringa.")
|
|
selected_cas = None
|
|
elif len(unique_cas) == 1:
|
|
selected_cas = unique_cas[0]
|
|
else:
|
|
selected_cas = st.selectbox(
|
|
label="Sono stati rilevati più CAS. Selezionane uno:",
|
|
options=unique_cas
|
|
)
|
|
|
|
if selected_cas:
|
|
st.session_state.selected_cas = selected_cas
|
|
match = next((inci for cas, inci in results if cas == selected_cas), None)
|
|
st.session_state.selected_inci = match
|
|
else:
|
|
st.warning("Nessun risultato trovato nel database.")
|
|
if st.button("Usa questo CAS") and search_type == "CAS":
|
|
st.session_state.selected_cas = search_input.strip()
|
|
st.session_state.selected_inci = None
|
|
st.success(f"CAS salvato: {search_input}")
|
|
else:
|
|
st.info("INCI non trovato, cerca per CAS o modifica l'input.")
|
|
|
|
if st.session_state.selected_cas:
|
|
st.info(f"CAS corrente: {st.session_state.selected_cas}")
|
|
if st.button("Vai a Ingredienti →", type="primary"):
|
|
st.switch_page(ingredients_page)
|
|
if real_time:
|
|
if st.button("Vai a ECHA →"):
|
|
st.switch_page(echa_page)
|
|
|
|
st.session_state.force_refresh = force_refresh
|
|
|
|
# CIR search results
|
|
if search_cir_enabled and search_input:
|
|
st.markdown("---")
|
|
st.markdown("**Rapporti CIR:**")
|
|
cir_results = search_cir(search_input)
|
|
if cir_results:
|
|
for name, inci, url in cir_results:
|
|
label = name if name == inci else f"{name} ({inci})"
|
|
st.markdown(f"- [{label}]({url})")
|
|
else:
|
|
st.caption("Nessun rapporto CIR trovato.")
|
|
|
|
# Guide section
|
|
st.divider()
|
|
with st.expander("📖 Guida all'utilizzo"):
|
|
st.markdown("""
|
|
### Ricerca ingredienti
|
|
La barra di ricerca in questa pagina permette di cercare un ingrediente per **CAS** o per **INCI**.
|
|
Il risultato viene poi visualizzato nella pagina **Ingrediente**, che contiene:
|
|
- I dati per determinare il **DAP** (caratteristiche fisico-chimiche da PubChem)
|
|
- Le **restrizioni normative** da CosIng (annex, restrizioni cosmetiche, opinioni SCCS)
|
|
- I dati di **tossicologia** con il miglior indicatore disponibile (NOAEL/LOAEL ecc.)
|
|
- La possibilità di **scaricare direttamente le fonti** (PDF ECHA e CosIng)
|
|
|
|
> **Ricerca online** — mostra anche la pagina **ECHA** con il dettaglio completo dei dossier.
|
|
> È un metodo di visualizzazione più vecchio; si consiglia di usare la pagina **Ingrediente**.
|
|
|
|
> **Aggiorna Ingrediente** — forza il ricalcolo e l'aggiornamento dell'ingrediente nel database,
|
|
> utile per verificare eventuali cambi di restrizioni normative.
|
|
|
|
---
|
|
|
|
### Ordini PIF (Punti 6, 7, 8)
|
|
Dalla sezione **PIF** è possibile:
|
|
|
|
- **Nuovo PIF** — inserire i dati del prodotto e la tabella QQ degli ingredienti per avviare
|
|
il calcolo automatico del **Margin of Safety (MoS)**. Il sistema seleziona automaticamente
|
|
gli indicatori tossicologici migliori per ciascun ingrediente e produce un **file Excel**
|
|
con SED, MoS e tabella QQ pronti per il PIF.
|
|
Per prodotti simili a uno già esistente, si può inserire il **numero d'ordine precedente**
|
|
per pre-compilare la tabella QQ.
|
|
|
|
- **Ordini PIF** — visualizza tutti gli ordini effettuati con il relativo stato. Da qui si può:
|
|
- Scaricare l'**Excel** (SED, MoS, tabella QQ)
|
|
- Scaricare lo **ZIP delle fonti** (tutti i PDF ECHA e CosIng dell'ordine)
|
|
- **Cancellare** un ordine (sconsigliato salvo necessità)
|
|
|
|
Tutti gli ordini vengono salvati nel database, inclusa la lista degli ingredienti, così da
|
|
poter individuare facilmente eventuali PIF da rivedere in caso di modifiche alle restrizioni.
|
|
È possibile filtrare gli ordini per **stato**, **nome cliente** e **data**.
|
|
|
|
---
|
|
|
|
### Preset di esposizione
|
|
Prima di calcolare il MoS è necessario avere almeno un **preset di esposizione** configurato
|
|
nella pagina **Esposizione**. Il preset definisce i parametri del prodotto (superficie esposta,
|
|
quantità giornaliera, fattore di ritenzione, vie di esposizione, ecc.).
|
|
|
|
- Scegliere un nome **chiaro ed esplicativo** (es. *Crema viso leave-on*)
|
|
- Non possono esistere due preset con lo stesso nome
|
|
- I preset possono essere cancellati se non più necessari
|
|
- I preset sono riutilizzabili per più ordini dello stesso tipo di prodotto
|
|
|
|
---
|
|
|
|
> Ogni pagina dell'applicazione contiene una propria guida dettagliata sul suo funzionamento.
|
|
""")
|
|
|
|
# Changelog section
|
|
with st.expander("📝 Registro degli aggiornamenti"):
|
|
st.markdown("""
|
|
### v1.0
|
|
- Aggiunta autenticazione con mail e password
|
|
- Modificato il backend per maggiore sicurezza
|
|
- Ottimizzazioni generali e refactoring del codice
|
|
- Aggiunto form per segnalare errori
|
|
- Migliorato l'algoritmo per scegliere l'indicatore tossicologico migliore:
|
|
* Ora si preferiscono indicatori con route più rilevante per il MoS (es. NOAEL dermal > NOAEL orale)
|
|
* In caso di più indicatori con stesso endpoint, si sceglie quello con la dose più bassa (solo per NOAEL/LOAEL)
|
|
|
|
### v0.9 - 2026-03-01
|
|
- Ottimizzazione della pagina 'Ingrediente', risolto un bug che impediva di caricare i dati CosIng
|
|
- Modificato la validazione dei dati in esposizione
|
|
- Creata una pagina di impostazione per:
|
|
* Creazione di parametri custom di tossicologia per un ingrediente
|
|
* Cancellazione di clienti (e relativi dati)
|
|
* Visualizzare gli ingredienti salvati nel database
|
|
|
|
### v0.8
|
|
*v0.8.0 - 2026-02-22
|
|
- Versione iniziale (v0.8.0) rilasciata per test interno e feedback
|
|
- Tutte le funzionalità principali implementate, ma in fase di test e ottimizzazione
|
|
- Modificato il flusso di ricerca, ora tutte le informazioni si trovano su Ingrediente
|
|
- Aggiustati numerosi bug minori e migliorata l'usabilità generale
|
|
* Si può visionare grado di ionizzazione e altri dati DAP direttamente su Ingrediente
|
|
* Ora si può scaricare direttamente il PDF del CosIng ufficiale (se esiste) dalla pagina Ingrediente
|
|
* Il migliore indicatore tossicologico viene automaticamente individuato
|
|
* Si può forzare l'aggiornamento dei dati di un ingrediente in caso di modifiche normative o nuovi dati tossicologici
|
|
- Aggiunta la funzionalità di creare ordini per PIF e scaricare i risultati in Excel
|
|
- Aggiunta la funzionalità di creare preset di esposizione personalizzati e cancellarli
|
|
- Aggiunta la funzionalità di visualizzare tutti i PIF creati e il loro stato di avanzamento (con possibilità di cancellarli)
|
|
- Aggiunta la funzionalità di scaricare un file ZIP con tutte le fonti (PDF ECHA e CosIng) per ogni ordine
|
|
|
|
### v0.3
|
|
*v0.3.0 - 2026-02-08
|
|
- Aggiunta pagina per la gestione dei preset dei parametri di esposizione
|
|
* Permette di creare, modificare e salvare preset personalizzati per i calcoli di esposizione
|
|
* In futuro per calcolare il MoS bisognerà prima selezionare un preset di esposizione
|
|
- Aggiunta pagina per la creazione degli ingredienti a partire da un CAS:
|
|
* Facendo una ricerca in 'Ingredienti' e selezionando un CAS, è possibile cliccare su 'Ricerca' per generare un nuovo ingrediente nel database a partire da quel CAS.
|
|
* Gli ingredienti creati in questo modo hanno già i dati di base, tossicologici e quelli normativi da CosIng
|
|
* E' consigliato cercare nuovi ingredienti tramite questa nuova funzione
|
|
|
|
### v0.2
|
|
*v0.2.1 - 2026-01-13*
|
|
- Fix minore su ricerca CosIng
|
|
|
|
**v0.2.0 - 2026-01-05**
|
|
- Aggiunta ricerca per nome INCI
|
|
- Possibilità di filtrare per singoli CAS in caso di multipli per stesso INCI
|
|
- Verifica se il link al download esiste prima di generare il PDF
|
|
- Aggiunta pagina per verificare i valori per determinare il DAP (da PubChem)
|
|
- La ricerca per ingrediente su ECHA non va più in errore se almeno uno dei tre dossier esiste
|
|
- Filtrati i dossier ECHA se sono di tipo 'full' e sono di tipo 'Lead' (sempre Active)
|
|
---
|
|
|
|
### v0.1
|
|
**v0.1.0 - 2025-12-18**
|
|
- Release iniziale
|
|
- Funzionalità di ricerca per Numero CAS
|
|
- Integrazione con ECHA
|
|
- Integrazione con CosIng
|
|
- Protezione con password
|
|
- Sistema di navigazione multi-pagina
|
|
- Download del PDF ECHA dossier
|
|
- Visualizzazione dati tossicologici ECHA e CosIng
|
|
""")
|
|
|
|
|
|
# Navigation
|
|
home_page = st.Page(home, title="Cerca", icon="🏠", default=True)
|
|
echa_page = st.Page("pages/echa.py", title="ECHA (legacy)", icon="🧪")
|
|
exposition_page = st.Page("pages/exposition_page.py", title="Esposizione", icon="☀️")
|
|
ingredients_page = st.Page("pages/ingredients_page.py", title="Ingrediente", icon="📋")
|
|
order_page = st.Page("pages/order_page.py", title="Nuovo PIF", icon="🛒")
|
|
list_orders = st.Page("pages/list_orders.py", title="Ordini PIF", icon="📦")
|
|
settings_page = st.Page("pages/settings_page.py", title="Impostazioni", icon="⚙️")
|
|
ticket_page = st.Page("pages/ticket.py", title="Segnala problema", icon="🚩")
|
|
|
|
pg = st.navigation({
|
|
"Ricerca": [home_page, ingredients_page],
|
|
"PIF": [list_orders, order_page, exposition_page],
|
|
"Utilità": [echa_page, settings_page, ticket_page],
|
|
})
|
|
|
|
with st.sidebar:
|
|
if st.button("Esci", use_container_width=True):
|
|
do_logout()
|
|
st.rerun()
|
|
|
|
pg.run()
|