# src/ui_components_original.py import gradio as gr import os import re import logging import base64 from datetime import datetime from PIL import Image import html from typing import Optional, Dict, Any # ---- Safe imports for local vs package execution ---- try: from .patient_history import PatientHistoryManager, ReportGenerator except Exception: from patient_history import PatientHistoryManager, ReportGenerator # local dev # ---- Optional spaces.GPU fallback (local dev) ---- try: import spaces def _SPACES_GPU(*args, **kwargs): return spaces.GPU(*args, **kwargs) except Exception: def _SPACES_GPU(*_args, **_kwargs): def deco(f): return f return deco logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") def pil_to_base64(pil_image: Image.Image) -> Optional[str]: """Convert PIL Image to base64 data URL""" import io if pil_image is None: return None try: if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') buffer = io.BytesIO() pil_image.save(buffer, format='PNG') img_str = base64.b64encode(buffer.getvalue()).decode() return f"data:image/png;base64,{img_str}" except Exception as e: logging.error(f"Error converting PIL image to base64: {e}") return None # ============================================================================= # GPU-DECORATED FUNCTION (STANDALONE) # ============================================================================= @_SPACES_GPU(enable_queue=True) def standalone_run_analysis( # instance/context ui_instance, current_user: Dict[str, Any], database_manager, wound_analyzer, # UI inputs mode, existing_label, np_name, np_age, np_gender, w_loc, w_dur, pain, moist, infect, diabetic, prev_tx, med_hist, meds, alls, notes, img_path ): """Runs in the ZeroGPU worker; returns HTML for the UI.""" def _label_to_id(label: str): if not label: return None try: return int(str(label).split("•", 1)[0].strip()) except Exception: return None def _fetch_patient_core(pid: int): row = database_manager.execute_query_one( "SELECT id, name, age, gender FROM patients WHERE id=%s LIMIT 1", (pid,) ) return row or {} def _response_to_patient_id(resp_ref): if isinstance(resp_ref, dict): pid = resp_ref.get("patient_id") if pid is not None: try: return int(pid) except Exception: pass resp_id = resp_ref.get("response_id") or resp_ref.get("id") else: resp_id = resp_ref if not resp_id: return None row = database_manager.execute_query_one( "SELECT patient_id FROM questionnaire_responses WHERE id=%s LIMIT 1", (int(resp_id),) ) try: return int(row["patient_id"]) if row and "patient_id" in row else None except Exception: return None try: if not img_path: return "
❌ Please upload a wound image.
" user_id = int(current_user.get("id", 0) or 0) if not user_id: return "
❌ Please login first.
" # Resolve patient if mode == "Existing patient": pid = _label_to_id(existing_label) if not pid: return "
⚠️ Select an existing patient.
" pcore = _fetch_patient_core(pid) patient_name_v = pcore.get("name") patient_age_v = pcore.get("age") patient_gender_v = pcore.get("gender") else: patient_name_v = np_name patient_age_v = np_age patient_gender_v = np_gender # Save questionnaire q_payload = { 'user_id': user_id, 'patient_name': patient_name_v, 'patient_age': patient_age_v, 'patient_gender': patient_gender_v, 'wound_location': w_loc, 'wound_duration': w_dur, 'pain_level': pain, 'moisture_level': moist, 'infection_signs': infect, 'diabetic_status': diabetic, 'previous_treatment': prev_tx, 'medical_history': med_hist, 'medications': meds, 'allergies': alls, 'additional_notes': notes } response_id = database_manager.save_questionnaire(q_payload) # normalize response_id = (response_id.get("response_id") if isinstance(response_id, dict) else response_id) try: response_id = int(response_id) except Exception: return "
❌ Could not resolve response ID.
" patient_id = _response_to_patient_id(response_id) if not patient_id: return "
❌ Could not resolve patient ID.
" # Save wound image binary try: with Image.open(img_path) as pil: pil = pil.convert("RGB") img_meta = database_manager.save_wound_image(patient_id, pil) image_db_id = img_meta["id"] if img_meta else None except Exception as e: logging.error(f"save_wound_image error: {e}") image_db_id = None # Prepare AI inputs q_for_ai = { 'age': patient_age_v, 'diabetic': 'Yes' if diabetic != 'Non-diabetic' else 'No', 'allergies': alls, 'date_of_injury': 'Unknown', 'professional_care': 'Yes', 'oozing_bleeding': 'Minor Oozing' if infect != 'None' else 'None', 'infection': 'Yes' if infect != 'None' else 'No', 'moisture': moist, 'patient_name': patient_name_v, 'patient_gender': patient_gender_v, 'wound_location': w_loc, 'wound_duration': w_dur, 'pain_level': pain, 'previous_treatment': prev_tx, 'medical_history': med_hist, 'medications': meds, 'additional_notes': notes } # Run AI analysis_result = wound_analyzer.analyze_wound(img_path, q_for_ai) if not analysis_result or not analysis_result.get("success"): err = (analysis_result or {}).get("error", "Unknown analysis error") return f"
❌ AI Analysis failed: {html.escape(str(err))}
" # Persist AI analysis try: database_manager.save_analysis(response_id, image_db_id, analysis_result) except Exception as e: logging.error(f"save_analysis error: {e}") # Format via instance method to keep UI consistent return ui_instance._format_comprehensive_analysis_results( analysis_result, img_path, q_for_ai ) except Exception as e: logging.exception("standalone_run_analysis exception") return f"
❌ System error in GPU worker: {html.escape(str(e))}
" # ============================================================================= # UI CLASS DEFINITION # ============================================================================= class UIComponents: def __init__(self, auth_manager, database_manager, wound_analyzer): self.auth_manager = auth_manager self.database_manager = database_manager self.wound_analyzer = wound_analyzer self.current_user = {} self.patient_history_manager = PatientHistoryManager(database_manager) self.report_generator = ReportGenerator() # Ensure uploads directory exists if not os.path.exists("uploads"): os.makedirs("uploads", exist_ok=True) def image_to_base64(self, image_path): """Convert image to base64 data URL for embedding in HTML""" if not image_path or not os.path.exists(image_path): return None try: with open(image_path, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()).decode() image_ext = os.path.splitext(image_path)[1].lower() if image_ext in [".jpg", ".jpeg"]: mime_type = "image/jpeg" elif image_ext == ".png": mime_type = "image/png" elif image_ext == ".gif": mime_type = "image/gif" else: mime_type = "image/png" return f"data:{mime_type};base64,{encoded_string}" except Exception as e: logging.error(f"Error converting image to base64: {e}") return None def markdown_to_html(self, markdown_text): """Convert markdown text to proper HTML format with enhanced support""" if not markdown_text: return "" # Escape HTML entities html_text = html.escape(markdown_text) # Headers html_text = re.sub(r"^### (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) html_text = re.sub(r"^## (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) html_text = re.sub(r"^# (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) # Bold, italic html_text = re.sub(r"\*\*(.*?)\*\*", r"\1", html_text) html_text = re.sub(r"\*(.*?)\*", r"\1", html_text) # Code blocks html_text = re.sub(r"```(.*?)```", r"
\1
", html_text, flags=re.DOTALL) # Inline code html_text = re.sub(r"`(.*?)`", r"\1", html_text) # Blockquotes html_text = re.sub(r"^> (.*?)$", r"
\1
", html_text, flags=re.MULTILINE) # Links html_text = re.sub(r"\[(.*?)\]\((.*?)\)", r"\1", html_text) # Horizontal rules html_text = re.sub(r"^\s*[-*_]{3,}\s*$", r"
", html_text, flags=re.MULTILINE) # Bullet points to ") return "\n".join(result_lines) def get_organizations_dropdown(self): """Get list of organizations for dropdown""" try: organizations = self.database_manager.get_organizations() return [f"{org['org_name']} - {org['location']}" for org in organizations] except Exception as e: logging.error(f"Error getting organizations: {e}") return ["Default Hospital - Location"] def get_custom_css(self): return """ /* =================== SMARTHEAL CSS =================== */ /* Global Styling */ body, html { margin: 0 !important; padding: 0 !important; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif !important; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; color: #1A202C !important; line-height: 1.6 !important; } /* Professional Header with Logo */ .medical-header { background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%) !important; color: white !important; padding: 32px 40px !important; border-radius: 20px 20px 0 0 !important; display: flex !important; align-items: center !important; justify-content: center !important; margin-bottom: 0 !important; box-shadow: 0 10px 40px rgba(49, 130, 206, 0.3) !important; border: none !important; position: relative !important; overflow: hidden !important; } .logo { width: 80px !important; height: 80px !important; border-radius: 50% !important; margin-right: 24px !important; border: 4px solid rgba(255, 255, 255, 0.3) !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2) !important; background: white !important; padding: 4px !important; } .medical-header h1 { font-size: 3.5rem !important; font-weight: 800 !important; margin: 0 !important; text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3) !important; background: linear-gradient(45deg, #ffffff, #f8f9fa) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3)) !important; } .medical-header p { font-size: 1.3rem !important; margin: 8px 0 0 0 !important; opacity: 0.95 !important; font-weight: 500 !important; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2) !important; } /* Enhanced Form Styling */ .gr-form { background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%) !important; border-radius: 20px !important; padding: 32px !important; margin: 24px 0 !important; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.1) !important; border: 1px solid rgba(229, 62, 62, 0.1) !important; backdrop-filter: blur(10px) !important; position: relative !important; overflow: hidden !important; } /* Professional Input Fields */ .gr-textbox, .gr-number { border-radius: 12px !important; border: 2px solid #E2E8F0 !important; background: #FFFFFF !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important; font-size: 1rem !important; color: #1A202C !important; padding: 16px 20px !important; } .gr-textbox:focus, .gr-number:focus, .gr-textbox input:focus, .gr-number input:focus { border-color: #E53E3E !important; box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important; background: #FFFFFF !important; outline: none !important; transform: translateY(-1px) !important; } /* Enhanced Button Styling */ button.gr-button, button.gr-button-primary { background: linear-gradient(135deg, #E53E3E 0%, #C53030 100%) !important; color: #FFFFFF !important; border: none !important; border-radius: 12px !important; font-weight: 700 !important; padding: 16px 32px !important; font-size: 1.1rem !important; letter-spacing: 0.5px !important; text-align: center !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; box-shadow: 0 4px 16px rgba(229, 62, 62, 0.3) !important; position: relative !important; overflow: hidden !important; text-transform: uppercase !important; cursor: pointer !important; } button.gr-button:hover, button.gr-button-primary:hover { background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important; box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important; transform: translateY(-3px) !important; } /* Professional Status Messages */ .status-success { background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important; border: 2px solid #38A169 !important; color: #22543D !important; padding: 20px 24px !important; border-radius: 16px !important; font-weight: 600 !important; margin: 16px 0 !important; box-shadow: 0 8px 24px rgba(56, 161, 105, 0.2) !important; backdrop-filter: blur(10px) !important; } .status-error { background: linear-gradient(135deg, #FFF5F5 0%, #FED7D7 100%) !important; border: 2px solid #E53E3E !important; color: #742A2A !important; padding: 20px 24px !important; border-radius: 16px !important; font-weight: 600 !important; margin: 16px 0 !important; box-shadow: 0 8px 24px rgba(229, 62, 62, 0.2) !important; backdrop-filter: blur(10px) !important; } .status-warning { background: linear-gradient(135deg, #FFFAF0 0%, #FEEBC8 100%) !important; border: 2px solid #DD6B20 !important; color: #9C4221 !important; padding: 20px 24px !important; border-radius: 16px !important; font-weight: 600 !important; margin: 16px 0 !important; box-shadow: 0 8px 24px rgba(221, 107, 32, 0.2) !important; backdrop-filter: blur(10px) !important; } /* Image gallery styling */ .image-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin: 20px 0; } .image-item { background: #f8f9fa; border-radius: 12px; padding: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); text-align: center; } .image-item img { max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); } .image-item h4 { margin: 15px 0 5px 0; color: #2d3748; font-weight: 600; } .image-item p { margin: 0; color: #666; font-size: 0.9em; } /* Analyze button */ #analyze-btn { background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important; color: #FFFFFF !important; border: none !important; border-radius: 8px !important; font-weight: 700 !important; padding: 14px 28px !important; font-size: 1.1rem !important; letter-spacing: 0.5px !important; text-align: center !important; transition: all 0.2s ease-in-out !important; } #analyze-btn:hover { background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important; box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important; transform: translateY(-2px) !important; } /* Responsive */ @media (max-width: 768px) { .medical-header { padding: 16px !important; text-align: center !important; } .medical-header h1 { font-size: 2rem !important; } .logo { width: 48px !important; height: 48px !important; margin-right: 16px !important; } .gr-form { padding: 16px !important; margin: 8px 0 !important; } .image-gallery { grid-template-columns: 1fr; } } """ def create_interface(self): """ SmartHeal UI – aligned with current DB + history manager: • Login (practitioner / organization) • Practitioner: Wound Analysis (existing vs new patient), Patient History, View Details """ import gradio as gr from PIL import Image import os, html, logging # ----------------------- helpers (inner) ----------------------- self._patient_choices = [] # list[str] labels in dropdown self._patient_map = {} # label -> patient_id def _to_data_url_if_local(path_or_url: str) -> str: if not path_or_url: return "" try: if os.path.exists(path_or_url): return self.image_to_base64(path_or_url) or "" return path_or_url except Exception: return "" def _refresh_patient_dropdown(user_id: int): """Query patient's list and prepare dropdown choices.""" self._patient_choices.clear() self._patient_map.clear() try: rows = self.patient_history_manager.get_patient_list(user_id) or [] for r in rows: pid = int(r.get("id") or 0) nm = r.get("patient_name") or "Unknown" age = r.get("patient_age") or "" gen = r.get("patient_gender") or "" v = int(r.get("total_visits") or 0) label = f"{pid} • {nm} ({age}y {gen}) — visits: {v}" self._patient_choices.append(label) self._patient_map[label] = pid except Exception as e: logging.error(f"refresh dropdown error: {e}") def _label_to_id(label: str): if not label: return None try: return int(str(label).split("•", 1)[0].strip()) except Exception: return None def _resolve_org_id_from_dropdown(label: str) -> Optional[int]: """ Dropdown items look like: 'Org Name - Location'. Try to resolve to organizations.id. """ if not label: return None try: if " - " in label: org_name, location = label.split(" - ", 1) row = self.database_manager.execute_query_one( "SELECT id FROM organizations WHERE name=%s AND location=%s ORDER BY id DESC LIMIT 1", (org_name.strip(), location.strip()) ) if row and "id" in row: return int(row["id"]) else: row = self.database_manager.execute_query_one( "SELECT id FROM organizations WHERE name=%s ORDER BY id DESC LIMIT 1", (label.strip(),) ) if row and "id" in row: return int(row["id"]) except Exception as e: logging.error(f"resolve org id error: {e}") return None # ----------------------- Blocks UI ----------------------- with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app: # Header logo_url = "https://scontent.fccu31-2.fna.fbcdn.net/v/t39.30808-6/275933824_102121829111657_3325198727201325354_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=6ee11a&_nc_ohc=UIBKBXaPiSsQ7kNvwHy41Wj&_nc_oc=Adm8WwTOq--itjR7UgI7mUy57nDeZQ8zZSh4YxQ6F0iq8gmoUxtQ4-nZV7vTAWlYJxY&_nc_zt=23&_nc_ht=scontent.fccu31-2.fna&_nc_gid=1Em7m4EFZJCDDExF5Mmlfg&oh=00_AfXodrOfPdY61Bes8PY2s9o0Z2kXAVdmkk0Yd4dNOo8mgw&oe=68A2358B" gr.HTML(f"""

SmartHeal AI

Advanced Wound Care Analysis & Clinical Support System

""") # Disclaimer gr.HTML("""

⚠️ IMPORTANT DISCLAIMER

This system is for testing/education and not a substitute for clinical judgment.

""") # Panels: auth vs practitioner vs organization with gr.Row(): with gr.Column(visible=True) as auth_panel: with gr.Tabs(): with gr.Tab("🔐 Professional Login"): login_username = gr.Textbox(label="👤 Username") login_password = gr.Textbox(label="🔒 Password", type="password") login_btn = gr.Button("🚀 Sign In", variant="primary") login_status = gr.HTML("
Please sign in.
") with gr.Tab("📝 New Registration"): signup_username = gr.Textbox(label="👤 Username") signup_email = gr.Textbox(label="📧 Email") signup_password = gr.Textbox(label="🔒 Password", type="password") signup_name = gr.Textbox(label="👨‍⚕️ Full Name") signup_role = gr.Radio(["practitioner", "organization"], label="Account Type", value="practitioner") with gr.Group(visible=False) as org_fields: org_name = gr.Textbox(label="Organization Name") phone = gr.Textbox(label="Phone") country_code = gr.Textbox(label="Country Code") department = gr.Textbox(label="Department") location = gr.Textbox(label="Location") with gr.Group(visible=True) as prac_fields: organization_dropdown = gr.Dropdown(choices=self.get_organizations_dropdown(), label="Select Organization") signup_btn = gr.Button("✨ Create Account", variant="primary") signup_status = gr.HTML() with gr.Column(visible=False) as practitioner_panel: user_info = gr.HTML("") logout_btn_prac = gr.Button("🚪 Logout", variant="secondary") with gr.Tabs(): # ------------------- WOUND ANALYSIS ------------------- with gr.Tab("🔬 Wound Analysis"): with gr.Row(): with gr.Column(scale=1): gr.HTML("

📋 Patient Selection

") patient_mode = gr.Radio( ["Existing patient", "New patient"], label="Patient mode", value="Existing patient" ) existing_patient_dd = gr.Dropdown( choices=[], label="Select existing patient (ID • Name)", interactive=True ) with gr.Group(visible=False) as new_patient_group: new_patient_name = gr.Textbox(label="Patient Name") new_patient_age = gr.Number(label="Age", value=30, minimum=0, maximum=120) new_patient_gender = gr.Dropdown(choices=["Male", "Female", "Other"], value="Male", label="Gender") gr.HTML("

🩹 Wound Information

") wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left ankle") wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks") pain_level = gr.Slider(0, 10, value=5, step=1, label="Pain Level (0-10)") gr.HTML("

⚕️ Clinical Assessment

") moisture_level = gr.Dropdown(["Dry", "Moist", "Wet", "Saturated"], value="Moist", label="Moisture Level") infection_signs = gr.Dropdown(["None", "Mild", "Moderate", "Severe"], value="None", label="Signs of Infection") diabetic_status = gr.Dropdown(["Non-diabetic", "Type 1", "Type 2", "Gestational"], value="Non-diabetic", label="Diabetic Status") with gr.Column(scale=1): gr.HTML("

📸 Wound Image

") wound_image = gr.Image(label="Upload Wound Image", type="filepath") gr.HTML("

📝 Medical History

") previous_treatment = gr.Textbox(label="Previous Treatment", lines=3) medical_history = gr.Textbox(label="Medical History", lines=3) medications = gr.Textbox(label="Current Medications", lines=2) allergies = gr.Textbox(label="Known Allergies", lines=2) additional_notes = gr.Textbox(label="Additional Notes", lines=3) analyze_btn = gr.Button("🔬 Analyze Wound", variant="primary", elem_id="analyze-btn") analysis_output = gr.HTML("") # ------------------- PATIENT HISTORY ------------------- with gr.Tab("📋 Patient History"): with gr.Row(): with gr.Column(scale=2): history_btn = gr.Button("📄 Load Patient History", variant="primary") patient_history_output = gr.HTML("") with gr.Column(scale=1): search_patient_name = gr.Textbox(label="Search patient by name") search_patient_btn = gr.Button("🔍 Search", variant="secondary") specific_patient_output = gr.HTML("") gr.HTML("
") with gr.Row(): view_details_dd = gr.Dropdown(choices=[], label="Select patient to view details") view_details_btn = gr.Button("📈 View Details (Timeline)", variant="primary") view_details_output = gr.HTML("") with gr.Column(visible=False) as organization_panel: gr.HTML("
Organization dashboard coming soon.
") logout_btn_org = gr.Button("🚪 Logout", variant="secondary") # ----------------------- handlers ----------------------- def toggle_role_fields(role): return { org_fields: gr.update(visible=(role == "organization")), prac_fields: gr.update(visible=(role != "organization")) } def handle_signup(username, email, password, name, role, org_name_v, phone_v, cc_v, dept_v, loc_v, org_dropdown): try: organization_id = None if role == "practitioner": organization_id = _resolve_org_id_from_dropdown(org_dropdown) ok = self.auth_manager.create_user( username=username, email=email, password=password, name=name, role=role, org_name=(org_name_v or name) if role == "organization" else "", phone=phone_v if role == "organization" else "", country_code=cc_v if role == "organization" else "", department=dept_v if role == "organization" else "", location=loc_v if role == "organization" else "", organization_id=organization_id ) if ok: return "
✅ Account created. Please log in.
" return "
❌ Could not create account. Username/email may exist.
" except Exception as e: return f"
❌ Error: {html.escape(str(e))}
" def handle_login(username, password): user = self.auth_manager.authenticate_user(username, password) if not user: return { login_status: "
❌ Invalid credentials.
" } self.current_user = user uid = int(user.get("id")) role = user.get("role") if role == "practitioner": _refresh_patient_dropdown(uid) info = f"
Welcome, {html.escape(user.get('name','User'))} — {html.escape(role)}
" updates = {login_status: info} if role == "practitioner": updates.update({ auth_panel: gr.update(visible=False), practitioner_panel: gr.update(visible=True), user_info: info, existing_patient_dd: gr.update(choices=self._patient_choices), view_details_dd: gr.update(choices=self._patient_choices), }) else: updates.update({ auth_panel: gr.update(visible=False), organization_panel: gr.update(visible=True), }) return updates def handle_logout(): self.current_user = {} return { auth_panel: gr.update(visible=True), practitioner_panel: gr.update(visible=False), organization_panel: gr.update(visible=False) } def on_patient_mode_change(mode): return { new_patient_group: gr.update(visible=(mode == "New patient")), existing_patient_dd: gr.update(interactive=(mode == "Existing patient")) } def load_history(): try: uid = int(self.current_user.get("id", 0) or 0) if not uid: return "
❌ Please login first.
" rows = self.patient_history_manager.get_user_patient_history(uid) or [] # inline images out = [] for r in rows or []: r = dict(r) if r.get("image_url"): r["image_url"] = _to_data_url_if_local(r["image_url"]) out.append(r) return self.patient_history_manager.format_history_for_display(out) except Exception as e: logging.error(f"load_history error: {e}") return f"
❌ Error: {html.escape(str(e))}
" def do_search(name): try: uid = int(self.current_user.get("id", 0) or 0) if not uid: return "
❌ Please login first.
" if not (name or "").strip(): return "
⚠️ Enter a name to search.
" rows = self.patient_history_manager.search_patient_by_name(uid, name.strip()) or [] out = [] for r in rows or []: r = dict(r) if r.get("image_url"): r["image_url"] = _to_data_url_if_local(r["image_url"]) out.append(r) return self.patient_history_manager.format_patient_data_for_display(out) except Exception as e: logging.error(f"search error: {e}") return f"
❌ Error: {html.escape(str(e))}
" def view_details(existing_label): try: uid = int(self.current_user.get("id", 0) or 0) if not uid: return "
❌ Please login first.
" pid = _label_to_id(existing_label) if not pid: return "
⚠️ Select a patient.
" rows = self.patient_history_manager.get_wound_progression_by_id(uid, pid) or [] out = [] for r in rows or []: r = dict(r) if r.get("image_url"): r["image_url"] = _to_data_url_if_local(r["image_url"]) out.append(r) return self.patient_history_manager.format_patient_progress_for_display(out) except Exception as e: logging.error(f"view_details error: {e}") return f"
❌ Error: {html.escape(str(e))}
" # ----------------------- wiring ----------------------- signup_role.change( toggle_role_fields, inputs=[signup_role], outputs=[org_fields, prac_fields] ) signup_btn.click( handle_signup, inputs=[signup_username, signup_email, signup_password, signup_name, signup_role, org_name, phone, country_code, department, location, organization_dropdown], outputs=[signup_status] ) login_btn.click( handle_login, inputs=[login_username, login_password], outputs=[login_status, auth_panel, practitioner_panel, organization_panel, user_info, existing_patient_dd, view_details_dd] ) logout_btn_prac.click(handle_logout, outputs=[auth_panel, practitioner_panel, organization_panel]) logout_btn_org.click(handle_logout, outputs=[auth_panel, practitioner_panel, organization_panel]) patient_mode.change( on_patient_mode_change, inputs=[patient_mode], outputs=[new_patient_group, existing_patient_dd] ) # --- IMPORTANT: call standalone GPU function via lambda to pass instance/ctx --- analyze_btn.click( fn=lambda mode, ex_lbl, np_n, np_a, np_g, wl, wd, p, m, i, d, pt, mh, med, al, nt, img: \ standalone_run_analysis( self, self.current_user, self.database_manager, self.wound_analyzer, mode, ex_lbl, np_n, np_a, np_g, wl, wd, p, m, i, d, pt, mh, med, al, nt, img ), inputs=[ patient_mode, existing_patient_dd, new_patient_name, new_patient_age, new_patient_gender, wound_location, wound_duration, pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment, medical_history, medications, allergies, additional_notes, wound_image ], outputs=[analysis_output] ) history_btn.click(load_history, outputs=[patient_history_output]) search_patient_btn.click(do_search, inputs=[search_patient_name], outputs=[specific_patient_output]) view_details_btn.click(view_details, inputs=[view_details_dd], outputs=[view_details_output]) return app # ----------------------- formatting & risk logic ----------------------- def _format_comprehensive_analysis_results(self, analysis_result, image_url=None, questionnaire_data=None): """Format comprehensive analysis results with all visualization images from AIProcessor.""" try: success = analysis_result.get('success', False) if not success: error_msg = analysis_result.get('error', 'Unknown error') return f"
❌ Analysis failed: {error_msg}
" visual_analysis = analysis_result.get('visual_analysis', {}) report = analysis_result.get('report', '') saved_image_path = analysis_result.get('saved_image_path', '') wound_type = visual_analysis.get('wound_type', 'Unknown') length_cm = visual_analysis.get('length_cm', 0) breadth_cm = visual_analysis.get('breadth_cm', 0) area_cm2 = visual_analysis.get('surface_area_cm2', 0) detection_confidence = visual_analysis.get('detection_confidence', 0) detection_image_path = visual_analysis.get('detection_image_path', '') segmentation_image_path = visual_analysis.get('segmentation_image_path', '') original_image_path = visual_analysis.get('original_image_path', '') original_image_base64 = None detection_image_base64 = None segmentation_image_base64 = None if image_url and os.path.exists(image_url): original_image_base64 = self.image_to_base64(image_url) elif original_image_path and os.path.exists(original_image_path): original_image_base64 = self.image_to_base64(original_image_path) elif saved_image_path and os.path.exists(saved_image_path): original_image_base64 = self.image_to_base64(saved_image_path) if detection_image_path and os.path.exists(detection_image_path): detection_image_base64 = self.image_to_base64(detection_image_path) if segmentation_image_path and os.path.exists(segmentation_image_path): segmentation_image_base64 = self.image_to_base64(segmentation_image_path) risk_assessment = self._generate_risk_assessment(questionnaire_data) risk_level = risk_assessment['risk_level'] risk_score = risk_assessment['risk_score'] risk_factors = risk_assessment['risk_factors'] risk_class = "low" if risk_level.lower() == "moderate": risk_class = "moderate" elif risk_level.lower() in ["high", "very high"]: risk_class = "high" risk_factors_html = "" if risk_factors else "

No specific risk factors identified.

" image_gallery_html = "" if original_image_base64 or detection_image_base64 or segmentation_image_base64: image_gallery_html = '' report_html = self.markdown_to_html(report) if report else "" html_output = f"""

🔬 SmartHeal AI Comprehensive Analysis

Advanced Computer Vision & Medical AI Assessment

Patient: {html.escape(str(questionnaire_data.get('patient_name', 'Unknown')))} | Analysis Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

✅ Analysis Status: Analysis completed successfully with comprehensive wound assessment

🖼️ Visual Analysis Gallery

{image_gallery_html}

🔍 Wound Detection & Classification

Wound Type

{html.escape(str(wound_type))}

Detection Confidence

{detection_confidence:.1%}

Location

{html.escape(str(questionnaire_data.get('wound_location', 'Not specified')))}

📏 Wound Measurements

Length

{length_cm:.2f} cm

Width

{breadth_cm:.2f} cm

Surface Area

{area_cm2:.2f} cm²

⚠️ Risk Assessment

{risk_level} RISK
Risk Score: {risk_score}/10

Identified Risk Factors:

{risk_factors_html}

👤 Patient Information Summary

Age: {html.escape(str(questionnaire_data.get('age', 'Not specified')))} years
Gender: {html.escape(str(questionnaire_data.get('patient_gender', 'Not specified')))}
Diabetic Status: {html.escape(str(questionnaire_data.get('diabetic', 'Unknown')))}
Pain Level: {html.escape(str(questionnaire_data.get('pain_level', 'Not assessed')))} / 10
Wound Duration: {html.escape(str(questionnaire_data.get('wound_duration', 'Not specified')))}
Moisture Level: {html.escape(str(questionnaire_data.get('moisture', 'Not assessed')))}
{f"
Medical History: {html.escape(str(questionnaire_data.get('medical_history', 'None provided')))}
" if questionnaire_data.get('medical_history') else ""} {f"
Current Medications: {html.escape(str(questionnaire_data.get('medications', 'None listed')))}
" if questionnaire_data.get('medications') else ""} {f"
Known Allergies: {html.escape(str(questionnaire_data.get('allergies', 'None listed')))}
" if questionnaire_data.get('allergies') else ""}
{f'

🤖 AI-Generated Clinical Report

{report_html}
' if report_html else ''}

⚠️ Important Medical Disclaimers

  • Not a Medical Diagnosis: This AI analysis is for informational purposes only and does not constitute medical advice, diagnosis, or treatment.
  • Professional Consultation Required: Always consult with qualified healthcare professionals for proper clinical assessment and treatment decisions.
  • Measurement Accuracy: All measurements are estimates based on computer vision algorithms and should be verified with clinical tools.
  • Risk Assessment Limitations: Risk factors are based on provided information and may not reflect the complete clinical picture.

🏥 Analysis completed by SmartHeal AI - Advanced Wound Care Assistant
Report generated on {datetime.now().strftime('%B %d, %Y at %I:%M %p')}

""" return html_output except Exception as e: logging.error(f"Error formatting comprehensive results: {e}") return f"
❌ Error displaying results: {str(e)}
" def _generate_risk_assessment(self, questionnaire_data): """Generate risk assessment based on questionnaire data""" if not questionnaire_data: return {'risk_level': 'Unknown', 'risk_score': 0, 'risk_factors': []} risk_factors = [] risk_score = 0 try: # Age age = questionnaire_data.get('age', 0) if isinstance(age, str): try: age = int(age) except ValueError: age = 0 if age > 65: risk_factors.append("Advanced age (>65 years)") risk_score += 2 elif age > 50: risk_factors.append("Older adult (50-65 years)") risk_score += 1 # Diabetes diabetic_status = str(questionnaire_data.get('diabetic', '')).lower() if 'yes' in diabetic_status: risk_factors.append("Diabetes mellitus") risk_score += 3 # Infection infection = str(questionnaire_data.get('infection', '')).lower() if 'yes' in infection: risk_factors.append("Signs of infection present") risk_score += 3 # Pain pain_level = questionnaire_data.get('pain_level', 0) if isinstance(pain_level, str): try: pain_level = float(pain_level) except ValueError: pain_level = 0 if pain_level >= 7: risk_factors.append("High pain level (≥7/10)") risk_score += 2 elif pain_level >= 5: risk_factors.append("Moderate pain level (5-6/10)") risk_score += 1 # Duration duration = str(questionnaire_data.get('wound_duration', '')).lower() if any(term in duration for term in ['month', 'months', 'year', 'years']): risk_factors.append("Chronic wound (>4 weeks)") risk_score += 3 # Moisture moisture = str(questionnaire_data.get('moisture', '')).lower() if any(term in moisture for term in ['wet', 'saturated']): risk_factors.append("Excessive wound exudate") risk_score += 1 # Medical history medical_history = str(questionnaire_data.get('medical_history', '')).lower() if any(term in medical_history for term in ['vascular', 'circulation', 'heart']): risk_factors.append("Cardiovascular disease") risk_score += 2 if any(term in medical_history for term in ['immune', 'cancer', 'steroid']): risk_factors.append("Immune system compromise") risk_score += 2 if any(term in medical_history for term in ['smoking', 'tobacco']): risk_factors.append("Smoking history") risk_score += 2 # Risk level if risk_score >= 8: risk_level = "Very High" elif risk_score >= 6: risk_level = "High" elif risk_score >= 3: risk_level = "Moderate" else: risk_level = "Low" return { 'risk_score': risk_score, 'risk_level': risk_level, 'risk_factors': risk_factors } except Exception as e: logging.error(f"Risk assessment error: {e}") return { 'risk_score': 0, 'risk_level': 'Unknown', 'risk_factors': ['Unable to assess risk due to data processing error'] }