SmartHeal commited on
Commit
043da85
Β·
verified Β·
1 Parent(s): 00831ad

Update src/ui_components_original.py

Browse files
Files changed (1) hide show
  1. src/ui_components_original.py +496 -496
src/ui_components_original.py CHANGED
@@ -376,519 +376,519 @@ button.gr-button:hover, button.gr-button-primary:hover {
376
  """
377
 
378
  def create_interface(self):
379
- """
380
- SmartHeal UI – aligned with current DB + history manager:
381
- β€’ Login (practitioner / organization)
382
- β€’ Practitioner: Wound Analysis (existing vs new patient), Patient History, View Details
383
- β€’ Images from disk are shown via data URLs for reliable rendering
384
- """
385
- import gradio as gr
386
- from PIL import Image
387
- import os, html, logging
388
-
389
- # ----------------------- helpers (inner) -----------------------
390
-
391
- self._patient_choices = [] # list[str] rendered in dropdown
392
- self._patient_map = {} # label -> patient_id (int)
393
-
394
- def _to_data_url_if_local(path_or_url: str) -> str:
395
- if not path_or_url:
396
- return ""
397
- try:
398
- if os.path.exists(path_or_url):
399
- return self.image_to_base64(path_or_url) or ""
400
- return path_or_url # already a URL
401
- except Exception:
402
- return ""
403
-
404
- def _refresh_patient_dropdown(user_id: int):
405
- """Query patient's list and prepare dropdown choices."""
406
- self._patient_choices = []
407
- self._patient_map = {}
408
- try:
409
- rows = self.patient_history_manager.get_patient_list(user_id) or []
410
- # label starts with id -> stable parse
411
- for r in rows:
412
- pid = int(r.get("id") or 0)
413
- nm = r.get("patient_name") or "Unknown"
414
- age = r.get("patient_age") or ""
415
- gen = r.get("patient_gender") or ""
416
- v = int(r.get("total_visits") or 0)
417
- label = f"{pid} β€’ {nm} ({age}y {gen}) β€” visits: {v}"
418
- self._patient_choices.append(label)
419
- self._patient_map[label] = pid
420
- except Exception as e:
421
- logging.error(f"refresh dropdown error: {e}")
422
-
423
- def _label_to_id(label: str):
424
- if not label: return None
425
- try:
426
- return int(str(label).split("β€’", 1)[0].strip())
427
- except Exception:
428
- return None
429
-
430
- def _fetch_patient_core(pid: int):
431
- """Get name/age/gender for an existing patient id."""
432
- row = self.database_manager.execute_query_one(
433
- "SELECT id, name, age, gender FROM patients WHERE id=%s LIMIT 1", (pid,)
434
- )
435
- return row or {}
436
-
437
- def _response_to_patient_id(resp_ref):
438
  """
439
- Accepts either a response_id int or a dict like
440
- {'response_id': 70, 'patient_id': 346, ...}.
441
- Returns patient_id (int) by preferring any direct dict field,
442
- otherwise queries by response id.
443
  """
444
- # If the caller already gave us a dict, take the shortcut
445
- if isinstance(resp_ref, dict):
446
- pid = resp_ref.get("patient_id")
447
- if pid is not None:
448
- try:
449
- return int(pid)
450
- except Exception:
451
- pass
452
- resp_id = resp_ref.get("response_id") or resp_ref.get("id")
 
 
 
453
  try:
454
- resp_id = int(resp_id) if resp_id is not None else None
 
 
455
  except Exception:
456
- resp_id = None
457
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  try:
459
- resp_id = int(resp_ref) if resp_ref is not None else None
460
  except Exception:
461
- resp_id = None
462
-
463
- if not resp_id:
464
- return None
465
-
466
- row = self.database_manager.execute_query_one(
467
- "SELECT patient_id FROM questionnaire_responses WHERE id=%s LIMIT 1",
468
- (resp_id,) # <-- ensure scalar param
469
- )
470
- try:
471
- return int(row["patient_id"]) if row and "patient_id" in row else None
472
- except Exception:
473
- return None
474
-
475
- def _rows_with_inline_images(rows: list[dict]) -> list[dict]:
476
- """Convert local file paths to data URLs so HTML displays them anywhere."""
477
- out = []
478
- for r in rows or []:
479
- r = dict(r)
480
- if r.get("image_url"):
481
- r["image_url"] = _to_data_url_if_local(r["image_url"])
482
- out.append(r)
483
- return out
484
-
485
- # ----------------------- Blocks UI -----------------------
486
-
487
- with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app:
488
- # Header
489
- 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=45krrEUpcSUQ7kNvwGVdiMW&_nc_oc=AdkTdxEC_TkYGiyDkEtTJZ_DFZELW17XKFmWpswmFqGB7JSdvTyWtnrQyLS0USngEiY&_nc_zt=23&_nc_ht=scontent.fccu31-2.fna&_nc_gid=ufAA4Hj5gTRwON5POYzz0Q&oh=00_AfW1-jLEN5RGeggqOvGgEaK_gdg0EDgxf_VhKbZwFLUO0Q&oe=6897A98B"
490
- gr.HTML(f"""
491
- <div class="medical-header">
492
- <img src="{logo_url}" class="logo" alt="SmartHeal Logo">
493
- <div>
494
- <h1>SmartHeal AI</h1>
495
- <p>Advanced Wound Care Analysis & Clinical Support System</p>
496
- </div>
497
- </div>
498
- """)
499
-
500
- # Disclaimer
501
- gr.HTML("""
502
- <div style="border:2px solid #FF6B6B;background:#FFE5E5;padding:15px;border-radius:12px;margin:10px 0;">
503
- <h3 style="color:#D63031;margin:0 0 8px 0;">⚠️ IMPORTANT DISCLAIMER</h3>
504
- <p><strong>This system is for testing/education and not a substitute for clinical judgment.</strong></p>
505
- </div>
506
- """)
507
-
508
- # Panels: auth vs practitioner vs organization
509
- with gr.Row():
510
- with gr.Column(visible=True) as auth_panel:
511
- with gr.Tabs():
512
- with gr.Tab("πŸ” Professional Login"):
513
- login_username = gr.Textbox(label="πŸ‘€ Username")
514
- login_password = gr.Textbox(label="πŸ”’ Password", type="password")
515
- login_btn = gr.Button("πŸš€ Sign In", variant="primary")
516
- login_status = gr.HTML("<div class='status-warning'>Please sign in.</div>")
517
-
518
- with gr.Tab("πŸ“ New Registration"):
519
- signup_username = gr.Textbox(label="πŸ‘€ Username")
520
- signup_email = gr.Textbox(label="πŸ“§ Email")
521
- signup_password = gr.Textbox(label="πŸ”’ Password", type="password")
522
- signup_name = gr.Textbox(label="πŸ‘¨β€βš•οΈ Full Name")
523
- signup_role = gr.Radio(["practitioner", "organization"], label="Account Type", value="practitioner")
524
-
525
- with gr.Group(visible=False) as org_fields:
526
- org_name = gr.Textbox(label="Organization Name")
527
- phone = gr.Textbox(label="Phone")
528
- country_code = gr.Textbox(label="Country Code")
529
- department = gr.Textbox(label="Department")
530
- location = gr.Textbox(label="Location")
531
-
532
- with gr.Group(visible=True) as prac_fields:
533
- organization_dropdown = gr.Dropdown(choices=self.get_organizations_dropdown(), label="Select Organization")
534
-
535
- signup_btn = gr.Button("✨ Create Account", variant="primary")
536
- signup_status = gr.HTML()
537
-
538
- with gr.Column(visible=False) as practitioner_panel:
539
- user_info = gr.HTML("")
540
- logout_btn_prac = gr.Button("πŸšͺ Logout", variant="secondary")
541
-
542
- with gr.Tabs():
543
- # ------------------- WOUND ANALYSIS -------------------
544
- with gr.Tab("πŸ”¬ Wound Analysis"):
545
- with gr.Row():
546
- with gr.Column(scale=1):
547
- gr.HTML("<h3>πŸ“‹ Patient Selection</h3>")
548
- patient_mode = gr.Radio(
549
- ["Existing patient", "New patient"],
550
- label="Patient mode",
551
- value="Existing patient"
552
- )
553
- existing_patient_dd = gr.Dropdown(
554
- choices=[],
555
- label="Select existing patient (ID β€’ Name)",
556
- interactive=True
557
- )
558
- with gr.Group(visible=False) as new_patient_group:
559
- new_patient_name = gr.Textbox(label="Patient Name")
560
- new_patient_age = gr.Number(label="Age", value=30, minimum=0, maximum=120)
561
- new_patient_gender = gr.Dropdown(choices=["Male", "Female", "Other"], value="Male", label="Gender")
562
-
563
- gr.HTML("<h3>🩹 Wound Information</h3>")
564
- wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left ankle")
565
- wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks")
566
- pain_level = gr.Slider(0, 10, value=5, step=1, label="Pain Level (0-10)")
567
-
568
- gr.HTML("<h3>βš•οΈ Clinical Assessment</h3>")
569
- moisture_level = gr.Dropdown(["Dry", "Moist", "Wet", "Saturated"], value="Moist", label="Moisture Level")
570
- infection_signs = gr.Dropdown(["None", "Mild", "Moderate", "Severe"], value="None", label="Signs of Infection")
571
- diabetic_status = gr.Dropdown(["Non-diabetic", "Type 1", "Type 2", "Gestational"], value="Non-diabetic", label="Diabetic Status")
572
-
573
- with gr.Column(scale=1):
574
- gr.HTML("<h3>πŸ“Έ Wound Image</h3>")
575
- wound_image = gr.Image(label="Upload Wound Image", type="filepath")
576
- gr.HTML("<h3>πŸ“ Medical History</h3>")
577
- previous_treatment = gr.Textbox(label="Previous Treatment", lines=3)
578
- medical_history = gr.Textbox(label="Medical History", lines=3)
579
- medications = gr.Textbox(label="Current Medications", lines=2)
580
- allergies = gr.Textbox(label="Known Allergies", lines=2)
581
- additional_notes = gr.Textbox(label="Additional Notes", lines=3)
582
-
583
- analyze_btn = gr.Button("πŸ”¬ Analyze Wound", variant="primary", elem_id="analyze-btn")
584
- analysis_output = gr.HTML("")
585
-
586
- # ------------------- PATIENT HISTORY -------------------
587
- with gr.Tab("πŸ“‹ Patient History"):
588
- with gr.Row():
589
- with gr.Column(scale=2):
590
- history_btn = gr.Button("πŸ“„ Load Patient History", variant="primary")
591
- patient_history_output = gr.HTML("")
592
- with gr.Column(scale=1):
593
- search_patient_name = gr.Textbox(label="Search patient by name")
594
- search_patient_btn = gr.Button("πŸ” Search", variant="secondary")
595
- specific_patient_output = gr.HTML("")
596
-
597
- gr.HTML("<hr style='margin:10px 0 6px 0;border:none;border-top:1px solid #e2e8f0'>")
598
- with gr.Row():
599
- view_details_dd = gr.Dropdown(choices=[], label="Select patient to view details")
600
- view_details_btn = gr.Button("πŸ“ˆ View Details (Timeline)", variant="primary")
601
- view_details_output = gr.HTML("")
602
-
603
- with gr.Column(visible=False) as organization_panel:
604
- gr.HTML("<div class='status-warning'>Organization dashboard coming soon.</div>")
605
- logout_btn_org = gr.Button("πŸšͺ Logout", variant="secondary")
606
-
607
- # ----------------------- handlers -----------------------
608
-
609
- def toggle_role_fields(role):
610
- return {
611
- org_fields: gr.update(visible=(role == "organization")),
612
- prac_fields: gr.update(visible=(role != "organization"))
613
- }
614
-
615
- def handle_signup(username, email, password, name, role, org_name_v, phone_v, cc_v, dept_v, loc_v, org_dropdown):
616
  try:
617
- if role == "organization":
618
- org_data = {
619
- 'org_name': org_name_v,
620
- 'email': email,
621
- 'phone': phone_v,
622
- 'country_code': cc_v,
623
- 'department': dept_v,
624
- 'location': loc_v
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  }
626
- org_id = self.database_manager.create_organization(org_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  else:
628
- # For now pick first org (or default)
629
- org_id = 1
630
-
631
- user_data = {
632
- 'username': username, 'email': email, 'password': password,
633
- 'name': name, 'role': role, 'org_id': org_id
634
- }
635
- ok = self.auth_manager.create_user(user_data)
636
- if ok:
637
- return "<div class='status-success'>βœ… Account created. Please log in.</div>"
638
- return "<div class='status-error'>❌ Could not create account. Username/email may exist.</div>"
639
- except Exception as e:
640
- return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
641
-
642
- def handle_login(username, password):
643
- user = self.auth_manager.authenticate_user(username, password)
644
- if not user:
645
  return {
646
- login_status: "<div class='status-error'>❌ Invalid credentials.</div>"
 
 
647
  }
648
- self.current_user = user
649
- uid = int(user.get("id"))
650
- role = user.get("role")
651
-
652
- # Preload patient dropdowns for practitioners
653
- if role == "practitioner":
654
- _refresh_patient_dropdown(uid)
655
-
656
- info = f"<div class='status-success'>Welcome, <strong>{html.escape(user.get('name','User'))}</strong> β€” {html.escape(role)}</div>"
657
- updates = {login_status: info}
658
-
659
- if role == "practitioner":
660
- updates.update({
661
- auth_panel: gr.update(visible=False),
662
- practitioner_panel: gr.update(visible=True),
663
- user_info: info,
664
- existing_patient_dd: gr.update(choices=self._patient_choices),
665
- view_details_dd: gr.update(choices=self._patient_choices),
666
- })
667
- else:
668
- updates.update({
669
- auth_panel: gr.update(visible=False),
670
- organization_panel: gr.update(visible=True),
671
- })
672
- return updates
673
-
674
- def handle_logout():
675
- self.current_user = {}
676
- return {
677
- auth_panel: gr.update(visible=True),
678
- practitioner_panel: gr.update(visible=False),
679
- organization_panel: gr.update(visible=False)
680
- }
681
-
682
- def on_patient_mode_change(mode):
683
- return {
684
- new_patient_group: gr.update(visible=(mode == "New patient")),
685
- existing_patient_dd: gr.update(interactive=(mode == "Existing patient"))
686
- }
687
-
688
- def run_analysis(mode, existing_label,
689
- np_name, np_age, np_gender,
690
- w_loc, w_dur, pain, moist, infect, diabetic,
691
- prev_tx, med_hist, meds, alls, notes, img_path):
692
- try:
693
- if not img_path:
694
- return "<div class='status-error'>❌ Please upload a wound image.</div>"
695
-
696
- user_id = int(self.current_user.get("id", 0) or 0)
697
- if not user_id:
698
- return "<div class='status-error'>❌ Please login first.</div>"
699
-
700
- # Determine patient core fields (ensures same patient_id for existing)
701
- if mode == "Existing patient":
702
- pid = _label_to_id(existing_label)
703
- if not pid:
704
- return "<div class='status-warning'>⚠️ Select an existing patient.</div>"
705
- pcore = _fetch_patient_core(pid)
706
- patient_name_v = pcore.get("name")
707
- patient_age_v = pcore.get("age")
708
- patient_gender_v = pcore.get("gender")
709
- else:
710
- patient_name_v = np_name
711
- patient_age_v = np_age
712
- patient_gender_v = np_gender
713
-
714
- # Build questionnaire payload
715
- q_payload = {
716
- 'user_id': user_id,
717
- 'patient_name': patient_name_v,
718
- 'patient_age': patient_age_v,
719
- 'patient_gender': patient_gender_v,
720
- 'wound_location': w_loc,
721
- 'wound_duration': w_dur,
722
- 'pain_level': pain,
723
- 'moisture_level': moist,
724
- 'infection_signs': infect,
725
- 'diabetic_status': diabetic,
726
- 'previous_treatment': prev_tx,
727
- 'medical_history': med_hist,
728
- 'medications': meds,
729
- 'allergies': alls,
730
- 'additional_notes': notes
731
  }
732
-
733
- # Save questionnaire -> response_id
734
- response_id = self.database_manager.save_questionnaire(q_payload)
735
-
736
- # πŸ”’ Normalize in case an older/alternate version returns a dict
737
- if isinstance(response_id, dict):
738
- response_id = response_id.get("response_id") or response_id.get("id")
739
  try:
740
- response_id = int(response_id)
741
- except Exception:
742
- return "<div class='status-error'>❌ Could not resolve response ID.</div>"
743
-
744
- # Resolve patient_id from response (works for new or existing)
745
- patient_id = _response_to_patient_id(response_id)
746
- if not patient_id:
747
- return "<div class='status-error'>❌ Could not resolve patient ID.</div>"
748
-
749
- # Save wound image to DB
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
  try:
751
- with Image.open(img_path) as pil:
752
- pil = pil.convert("RGB")
753
- img_meta = self.database_manager.save_wound_image(patient_id, pil)
754
- image_db_id = img_meta["id"] if img_meta else None
 
 
755
  except Exception as e:
756
- logging.error(f"save_wound_image error: {e}")
757
- image_db_id = None
758
-
759
- # Prepare AI analyzer questionnaire dict
760
- q_for_ai = {
761
- 'age': patient_age_v,
762
- 'diabetic': 'Yes' if diabetic != 'Non-diabetic' else 'No',
763
- 'allergies': alls,
764
- 'date_of_injury': 'Unknown',
765
- 'professional_care': 'Yes',
766
- 'oozing_bleeding': 'Minor Oozing' if infect != 'None' else 'None',
767
- 'infection': 'Yes' if infect != 'None' else 'No',
768
- 'moisture': moist,
769
- 'patient_name': patient_name_v,
770
- 'patient_gender': patient_gender_v,
771
- 'wound_location': w_loc,
772
- 'wound_duration': w_dur,
773
- 'pain_level': pain,
774
- 'previous_treatment': prev_tx,
775
- 'medical_history': med_hist,
776
- 'medications': meds,
777
- 'additional_notes': notes
778
- }
779
-
780
- # Run AI
781
- analysis_result = self.wound_analyzer.analyze_wound(img_path, q_for_ai)
782
- if not analysis_result or not analysis_result.get("success"):
783
- err = (analysis_result or {}).get("error", "Unknown analysis error")
784
- return f"<div class='status-error'>❌ AI Analysis failed: {html.escape(str(err))}</div>"
785
-
786
- # Persist AI analysis (ties back to template via response->questionnaire_id)
787
  try:
788
- self.database_manager.save_analysis(response_id, image_db_id, analysis_result)
 
 
 
 
 
 
 
789
  except Exception as e:
790
- logging.error(f"save_analysis error: {e}")
791
-
792
- # If a new patient was created, refresh dropdowns
793
- if mode == "New patient":
794
- _refresh_patient_dropdown(user_id)
795
-
796
- # Render fancy results
797
- return self._format_comprehensive_analysis_results(
798
- analysis_result, img_path, q_for_ai
799
- )
800
- except Exception as e:
801
- logging.exception("run_analysis exception")
802
- return f"<div class='status-error'>❌ System error: {html.escape(str(e))}</div>"
803
-
804
- def load_history():
805
- try:
806
- uid = int(self.current_user.get("id", 0) or 0)
807
- if not uid:
808
- return "<div class='status-error'>❌ Please login first.</div>"
809
- rows = self.patient_history_manager.get_user_patient_history(uid) or []
810
- rows = _rows_with_inline_images(rows)
811
- return self.patient_history_manager.format_history_for_display(rows)
812
- except Exception as e:
813
- logging.error(f"load_history error: {e}")
814
- return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
815
-
816
- def do_search(name):
817
- try:
818
- uid = int(self.current_user.get("id", 0) or 0)
819
- if not uid:
820
- return "<div class='status-error'>❌ Please login first.</div>"
821
- if not (name or "").strip():
822
- return "<div class='status-warning'>⚠️ Enter a name to search.</div>"
823
- rows = self.patient_history_manager.search_patient_by_name(uid, name.strip()) or []
824
- rows = _rows_with_inline_images(rows)
825
- return self.patient_history_manager.format_patient_data_for_display(rows)
826
- except Exception as e:
827
- logging.error(f"search error: {e}")
828
- return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
829
-
830
- def view_details(existing_label):
831
- try:
832
- uid = int(self.current_user.get("id", 0) or 0)
833
- if not uid:
834
- return "<div class='status-error'>❌ Please login first.</div>"
835
- pid = _label_to_id(existing_label)
836
- if not pid:
837
- return "<div class='status-warning'>⚠️ Select a patient.</div>"
838
- rows = self.patient_history_manager.get_wound_progression_by_id(uid, pid) or []
839
- rows = _rows_with_inline_images(rows)
840
- return self.patient_history_manager.format_patient_progress_for_display(rows)
841
- except Exception as e:
842
- logging.error(f"view_details error: {e}")
843
- return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
844
-
845
- # ----------------------- wiring -----------------------
846
-
847
- signup_role.change(
848
- toggle_role_fields,
849
- inputs=[signup_role],
850
- outputs=[org_fields, prac_fields]
851
- )
852
-
853
- signup_btn.click(
854
- handle_signup,
855
- inputs=[signup_username, signup_email, signup_password, signup_name, signup_role,
856
- org_name, phone, country_code, department, location, organization_dropdown],
857
- outputs=[signup_status]
858
- )
859
-
860
- login_btn.click(
861
- handle_login,
862
- inputs=[login_username, login_password],
863
- outputs=[login_status, auth_panel, practitioner_panel, organization_panel,
864
- user_info, existing_patient_dd, view_details_dd]
865
- )
866
-
867
- logout_btn_prac.click(handle_logout, outputs=[auth_panel, practitioner_panel, organization_panel])
868
- logout_btn_org.click(handle_logout, outputs=[auth_panel, practitioner_panel, organization_panel])
869
-
870
- patient_mode.change(
871
- on_patient_mode_change,
872
- inputs=[patient_mode],
873
- outputs=[new_patient_group, existing_patient_dd]
874
- )
875
-
876
- analyze_btn.click(
877
- run_analysis,
878
- inputs=[
879
- patient_mode, existing_patient_dd,
880
- new_patient_name, new_patient_age, new_patient_gender,
881
- wound_location, wound_duration, pain_level, moisture_level, infection_signs, diabetic_status,
882
- previous_treatment, medical_history, medications, allergies, additional_notes, wound_image
883
- ],
884
- outputs=[analysis_output]
885
- )
886
-
887
- history_btn.click(load_history, outputs=[patient_history_output])
888
- search_patient_btn.click(do_search, inputs=[search_patient_name], outputs=[specific_patient_output])
889
- view_details_btn.click(view_details, inputs=[view_details_dd], outputs=[view_details_output])
890
-
891
- return app
892
 
893
 
894
  def _format_comprehensive_analysis_results(self, analysis_result, image_url=None, questionnaire_data=None):
 
376
  """
377
 
378
  def create_interface(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  """
380
+ SmartHeal UI – aligned with current DB + history manager:
381
+ β€’ Login (practitioner / organization)
382
+ β€’ Practitioner: Wound Analysis (existing vs new patient), Patient History, View Details
383
+ β€’ Images from disk are shown via data URLs for reliable rendering
384
  """
385
+ import gradio as gr
386
+ from PIL import Image
387
+ import os, html, logging
388
+
389
+ # ----------------------- helpers (inner) -----------------------
390
+
391
+ self._patient_choices = [] # list[str] rendered in dropdown
392
+ self._patient_map = {} # label -> patient_id (int)
393
+
394
+ def _to_data_url_if_local(path_or_url: str) -> str:
395
+ if not path_or_url:
396
+ return ""
397
  try:
398
+ if os.path.exists(path_or_url):
399
+ return self.image_to_base64(path_or_url) or ""
400
+ return path_or_url # already a URL
401
  except Exception:
402
+ return ""
403
+
404
+ def _refresh_patient_dropdown(user_id: int):
405
+ """Query patient's list and prepare dropdown choices."""
406
+ self._patient_choices = []
407
+ self._patient_map = {}
408
+ try:
409
+ rows = self.patient_history_manager.get_patient_list(user_id) or []
410
+ # label starts with id -> stable parse
411
+ for r in rows:
412
+ pid = int(r.get("id") or 0)
413
+ nm = r.get("patient_name") or "Unknown"
414
+ age = r.get("patient_age") or ""
415
+ gen = r.get("patient_gender") or ""
416
+ v = int(r.get("total_visits") or 0)
417
+ label = f"{pid} β€’ {nm} ({age}y {gen}) β€” visits: {v}"
418
+ self._patient_choices.append(label)
419
+ self._patient_map[label] = pid
420
+ except Exception as e:
421
+ logging.error(f"refresh dropdown error: {e}")
422
+
423
+ def _label_to_id(label: str):
424
+ if not label: return None
425
  try:
426
+ return int(str(label).split("β€’", 1)[0].strip())
427
  except Exception:
428
+ return None
429
+
430
+ def _fetch_patient_core(pid: int):
431
+ """Get name/age/gender for an existing patient id."""
432
+ row = self.database_manager.execute_query_one(
433
+ "SELECT id, name, age, gender FROM patients WHERE id=%s LIMIT 1", (pid,)
434
+ )
435
+ return row or {}
436
+
437
+ def _response_to_patient_id(resp_ref):
438
+ """
439
+ Accepts either a response_id int or a dict like
440
+ {'response_id': 70, 'patient_id': 346, ...}.
441
+ Returns patient_id (int) by preferring any direct dict field,
442
+ otherwise queries by response id.
443
+ """
444
+ # If the caller already gave us a dict, take the shortcut
445
+ if isinstance(resp_ref, dict):
446
+ pid = resp_ref.get("patient_id")
447
+ if pid is not None:
448
+ try:
449
+ return int(pid)
450
+ except Exception:
451
+ pass
452
+ resp_id = resp_ref.get("response_id") or resp_ref.get("id")
453
+ try:
454
+ resp_id = int(resp_id) if resp_id is not None else None
455
+ except Exception:
456
+ resp_id = None
457
+ else:
458
+ try:
459
+ resp_id = int(resp_ref) if resp_ref is not None else None
460
+ except Exception:
461
+ resp_id = None
462
+
463
+ if not resp_id:
464
+ return None
465
+
466
+ row = self.database_manager.execute_query_one(
467
+ "SELECT patient_id FROM questionnaire_responses WHERE id=%s LIMIT 1",
468
+ (resp_id,) # <-- ensure scalar param
469
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  try:
471
+ return int(row["patient_id"]) if row and "patient_id" in row else None
472
+ except Exception:
473
+ return None
474
+
475
+ def _rows_with_inline_images(rows: list[dict]) -> list[dict]:
476
+ """Convert local file paths to data URLs so HTML displays them anywhere."""
477
+ out = []
478
+ for r in rows or []:
479
+ r = dict(r)
480
+ if r.get("image_url"):
481
+ r["image_url"] = _to_data_url_if_local(r["image_url"])
482
+ out.append(r)
483
+ return out
484
+
485
+ # ----------------------- Blocks UI -----------------------
486
+
487
+ with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app:
488
+ # Header
489
+ 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=45krrEUpcSUQ7kNvwGVdiMW&_nc_oc=AdkTdxEC_TkYGiyDkEtTJZ_DFZELW17XKFmWpswmFqGB7JSdvTyWtnrQyLS0USngEiY&_nc_zt=23&_nc_ht=scontent.fccu31-2.fna&_nc_gid=ufAA4Hj5gTRwON5POYzz0Q&oh=00_AfW1-jLEN5RGeggqOvGgEaK_gdg0EDgxf_VhKbZwFLUO0Q&oe=6897A98B"
490
+ gr.HTML(f"""
491
+ <div class="medical-header">
492
+ <img src="{logo_url}" class="logo" alt="SmartHeal Logo">
493
+ <div>
494
+ <h1>SmartHeal AI</h1>
495
+ <p>Advanced Wound Care Analysis & Clinical Support System</p>
496
+ </div>
497
+ </div>
498
+ """)
499
+
500
+ # Disclaimer
501
+ gr.HTML("""
502
+ <div style="border:2px solid #FF6B6B;background:#FFE5E5;padding:15px;border-radius:12px;margin:10px 0;">
503
+ <h3 style="color:#D63031;margin:0 0 8px 0;">⚠️ IMPORTANT DISCLAIMER</h3>
504
+ <p><strong>This system is for testing/education and not a substitute for clinical judgment.</strong></p>
505
+ </div>
506
+ """)
507
+
508
+ # Panels: auth vs practitioner vs organization
509
+ with gr.Row():
510
+ with gr.Column(visible=True) as auth_panel:
511
+ with gr.Tabs():
512
+ with gr.Tab("πŸ” Professional Login"):
513
+ login_username = gr.Textbox(label="πŸ‘€ Username")
514
+ login_password = gr.Textbox(label="πŸ”’ Password", type="password")
515
+ login_btn = gr.Button("πŸš€ Sign In", variant="primary")
516
+ login_status = gr.HTML("<div class='status-warning'>Please sign in.</div>")
517
+
518
+ with gr.Tab("πŸ“ New Registration"):
519
+ signup_username = gr.Textbox(label="πŸ‘€ Username")
520
+ signup_email = gr.Textbox(label="πŸ“§ Email")
521
+ signup_password = gr.Textbox(label="πŸ”’ Password", type="password")
522
+ signup_name = gr.Textbox(label="πŸ‘¨β€βš•οΈ Full Name")
523
+ signup_role = gr.Radio(["practitioner", "organization"], label="Account Type", value="practitioner")
524
+
525
+ with gr.Group(visible=False) as org_fields:
526
+ org_name = gr.Textbox(label="Organization Name")
527
+ phone = gr.Textbox(label="Phone")
528
+ country_code = gr.Textbox(label="Country Code")
529
+ department = gr.Textbox(label="Department")
530
+ location = gr.Textbox(label="Location")
531
+
532
+ with gr.Group(visible=True) as prac_fields:
533
+ organization_dropdown = gr.Dropdown(choices=self.get_organizations_dropdown(), label="Select Organization")
534
+
535
+ signup_btn = gr.Button("✨ Create Account", variant="primary")
536
+ signup_status = gr.HTML()
537
+
538
+ with gr.Column(visible=False) as practitioner_panel:
539
+ user_info = gr.HTML("")
540
+ logout_btn_prac = gr.Button("πŸšͺ Logout", variant="secondary")
541
+
542
+ with gr.Tabs():
543
+ # ------------------- WOUND ANALYSIS -------------------
544
+ with gr.Tab("πŸ”¬ Wound Analysis"):
545
+ with gr.Row():
546
+ with gr.Column(scale=1):
547
+ gr.HTML("<h3>πŸ“‹ Patient Selection</h3>")
548
+ patient_mode = gr.Radio(
549
+ ["Existing patient", "New patient"],
550
+ label="Patient mode",
551
+ value="Existing patient"
552
+ )
553
+ existing_patient_dd = gr.Dropdown(
554
+ choices=[],
555
+ label="Select existing patient (ID β€’ Name)",
556
+ interactive=True
557
+ )
558
+ with gr.Group(visible=False) as new_patient_group:
559
+ new_patient_name = gr.Textbox(label="Patient Name")
560
+ new_patient_age = gr.Number(label="Age", value=30, minimum=0, maximum=120)
561
+ new_patient_gender = gr.Dropdown(choices=["Male", "Female", "Other"], value="Male", label="Gender")
562
+
563
+ gr.HTML("<h3>🩹 Wound Information</h3>")
564
+ wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left ankle")
565
+ wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks")
566
+ pain_level = gr.Slider(0, 10, value=5, step=1, label="Pain Level (0-10)")
567
+
568
+ gr.HTML("<h3>βš•οΈ Clinical Assessment</h3>")
569
+ moisture_level = gr.Dropdown(["Dry", "Moist", "Wet", "Saturated"], value="Moist", label="Moisture Level")
570
+ infection_signs = gr.Dropdown(["None", "Mild", "Moderate", "Severe"], value="None", label="Signs of Infection")
571
+ diabetic_status = gr.Dropdown(["Non-diabetic", "Type 1", "Type 2", "Gestational"], value="Non-diabetic", label="Diabetic Status")
572
+
573
+ with gr.Column(scale=1):
574
+ gr.HTML("<h3>πŸ“Έ Wound Image</h3>")
575
+ wound_image = gr.Image(label="Upload Wound Image", type="filepath")
576
+ gr.HTML("<h3>πŸ“ Medical History</h3>")
577
+ previous_treatment = gr.Textbox(label="Previous Treatment", lines=3)
578
+ medical_history = gr.Textbox(label="Medical History", lines=3)
579
+ medications = gr.Textbox(label="Current Medications", lines=2)
580
+ allergies = gr.Textbox(label="Known Allergies", lines=2)
581
+ additional_notes = gr.Textbox(label="Additional Notes", lines=3)
582
+
583
+ analyze_btn = gr.Button("πŸ”¬ Analyze Wound", variant="primary", elem_id="analyze-btn")
584
+ analysis_output = gr.HTML("")
585
+
586
+ # ------------------- PATIENT HISTORY -------------------
587
+ with gr.Tab("πŸ“‹ Patient History"):
588
+ with gr.Row():
589
+ with gr.Column(scale=2):
590
+ history_btn = gr.Button("πŸ“„ Load Patient History", variant="primary")
591
+ patient_history_output = gr.HTML("")
592
+ with gr.Column(scale=1):
593
+ search_patient_name = gr.Textbox(label="Search patient by name")
594
+ search_patient_btn = gr.Button("πŸ” Search", variant="secondary")
595
+ specific_patient_output = gr.HTML("")
596
+
597
+ gr.HTML("<hr style='margin:10px 0 6px 0;border:none;border-top:1px solid #e2e8f0'>")
598
+ with gr.Row():
599
+ view_details_dd = gr.Dropdown(choices=[], label="Select patient to view details")
600
+ view_details_btn = gr.Button("πŸ“ˆ View Details (Timeline)", variant="primary")
601
+ view_details_output = gr.HTML("")
602
+
603
+ with gr.Column(visible=False) as organization_panel:
604
+ gr.HTML("<div class='status-warning'>Organization dashboard coming soon.</div>")
605
+ logout_btn_org = gr.Button("πŸšͺ Logout", variant="secondary")
606
+
607
+ # ----------------------- handlers -----------------------
608
+
609
+ def toggle_role_fields(role):
610
+ return {
611
+ org_fields: gr.update(visible=(role == "organization")),
612
+ prac_fields: gr.update(visible=(role != "organization"))
613
+ }
614
+
615
+ def handle_signup(username, email, password, name, role, org_name_v, phone_v, cc_v, dept_v, loc_v, org_dropdown):
616
+ try:
617
+ if role == "organization":
618
+ org_data = {
619
+ 'org_name': org_name_v,
620
+ 'email': email,
621
+ 'phone': phone_v,
622
+ 'country_code': cc_v,
623
+ 'department': dept_v,
624
+ 'location': loc_v
625
+ }
626
+ org_id = self.database_manager.create_organization(org_data)
627
+ else:
628
+ # For now pick first org (or default)
629
+ org_id = 1
630
+
631
+ user_data = {
632
+ 'username': username, 'email': email, 'password': password,
633
+ 'name': name, 'role': role, 'org_id': org_id
634
  }
635
+ ok = self.auth_manager.create_user(user_data)
636
+ if ok:
637
+ return "<div class='status-success'>βœ… Account created. Please log in.</div>"
638
+ return "<div class='status-error'>❌ Could not create account. Username/email may exist.</div>"
639
+ except Exception as e:
640
+ return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
641
+
642
+ def handle_login(username, password):
643
+ user = self.auth_manager.authenticate_user(username, password)
644
+ if not user:
645
+ return {
646
+ login_status: "<div class='status-error'>❌ Invalid credentials.</div>"
647
+ }
648
+ self.current_user = user
649
+ uid = int(user.get("id"))
650
+ role = user.get("role")
651
+
652
+ # Preload patient dropdowns for practitioners
653
+ if role == "practitioner":
654
+ _refresh_patient_dropdown(uid)
655
+
656
+ info = f"<div class='status-success'>Welcome, <strong>{html.escape(user.get('name','User'))}</strong> β€” {html.escape(role)}</div>"
657
+ updates = {login_status: info}
658
+
659
+ if role == "practitioner":
660
+ updates.update({
661
+ auth_panel: gr.update(visible=False),
662
+ practitioner_panel: gr.update(visible=True),
663
+ user_info: info,
664
+ existing_patient_dd: gr.update(choices=self._patient_choices),
665
+ view_details_dd: gr.update(choices=self._patient_choices),
666
+ })
667
  else:
668
+ updates.update({
669
+ auth_panel: gr.update(visible=False),
670
+ organization_panel: gr.update(visible=True),
671
+ })
672
+ return updates
673
+
674
+ def handle_logout():
675
+ self.current_user = {}
 
 
 
 
 
 
 
 
 
676
  return {
677
+ auth_panel: gr.update(visible=True),
678
+ practitioner_panel: gr.update(visible=False),
679
+ organization_panel: gr.update(visible=False)
680
  }
681
+
682
+ def on_patient_mode_change(mode):
683
+ return {
684
+ new_patient_group: gr.update(visible=(mode == "New patient")),
685
+ existing_patient_dd: gr.update(interactive=(mode == "Existing patient"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
  }
687
+
688
+ def run_analysis(mode, existing_label,
689
+ np_name, np_age, np_gender,
690
+ w_loc, w_dur, pain, moist, infect, diabetic,
691
+ prev_tx, med_hist, meds, alls, notes, img_path):
 
 
692
  try:
693
+ if not img_path:
694
+ return "<div class='status-error'>❌ Please upload a wound image.</div>"
695
+
696
+ user_id = int(self.current_user.get("id", 0) or 0)
697
+ if not user_id:
698
+ return "<div class='status-error'>❌ Please login first.</div>"
699
+
700
+ # Determine patient core fields (ensures same patient_id for existing)
701
+ if mode == "Existing patient":
702
+ pid = _label_to_id(existing_label)
703
+ if not pid:
704
+ return "<div class='status-warning'>⚠️ Select an existing patient.</div>"
705
+ pcore = _fetch_patient_core(pid)
706
+ patient_name_v = pcore.get("name")
707
+ patient_age_v = pcore.get("age")
708
+ patient_gender_v = pcore.get("gender")
709
+ else:
710
+ patient_name_v = np_name
711
+ patient_age_v = np_age
712
+ patient_gender_v = np_gender
713
+
714
+ # Build questionnaire payload
715
+ q_payload = {
716
+ 'user_id': user_id,
717
+ 'patient_name': patient_name_v,
718
+ 'patient_age': patient_age_v,
719
+ 'patient_gender': patient_gender_v,
720
+ 'wound_location': w_loc,
721
+ 'wound_duration': w_dur,
722
+ 'pain_level': pain,
723
+ 'moisture_level': moist,
724
+ 'infection_signs': infect,
725
+ 'diabetic_status': diabetic,
726
+ 'previous_treatment': prev_tx,
727
+ 'medical_history': med_hist,
728
+ 'medications': meds,
729
+ 'allergies': alls,
730
+ 'additional_notes': notes
731
+ }
732
+
733
+ # Save questionnaire -> response_id
734
+ response_id = self.database_manager.save_questionnaire(q_payload)
735
+
736
+ # πŸ”’ Normalize in case an older/alternate version returns a dict
737
+ if isinstance(response_id, dict):
738
+ response_id = response_id.get("response_id") or response_id.get("id")
739
+ try:
740
+ response_id = int(response_id)
741
+ except Exception:
742
+ return "<div class='status-error'>❌ Could not resolve response ID.</div>"
743
+
744
+ # Resolve patient_id from response (works for new or existing)
745
+ patient_id = _response_to_patient_id(response_id)
746
+ if not patient_id:
747
+ return "<div class='status-error'>❌ Could not resolve patient ID.</div>"
748
+
749
+ # Save wound image to DB
750
+ try:
751
+ with Image.open(img_path) as pil:
752
+ pil = pil.convert("RGB")
753
+ img_meta = self.database_manager.save_wound_image(patient_id, pil)
754
+ image_db_id = img_meta["id"] if img_meta else None
755
+ except Exception as e:
756
+ logging.error(f"save_wound_image error: {e}")
757
+ image_db_id = None
758
+
759
+ # Prepare AI analyzer questionnaire dict
760
+ q_for_ai = {
761
+ 'age': patient_age_v,
762
+ 'diabetic': 'Yes' if diabetic != 'Non-diabetic' else 'No',
763
+ 'allergies': alls,
764
+ 'date_of_injury': 'Unknown',
765
+ 'professional_care': 'Yes',
766
+ 'oozing_bleeding': 'Minor Oozing' if infect != 'None' else 'None',
767
+ 'infection': 'Yes' if infect != 'None' else 'No',
768
+ 'moisture': moist,
769
+ 'patient_name': patient_name_v,
770
+ 'patient_gender': patient_gender_v,
771
+ 'wound_location': w_loc,
772
+ 'wound_duration': w_dur,
773
+ 'pain_level': pain,
774
+ 'previous_treatment': prev_tx,
775
+ 'medical_history': med_hist,
776
+ 'medications': meds,
777
+ 'additional_notes': notes
778
+ }
779
+
780
+ # Run AI
781
+ analysis_result = self.wound_analyzer.analyze_wound(img_path, q_for_ai)
782
+ if not analysis_result or not analysis_result.get("success"):
783
+ err = (analysis_result or {}).get("error", "Unknown analysis error")
784
+ return f"<div class='status-error'>❌ AI Analysis failed: {html.escape(str(err))}</div>"
785
+
786
+ # Persist AI analysis (ties back to template via response->questionnaire_id)
787
+ try:
788
+ self.database_manager.save_analysis(response_id, image_db_id, analysis_result)
789
+ except Exception as e:
790
+ logging.error(f"save_analysis error: {e}")
791
+
792
+ # If a new patient was created, refresh dropdowns
793
+ if mode == "New patient":
794
+ _refresh_patient_dropdown(user_id)
795
+
796
+ # Render fancy results
797
+ return self._format_comprehensive_analysis_results(
798
+ analysis_result, img_path, q_for_ai
799
+ )
800
+ except Exception as e:
801
+ logging.exception("run_analysis exception")
802
+ return f"<div class='status-error'>❌ System error: {html.escape(str(e))}</div>"
803
+
804
+ def load_history():
805
  try:
806
+ uid = int(self.current_user.get("id", 0) or 0)
807
+ if not uid:
808
+ return "<div class='status-error'>❌ Please login first.</div>"
809
+ rows = self.patient_history_manager.get_user_patient_history(uid) or []
810
+ rows = _rows_with_inline_images(rows)
811
+ return self.patient_history_manager.format_history_for_display(rows)
812
  except Exception as e:
813
+ logging.error(f"load_history error: {e}")
814
+ return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
815
+
816
+ def do_search(name):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
817
  try:
818
+ uid = int(self.current_user.get("id", 0) or 0)
819
+ if not uid:
820
+ return "<div class='status-error'>❌ Please login first.</div>"
821
+ if not (name or "").strip():
822
+ return "<div class='status-warning'>⚠️ Enter a name to search.</div>"
823
+ rows = self.patient_history_manager.search_patient_by_name(uid, name.strip()) or []
824
+ rows = _rows_with_inline_images(rows)
825
+ return self.patient_history_manager.format_patient_data_for_display(rows)
826
  except Exception as e:
827
+ logging.error(f"search error: {e}")
828
+ return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
829
+
830
+ def view_details(existing_label):
831
+ try:
832
+ uid = int(self.current_user.get("id", 0) or 0)
833
+ if not uid:
834
+ return "<div class='status-error'>❌ Please login first.</div>"
835
+ pid = _label_to_id(existing_label)
836
+ if not pid:
837
+ return "<div class='status-warning'>⚠️ Select a patient.</div>"
838
+ rows = self.patient_history_manager.get_wound_progression_by_id(uid, pid) or []
839
+ rows = _rows_with_inline_images(rows)
840
+ return self.patient_history_manager.format_patient_progress_for_display(rows)
841
+ except Exception as e:
842
+ logging.error(f"view_details error: {e}")
843
+ return f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"
844
+
845
+ # ----------------------- wiring -----------------------
846
+
847
+ signup_role.change(
848
+ toggle_role_fields,
849
+ inputs=[signup_role],
850
+ outputs=[org_fields, prac_fields]
851
+ )
852
+
853
+ signup_btn.click(
854
+ handle_signup,
855
+ inputs=[signup_username, signup_email, signup_password, signup_name, signup_role,
856
+ org_name, phone, country_code, department, location, organization_dropdown],
857
+ outputs=[signup_status]
858
+ )
859
+
860
+ login_btn.click(
861
+ handle_login,
862
+ inputs=[login_username, login_password],
863
+ outputs=[login_status, auth_panel, practitioner_panel, organization_panel,
864
+ user_info, existing_patient_dd, view_details_dd]
865
+ )
866
+
867
+ logout_btn_prac.click(handle_logout, outputs=[auth_panel, practitioner_panel, organization_panel])
868
+ logout_btn_org.click(handle_logout, outputs=[auth_panel, practitioner_panel, organization_panel])
869
+
870
+ patient_mode.change(
871
+ on_patient_mode_change,
872
+ inputs=[patient_mode],
873
+ outputs=[new_patient_group, existing_patient_dd]
874
+ )
875
+
876
+ analyze_btn.click(
877
+ run_analysis,
878
+ inputs=[
879
+ patient_mode, existing_patient_dd,
880
+ new_patient_name, new_patient_age, new_patient_gender,
881
+ wound_location, wound_duration, pain_level, moisture_level, infection_signs, diabetic_status,
882
+ previous_treatment, medical_history, medications, allergies, additional_notes, wound_image
883
+ ],
884
+ outputs=[analysis_output]
885
+ )
886
+
887
+ history_btn.click(load_history, outputs=[patient_history_output])
888
+ search_patient_btn.click(do_search, inputs=[search_patient_name], outputs=[specific_patient_output])
889
+ view_details_btn.click(view_details, inputs=[view_details_dd], outputs=[view_details_output])
890
+
891
+ return app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
 
893
 
894
  def _format_comprehensive_analysis_results(self, analysis_result, image_url=None, questionnaire_data=None):