Spaces:
Runtime error
Runtime error
| """User profile management page.""" | |
| import sys | |
| from pathlib import Path | |
| # Add the project root to the Python path | |
| # This is necessary for Streamlit to find modules in the 'apps' directory | |
| project_root = Path(__file__).resolve().parents[3] | |
| if str(project_root) not in sys.path: | |
| sys.path.append(str(project_root)) | |
| import pandas as pd | |
| import streamlit as st | |
| from apps.streamlit_ui.page_versions.profile import v1, v2 | |
| from sentinel.models import ( | |
| ClinicalObservation, | |
| Demographics, | |
| FamilyMemberCancer, | |
| FemaleSpecific, | |
| Lifestyle, | |
| PersonalMedicalHistory, | |
| UserInput, | |
| ) | |
| from sentinel.utils import load_user_file | |
| # --- Helper Functions --- | |
| def clear_profile_state(): | |
| """Callback function to reset profile-related session state.""" | |
| st.session_state.user_profile = None | |
| if "profile_upload" in st.session_state: | |
| del st.session_state["profile_upload"] | |
| # --- Main Page Layout --- | |
| st.title("👤 User Profile") | |
| # --- Sidebar for Version Selection and Upload --- | |
| with st.sidebar: | |
| st.header("Controls") | |
| # Version selection | |
| version_options = ["V2 (Editable Form)", "V1 (JSON Viewer)"] | |
| version = st.radio( | |
| "Select Demo Version", | |
| version_options, | |
| help="Choose the version of the profile page to display.", | |
| ) | |
| st.divider() | |
| # Example Profile Selector | |
| examples_dir = project_root / "examples" | |
| # Collect all example profiles | |
| profile_files = [] | |
| if examples_dir.exists(): | |
| # Get profiles from dev/ | |
| dev_dir = examples_dir / "dev" | |
| if dev_dir.exists(): | |
| profile_files.extend(sorted(dev_dir.glob("*.yaml"))) | |
| profile_files.extend(sorted(dev_dir.glob("*.json"))) | |
| # Get profiles from synthetic/ | |
| synthetic_dir = examples_dir / "synthetic" | |
| if synthetic_dir.exists(): | |
| for subdir in sorted(synthetic_dir.iterdir()): | |
| if subdir.is_dir(): | |
| profile_files.extend(sorted(subdir.glob("*.yaml"))) | |
| profile_files.extend(sorted(subdir.glob("*.json"))) | |
| # Create display names (relative to examples/) | |
| profile_options = {} | |
| if profile_files: | |
| for p in profile_files: | |
| rel_path = p.relative_to(examples_dir) | |
| profile_options[str(rel_path)] = p | |
| # Dropdown selector | |
| if profile_options: | |
| selected = st.selectbox( | |
| "Load Example Profile", | |
| options=["-- Select a profile --", *profile_options.keys()], | |
| key="profile_selector", | |
| ) | |
| if selected != "-- Select a profile --": | |
| try: | |
| profile_path = profile_options[selected] | |
| st.session_state.user_profile = load_user_file(str(profile_path)) | |
| st.success(f"✅ Loaded: {selected}") | |
| except Exception as e: | |
| st.error(f"Failed to load profile: {e}") | |
| # Clear Profile Button | |
| if st.session_state.get("user_profile"): | |
| st.button( | |
| "Clear Loaded Profile", | |
| on_click=clear_profile_state, | |
| use_container_width=True, | |
| ) | |
| # --- Page Content Dispatcher --- | |
| # Render the selected page version | |
| if version == "V1 (JSON Viewer)": | |
| v1.render() | |
| else: # Default to V2 | |
| v2.render() | |
| # The manual creation form can be a persistent feature at the bottom of the page | |
| with st.expander("Create New Profile Manually"): | |
| # --- STEP 1: Move the sex selector OUTSIDE the form. --- | |
| # This allows it to trigger a rerun and update the UI dynamically. | |
| # Give it a unique key to avoid conflicts with other widgets. | |
| sex = st.selectbox( | |
| "Biological Sex", ["Male", "Female", "Other"], key="manual_profile_sex" | |
| ) | |
| with st.form("manual_profile_form"): | |
| st.subheader("Demographics") | |
| age = st.number_input("Age", min_value=0, step=1) | |
| # The 'sex' variable is now taken from the selector above the form. | |
| ethnicity = st.text_input("Ethnicity") | |
| st.subheader("Lifestyle") | |
| smoking_status = st.selectbox("Smoking Status", ["never", "former", "current"]) | |
| smoking_pack_years = st.number_input("Pack-Years", min_value=0, step=1) | |
| alcohol_consumption = st.selectbox( | |
| "Alcohol Consumption", ["none", "light", "moderate", "heavy"] | |
| ) | |
| dietary_habits = st.text_area("Dietary Habits") | |
| physical_activity_level = st.text_area("Physical Activity") | |
| st.subheader("Personal Medical History") | |
| known_genetic_mutations = st.text_input( | |
| "Known Genetic Mutations (comma-separated)" | |
| ) | |
| previous_cancers = st.text_input("Previous Cancers (comma-separated)") | |
| chronic_illnesses = st.text_input("Chronic Illnesses (comma-separated)") | |
| st.subheader("Family History") | |
| fam_cols = ["relative", "cancer_type", "age_at_diagnosis"] | |
| fam_df = st.data_editor( | |
| pd.DataFrame(columns=fam_cols), | |
| num_rows="dynamic", | |
| key="family_history_editor", | |
| ) | |
| st.subheader("Clinical Observations") | |
| obs_cols = ["test_name", "value", "unit", "reference_range", "date"] | |
| obs_df = st.data_editor( | |
| pd.DataFrame(columns=obs_cols), | |
| num_rows="dynamic", | |
| key="clinical_obs_editor", | |
| ) | |
| female_specific_data = {} | |
| # --- STEP 2: The conditional check now works correctly. --- | |
| # The 'if' statement is evaluated on each rerun when the 'sex' selector changes. | |
| if sex == "Female": | |
| st.subheader("Female-Specific") | |
| female_specific_data["age_at_first_period"] = st.number_input( | |
| "Age at First Period", min_value=0, step=1 | |
| ) | |
| female_specific_data["age_at_menopause"] = st.number_input( | |
| "Age at Menopause", min_value=0, step=1 | |
| ) | |
| female_specific_data["num_live_births"] = st.number_input( | |
| "Number of Live Births", min_value=0, step=1 | |
| ) | |
| female_specific_data["age_at_first_live_birth"] = st.number_input( | |
| "Age at First Live Birth", min_value=0, step=1 | |
| ) | |
| female_specific_data["hormone_therapy_use"] = st.text_input( | |
| "Hormone Therapy Use" | |
| ) | |
| current_concerns = st.text_area("Current Concerns or Symptoms") | |
| submitted = st.form_submit_button("Save New Profile") | |
| if submitted: | |
| # --- STEP 3: Use the 'sex' variable from the external selector during submission. --- | |
| demographics = Demographics( | |
| age=int(age), sex=sex, ethnicity=ethnicity or None | |
| ) | |
| lifestyle = Lifestyle( | |
| smoking_status=smoking_status, | |
| smoking_pack_years=int(smoking_pack_years) or None, | |
| alcohol_consumption=alcohol_consumption, | |
| dietary_habits=dietary_habits or None, | |
| physical_activity_level=physical_activity_level or None, | |
| ) | |
| pmh = PersonalMedicalHistory( | |
| known_genetic_mutations=[ | |
| m.strip() for m in known_genetic_mutations.split(",") if m.strip() | |
| ], | |
| previous_cancers=[ | |
| c.strip() for c in previous_cancers.split(",") if c.strip() | |
| ], | |
| chronic_illnesses=[ | |
| i.strip() for i in chronic_illnesses.split(",") if i.strip() | |
| ], | |
| ) | |
| family_history = [] | |
| for _, row in fam_df.dropna(how="all").iterrows(): | |
| if row.get("relative") and row.get("cancer_type"): | |
| family_history.append( | |
| FamilyMemberCancer( | |
| relative=str(row["relative"]), | |
| cancer_type=str(row["cancer_type"]), | |
| age_at_diagnosis=int(row["age_at_diagnosis"]) | |
| if row["age_at_diagnosis"] not in ["", None] | |
| else None, | |
| ) | |
| ) | |
| observations = [] | |
| for _, row in obs_df.dropna(how="all").iterrows(): | |
| if row.get("test_name") and row.get("value") and row.get("unit"): | |
| observations.append( | |
| ClinicalObservation( | |
| test_name=str(row["test_name"]), | |
| value=str(row["value"]), | |
| unit=str(row["unit"]), | |
| reference_range=( | |
| str(row["reference_range"]) | |
| if row["reference_range"] not in ["", None] | |
| else None | |
| ), | |
| date=str(row["date"]) | |
| if row["date"] not in ["", None] | |
| else None, | |
| ) | |
| ) | |
| female_specific = None | |
| if sex == "Female": | |
| female_specific = FemaleSpecific(**female_specific_data) | |
| new_profile = UserInput( | |
| demographics=demographics, | |
| lifestyle=lifestyle, | |
| family_history=family_history, | |
| personal_medical_history=pmh, | |
| female_specific=female_specific, | |
| current_concerns_or_symptoms=current_concerns or None, | |
| clinical_observations=observations, | |
| ) | |
| st.success("Profile saved") | |
| # --- STEP 4: Compute the risk scores --- | |
| with st.spinner("Calculating risk scores..."): | |
| from sentinel.risk_models import RISK_MODELS | |
| risks_scores = [] | |
| for model in RISK_MODELS: | |
| risk_score = model().run(new_profile) | |
| # Handle models that return multiple scores (e.g., QCancer) | |
| if isinstance(risk_score, list): | |
| risks_scores.extend(risk_score) | |
| else: | |
| risks_scores.append(risk_score) | |
| new_profile.risks_scores = risks_scores | |
| st.session_state.user_profile = new_profile | |
| st.success("Risk scores calculated!") | |
| st.rerun() | |