352 lines
No EOL
18 KiB
Python
352 lines
No EOL
18 KiB
Python
import streamlit as st
|
||
import json
|
||
import os
|
||
from typing import Any
|
||
import requests
|
||
import pandas as pd
|
||
|
||
from functions import clean_and_transform_json, api_req
|
||
|
||
st.set_page_config(page_title="Echa Toxicological Reports", layout="centered")
|
||
|
||
st.title("Echa Toxicological Reports")
|
||
|
||
result = {}
|
||
|
||
# Single-ingredient input
|
||
query = st.text_input("Search by CAS", value="50-00-0")
|
||
|
||
if st.button("Search"):
|
||
with st.spinner("Fetching data..."):
|
||
try:
|
||
result = api_req(query)
|
||
if 'error' in result:
|
||
st.error(result['error'])
|
||
else:
|
||
st.success(f"Data fetched successfully! Last updated: {result['last_update']}")
|
||
|
||
data = clean_and_transform_json(result)
|
||
|
||
tab1, tab2, tab3 = st.tabs(["Toxicological Information", "Repeated Dose Toxicity", "Acute Toxicity"])
|
||
|
||
with tab1:
|
||
st.header("Toxicological Information")
|
||
table_data = []
|
||
txi = data['toxicological_information']
|
||
|
||
for section_name, section_data in txi['sections'].items():
|
||
if section_name == "Administrative data":
|
||
continue
|
||
elif "Additional information" in section_name:
|
||
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')
|
||
})
|
||
|
||
# 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
|
||
st.subheader("📊 Toxicity Study Results")
|
||
if not df_toxicity.empty:
|
||
st.dataframe(df_toxicity, use_container_width=True, height=400)
|
||
else:
|
||
st.info("No toxicity data available")
|
||
|
||
# Display summary metrics
|
||
col1, col2, col3, col4 = st.columns(4)
|
||
with col1:
|
||
st.metric("Total Studies", len(df_toxicity) if not df_toxicity.empty else 0)
|
||
with col2:
|
||
unique_routes = df_toxicity['Route'].nunique() if not df_toxicity.empty else 0
|
||
st.metric("Routes Tested", unique_routes)
|
||
with col3:
|
||
unique_species = df_toxicity['Species'].nunique() if not df_toxicity.empty else 0
|
||
st.metric("Species Tested", unique_species)
|
||
with col4:
|
||
st.metric("Classification", "Not required")
|
||
|
||
# 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', '-')
|
||
})
|
||
|
||
# Create DataFrame
|
||
df_toxicity = pd.DataFrame(table_data)
|
||
|
||
# Display the main toxicity table
|
||
st.subheader("📊 Acute Toxicity Study Results")
|
||
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
|
||
col1, col2, col3, col4 = st.columns(4)
|
||
with col1:
|
||
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.markdown("---")
|
||
st.subheader("📋 Key Study Information")
|
||
key_info = data['acute_toxicity']['sections']['Description of key information']['KeyInformation']
|
||
|
||
# Format the key information for better readability
|
||
key_info_formatted = key_info.replace(".", ".\n").replace("The acute", "\n• The acute").replace("In an", "\n• In an")
|
||
st.info(key_info_formatted)
|
||
|
||
# Display classification justification
|
||
st.markdown("---")
|
||
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
|
||
st.markdown("---")
|
||
st.subheader("📝 Additional Information & Discussion")
|
||
discussion = data['acute_toxicity']['sections']['Additional information'].get('Discussion', '')
|
||
|
||
if discussion:
|
||
# Parse discussion to highlight key points
|
||
discussion_parts = discussion.split('.')
|
||
|
||
# Display main conclusion
|
||
st.info("💡 **Main Conclusion:** " + discussion_parts[0] + ".")
|
||
|
||
# Display full discussion in expander
|
||
with st.expander("Click to view full discussion and references"):
|
||
# Format the discussion for better readability
|
||
formatted_discussion = discussion.replace(".", ".\n\n")
|
||
st.text_area("", formatted_discussion, height=400, disabled=True)
|
||
|
||
# Option to download the data as CSV
|
||
st.markdown("---")
|
||
csv = df_toxicity.to_csv(index=False)
|
||
st.download_button(
|
||
label="📥 Download Acute Toxicity Data as CSV",
|
||
data=csv,
|
||
file_name='acute_toxicity_data.csv',
|
||
mime='text/csv'
|
||
)
|
||
|
||
except Exception as e:
|
||
st.error(f"An error occurred: {e}") |