first release
This commit is contained in:
parent
a95e711dba
commit
28d7028095
10 changed files with 14544 additions and 2560 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -205,3 +205,6 @@ cython_debug/
|
||||||
marimo/_static/
|
marimo/_static/
|
||||||
marimo/_lsp/
|
marimo/_lsp/
|
||||||
__marimo__/
|
__marimo__/
|
||||||
|
|
||||||
|
# Streamlit secrets
|
||||||
|
.streamlit/secrets.toml
|
||||||
|
|
|
||||||
129
app.py
129
app.py
|
|
@ -1,50 +1,93 @@
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
from datetime import datetime
|
|
||||||
import os
|
|
||||||
|
|
||||||
st.set_page_config(page_title="LMB Suite Alpha v1", layout="centered")
|
from functions_ui import search_cas_number
|
||||||
|
|
||||||
st.title("LMB Suite — Alpha v1")
|
# Configure page
|
||||||
st.markdown("""
|
st.set_page_config(
|
||||||
LMB Suite is an experimental frontend for cosmetic ingredient data tooling.
|
page_title="LMB App",
|
||||||
Use this simple home page to see the changelog, current feature status,
|
page_icon="🔬",
|
||||||
and to report bugs or request features.
|
layout="wide"
|
||||||
""")
|
)
|
||||||
|
|
||||||
st.header("Changelog")
|
# Password protection
|
||||||
with st.expander("Latest changes"):
|
def check_password():
|
||||||
st.write("""
|
"""Returns `True` if the user had the correct password."""
|
||||||
- 2025-11-23: Alpha front page created with changelog and features list.
|
|
||||||
- 2025-11-20: Initial scaffolding and basic feature flags added.
|
|
||||||
""")
|
|
||||||
|
|
||||||
st.header("Features")
|
def password_entered():
|
||||||
features = [
|
"""Checks whether a password entered by the user is correct."""
|
||||||
("ECHA (European Chemicals Agency)", "In Progress"),
|
if st.session_state["password"] == st.secrets["passwords"]["app_password"]:
|
||||||
("CoSIng (Cosmetic Ingredients)", "In Progress"),
|
st.session_state["password_correct"] = True
|
||||||
("PubChem", "In Progress"),
|
del st.session_state["password"] # Don't store password
|
||||||
("Ingredient Parsing", "Planned"),
|
|
||||||
("SED automatic AI calculation", "Planned"),
|
|
||||||
("Complete PIF generation", "Planned")
|
|
||||||
]
|
|
||||||
|
|
||||||
cols = st.columns([3,1])
|
|
||||||
with cols[0]:
|
|
||||||
st.subheader("Feature")
|
|
||||||
with cols[1]:
|
|
||||||
st.subheader("Status")
|
|
||||||
|
|
||||||
for name, status in features:
|
|
||||||
cols = st.columns([3,1])
|
|
||||||
with cols[0]:
|
|
||||||
st.write(name)
|
|
||||||
with cols[1]:
|
|
||||||
if status.lower() == "complete":
|
|
||||||
st.success(status)
|
|
||||||
elif status.lower() == "in progress":
|
|
||||||
st.warning(status)
|
|
||||||
else:
|
else:
|
||||||
st.info(status)
|
st.session_state["password_correct"] = False
|
||||||
|
|
||||||
st.markdown("---")
|
# First run, show input for password
|
||||||
st.caption("LMB Suite Alpha v1 — Experimental.")
|
if "password_correct" not in st.session_state:
|
||||||
|
st.text_input(
|
||||||
|
"Password", type="password", on_change=password_entered, key="password"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
# Password not correct, show input + error
|
||||||
|
elif not st.session_state["password_correct"]:
|
||||||
|
st.text_input(
|
||||||
|
"Password", type="password", on_change=password_entered, key="password"
|
||||||
|
)
|
||||||
|
st.error("😕 Password incorrect")
|
||||||
|
return False
|
||||||
|
# Password correct
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not check_password():
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
# Define home page function
|
||||||
|
def home():
|
||||||
|
st.title("LMB App: PIF & Toxicological Data Viewer")
|
||||||
|
|
||||||
|
# Inizializza session_state per il CAS number se non esiste
|
||||||
|
if 'selected_cas' not in st.session_state:
|
||||||
|
st.session_state.selected_cas = None
|
||||||
|
|
||||||
|
input = st.text_input("Enter CAS Number:", "")
|
||||||
|
if input:
|
||||||
|
st.caption(f"Ricerca per {input}: trovati i seguenti ingredienti.")
|
||||||
|
results = search_cas_number(input)
|
||||||
|
|
||||||
|
if results:
|
||||||
|
# Crea le stringhe per la selectbox: "CAS - INCI"
|
||||||
|
display_options = [f"{cas} - {inci}" for cas, inci in results]
|
||||||
|
|
||||||
|
# Selectbox con i risultati formattati
|
||||||
|
selected_display = st.selectbox("Results", options=[""] + display_options, key="cas_selectbox")
|
||||||
|
|
||||||
|
# Salva solo il CAS selezionato nel session_state (estrae la parte prima del " - ")
|
||||||
|
if selected_display and selected_display != "":
|
||||||
|
selected_cas = selected_display.split(" - ")[0]
|
||||||
|
st.session_state.selected_cas = selected_cas
|
||||||
|
st.success(f"CAS Number selezionato: {selected_cas}")
|
||||||
|
else:
|
||||||
|
# Nessun risultato trovato: permetti di usare l'input manuale
|
||||||
|
st.warning("Nessun risultato trovato nel database.")
|
||||||
|
if st.button("Usa questo CAS Number"):
|
||||||
|
st.session_state.selected_cas = input
|
||||||
|
st.success(f"CAS Number salvato: {input}")
|
||||||
|
|
||||||
|
# Mostra il CAS attualmente selezionato
|
||||||
|
if st.session_state.selected_cas:
|
||||||
|
st.info(f"CAS Number corrente: {st.session_state.selected_cas}")
|
||||||
|
|
||||||
|
# Navigation
|
||||||
|
home_page = st.Page(home, title="Home", icon="🏠", default=True)
|
||||||
|
echa_page = st.Page("pages/echa.py", title="ECHA Database", icon="🧪")
|
||||||
|
cosing_page = st.Page("pages/cosing.py", title="CosIng", icon="💄")
|
||||||
|
#pubchem_page = st.Page("pages/pubchem.py", title="PubChem", icon="🧬")
|
||||||
|
#cir_page = st.Page("pages/cir.py", title="CIR", icon="📊")
|
||||||
|
|
||||||
|
|
||||||
|
pg = st.navigation({
|
||||||
|
"Ricerca": [home_page],
|
||||||
|
"Database": [echa_page, cosing_page]
|
||||||
|
})
|
||||||
|
|
||||||
|
pg.run()
|
||||||
1016
echa_example.json
1016
echa_example.json
File diff suppressed because one or more lines are too long
|
|
@ -1,947 +0,0 @@
|
||||||
{
|
|
||||||
"_id": {
|
|
||||||
"$oid": "6923609db2339f52631f60fd"
|
|
||||||
},
|
|
||||||
"substance": {
|
|
||||||
"rmlCas": "56-81-5",
|
|
||||||
"rmlId": "100.000.263",
|
|
||||||
"rmlEc": "200-289-5",
|
|
||||||
"rmlName": "Glycerol"
|
|
||||||
},
|
|
||||||
"dossier_info": {
|
|
||||||
"lastUpdatedDate": "2022-09-14",
|
|
||||||
"registrationStatus": "Active",
|
|
||||||
"registrationStatusChangedDate": "2010-08-23",
|
|
||||||
"registrationRole": "Lead (joint submission)",
|
|
||||||
"assetExternalId": "3b5de1db-9033-4e96-9022-c68ca931d367",
|
|
||||||
"rootKey": "IUC5-6e7c951c-5670-46f1-adba-006d4fa0d3f2_fa0793fe-428d-4286-b96d-4b97c5dc72fe"
|
|
||||||
},
|
|
||||||
"index": {
|
|
||||||
"toxicological_information_link": "https://chem.echa.europa.eu/html-pages-prod/3b5de1db-9033-4e96-9022-c68ca931d367/documents/aec04834-0acf-4132-b6c8-9273ad6c6bbd_fa0793fe-428d-4286-b96d-4b97c5dc72fe.html",
|
|
||||||
"repeated_dose_toxicity_link": "https://chem.echa.europa.eu/html-pages-prod/3b5de1db-9033-4e96-9022-c68ca931d367/documents/IUC5-6a907739-cd5e-4fcf-9949-e31a6ed6fde5_fa0793fe-428d-4286-b96d-4b97c5dc72fe.html",
|
|
||||||
"acute_toxicity_link": "https://chem.echa.europa.eu/html-pages-prod/3b5de1db-9033-4e96-9022-c68ca931d367/documents/IUC5-600a9e6c-7f74-4855-97e8-92c31ebbeec2_fa0793fe-428d-4286-b96d-4b97c5dc72fe.html"
|
|
||||||
},
|
|
||||||
"toxicological_information": {
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"label": "Administrative data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Workers - Hazard via inhalation route",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"JustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"Extrapolated": false,
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"DoseDescriptorJustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Local effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"Extrapolated": false,
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Workers - Hazard via dermal route",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"JustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"Extrapolated": false,
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"DoseDescriptorJustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Local effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Workers - Hazard for the eyes",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Local effects",
|
|
||||||
"Conclusion": "no hazard identified"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Additional information - workers",
|
|
||||||
"DiscussionWorkers": "All NOAEL values are above the limit dose given in the OECD Test Guidelines thus derivation of DNELs is not justified.The only effect below a limit dose was the metaplasia of the squamous epithelium of the larynx seen in rats at 662 mg/m3 which was only minimal to slight, and thus is not interpreted as adverse."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "General Population - Hazard via inhalation route",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"JustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"Extrapolated": false,
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"DoseDescriptorJustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Local effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"Extrapolated": false,
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "General Population - Hazard via dermal route",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"JustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"Extrapolated": false,
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"DoseDescriptorJustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Local effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"DoseDescriptorStart": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "General Population - Hazard via oral route",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Long term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"AssessmentFactor": "",
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"JustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"DiffInDurationAF": "",
|
|
||||||
"DiffInDurationJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustif": "",
|
|
||||||
"DatabaseQualityAF": "",
|
|
||||||
"DatabaseJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"OthUncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute/short term exposure",
|
|
||||||
"HazardAssessment": "no hazard identified",
|
|
||||||
"StDose": "",
|
|
||||||
"MostSensitiveEndpoint": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "DNEL related information",
|
|
||||||
"DerivationMethod": "",
|
|
||||||
"OverallAssessmentFactor": "",
|
|
||||||
"Extrapolated": false,
|
|
||||||
"DoseDescriptorStartingPoint": "",
|
|
||||||
"StDose": "",
|
|
||||||
"DoseDescriptorStartRTR": "",
|
|
||||||
"StDoseRTR": "",
|
|
||||||
"DoseDescriptorJustificationRTR": "",
|
|
||||||
"DoseResponseAF": "",
|
|
||||||
"DoseResponseJustif": "",
|
|
||||||
"InterspeciesAF": "",
|
|
||||||
"InterspeciesJustif": "",
|
|
||||||
"OthInterspeciesAF": "",
|
|
||||||
"OthInterspeciesJustif": "",
|
|
||||||
"IntraspeciesAF": "",
|
|
||||||
"IntraspeciesJustifAF": "",
|
|
||||||
"DatabaseQualityDatabaseQualityAF": "",
|
|
||||||
"DatabaseQualityJustif": "",
|
|
||||||
"OthUncertaintiesAF": "",
|
|
||||||
"UncertaintiesJustif": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Explanation for hazard conclusion"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "General Population - Hazard for the eyes",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Local effects",
|
|
||||||
"Conclusion": "no hazard identified"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Additional information - General Population",
|
|
||||||
"DiscussionGenPop": "All NOAEL values are above the limit dose given in the OECD Test Guidelines thus derivation of DNELs is not justified.The only effect below a limit dose was the metaplasia of the squamous epithelium of the larynx seen in rats at 662 mg/m3 which was only minimal to slight, and thus is not interpreted as adverse."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"acute_toxicity": {
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"label": "Administrative data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Description of key information",
|
|
||||||
"KeyInformation": "The acute oral LD50 was determined in three species, rat, mice and guinea pigs. In all three species the oral LD50 was >/= 11,500 mg/kg.The acute dermal toxicity of glycerin was examined in guinea pigs.The dermal LD50 was determined to be 45 ml/kg (56,750 mg/kg) in guinea pigs.In an inhalation study, rats were exposed to aerosol from test material at targeted concentrations of 1.0,2.0,or4.0mg glycerol/L for 6 hours. The 4 h inhalation LC50 was determined to be above 5.85 mg/L in rats."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Key value for assessment",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Acute toxicity: via oral route",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EndpointConclusion": "no adverse effect observed",
|
|
||||||
"EffectLevelUnit": "LD50",
|
|
||||||
"EffectLevelValue": "11,500mg/kg bw"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute toxicity: via dermal route",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EndpointConclusion": "no adverse effect observed",
|
|
||||||
"EffectLevelUnit": "LD50",
|
|
||||||
"EffectLevelValue": "56,750mg/kg bw"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute toxicity: via inhalation route",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EndpointConclusion": "no adverse effect observed",
|
|
||||||
"EffectLevelUnit": "LC50",
|
|
||||||
"EffectLevelValue": ">5.85mg/L",
|
|
||||||
"PhysicalForm": "inhalation: aerosol"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Acute toxicity: other routes",
|
|
||||||
"LinkToRelevantStudyRecord": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Justification for classification or non-classification",
|
|
||||||
"JustifClassif": "There is no justification for classification based on data from available studies.",
|
|
||||||
"JustifClassif_STOTSE": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Additional information",
|
|
||||||
"Discussion": "Glycerol is essentially non-toxic following acute administration.In the available evaluations performed by official bodies (MAK 2007, OECD 2002, EFSA 2017) a number of additional acute toxicity studies were evaluated and summarised.All considered glycerol to be of low acute toxicity to mammals. The range of acute oral LD50 values derived from studies in experimental animals is between >4,000 and < 38,000 mg/kg, with the majority of values being between 23,000 and 38,000 mg/kg. For acute dermal toxicity a single LD50 of >18,700 mg/kg for rabbits was summarised by the OECD. The low level of acute toxicity in rodents is further supported by the results of the Danish (Q)SAR Database (LD50 rat oral 7500 mg/kg, LD50 mouse oral 13,000 mg/kg). There was no new relevant information identified up to and including 2021 (most recent literature research).(Q)SAR predicted profile for glycerol CAS -Nr 56-81-5, Danish (Q)SAR Database, http://qsar.food.dtu.dk Date: 09-02-2021Glycerin [MAK Value Documentation in German language, 2007]Re-evaluation of glycerol (E 422) as a food additive, EFSA Journal 2017;15(3):4720Glycerol, CAS Number 56-81-5, OECD SIDS Initial Assessment Report For SIAM 14 Paris, France, 26-28 March 2002",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Attached background material"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"repeated_dose_toxicity": {
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"label": "Administrative data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Description of key information",
|
|
||||||
"KeyInformation": "In the best available dietary study, groups of 22 rats (Long-Evans)/sex/treatment received 5, 10 and 20% glycerol (natural or synthetic) in their diet (males 2000, 4000 and 8000 mg/kg bw; females 2500, 5000 and 10000 mg/kg bw) for 2 years. Although the results were not described in detail, based on this limited dietary study it can be concluded that no adverse effects were observed at up to 10,000 mg/kg bw.The effect of glycerine following administration for 90 days in a subchronic toxicity study was examined. Rats fed 5 or 20% glycerine in the diet for 90 days gained weight at a faster rate than control animals. There were no adverse treatment related effects noted in male or female rats fed 5% glycerine in the diet. In the male rats which received 20 percent glycerine, there was an increase in the final liver/body weight ratio and upon microscopic examination generalized cloudy swelling and hypertrophy of the parenchymal cells was observed. The only effect in the female rats on this level was some generalized cloudy selling upon microscopic examination of the liver. A 5% glycerol in the diet corresponded to 4580 and 6450 mg/kg/day for male and female rats, respectively, after 4 weeks and a 20% glycerol in the diet corresponded to 18,750 and 25,800 mg/kg/day for male and female rats, respectively, after 4 weeks.A number of other studies have been incorporated in the dossier. These studies are considered less reliable indicators of the systemic effects of glycerol following repeated administration, mainly because of limited toxicity assessments and/or deficient experimental design. The effects they do report are consistent with those observed in the key studies and as such they may contribute to the overall assessment of toxicity of glycerol.The effects following repeated dermal application of glycerin was examined. There were no effects noted in rabbits dosed 8 hours/day, 5 days/week for 45 weeks with dose levels as high as 4.0 ml/kg. Using a density of 1.2611 g/cm3 at 20 °C, a dose of 4.0 ml/kg corresponds to 5040 mg/kg/day.The subchronic toxicity of glycerol was examined following aerosol exposure. The NOAEC for local and systemic toxicity was 622 mg/m3.Migrated Data from field(s) Field \"Quality of whole database\" (Path: ENDPOINT_SUMMARY.RepeatedDoseToxicity.KeyValueForChemicalSafetyAssessment.RepeatedDoseToxicityInhalationLocalEffects.EndpointConclusion.DataBaseQuality): The available information meets the information requirements under REACH Field \"Quality of whole database\" (Path: ENDPOINT_SUMMARY.RepeatedDoseToxicity.KeyValueForChemicalSafetyAssessment.RepeatedDoseToxicityInhalationSystemicEffects.EndpointConclusion.DataBaseQuality): The available information meets the information requirements under REACH"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Key value for assessment",
|
|
||||||
"ToxicEffectType": "",
|
|
||||||
"EndpointConclusionSystemicEffectsOralRoute": "no adverse effect observed",
|
|
||||||
"EndpointConclusionSystemicEffectsDermal": "no adverse effect observed",
|
|
||||||
"EndpointConclusionSystemicEffectsInhalation": "",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Short-term repeated dose toxicity – systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Oral route",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EffectLevelUnit": "",
|
|
||||||
"EffectLevelValue": "",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Dermal",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EffectLevelUnit": "",
|
|
||||||
"EffectLevelValue": "",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Inhalation",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EffectLevelUnit": "",
|
|
||||||
"EffectLevelValue": "",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Sub-chronic toxicity – systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Oral route",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EffectLevelUnit": "",
|
|
||||||
"EffectLevelValue": "",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Dermal",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EffectLevelUnit": "NOAEL",
|
|
||||||
"EffectLevelValue": "5,040mg/kg bw/day",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "rabbit",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Inhalation",
|
|
||||||
"LinkToRelevantStudyRecord": "001 | Key | Experimental study",
|
|
||||||
"EffectLevelUnit": "NOAEC",
|
|
||||||
"EffectLevelValue": "622mg/m³",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "rat",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Chronic toxicity – systemic effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Oral route",
|
|
||||||
"LinkToRelevantStudyRecord": "002 | Key | Experimental study001 | Key | Experimental study",
|
|
||||||
"EffectLevelUnit": "NOAEL",
|
|
||||||
"EffectLevelValue": "10,000mg/kg bw/day",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "rat",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Dermal",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EffectLevelUnit": "",
|
|
||||||
"EffectLevelValue": "",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Inhalation",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EffectLevelUnit": "",
|
|
||||||
"EffectLevelValue": "",
|
|
||||||
"ExperimentalExposureTimePerWeek": "",
|
|
||||||
"Species": "",
|
|
||||||
"System": "",
|
|
||||||
"Organ": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Repeated dose toxicity – local effects",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Dermal",
|
|
||||||
"LinkToRelevantStudyRecord": "",
|
|
||||||
"EndpointConclusion": "",
|
|
||||||
"EffectLevelUnit": "",
|
|
||||||
"EffectLevelValue": "",
|
|
||||||
"TestType": "",
|
|
||||||
"Species": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Inhalation",
|
|
||||||
"LinkToRelevantStudyRecord": "003 | Other | Other result type001 | Key | Experimental study",
|
|
||||||
"EndpointConclusion": "no adverse effect observed",
|
|
||||||
"EffectLevelUnit": "NOAEC",
|
|
||||||
"EffectLevelValue": "662mg/m³",
|
|
||||||
"TestType": "subchronic",
|
|
||||||
"Species": "rat"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Repeated dose toxicity: other routes",
|
|
||||||
"LinkToRelevantStudyRecord": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Mode of Action Analysis / Human Relevance Framework",
|
|
||||||
"ModeOfActionAnalysis": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Justification for classification or non-classification (Specific target organ toxicity-repeated exposure (STOT RE))",
|
|
||||||
"JustifClassif": "There is no justification for classification based on data from available studies."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Additional information",
|
|
||||||
"Discussion": "Study via the dietary, dermal and respiratory route demonstrate the low level of concern about glycerol.In the available evaluations performed by official bodies (MAK 2007, OECD 2002, EFSA 2017) a number of additional repeated dose toxicity studies were evaluated and summarised.Repeated dose toxicity oralIn the OECD SIDS document the study reported by Hine et al. was identified as the most relevant key study. This study is included in the REACH dossier in detail as RSS.A number of other studies have been incorporated in a table in the OECD SIDS document. These studies are considered less reliable indicators of the systemic effects of glycerol following repeated administration, mainly because of limited toxicity assessments and/or deficient experimental design. The effects they do report are consistent with those observed in the key studies and as such they may contribute to the overall assessment of toxicity of glycerol.EFSA conclusion: Short-term or subchronic studies were not performed according to current test guidelines. In a subchronic toxicity study (in drinking water) in rats, the effects reported were observed with doses in the range of the LD50 for glycerol. The Panel considered that the local irritating effects of glycerol in the gastrointestinal tract reported in some gavage studies in rat (100% glycerol at 2,800 mg/kg bw per day, the lowest dose tested (Staples et al., 1967)), and dogs (100% glycerol at 5,600 mg/kg bw per day (Staples et al., 1967)) was likely due to the hygroscopic and osmotic effects of glycerol.From the available chronic toxicity and carcinogenicity studies, glycerol was not carcinogenic in mice and rats and did not show evidence of adverse effects in a 2-year chronic toxicity study. The Panel noted that no adverse effects were reported in rats receiving doses up to 10,000 mg/kg bw per day for 1 year, the highest dose tested. The Panel also noted that there was no increase in the tumours incidences in rats receiving doses up to 5,000 mg/kg bw per day for 2 years, the highest dose tested.The Panel considered that none of the animal studies available identified an adverse effect for glycerol.Repeated dose toxicity inhalationThe key studies identified and evaluated by the OECD are compiled as RSS in the dossier (Renne 1992). Based on an increased incidence of “minimal” to “mild” squamous metaplasia of the epiglottis, the NOAEC for local irritant effects to the upper respiratory tract was derived at 165 mg/m3 and 662 mg/m3 for systemic effects.The German Commission for the Investigation of Health Hazards of Chemical Compounds in the Work Area has re-evaluated glycerol [56-81-5]in 2016, considering the endpoints irritation of the respiratory tract.Based on the results of an expert workshop, the NOAEC value for local toxicity was re-evaluated. Because the metaplasia of the squamous epithelium of the larynx seen in rats at 662 mg/m3 with glycerol aerosol was only minimal to slight, is not interpreted as adverse (Kaufmann et al. 2009). Furthermore it has been taken into account, that the response does not increase with the exposure duration.The NOAEC in rats for local and systemic effects after 13-week exposure is 662 mg/m3.Glycerin [MAK Value Documentation in German language, 2007]The MAK Collection for Occupational Health and Safety 2017, Vol 2, No 2Re-evaluation of glycerol (E 422) as a food additive, EFSA Journal 2017;15(3):4720Glycerol, CAS Number 56-81-5, OECD SIDS Initial Assessment Report For SIAM 14 Paris, France, 26-28 March 2002Kaufmann W, Bader R, Ernst H, Harada T, Hardisty J, Kittel B, Kolling A, Pino M, Renne R, Rittinghausen S, Schulte A, Wöhrmann T, Rosenbruch M (2009) 1st International ESTP Expert Workshop: “Larynx squamous metaplasia”. A re-consideration of morphology and diagnostic approaches in rodent studies and its relevance for human risk assessment. Exp Toxicol Pathol 61: 591–603",
|
|
||||||
"subsections": [
|
|
||||||
{
|
|
||||||
"label": "Attached background material"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
219
functions.py
219
functions.py
|
|
@ -2,192 +2,45 @@ import json
|
||||||
from typing import Any, Dict, List, Union
|
from typing import Any, Dict, List, Union
|
||||||
import requests
|
import requests
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
def echa_request(cas_num: str) -> Dict[str, Any]:
|
||||||
def remove_empty_values(data: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Rimuove ricorsivamente tutte le chiavi con valori vuoti (stringa vuota, None, False, liste/dict vuoti).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: Il dato da pulire (può essere dict, list o valore primitivo)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Il dato pulito senza valori vuoti
|
|
||||||
"""
|
|
||||||
if isinstance(data, dict):
|
|
||||||
# Filtra il dizionario rimuovendo chiavi con valori vuoti
|
|
||||||
cleaned = {}
|
|
||||||
for key, value in data.items():
|
|
||||||
# Ricorsivamente pulisci il valore
|
|
||||||
cleaned_value = remove_empty_values(value)
|
|
||||||
|
|
||||||
# Aggiungi solo se il valore non è vuoto
|
|
||||||
if cleaned_value not in [None, "", [], {}, False]:
|
|
||||||
cleaned[key] = cleaned_value
|
|
||||||
|
|
||||||
return cleaned if cleaned else None
|
|
||||||
|
|
||||||
elif isinstance(data, list):
|
|
||||||
# Pulisci ogni elemento della lista
|
|
||||||
cleaned = []
|
|
||||||
for item in data:
|
|
||||||
cleaned_item = remove_empty_values(item)
|
|
||||||
if cleaned_item not in [None, "", [], {}, False]:
|
|
||||||
cleaned.append(cleaned_item)
|
|
||||||
return cleaned if cleaned else None
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Per valori primitivi, ritorna il valore se non è vuoto
|
|
||||||
if data in [None, "", False]:
|
|
||||||
return None
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def transform_labels_to_keys(data: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Trasforma ricorsivamente le 'label' in chiavi del dizionario.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: Il dato da trasformare
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Il dato trasformato con le label come chiavi
|
|
||||||
"""
|
|
||||||
if isinstance(data, dict):
|
|
||||||
# Se c'è una label, usala come chiave principale
|
|
||||||
if 'label' in data:
|
|
||||||
label = data['label']
|
|
||||||
# Crea una copia del dizionario senza la label
|
|
||||||
new_dict = {k: v for k, v in data.items() if k != 'label'}
|
|
||||||
|
|
||||||
# Se ci sono subsections, trasformale ricorsivamente
|
|
||||||
if 'subsections' in new_dict:
|
|
||||||
subsections = new_dict.pop('subsections')
|
|
||||||
# Trasforma ogni subsection
|
|
||||||
for subsection in subsections:
|
|
||||||
transformed_sub = transform_labels_to_keys(subsection)
|
|
||||||
if isinstance(transformed_sub, dict):
|
|
||||||
new_dict.update(transformed_sub)
|
|
||||||
|
|
||||||
# Trasforma ricorsivamente tutti gli altri valori
|
|
||||||
for key, value in list(new_dict.items()):
|
|
||||||
new_dict[key] = transform_labels_to_keys(value)
|
|
||||||
|
|
||||||
# Ritorna con la label come chiave principale
|
|
||||||
return {label: new_dict if new_dict else {}}
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Se non c'è label, trasforma ricorsivamente tutti i valori
|
|
||||||
result = {}
|
|
||||||
for key, value in data.items():
|
|
||||||
if key == 'subsections' and isinstance(value, list):
|
|
||||||
# Gestisci le subsections senza label nel dizionario padre
|
|
||||||
for subsection in value:
|
|
||||||
transformed = transform_labels_to_keys(subsection)
|
|
||||||
if isinstance(transformed, dict):
|
|
||||||
result.update(transformed)
|
|
||||||
elif key == 'sections' and isinstance(value, list):
|
|
||||||
# Gestisci le sections trasformandole in un dizionario
|
|
||||||
sections_dict = {}
|
|
||||||
for section in value:
|
|
||||||
transformed = transform_labels_to_keys(section)
|
|
||||||
if isinstance(transformed, dict):
|
|
||||||
sections_dict.update(transformed)
|
|
||||||
result[key] = sections_dict
|
|
||||||
else:
|
|
||||||
result[key] = transform_labels_to_keys(value)
|
|
||||||
return result
|
|
||||||
|
|
||||||
elif isinstance(data, list):
|
|
||||||
# Trasforma ogni elemento della lista
|
|
||||||
return [transform_labels_to_keys(item) for item in data]
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Ritorna il valore così com'è
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def clean_and_transform_json(json_data: Union[str, dict], keep_false: bool = False) -> dict:
|
|
||||||
"""
|
|
||||||
Funzione principale che pulisce e trasforma il JSON.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
json_data: Il JSON come stringa o dizionario
|
|
||||||
keep_false: Se True, mantiene i valori False (default: False li rimuove)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Il JSON pulito e trasformato
|
|
||||||
"""
|
|
||||||
# Se è una stringa, parsala
|
|
||||||
if isinstance(json_data, str):
|
|
||||||
data = json.loads(json_data)
|
|
||||||
else:
|
|
||||||
data = json_data
|
|
||||||
|
|
||||||
# Step 1: Rimuovi i valori vuoti
|
|
||||||
if not keep_false:
|
|
||||||
cleaned_data = remove_empty_values(data)
|
|
||||||
else:
|
|
||||||
# Versione alternativa che mantiene False
|
|
||||||
def remove_empty_keep_false(d):
|
|
||||||
if isinstance(d, dict):
|
|
||||||
cleaned = {}
|
|
||||||
for key, value in d.items():
|
|
||||||
cleaned_value = remove_empty_keep_false(value)
|
|
||||||
if cleaned_value not in [None, "", [], {}]:
|
|
||||||
cleaned[key] = cleaned_value
|
|
||||||
return cleaned if cleaned else None
|
|
||||||
elif isinstance(d, list):
|
|
||||||
cleaned = []
|
|
||||||
for item in d:
|
|
||||||
cleaned_item = remove_empty_keep_false(item)
|
|
||||||
if cleaned_item not in [None, "", [], {}]:
|
|
||||||
cleaned.append(cleaned_item)
|
|
||||||
return cleaned if cleaned else None
|
|
||||||
else:
|
|
||||||
if d in [None, ""]:
|
|
||||||
return None
|
|
||||||
return d
|
|
||||||
|
|
||||||
cleaned_data = remove_empty_keep_false(data)
|
|
||||||
|
|
||||||
# Step 2: Trasforma le label in chiavi
|
|
||||||
if cleaned_data:
|
|
||||||
transformed_data = transform_labels_to_keys(cleaned_data)
|
|
||||||
else:
|
|
||||||
transformed_data = {}
|
|
||||||
|
|
||||||
return transformed_data
|
|
||||||
|
|
||||||
|
|
||||||
def api_req(query: str) -> Dict[str, Any]:
|
|
||||||
url = 'https://api.cosmoguard.it/api/v1/echa/search'
|
url = 'https://api.cosmoguard.it/api/v1/echa/search'
|
||||||
response = requests.post(url, json={'cas': query})
|
response = requests.post(url, json={'cas': cas_num})
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if data['success'] == True:
|
if data['success'] == True:
|
||||||
results = data['data']
|
return data['data']
|
||||||
if results:
|
else:
|
||||||
substance_info = data['data']['substance']
|
return data['error']
|
||||||
last_update = data['data']['dossier_info']['lastUpdatedDate']
|
|
||||||
tox = data['data']['index']['toxicological_information_link']
|
def cosing_request(cas_num: str) -> Dict[str, Any]:
|
||||||
rpt = data['data']['index']['repeated_dose_toxicity_link']
|
url = 'https://api.cosmoguard.it/api/v1/cosing/search'
|
||||||
act = data['data']['index']['acute_toxicity_link']
|
response = requests.post(url, json={
|
||||||
toxicological_information = data['data']['toxicological_information']
|
"full": True,
|
||||||
repeated_dose_toxicity = data['data']['repeated_dose_toxicity']
|
"mode": "cas",
|
||||||
acute_toxicity = data['data']['acute_toxicity']
|
"text": cas_num
|
||||||
return {
|
})
|
||||||
'substance_info': substance_info,
|
data = response.json()
|
||||||
'last_update': last_update,
|
if data['success'] == True:
|
||||||
'toxicological_information': toxicological_information,
|
return data['data']
|
||||||
'repeated_dose_toxicity': repeated_dose_toxicity,
|
else:
|
||||||
'acute_toxicity': acute_toxicity,
|
return data['error']
|
||||||
'link': {
|
|
||||||
'toxicological_information': tox,
|
def generate_pdf_download(cas, origin, link):
|
||||||
'repeated_dose_toxicity': rpt,
|
url = 'https://api.cosmoguard.it/api/v1/common/generate-pdf'
|
||||||
'acute_toxicity': act
|
name = f'{cas}_{origin}'
|
||||||
}
|
response = requests.post(
|
||||||
}
|
url,
|
||||||
else:
|
json = {
|
||||||
return {'error': 'No results found for the given CAS number.'}
|
'link': link,
|
||||||
|
'name': name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
data = response.json()
|
||||||
|
if data['success'] == True:
|
||||||
|
url = f'https://api.cosmoguard.it/api/v1/common/download-pdf/{name}'
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.content
|
||||||
else:
|
else:
|
||||||
return data['error']
|
return data['error']
|
||||||
49
functions_ui.py
Normal file
49
functions_ui.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import streamlit as st
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from functions import generate_pdf_download
|
||||||
|
|
||||||
|
def open_csv_file(file_path):
|
||||||
|
"""Apre un file CSV e restituisce i dati come dataframe."""
|
||||||
|
import duckdb
|
||||||
|
|
||||||
|
# Usa DuckDB per leggere il file CSV
|
||||||
|
con = duckdb.connect(database=':memory:')
|
||||||
|
query = f"CREATE TABLE index AS SELECT * FROM read_csv_auto('{file_path}')"
|
||||||
|
con.execute(query)
|
||||||
|
return con
|
||||||
|
|
||||||
|
def search_cas_number(cas_number, type = 'cas'):
|
||||||
|
"""Cerca un numero CAS nei dati forniti e restituisce CAS e INCI."""
|
||||||
|
con = open_csv_file('data.csv')
|
||||||
|
|
||||||
|
query = f"SELECT * FROM index WHERE casNo LIKE '%{cas_number}%'"
|
||||||
|
results = con.execute(query).fetchdf()
|
||||||
|
|
||||||
|
# Restituisce una lista di tuple (casNo, inciName)
|
||||||
|
if not results.empty:
|
||||||
|
return list(zip(results['casNo'].tolist(), results['inciName'].tolist()))
|
||||||
|
return []
|
||||||
|
|
||||||
|
def download_pdf(casNo, origin, link):
|
||||||
|
"""Scarica un PDF generato dall'API."""
|
||||||
|
if st.button("Generate PDF", key= f"gen_{casNo}_{origin}"):
|
||||||
|
with st.spinner("Fetching PDF..."):
|
||||||
|
pdf_data = generate_pdf_download(
|
||||||
|
cas=casNo,
|
||||||
|
origin=origin,
|
||||||
|
link=link
|
||||||
|
)
|
||||||
|
|
||||||
|
st.download_button(
|
||||||
|
label="Download PDF",
|
||||||
|
data=pdf_data,
|
||||||
|
file_name=f"{casNo}_{origin}.pdf",
|
||||||
|
mime="application/pdf",
|
||||||
|
key=f"dl_{casNo}_{origin}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
data = search_cas_number('102242-62-6')
|
||||||
|
print(data)
|
||||||
149
pages/cosing.py
Normal file
149
pages/cosing.py
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
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"
|
||||||
|
chemical_name = data.get("chemicalName", "")
|
||||||
|
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")
|
||||||
|
with col_header2:
|
||||||
|
if chemical_name and chemical_name.upper() != name.upper():
|
||||||
|
st.caption(f"*{chemical_name}*")
|
||||||
|
|
||||||
|
# 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", "—")
|
||||||
|
|
||||||
|
with cols[2]:
|
||||||
|
if ref_no:
|
||||||
|
st.metric("Ref. No.", ref_no)
|
||||||
|
else:
|
||||||
|
st.metric("Ref. No.", "—")
|
||||||
|
|
||||||
|
# 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()
|
||||||
477
pages/echa.py
477
pages/echa.py
|
|
@ -1,352 +1,181 @@
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
from typing import Any
|
|
||||||
import requests
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from functions import clean_and_transform_json, api_req
|
from functions import echa_request
|
||||||
|
from functions_ui import download_pdf
|
||||||
|
|
||||||
st.set_page_config(page_title="Echa Toxicological Reports", layout="centered")
|
st.set_page_config(
|
||||||
|
page_title="ECHA Toxicological Data Viewer",
|
||||||
|
page_icon="🧊",
|
||||||
|
layout="wide",
|
||||||
|
initial_sidebar_state="expanded"
|
||||||
|
)
|
||||||
|
|
||||||
st.title("Echa Toxicological Reports")
|
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
|
||||||
|
|
||||||
result = {}
|
DATA = echa_request(cas_number)
|
||||||
|
|
||||||
# Single-ingredient input
|
def extract_tox_info_values(data):
|
||||||
query = st.text_input("Search by CAS", value="50-00-0")
|
"""Extract DNEL values from toxicological information."""
|
||||||
|
rows = []
|
||||||
|
sections = data.get("toxicological_information", {}).get("sections", [])
|
||||||
|
|
||||||
if st.button("Search"):
|
for section in sections:
|
||||||
with st.spinner("Fetching data..."):
|
label = section.get("label", "")
|
||||||
try:
|
if "subsections" in section:
|
||||||
result = api_req(query)
|
for subsec in section["subsections"]:
|
||||||
if 'error' in result:
|
effect_type = subsec.get("label", "")
|
||||||
st.error(result['error'])
|
if "subsections" in subsec:
|
||||||
else:
|
for sub2 in subsec["subsections"]:
|
||||||
st.success(f"Data fetched successfully! Last updated: {result['last_update']}")
|
dose = sub2.get("StDose", {})
|
||||||
|
if isinstance(dose, dict) and dose.get("value"):
|
||||||
|
rows.append({
|
||||||
|
"Population/Route": label,
|
||||||
|
"Effect Type": effect_type,
|
||||||
|
"Exposure": sub2.get("label", ""),
|
||||||
|
"Assessment": sub2.get("HazardAssessment", ""),
|
||||||
|
"Value numerical": dose.get("value", ""),
|
||||||
|
"Unit": dose.get("unit", ""),
|
||||||
|
"Endpoint": sub2.get("MostSensitiveEndpoint", "")
|
||||||
|
})
|
||||||
|
return rows
|
||||||
|
|
||||||
data = clean_and_transform_json(result)
|
|
||||||
|
|
||||||
tab1, tab2, tab3 = st.tabs(["Toxicological Information", "Repeated Dose Toxicity", "Acute Toxicity"])
|
def extract_acute_values(data):
|
||||||
|
"""Extract acute toxicity values."""
|
||||||
|
rows = []
|
||||||
|
sections = data.get("acute_toxicity", {}).get("sections", [])
|
||||||
|
|
||||||
with tab1:
|
for section in sections:
|
||||||
st.header("Toxicological Information")
|
if section.get("label") == "Key value for assessment":
|
||||||
table_data = []
|
for subsec in section.get("subsections", []):
|
||||||
txi = data['toxicological_information']
|
if subsec.get("EffectLevelValue"):
|
||||||
|
rows.append({
|
||||||
for section_name, section_data in txi['sections'].items():
|
"Route": subsec.get("label", "").replace("Acute toxicity: ", ""),
|
||||||
if section_name == "Administrative data":
|
"Endpoint": subsec.get("EffectLevelUnit", ""),
|
||||||
continue
|
"Value": subsec.get("EffectLevelValue", ""),
|
||||||
elif "Additional information" in section_name:
|
"Conclusion": subsec.get("EndpointConclusion", "")
|
||||||
continue
|
|
||||||
elif "eyes" in section_name.lower():
|
|
||||||
population = 'Workers' if 'Workers' in section_name else 'General Population'
|
|
||||||
table_data.append({
|
|
||||||
'Population': population,
|
|
||||||
'Exposure Route': 'Eyes',
|
|
||||||
'Effect Type': 'Local effects',
|
|
||||||
'Exposure Duration': '-',
|
|
||||||
'Hazard Assessment': section_data['Local effects'].get('Conclusion', 'N/A')
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
# Determine population and route
|
|
||||||
population = 'Workers' if 'Workers' in section_name else 'General Population'
|
|
||||||
|
|
||||||
if 'inhalation' in section_name.lower():
|
|
||||||
route = 'Inhalation'
|
|
||||||
elif 'dermal' in section_name.lower():
|
|
||||||
route = 'Dermal'
|
|
||||||
elif 'oral' in section_name.lower():
|
|
||||||
route = 'Oral'
|
|
||||||
else:
|
|
||||||
route = 'Unknown'
|
|
||||||
|
|
||||||
# Process nested data
|
|
||||||
for effect_type, effect_data in section_data.items():
|
|
||||||
if isinstance(effect_data, dict):
|
|
||||||
for exposure_duration, exposure_data in effect_data.items():
|
|
||||||
if isinstance(exposure_data, dict) and 'HazardAssessment' in exposure_data:
|
|
||||||
table_data.append({
|
|
||||||
'Population': population,
|
|
||||||
'Exposure Route': route,
|
|
||||||
'Effect Type': effect_type,
|
|
||||||
'Exposure Duration': exposure_duration,
|
|
||||||
'Hazard Assessment': exposure_data.get('HazardAssessment', 'N/A')
|
|
||||||
})
|
|
||||||
|
|
||||||
# Create DataFrame
|
|
||||||
df = pd.DataFrame(table_data)
|
|
||||||
|
|
||||||
# Display the main table
|
|
||||||
st.subheader("📊 Complete Hazard Assessment Overview")
|
|
||||||
st.dataframe(df, use_container_width=True, height=500)
|
|
||||||
|
|
||||||
# Display summary metrics
|
|
||||||
col1, col2, col3, col4 = st.columns(4)
|
|
||||||
with col1:
|
|
||||||
st.metric("Total Assessments", len(df))
|
|
||||||
with col2:
|
|
||||||
st.metric("Workers Assessments", len(df[df['Population'] == 'Workers']))
|
|
||||||
with col3:
|
|
||||||
st.metric("General Population", len(df[df['Population'] == 'General Population']))
|
|
||||||
with col4:
|
|
||||||
st.metric("Unique Routes", df['Exposure Route'].nunique())
|
|
||||||
|
|
||||||
# Display additional information
|
|
||||||
st.markdown("---")
|
|
||||||
st.subheader("📝 Additional Information")
|
|
||||||
|
|
||||||
col1, col2 = st.columns(2)
|
|
||||||
with col1:
|
|
||||||
st.markdown("**Workers:**")
|
|
||||||
st.info(txi['sections']['Additional information - workers']['DiscussionWorkers'])
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
st.markdown("**General Population:**")
|
|
||||||
st.info(txi['sections']['Additional information - General Population']['DiscussionGenPop'])
|
|
||||||
|
|
||||||
# Option to download the data as CSV
|
|
||||||
st.markdown("---")
|
|
||||||
csv = df.to_csv(index=False)
|
|
||||||
st.download_button(
|
|
||||||
label="📥 Download as CSV",
|
|
||||||
data=csv,
|
|
||||||
file_name='hazard_assessment_data.csv',
|
|
||||||
mime='text/csv'
|
|
||||||
)
|
|
||||||
|
|
||||||
with tab2:
|
|
||||||
st.header("Repeated Dose Toxicity")
|
|
||||||
table_data = []
|
|
||||||
|
|
||||||
key_values = data['repeated_dose_toxicity']['sections']['Key value for assessment']
|
|
||||||
|
|
||||||
# Process Sub-chronic toxicity - systemic effects
|
|
||||||
subchronic = key_values.get('Sub-chronic toxicity – systemic effects', {})
|
|
||||||
for route, route_data in subchronic.items():
|
|
||||||
if route_data and isinstance(route_data, dict):
|
|
||||||
table_data.append({
|
|
||||||
'Toxicity Type': 'Sub-chronic',
|
|
||||||
'Effect Type': 'Systemic effects',
|
|
||||||
'Route': route,
|
|
||||||
'Effect Level': route_data.get('EffectLevelUnit', '-'),
|
|
||||||
'Value': route_data.get('EffectLevelValue', '-'),
|
|
||||||
'Species': route_data.get('Species', '-'),
|
|
||||||
'Study Reference': route_data.get('LinkToRelevantStudyRecord', '-')[:50] + '...' if route_data.get('LinkToRelevantStudyRecord', '') and len(route_data.get('LinkToRelevantStudyRecord', '')) > 50 else route_data.get('LinkToRelevantStudyRecord', '-')
|
|
||||||
})
|
|
||||||
|
|
||||||
# Process Chronic toxicity - systemic effects
|
|
||||||
chronic = key_values.get('Chronic toxicity – systemic effects', {})
|
|
||||||
for route, route_data in chronic.items():
|
|
||||||
if route_data and isinstance(route_data, dict):
|
|
||||||
table_data.append({
|
|
||||||
'Toxicity Type': 'Chronic',
|
|
||||||
'Effect Type': 'Systemic effects',
|
|
||||||
'Route': route,
|
|
||||||
'Effect Level': route_data.get('EffectLevelUnit', '-'),
|
|
||||||
'Value': route_data.get('EffectLevelValue', '-'),
|
|
||||||
'Species': route_data.get('Species', '-'),
|
|
||||||
'Study Reference': route_data.get('LinkToRelevantStudyRecord', '-')[:50] + '...' if route_data.get('LinkToRelevantStudyRecord', '') and len(route_data.get('LinkToRelevantStudyRecord', '')) > 50 else route_data.get('LinkToRelevantStudyRecord', '-')
|
|
||||||
})
|
|
||||||
|
|
||||||
# Process Repeated dose toxicity - local effects
|
|
||||||
local_effects = key_values.get('Repeated dose toxicity – local effects', {})
|
|
||||||
for route, route_data in local_effects.items():
|
|
||||||
if route_data and isinstance(route_data, dict):
|
|
||||||
table_data.append({
|
|
||||||
'Toxicity Type': route_data.get('TestType', 'Repeated dose').capitalize() if route_data.get('TestType') else 'Repeated dose',
|
|
||||||
'Effect Type': 'Local effects',
|
|
||||||
'Route': route,
|
|
||||||
'Effect Level': route_data.get('EffectLevelUnit', '-'),
|
|
||||||
'Value': route_data.get('EffectLevelValue', '-'),
|
|
||||||
'Species': route_data.get('Species', '-'),
|
|
||||||
'Study Reference': route_data.get('LinkToRelevantStudyRecord', '-')[:50] + '...' if route_data.get('LinkToRelevantStudyRecord', '') and len(route_data.get('LinkToRelevantStudyRecord', '')) > 50 else route_data.get('LinkToRelevantStudyRecord', '-')
|
|
||||||
})
|
|
||||||
|
|
||||||
# Add endpoint conclusions
|
|
||||||
conclusions_data = []
|
|
||||||
if key_values.get('EndpointConclusionSystemicEffectsOralRoute'):
|
|
||||||
conclusions_data.append({
|
|
||||||
'Route': 'Oral',
|
|
||||||
'Effect Type': 'Systemic effects',
|
|
||||||
'Conclusion': key_values.get('EndpointConclusionSystemicEffectsOralRoute')
|
|
||||||
})
|
|
||||||
if key_values.get('EndpointConclusionSystemicEffectsDermal'):
|
|
||||||
conclusions_data.append({
|
|
||||||
'Route': 'Dermal',
|
|
||||||
'Effect Type': 'Systemic effects',
|
|
||||||
'Conclusion': key_values.get('EndpointConclusionSystemicEffectsDermal')
|
|
||||||
})
|
})
|
||||||
|
return rows
|
||||||
|
|
||||||
# Create DataFrames
|
|
||||||
df_toxicity = pd.DataFrame(table_data) if table_data else pd.DataFrame()
|
|
||||||
df_conclusions = pd.DataFrame(conclusions_data) if conclusions_data else pd.DataFrame()
|
|
||||||
|
|
||||||
# Display the main toxicity table
|
def extract_repeated_values(data):
|
||||||
st.subheader("📊 Toxicity Study Results")
|
"""Extract repeated dose toxicity values."""
|
||||||
if not df_toxicity.empty:
|
rows = []
|
||||||
st.dataframe(df_toxicity, use_container_width=True, height=400)
|
sections = data.get("repeated_dose_toxicity", {}).get("sections", [])
|
||||||
else:
|
|
||||||
st.info("No toxicity data available")
|
|
||||||
|
|
||||||
# Display summary metrics
|
for section in sections:
|
||||||
col1, col2, col3, col4 = st.columns(4)
|
if section.get("label") == "Key value for assessment":
|
||||||
with col1:
|
for subsec in section.get("subsections", []):
|
||||||
st.metric("Total Studies", len(df_toxicity) if not df_toxicity.empty else 0)
|
study_type = subsec.get("label", "")
|
||||||
with col2:
|
for sub2 in subsec.get("subsections", []):
|
||||||
unique_routes = df_toxicity['Route'].nunique() if not df_toxicity.empty else 0
|
if sub2.get("EffectLevelValue"):
|
||||||
st.metric("Routes Tested", unique_routes)
|
rows.append({
|
||||||
with col3:
|
"Study Type": study_type,
|
||||||
unique_species = df_toxicity['Species'].nunique() if not df_toxicity.empty else 0
|
"Route": sub2.get("label", ""),
|
||||||
st.metric("Species Tested", unique_species)
|
"Endpoint": sub2.get("EffectLevelUnit", ""),
|
||||||
with col4:
|
"Value": sub2.get("EffectLevelValue", ""),
|
||||||
st.metric("Classification", "Not required")
|
"Species": sub2.get("Species", "-")
|
||||||
|
|
||||||
# Display endpoint conclusions
|
|
||||||
if not df_conclusions.empty:
|
|
||||||
st.markdown("---")
|
|
||||||
st.subheader("🎯 Endpoint Conclusions")
|
|
||||||
st.dataframe(df_conclusions, use_container_width=True, hide_index=True)
|
|
||||||
|
|
||||||
# Display key information
|
|
||||||
st.markdown("---")
|
|
||||||
st.subheader("📋 Key Study Information")
|
|
||||||
key_info = data['repeated_dose_toxicity']['sections']['Description of key information']['KeyInformation']
|
|
||||||
# Split the key information into paragraphs for better readability
|
|
||||||
key_info_paragraphs = key_info.split('.')
|
|
||||||
key_info_formatted = '. '.join([p.strip() for p in key_info_paragraphs if p.strip()])
|
|
||||||
st.info(key_info_formatted[:1000] + "..." if len(key_info_formatted) > 1000 else key_info_formatted)
|
|
||||||
|
|
||||||
# Display classification justification
|
|
||||||
st.markdown("---")
|
|
||||||
st.subheader("⚖️ Classification Justification")
|
|
||||||
justif = data['repeated_dose_toxicity']['sections']['Justification for classification or non-classification (Specific target organ toxicity-repeated exposure (STOT RE))']
|
|
||||||
st.success(justif.get('JustifClassif', 'No information available'))
|
|
||||||
|
|
||||||
# Display additional discussion (truncated)
|
|
||||||
st.markdown("---")
|
|
||||||
st.subheader("📝 Additional Discussion")
|
|
||||||
discussion = data['repeated_dose_toxicity']['sections']['Additional information'].get('Discussion', '')
|
|
||||||
if discussion:
|
|
||||||
# Show first 500 characters of discussion
|
|
||||||
with st.expander("Click to expand full discussion"):
|
|
||||||
st.text_area("", discussion, height=300, disabled=True)
|
|
||||||
|
|
||||||
# Option to download the data as CSV
|
|
||||||
st.markdown("---")
|
|
||||||
if not df_toxicity.empty:
|
|
||||||
csv = df_toxicity.to_csv(index=False)
|
|
||||||
st.download_button(
|
|
||||||
label="📥 Download Toxicity Data as CSV",
|
|
||||||
data=csv,
|
|
||||||
file_name='repeated_dose_toxicity_data.csv',
|
|
||||||
mime='text/csv'
|
|
||||||
)
|
|
||||||
|
|
||||||
with tab3:
|
|
||||||
st.header("Acute Toxicity")
|
|
||||||
table_data = []
|
|
||||||
|
|
||||||
key_values = data['acute_toxicity']['sections']['Key value for assessment']
|
|
||||||
|
|
||||||
# Process each route of exposure
|
|
||||||
for route_name, route_data in key_values.items():
|
|
||||||
if route_data and isinstance(route_data, dict) and 'EffectLevelUnit' in route_data:
|
|
||||||
# Extract route type
|
|
||||||
if 'oral' in route_name:
|
|
||||||
route = 'Oral'
|
|
||||||
species = 'Rat, Mice, Guinea pig' # From key information
|
|
||||||
elif 'dermal' in route_name:
|
|
||||||
route = 'Dermal'
|
|
||||||
species = 'Guinea pig'
|
|
||||||
elif 'inhalation' in route_name:
|
|
||||||
route = 'Inhalation'
|
|
||||||
species = 'Rat'
|
|
||||||
else:
|
|
||||||
route = 'Other'
|
|
||||||
species = '-'
|
|
||||||
|
|
||||||
table_data.append({
|
|
||||||
'Route of Exposure': route,
|
|
||||||
'Effect Level': route_data.get('EffectLevelUnit', '-'),
|
|
||||||
'Value': route_data.get('EffectLevelValue', '-'),
|
|
||||||
'Physical Form': route_data.get('PhysicalForm', '-'),
|
|
||||||
'Species': species,
|
|
||||||
'Endpoint Conclusion': route_data.get('EndpointConclusion', '-')
|
|
||||||
})
|
})
|
||||||
|
return rows
|
||||||
|
|
||||||
# Create DataFrame
|
|
||||||
df_toxicity = pd.DataFrame(table_data)
|
|
||||||
|
|
||||||
# Display the main toxicity table
|
# App
|
||||||
st.subheader("📊 Acute Toxicity Study Results")
|
st.title("Toxicological Data Viewer")
|
||||||
st.dataframe(
|
|
||||||
df_toxicity,
|
|
||||||
use_container_width=True,
|
|
||||||
height=200,
|
|
||||||
column_config={
|
|
||||||
"Route of Exposure": st.column_config.TextColumn("Route", width="small"),
|
|
||||||
"Effect Level": st.column_config.TextColumn("Parameter", width="small"),
|
|
||||||
"Value": st.column_config.TextColumn("Value", width="medium"),
|
|
||||||
"Physical Form": st.column_config.TextColumn("Form", width="medium"),
|
|
||||||
"Species": st.column_config.TextColumn("Test Species", width="medium"),
|
|
||||||
"Endpoint Conclusion": st.column_config.TextColumn("Conclusion", width="large")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Display summary metrics
|
# Substance info
|
||||||
col1, col2, col3, col4 = st.columns(4)
|
substance = DATA["substance"]
|
||||||
with col1:
|
dossier = DATA["dossier_info"]
|
||||||
st.metric("Routes Tested", len(df_toxicity))
|
|
||||||
with col2:
|
|
||||||
st.metric("Oral LD50", "11,500 mg/kg bw")
|
|
||||||
with col3:
|
|
||||||
st.metric("Dermal LD50", "56,750 mg/kg bw")
|
|
||||||
with col4:
|
|
||||||
st.metric("Inhalation LC50", ">5.85 mg/L")
|
|
||||||
|
|
||||||
# Display key information
|
st.subheader(f"{substance['rmlName']}")
|
||||||
st.markdown("---")
|
|
||||||
st.subheader("📋 Key Study Information")
|
|
||||||
key_info = data['acute_toxicity']['sections']['Description of key information']['KeyInformation']
|
|
||||||
|
|
||||||
# Format the key information for better readability
|
with st.container(border=True):
|
||||||
key_info_formatted = key_info.replace(".", ".\n").replace("The acute", "\n• The acute").replace("In an", "\n• In an")
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
st.info(key_info_formatted)
|
with col1:
|
||||||
|
st.caption("CAS Number")
|
||||||
|
st.write(substance['rmlCas'])
|
||||||
|
with col2:
|
||||||
|
st.caption("EC Number")
|
||||||
|
st.write(substance['rmlEc'])
|
||||||
|
with col3:
|
||||||
|
st.caption("Status")
|
||||||
|
st.write(dossier['registrationStatus'])
|
||||||
|
with col4:
|
||||||
|
st.caption("Last Updated")
|
||||||
|
st.write(dossier['lastUpdatedDate'])
|
||||||
|
|
||||||
# Display classification justification
|
# Tabs
|
||||||
st.markdown("---")
|
tab1, tab2, tab3 = st.tabs(["Toxicological Information", "Acute Toxicity", "Repeated Dose Toxicity"])
|
||||||
st.subheader("⚖️ Classification Justification")
|
|
||||||
justif = data['acute_toxicity']['sections']['Justification for classification or non-classification']
|
|
||||||
st.success(justif.get('JustifClassif', 'No information available'))
|
|
||||||
|
|
||||||
# Display additional discussion
|
with tab1:
|
||||||
st.markdown("---")
|
st.subheader("Derived No Effect Levels (DNEL)")
|
||||||
st.subheader("📝 Additional Information & Discussion")
|
tox_rows = extract_tox_info_values(DATA)
|
||||||
discussion = data['acute_toxicity']['sections']['Additional information'].get('Discussion', '')
|
if tox_rows:
|
||||||
|
st.table(tox_rows)
|
||||||
|
else:
|
||||||
|
st.info("No DNEL values found.")
|
||||||
|
|
||||||
if discussion:
|
download_pdf(
|
||||||
# Parse discussion to highlight key points
|
casNo=substance['rmlCas'],
|
||||||
discussion_parts = discussion.split('.')
|
origin='echa_tox_info',
|
||||||
|
link=DATA['index']['toxicological_information_link']
|
||||||
|
)
|
||||||
|
|
||||||
# Display main conclusion
|
with tab2:
|
||||||
st.info("💡 **Main Conclusion:** " + discussion_parts[0] + ".")
|
st.subheader("Acute Toxicity Values")
|
||||||
|
acute_rows = extract_acute_values(DATA)
|
||||||
|
if acute_rows:
|
||||||
|
st.table(acute_rows)
|
||||||
|
else:
|
||||||
|
st.info("No acute toxicity values found.")
|
||||||
|
|
||||||
# Display full discussion in expander
|
download_pdf(
|
||||||
with st.expander("Click to view full discussion and references"):
|
casNo=substance['rmlCas'],
|
||||||
# Format the discussion for better readability
|
origin='echa_acute_tox',
|
||||||
formatted_discussion = discussion.replace(".", ".\n\n")
|
link=DATA['index']['acute_toxicity_link']
|
||||||
st.text_area("", formatted_discussion, height=400, disabled=True)
|
)
|
||||||
|
|
||||||
# Option to download the data as CSV
|
with tab3:
|
||||||
st.markdown("---")
|
st.subheader("Repeated Dose Toxicity Values")
|
||||||
csv = df_toxicity.to_csv(index=False)
|
repeated_rows = extract_repeated_values(DATA)
|
||||||
st.download_button(
|
if repeated_rows:
|
||||||
label="📥 Download Acute Toxicity Data as CSV",
|
st.table(repeated_rows)
|
||||||
data=csv,
|
else:
|
||||||
file_name='acute_toxicity_data.csv',
|
st.info("No repeated dose toxicity values found.")
|
||||||
mime='text/csv'
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
download_pdf(
|
||||||
st.error(f"An error occurred: {e}")
|
casNo=substance['rmlCas'],
|
||||||
|
origin='echa_repeated_tox',
|
||||||
|
link=DATA['index']['repeated_dose_toxicity_link']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Key Information sections
|
||||||
|
st.divider()
|
||||||
|
st.subheader("Key Information")
|
||||||
|
|
||||||
|
acute_key_info = None
|
||||||
|
repeated_key_info = None
|
||||||
|
|
||||||
|
for section in DATA.get("acute_toxicity", {}).get("sections", []):
|
||||||
|
if section.get("label") == "Description of key information":
|
||||||
|
acute_key_info = section.get("KeyInformation")
|
||||||
|
break
|
||||||
|
|
||||||
|
for section in DATA.get("repeated_dose_toxicity", {}).get("sections", []):
|
||||||
|
if section.get("label") == "Description of key information":
|
||||||
|
repeated_key_info = section.get("KeyInformation")
|
||||||
|
break
|
||||||
|
|
||||||
|
if acute_key_info:
|
||||||
|
with st.expander("Acute Toxicity - Key Information"):
|
||||||
|
st.write(acute_key_info)
|
||||||
|
|
||||||
|
if repeated_key_info:
|
||||||
|
with st.expander("Repeated Dose Toxicity - Key Information"):
|
||||||
|
st.write(repeated_key_info)
|
||||||
27
report.py
27
report.py
|
|
@ -1,27 +0,0 @@
|
||||||
import streamlit as st
|
|
||||||
from datetime import datetime
|
|
||||||
import os
|
|
||||||
|
|
||||||
st.header("Report a bug or request a feature")
|
|
||||||
st.write("Use the box below to submit a short report. Submissions are saved locally to `reports.txt`.")
|
|
||||||
|
|
||||||
report_type = st.selectbox("Type", ["Bug", "Feature request", "Other"])
|
|
||||||
report_email = st.text_input("Your name (optional)")
|
|
||||||
report_text = st.text_area("Describe the issue or request", height=160)
|
|
||||||
|
|
||||||
if st.button("Submit report"):
|
|
||||||
if not report_text.strip():
|
|
||||||
st.error("Please enter a description before submitting.")
|
|
||||||
else:
|
|
||||||
timestamp = datetime.utcnow().isoformat() + "Z"
|
|
||||||
entry = f"[{timestamp}]\t{report_type}\t{report_email}\t{report_text.replace('\n', ' ')}\n"
|
|
||||||
try:
|
|
||||||
base = os.path.dirname(__file__)
|
|
||||||
path = os.path.join(base, "reports.txt")
|
|
||||||
with open(path, "a", encoding="utf-8") as f:
|
|
||||||
f.write(entry)
|
|
||||||
st.success("Thanks — your report was saved.")
|
|
||||||
st.write("Saved to:", path)
|
|
||||||
st.markdown("---")
|
|
||||||
except Exception as e:
|
|
||||||
st.error(f"Could not save report: {e}")
|
|
||||||
Loading…
Reference in a new issue