added settings
This commit is contained in:
parent
44bcb38488
commit
83c28bff2f
3 changed files with 169 additions and 259 deletions
138
old/cosing.py
138
old/cosing.py
|
|
@ -1,138 +0,0 @@
|
||||||
import streamlit as st
|
|
||||||
import json
|
|
||||||
|
|
||||||
from functions import cosing_request
|
|
||||||
from functions_ui import download_pdf
|
|
||||||
|
|
||||||
st.title("CosIng Database Viewer")
|
|
||||||
|
|
||||||
st.set_page_config(
|
|
||||||
page_title="CosIng",
|
|
||||||
page_icon="🧪",
|
|
||||||
layout="wide"
|
|
||||||
)
|
|
||||||
|
|
||||||
def display_ingredient(data: dict, level: int = 0):
|
|
||||||
"""Display ingredient information using containers and metrics."""
|
|
||||||
|
|
||||||
# Get names
|
|
||||||
name = data.get("commonName") or data.get("inciName") or "Unknown"
|
|
||||||
item_type = data.get("itemType", "ingredient")
|
|
||||||
|
|
||||||
# Header
|
|
||||||
if level == 0:
|
|
||||||
st.title(f"🧪 {name}")
|
|
||||||
else:
|
|
||||||
st.markdown(f"### {name}")
|
|
||||||
|
|
||||||
# Type badge and chemical name
|
|
||||||
col_header1, col_header2 = st.columns([1, 3])
|
|
||||||
with col_header1:
|
|
||||||
if item_type == "substance":
|
|
||||||
st.caption("🔬 Substance")
|
|
||||||
else:
|
|
||||||
st.caption("🧴 Ingredient")
|
|
||||||
|
|
||||||
# Identifiers container
|
|
||||||
cas_numbers = data.get("casNo", [])
|
|
||||||
ec_numbers = data.get("ecNo", [])
|
|
||||||
ref_no = data.get("refNo", "")
|
|
||||||
|
|
||||||
if cas_numbers or ec_numbers or ref_no:
|
|
||||||
with st.container(border=True):
|
|
||||||
cols = st.columns(3)
|
|
||||||
|
|
||||||
with cols[0]:
|
|
||||||
if cas_numbers:
|
|
||||||
st.metric("CAS", ", ".join(cas_numbers))
|
|
||||||
else:
|
|
||||||
st.metric("CAS", "—")
|
|
||||||
|
|
||||||
with cols[1]:
|
|
||||||
if ec_numbers:
|
|
||||||
st.metric("EC", ", ".join(ec_numbers))
|
|
||||||
else:
|
|
||||||
st.metric("EC", "—")
|
|
||||||
# Functions
|
|
||||||
functions = data.get("functionName", [])
|
|
||||||
if functions:
|
|
||||||
with st.container(border=True):
|
|
||||||
st.markdown("**Functions**")
|
|
||||||
func_cols = st.columns(len(functions))
|
|
||||||
for i, func in enumerate(functions):
|
|
||||||
with func_cols[i]:
|
|
||||||
st.success(func.title())
|
|
||||||
|
|
||||||
# Regulatory info
|
|
||||||
restrictions = data.get("otherRestrictions", [])
|
|
||||||
annex = data.get("annexNo", [])
|
|
||||||
regulations = data.get("otherRegulations", [])
|
|
||||||
opinions = data.get("sccsOpinion", [])
|
|
||||||
opinion_urls = data.get("sccsOpinionUrls", [])
|
|
||||||
|
|
||||||
if restrictions or annex or regulations or opinions or opinion_urls:
|
|
||||||
with st.container(border=True):
|
|
||||||
st.markdown("**Regulatory Information**")
|
|
||||||
|
|
||||||
if annex:
|
|
||||||
st.info(f"📋 **Annex {', '.join(annex)}**")
|
|
||||||
|
|
||||||
if restrictions:
|
|
||||||
for r in restrictions:
|
|
||||||
st.warning(f"⚠️ {r}")
|
|
||||||
|
|
||||||
if regulations:
|
|
||||||
st.write("Other regulations: " + "; ".join(regulations))
|
|
||||||
|
|
||||||
if opinions:
|
|
||||||
for opinion in opinions:
|
|
||||||
st.write(f"📄 {opinion}")
|
|
||||||
|
|
||||||
if opinion_urls:
|
|
||||||
for url in opinion_urls:
|
|
||||||
st.link_button("View SCCS Opinion", url)
|
|
||||||
|
|
||||||
# Source link
|
|
||||||
cosing_url = data.get("cosingUrl", "")
|
|
||||||
if cosing_url:
|
|
||||||
st.link_button("🔗 View on CosIng", cosing_url)
|
|
||||||
|
|
||||||
|
|
||||||
# Identified Ingredients (recursive)
|
|
||||||
identified = data.get("identifiedIngredient", [])
|
|
||||||
if identified:
|
|
||||||
st.divider()
|
|
||||||
|
|
||||||
# Check if it's a list of IDs or full objects
|
|
||||||
if identified and isinstance(identified[0], dict):
|
|
||||||
st.subheader(f"🔬 Related Substances ({len(identified)})")
|
|
||||||
for idx, ing in enumerate(identified):
|
|
||||||
ing_name = ing.get("commonName") or ing.get("inciName") or f"Substance {idx + 1}"
|
|
||||||
with st.expander(ing_name):
|
|
||||||
display_ingredient(ing, level=level + 1)
|
|
||||||
else:
|
|
||||||
# List of IDs only
|
|
||||||
st.subheader(f"🔬 Related Substances ({len(identified)} IDs)")
|
|
||||||
with st.expander("Show substance IDs"):
|
|
||||||
# Display in a grid
|
|
||||||
id_text = ", ".join(str(i) for i in identified[:20])
|
|
||||||
if len(identified) > 20:
|
|
||||||
id_text += f"... and {len(identified) - 20} more"
|
|
||||||
st.code(id_text)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if st.session_state.get('selected_cas', None) is None:
|
|
||||||
st.warning("Nessun CAS Number selezionato. Torna alla pagina principale per effettuare una ricerca.")
|
|
||||||
st.stop()
|
|
||||||
else:
|
|
||||||
cas_number = st.session_state.selected_cas
|
|
||||||
|
|
||||||
DATA = cosing_request(cas_number)
|
|
||||||
display_ingredient(DATA)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
121
old/dap.py
121
old/dap.py
|
|
@ -1,121 +0,0 @@
|
||||||
import streamlit as st
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
st.title("PubChem Data Viewer")
|
|
||||||
|
|
||||||
if st.session_state.get('selected_cas', None) is None:
|
|
||||||
st.warning("Nessun CAS Number selezionato. Torna alla pagina principale per effettuare una ricerca.")
|
|
||||||
st.stop()
|
|
||||||
else:
|
|
||||||
cas_number = st.session_state.selected_cas
|
|
||||||
|
|
||||||
# Make API request
|
|
||||||
with st.spinner("Fetching data from PubChem..."):
|
|
||||||
try:
|
|
||||||
response = requests.post(
|
|
||||||
"https://api.cosmoguard.it/api/v1/common/pubchem",
|
|
||||||
json={"cas": cas_number}
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
result = response.json()
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
st.error(f"Error fetching data: {e}")
|
|
||||||
st.stop()
|
|
||||||
|
|
||||||
# Check if request was successful
|
|
||||||
if not result.get("success", False):
|
|
||||||
st.error(f"API Error: {result.get('error', 'Unknown error')}")
|
|
||||||
st.stop()
|
|
||||||
|
|
||||||
data = result.get("data", {})
|
|
||||||
|
|
||||||
# Display substance header
|
|
||||||
st.subheader(f"{data.get('first_pubchem_name', 'Unknown').title()}")
|
|
||||||
|
|
||||||
# Basic info container
|
|
||||||
with st.container(border=True):
|
|
||||||
col1, col2, col3 = st.columns(3)
|
|
||||||
with col1:
|
|
||||||
st.caption("CAS Number")
|
|
||||||
st.write(data.get("CAS", "—"))
|
|
||||||
with col2:
|
|
||||||
st.caption("PubChem CID")
|
|
||||||
st.write(data.get("CID", "—"))
|
|
||||||
with col3:
|
|
||||||
if data.get("pubchem_link"):
|
|
||||||
st.link_button("View on PubChem", data.get("pubchem_link"))
|
|
||||||
|
|
||||||
st.divider()
|
|
||||||
|
|
||||||
# Physical/Chemical Properties
|
|
||||||
st.subheader("Physical & Chemical Properties")
|
|
||||||
|
|
||||||
with st.container(border=True):
|
|
||||||
prop_col1, prop_col2, prop_col3, prop_col4 = st.columns(4)
|
|
||||||
|
|
||||||
with prop_col1:
|
|
||||||
st.metric("Molecular Weight", f"{data.get('MolecularWeight', '—')} g/mol" if data.get('MolecularWeight') else "—")
|
|
||||||
|
|
||||||
with prop_col2:
|
|
||||||
st.metric("XLogP", data.get('XLogP', '—'))
|
|
||||||
|
|
||||||
with prop_col3:
|
|
||||||
st.metric("Exact Mass", data.get('ExactMass', '—'))
|
|
||||||
|
|
||||||
with prop_col4:
|
|
||||||
st.metric("TPSA", f"{data.get('TPSA', '—')} Ų" if data.get('TPSA') else "—")
|
|
||||||
|
|
||||||
st.divider()
|
|
||||||
|
|
||||||
# Melting Point
|
|
||||||
melting_points = data.get("Melting Point", [])
|
|
||||||
if melting_points:
|
|
||||||
st.subheader("Melting Point")
|
|
||||||
|
|
||||||
with st.expander(f"View {len(melting_points)} reference(s)", expanded=True):
|
|
||||||
for idx, mp in enumerate(melting_points):
|
|
||||||
with st.container(border=True):
|
|
||||||
st.markdown(f"**Reference {idx + 1}**")
|
|
||||||
|
|
||||||
if mp.get("Value"):
|
|
||||||
st.info(mp.get("Value"))
|
|
||||||
|
|
||||||
if mp.get("Reference"):
|
|
||||||
st.caption(f"📚 {mp.get('Reference')}")
|
|
||||||
|
|
||||||
if mp.get("Description"):
|
|
||||||
st.caption(f"ℹ️ {mp.get('Description')}")
|
|
||||||
|
|
||||||
if mp.get("ReferenceNumber"):
|
|
||||||
st.caption(f"Ref #: {mp.get('ReferenceNumber')}")
|
|
||||||
|
|
||||||
# Dissociation Constants
|
|
||||||
dissociation_constants = data.get("Dissociation Constants", [])
|
|
||||||
if dissociation_constants:
|
|
||||||
st.divider()
|
|
||||||
st.subheader("Dissociation Constants (pKa)")
|
|
||||||
|
|
||||||
with st.expander(f"View {len(dissociation_constants)} reference(s)", expanded=True):
|
|
||||||
for idx, dc in enumerate(dissociation_constants):
|
|
||||||
with st.container(border=True):
|
|
||||||
st.markdown(f"**Reference {idx + 1}**")
|
|
||||||
|
|
||||||
if dc.get("Value"):
|
|
||||||
# Check if it's a dictionary or string
|
|
||||||
value = dc.get("Value")
|
|
||||||
if isinstance(value, dict):
|
|
||||||
st.code(json.dumps(value, indent=2))
|
|
||||||
else:
|
|
||||||
st.info(value)
|
|
||||||
|
|
||||||
if dc.get("Reference"):
|
|
||||||
st.caption(f"📚 {dc.get('Reference')}")
|
|
||||||
|
|
||||||
if dc.get("ReferenceNumber"):
|
|
||||||
st.caption(f"Ref #: {dc.get('ReferenceNumber')}")
|
|
||||||
|
|
||||||
# Raw JSON viewer
|
|
||||||
st.divider()
|
|
||||||
with st.expander("View Raw JSON Response"):
|
|
||||||
st.json(result)
|
|
||||||
169
pages/settings_page.py
Normal file
169
pages/settings_page.py
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
import streamlit as st
|
||||||
|
import requests
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
API_BASE = "https://api.cosmoguard.it/api/v1"
|
||||||
|
|
||||||
|
st.title("Impostazioni")
|
||||||
|
|
||||||
|
tab_tox, tab_ingredienti, tab_clienti = st.tabs([
|
||||||
|
"Indicatore Tox Custom",
|
||||||
|
"Inventario Ingredienti",
|
||||||
|
"Gestione Clienti",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TAB 1 — Indicatore Tox Custom
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
with tab_tox:
|
||||||
|
st.subheader("Aggiungi indicatore tossicologico custom")
|
||||||
|
|
||||||
|
with st.form("form_tox_indicator"):
|
||||||
|
cas = st.text_input("CAS Number", placeholder="es. 56-81-5")
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
with col1:
|
||||||
|
indicator = st.selectbox("Indicatore", ["NOAEL", "LOAEL", "LD50"])
|
||||||
|
value = st.number_input("Valore", min_value=0.0, step=0.1)
|
||||||
|
unit = st.text_input("Unità", placeholder="es. mg/kg bw/day")
|
||||||
|
with col2:
|
||||||
|
route = st.text_input("Via di esposizione", placeholder="es. oral, dermal")
|
||||||
|
toxicity_type = st.selectbox(
|
||||||
|
"Tipo tossicità",
|
||||||
|
["", "repeated_dose_toxicity", "acute_toxicity"],
|
||||||
|
format_func=lambda x: x if x else "— non specificato —"
|
||||||
|
)
|
||||||
|
ref = st.text_input("Riferimento / Fonte", placeholder="es. Studio interno 2024")
|
||||||
|
|
||||||
|
submitted = st.form_submit_button("Aggiungi indicatore", type="primary")
|
||||||
|
|
||||||
|
if submitted:
|
||||||
|
if not cas or not unit or not route:
|
||||||
|
st.error("CAS, unità e via di esposizione sono obbligatori.")
|
||||||
|
else:
|
||||||
|
payload = {
|
||||||
|
"cas": cas,
|
||||||
|
"indicator": indicator,
|
||||||
|
"value": value,
|
||||||
|
"unit": unit,
|
||||||
|
"route": route,
|
||||||
|
"toxicity_type": toxicity_type or None,
|
||||||
|
"ref": ref or None,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
resp = requests.post(f"{API_BASE}/ingredients/add-tox-indicator", json=payload, timeout=30)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
tox = data.get("data", {}).get("toxicity", {})
|
||||||
|
best = tox.get("best_case")
|
||||||
|
st.success(f"Indicatore aggiunto per CAS {cas}.")
|
||||||
|
if best:
|
||||||
|
st.info(f"Best case aggiornato: **{best['indicator']}** = {best['value']} {best['unit']} ({best['route']})")
|
||||||
|
elif resp.status_code == 404:
|
||||||
|
st.error(f"CAS {cas} non trovato in cache. Esegui prima una ricerca nella pagina Ingredienti.")
|
||||||
|
else:
|
||||||
|
st.error(f"Errore {resp.status_code}: {resp.json().get('detail', 'errore sconosciuto')}")
|
||||||
|
except requests.ConnectionError:
|
||||||
|
st.error("Impossibile connettersi all'API.")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Errore: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TAB 2 — Inventario Ingredienti
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
with tab_ingredienti:
|
||||||
|
st.subheader("Ingredienti nel database")
|
||||||
|
|
||||||
|
if st.button("Aggiorna", key="refresh_ingredienti"):
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{API_BASE}/ingredients/list", timeout=10)
|
||||||
|
result = resp.json()
|
||||||
|
|
||||||
|
if result.get("success") and result.get("data"):
|
||||||
|
st.caption(f"{result['total']} ingredienti totali")
|
||||||
|
df = pd.DataFrame(result["data"])
|
||||||
|
df["dap"] = df["dap"].map({True: "OK", False: "-"})
|
||||||
|
df["cosing"] = df["cosing"].map({True: "OK", False: "-"})
|
||||||
|
df["tox"] = df["tox"].map({True: "OK", False: "-"})
|
||||||
|
df = df.rename(columns={
|
||||||
|
"cas": "CAS",
|
||||||
|
"dap": "DAP",
|
||||||
|
"cosing": "COSING",
|
||||||
|
"tox": "TOX",
|
||||||
|
"created_at": "Data Acquisizione",
|
||||||
|
})
|
||||||
|
st.dataframe(
|
||||||
|
df[["CAS", "DAP", "COSING", "TOX", "Data Acquisizione"]],
|
||||||
|
use_container_width=True,
|
||||||
|
hide_index=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
st.info("Nessun ingrediente trovato nel database.")
|
||||||
|
|
||||||
|
except requests.ConnectionError:
|
||||||
|
st.error("Impossibile connettersi all'API.")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Errore nel caricamento: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TAB 3 — Gestione Clienti
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
with tab_clienti:
|
||||||
|
st.subheader("Clienti registrati")
|
||||||
|
|
||||||
|
if st.button("Aggiorna", key="refresh_clienti"):
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{API_BASE}/ingredients/clients", timeout=10)
|
||||||
|
result = resp.json()
|
||||||
|
clienti = result.get("data", []) if result.get("success") else []
|
||||||
|
except requests.ConnectionError:
|
||||||
|
st.error("Impossibile connettersi all'API.")
|
||||||
|
clienti = []
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Errore nel caricamento: {e}")
|
||||||
|
clienti = []
|
||||||
|
|
||||||
|
if not clienti:
|
||||||
|
st.info("Nessun cliente trovato.")
|
||||||
|
else:
|
||||||
|
for cliente in clienti:
|
||||||
|
col_nome, col_id, col_btn = st.columns([4, 1, 1])
|
||||||
|
with col_nome:
|
||||||
|
st.write(cliente["nome_cliente"])
|
||||||
|
with col_id:
|
||||||
|
st.caption(f"id: {cliente['id_cliente']}")
|
||||||
|
with col_btn:
|
||||||
|
key = f"del_{cliente['id_cliente']}"
|
||||||
|
if st.button("Elimina", key=key, type="secondary"):
|
||||||
|
st.session_state[f"confirm_{cliente['id_cliente']}"] = True
|
||||||
|
|
||||||
|
if st.session_state.get(f"confirm_{cliente['id_cliente']}"):
|
||||||
|
nome = cliente["nome_cliente"]
|
||||||
|
st.warning(f"Confermi l'eliminazione di **{nome}**?")
|
||||||
|
col_si, col_no, _ = st.columns([1, 1, 6])
|
||||||
|
with col_si:
|
||||||
|
if st.button("Sì, elimina", key=f"yes_{cliente['id_cliente']}", type="primary"):
|
||||||
|
try:
|
||||||
|
r = requests.delete(f"{API_BASE}/ingredients/clients/{nome}", timeout=10)
|
||||||
|
if r.status_code == 200:
|
||||||
|
st.success(f"Cliente '{nome}' eliminato.")
|
||||||
|
st.session_state.pop(f"confirm_{cliente['id_cliente']}", None)
|
||||||
|
st.rerun()
|
||||||
|
elif r.status_code == 409:
|
||||||
|
st.error(r.json().get("detail", "Il cliente ha ordini collegati."))
|
||||||
|
st.session_state.pop(f"confirm_{cliente['id_cliente']}", None)
|
||||||
|
else:
|
||||||
|
st.error(f"Errore {r.status_code}: {r.json().get('detail', '')}")
|
||||||
|
st.session_state.pop(f"confirm_{cliente['id_cliente']}", None)
|
||||||
|
except requests.ConnectionError:
|
||||||
|
st.error("Impossibile connettersi all'API.")
|
||||||
|
with col_no:
|
||||||
|
if st.button("Annulla", key=f"no_{cliente['id_cliente']}"):
|
||||||
|
st.session_state.pop(f"confirm_{cliente['id_cliente']}", None)
|
||||||
|
st.rerun()
|
||||||
Loading…
Reference in a new issue