SmartHeal commited on
Commit
16d2a6f
Β·
verified Β·
1 Parent(s): ea28d19

Update src/ui_components_original.py

Browse files
Files changed (1) hide show
  1. src/ui_components_original.py +343 -410
src/ui_components_original.py CHANGED
@@ -376,12 +376,18 @@ button.gr-button:hover, button.gr-button-primary:hover {
376
  """
377
 
378
  def create_interface(self):
379
- """Create the main Gradio interface with comprehensive analysis display"""
380
- with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app:
381
-
382
- # Header with SmartHeal logo
 
 
 
 
 
 
383
  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"
384
-
385
  gr.HTML(f"""
386
  <div class="medical-header">
387
  <img src="{logo_url}" class="logo" alt="SmartHeal Logo">
@@ -391,525 +397,452 @@ button.gr-button:hover, button.gr-button-primary:hover {
391
  </div>
392
  </div>
393
  """)
394
-
395
- # Professional disclaimer
396
  gr.HTML("""
397
- <div style="border: 2px solid #FF6B6B; background-color: #FFE5E5; padding: 15px; border-radius: 12px; margin: 10px 0;">
398
- <h3 style="color: #D63031; margin-top: 0;">⚠️ IMPORTANT DISCLAIMER</h3>
399
- <p><strong>This model is for testing and educational purposes only and is NOT a replacement for professional medical advice.</strong></p>
400
- <p>Information generated may be inaccurate. Always consult a qualified healthcare provider for medical concerns. This AI system uses chain-of-thought reasoning to show its decision-making process, but should never be used as the sole basis for clinical decisions.</p>
401
- <p><em>Uploaded images may be stored and used for testing and model improvement purposes.</em></p>
402
- </div>
403
- """)
404
-
405
- # Main interface with conditional visibility
406
  with gr.Row():
407
- # Authentication Panel (visible when not logged in)
408
  with gr.Column(visible=True) as auth_panel:
409
  gr.HTML("""
410
  <div style="text-align: center; margin: 40px 0;">
411
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px; border-radius: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto;">
412
- <h2 style="color: white; font-size: 2.5rem; margin-bottom: 10px; font-weight: 700;">πŸ₯ SmartHeal Access</h2>
413
- <p style="color: rgba(255,255,255,0.9); font-size: 1.1rem; margin-bottom: 30px;">Secure Healthcare Professional Portal</p>
414
  </div>
415
  </div>
416
  """)
417
-
418
  with gr.Tabs():
419
- with gr.Tab("πŸ” Professional Login") as login_tab:
420
- gr.HTML("""
421
- <div style="background: white; padding: 40px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.1); margin: 20px auto; max-width: 450px;">
422
- <div style="text-align: center; margin-bottom: 30px;">
423
- <h3 style="color: #2d3748; font-size: 1.8rem; margin-bottom: 8px;">Welcome Back</h3>
424
- <p style="color: #718096; font-size: 1rem;">Access your professional dashboard</p>
425
- </div>
426
- </div>
427
- """)
428
-
429
- login_username = gr.Textbox(
430
- label="πŸ‘€ Username",
431
- placeholder="Enter your username"
432
- )
433
- login_password = gr.Textbox(
434
- label="πŸ”’ Password",
435
- type="password",
436
- placeholder="Enter your secure password"
437
- )
438
-
439
- login_btn = gr.Button(
440
- "πŸš€ Sign In to Dashboard",
441
- variant="primary",
442
- size="lg"
443
- )
444
-
445
- login_status = gr.HTML(
446
- value="<div style='text-align: center; color: #718096; font-size: 0.9rem; margin-top: 15px;'>Enter your credentials to access the system</div>"
447
- )
448
-
449
- with gr.Tab("πŸ“ New Registration") as signup_tab:
450
- gr.HTML("""
451
- <div style="background: white; padding: 40px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.1); margin: 20px auto; max-width: 450px;">
452
- <div style="text-align: center; margin-bottom: 30px;">
453
- <h3 style="color: #2d3748; font-size: 1.8rem; margin-bottom: 8px;">Create Account</h3>
454
- <p style="color: #718096; font-size: 1rem;">Join the SmartHeal healthcare network</p>
455
- </div>
456
- </div>
457
- """)
458
-
459
- signup_username = gr.Textbox(
460
- label="πŸ‘€ Username",
461
- placeholder="Choose a unique username"
462
- )
463
- signup_email = gr.Textbox(
464
- label="πŸ“§ Email Address",
465
- placeholder="Enter your professional email"
466
- )
467
- signup_password = gr.Textbox(
468
- label="πŸ”’ Password",
469
- type="password",
470
- placeholder="Create a strong password"
471
- )
472
- signup_name = gr.Textbox(
473
- label="πŸ‘¨β€βš•οΈ Full Name",
474
- placeholder="Enter your full professional name"
475
- )
476
- signup_role = gr.Radio(
477
- ["practitioner", "organization"],
478
- label="πŸ₯ Account Type",
479
- value="practitioner"
480
- )
481
-
482
- # Organization-specific fields
483
  with gr.Group(visible=False) as org_fields:
484
- gr.HTML("<h4 style='color: #2d3748; margin: 20px 0 10px 0;'>🏒 Organization Details</h4>")
485
  org_name = gr.Textbox(label="Organization Name", placeholder="Enter organization name")
486
  phone = gr.Textbox(label="Phone Number", placeholder="Enter contact number")
487
  country_code = gr.Textbox(label="Country Code", placeholder="e.g., +1, +44")
488
  department = gr.Textbox(label="Department", placeholder="e.g., Emergency, Surgery")
489
  location = gr.Textbox(label="Location", placeholder="City, State/Province, Country")
490
-
491
- # Practitioner-specific fields
492
  with gr.Group(visible=True) as prac_fields:
493
- gr.HTML("<h4 style='color: #2d3748; margin: 20px 0 10px 0;'>πŸ₯ Affiliation</h4>")
494
- organization_dropdown = gr.Dropdown(
495
- choices=self.get_organizations_dropdown(),
496
- label="Select Your Organization"
497
- )
498
-
499
- signup_btn = gr.Button(
500
- "✨ Create Professional Account",
501
- variant="primary",
502
- size="lg"
503
- )
504
-
505
- signup_status = gr.HTML(
506
- value="<div style='text-align: center; color: #718096; font-size: 0.9rem; margin-top: 15px;'>Fill in your details to create an account</div>"
507
- )
508
-
509
- # Practitioner Interface (hidden initially)
510
  with gr.Column(visible=False) as practitioner_panel:
511
- gr.HTML('<div class="medical-card-title">πŸ‘©β€βš•οΈ Practitioner Dashboard</div>')
512
-
513
  user_info = gr.HTML("")
514
  logout_btn_prac = gr.Button("πŸšͺ Logout", variant="secondary")
515
-
516
- # Main tabs for different functions
517
  with gr.Tabs():
518
- # WOUND ANALYSIS TAB
519
  with gr.Tab("πŸ”¬ Wound Analysis"):
520
  with gr.Row():
521
  with gr.Column(scale=1):
522
  gr.HTML("<h3>πŸ“‹ Patient Information</h3>")
523
  patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient's full name")
524
  patient_age = gr.Number(label="Age", value=30, minimum=0, maximum=120)
525
- patient_gender = gr.Dropdown(
526
- choices=["Male", "Female", "Other"],
527
- label="Gender",
528
- value="Male"
529
- )
530
-
531
  gr.HTML("<h3>🩹 Wound Information</h3>")
532
  wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left ankle, Right arm")
533
  wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks, 1 month")
534
- pain_level = gr.Slider(
535
- minimum=0, maximum=10, value=5, step=1,
536
- label="Pain Level (0-10)"
537
- )
538
-
539
  gr.HTML("<h3>βš•οΈ Clinical Assessment</h3>")
540
- moisture_level = gr.Dropdown(
541
- choices=["Dry", "Moist", "Wet", "Saturated"],
542
- label="Moisture Level",
543
- value="Moist"
544
- )
545
- infection_signs = gr.Dropdown(
546
- choices=["None", "Mild", "Moderate", "Severe"],
547
- label="Signs of Infection",
548
- value="None"
549
- )
550
- diabetic_status = gr.Dropdown(
551
- choices=["Non-diabetic", "Type 1", "Type 2", "Gestational"],
552
- label="Diabetic Status",
553
- value="Non-diabetic"
554
- )
555
-
556
  with gr.Column(scale=1):
557
  gr.HTML("<h3>πŸ“Έ Wound Image Upload</h3>")
558
- wound_image = gr.Image(
559
- label="Upload Wound Image",
560
- type="filepath"
561
- )
562
-
563
  gr.HTML("<h3>πŸ“ Medical History</h3>")
564
- previous_treatment = gr.Textbox(
565
- label="Previous Treatment",
566
- placeholder="Describe any previous treatments...",
567
- lines=3
568
- )
569
- medical_history = gr.Textbox(
570
- label="Medical History",
571
- placeholder="Relevant medical conditions, surgeries, etc...",
572
- lines=3
573
- )
574
- medications = gr.Textbox(
575
- label="Current Medications",
576
- placeholder="List current medications...",
577
- lines=2
578
- )
579
- allergies = gr.Textbox(
580
- label="Known Allergies",
581
- placeholder="List any known allergies...",
582
- lines=2
583
- )
584
- additional_notes = gr.Textbox(
585
- label="Additional Notes",
586
- placeholder="Any additional clinical observations...",
587
- lines=3
588
- )
589
-
590
  analyze_btn = gr.Button("πŸ”¬ Analyze Wound", variant="primary", size="lg", elem_id="analyze-btn")
591
  analysis_output = gr.HTML("")
592
-
593
- # PATIENT HISTORY TAB
594
  with gr.Tab("πŸ“‹ Patient History"):
595
  with gr.Row():
596
  with gr.Column(scale=2):
597
  gr.HTML("<h3>πŸ“Š Patient History Dashboard</h3>")
598
  history_btn = gr.Button("πŸ“‹ Load Patient History", variant="primary")
599
  patient_history_output = gr.HTML("")
600
-
601
  with gr.Column(scale=1):
602
- gr.HTML("<h3>πŸ” Search Specific Patient</h3>")
603
- search_patient_name = gr.Textbox(
604
- label="Patient Name",
605
- placeholder="Enter patient name to search..."
 
 
 
 
606
  )
607
- search_patient_btn = gr.Button("πŸ” Search Patient History", variant="secondary")
 
 
 
 
 
 
 
608
  specific_patient_output = gr.HTML("")
609
-
610
- # Event handlers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  def handle_login(username, password):
612
  user_data = self.auth_manager.authenticate_user(username, password)
613
  if user_data:
614
  self.current_user = user_data
 
615
  return {
616
  auth_panel: gr.update(visible=False),
617
  practitioner_panel: gr.update(visible=True),
618
- login_status: "<div class='status-success'>βœ… Login successful! Welcome to SmartHeal</div>"
 
619
  }
620
  else:
621
- return {
622
- login_status: "<div class='status-error'>❌ Invalid credentials. Please try again.</div>"
623
- }
624
-
625
- def handle_signup(username, email, password, name, role, org_name, phone, country_code, department, location, organization_dropdown):
626
  try:
627
  if role == "organization":
628
  org_data = {
629
- 'org_name': org_name,
630
- 'phone': phone,
631
- 'country_code': country_code,
632
- 'department': department,
633
- 'location': location
634
- }
635
- org_id = self.database_manager.create_organization(org_data)
636
-
637
- user_data = {
638
- 'username': username,
639
  'email': email,
640
- 'password': password,
641
- 'name': name,
642
- 'role': role,
643
- 'org_id': org_id
644
  }
 
 
645
  else:
646
- # Extract org_id from dropdown selection
647
- org_id = 1 # Default organization for now
648
- user_data = {
649
- 'username': username,
650
- 'email': email,
651
- 'password': password,
652
- 'name': name,
653
- 'role': role,
654
- 'org_id': org_id
655
- }
656
-
657
  if self.auth_manager.create_user(user_data):
658
- return {
659
- signup_status: "<div class='status-success'>βœ… Account created successfully! Please login.</div>"
660
- }
661
  else:
662
- return {
663
- signup_status: "<div class='status-error'>❌ Failed to create account. Username or email may already exist.</div>"
664
- }
665
  except Exception as e:
666
- return {
667
- signup_status: f"<div class='status-error'>❌ Error: {str(e)}</div>"
668
- }
669
-
670
- def handle_analysis(patient_name, patient_age, patient_gender, wound_location, wound_duration,
671
- pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment,
672
- medical_history, medications, allergies, additional_notes, wound_image):
 
 
 
 
 
 
 
 
 
 
673
  try:
674
- if not wound_image:
675
  return "<div class='status-error'>❌ Please upload a wound image for analysis.</div>"
676
-
677
- # Show loading state first
678
- loading_html = """
679
- <div style="text-align:center; padding: 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; color: white; margin: 20px 0;">
680
- <div style="display:inline-block; border:4px solid rgba(255,255,255,0.3); border-radius:50%; border-top-color:white; width:50px; height:50px; animation:spin 1s linear infinite; margin-bottom: 20px;"></div>
681
- <h3 style="margin: 0; font-size: 1.5rem;">πŸ”¬ SmartHeal AI Processing</h3>
682
- <p style="margin: 10px 0 0 0; font-size: 1rem; opacity: 0.9;">Analyzing wound image with advanced computer vision...</p>
683
- <div style="background: rgba(255,255,255,0.1); border-radius: 8px; padding: 10px; margin-top: 15px;">
684
- <p style="margin: 0; font-size: 0.9rem;">⚑ Detection β†’ πŸ“ Segmentation β†’ πŸ€– AI Report</p>
685
- </div>
686
- <style>@keyframes spin {0% {transform:rotate(0deg)} 100% {transform:rotate(360deg)}}</style>
687
- </div>
688
- """
689
-
690
- # 1. Save questionnaire FIRST to get valid ID
691
  questionnaire_data_for_db = {
692
- 'user_id': self.current_user.get('id', 1), # Default to 1 if no user logged in
693
- 'patient_name': patient_name,
694
- 'patient_age': patient_age,
695
- 'patient_gender': patient_gender,
696
- 'wound_location': wound_location,
697
- 'wound_duration': wound_duration,
698
- 'pain_level': pain_level,
699
- 'moisture_level': moisture_level,
700
- 'infection_signs': infection_signs,
701
- 'diabetic_status': diabetic_status,
702
- 'previous_treatment': previous_treatment,
703
- 'medical_history': medical_history,
704
- 'medications': medications,
705
- 'allergies': allergies,
706
- 'additional_notes': additional_notes
707
  }
708
-
709
- # Save questionnaire FIRST to get the ID
710
- questionnaire_response_id = self.database_manager.save_questionnaire(questionnaire_data_for_db)
711
- logging.info(f"βœ… Questionnaire saved with response ID: {questionnaire_response_id}")
712
-
713
- # 2. Create AIProcessor questionnaire format
714
  questionnaire_data_for_ai = {
715
- 'age': patient_age,
716
- 'diabetic': 'Yes' if diabetic_status != 'Non-diabetic' else 'No',
717
- 'allergies': allergies,
718
- 'date_of_injury': 'Unknown', # Not collected in this form
719
- 'professional_care': 'Yes', # Assumed since using professional interface
720
- 'oozing_bleeding': 'Minor Oozing' if infection_signs != 'None' else 'None',
721
- 'infection': 'Yes' if infection_signs != 'None' else 'No',
722
- 'moisture': moisture_level,
723
- # Additional comprehensive fields
724
- 'patient_name': patient_name,
725
- 'patient_gender': patient_gender,
726
- 'wound_location': wound_location,
727
- 'wound_duration': wound_duration,
728
- 'pain_level': pain_level,
729
- 'previous_treatment': previous_treatment,
730
- 'medical_history': medical_history,
731
- 'medications': medications,
732
- 'additional_notes': additional_notes
733
  }
734
-
735
- # 3. Run AI analysis using the AIProcessor class
 
 
 
 
736
  try:
737
- logging.info(f"πŸ”¬ Starting AI analysis for image: {wound_image}")
738
-
739
- # Use the AIProcessor analyze_wound method
740
- analysis_result = self.wound_analyzer.analyze_wound(wound_image, questionnaire_data_for_ai)
741
-
742
- if not analysis_result.get('success', False):
743
- error_msg = analysis_result.get('error', 'Analysis failed for unknown reason')
744
- logging.error(f"❌ AI Analysis failed: {error_msg}")
745
-
746
- return f"""
747
- <div class='status-error' style='padding: 30px; background: #fff5f5; border-left: 5px solid #e53e3e; margin: 20px 0; border-radius: 12px;'>
748
- <h3 style="color: #c53030; margin-top: 0;">❌ AI Analysis Error</h3>
749
- <p style="color: #742a2a; font-size: 1.1rem;"><strong>Error Details:</strong></p>
750
- <div style='background: #fed7d7; padding: 15px; border-radius: 8px; margin: 10px 0;'>
751
- <code style='color: #742a2a; font-size: 0.9rem;'>{error_msg}</code>
752
- </div>
753
- <p style="color: #742a2a;">Please try again with a different image or contact technical support.</p>
754
- <div style="margin-top: 15px; padding: 15px; background: #fff; border-radius: 8px; border: 1px solid #feb2b2;">
755
- <strong>Troubleshooting Tips:</strong>
756
- <ul style="margin: 10px 0; color: #742a2a;">
757
- <li>Ensure image is clear and well-lit</li>
758
- <li>Wound should be clearly visible in the image</li>
759
- <li>Try a different image format (PNG, JPG)</li>
760
- <li>Check image file size (max 10MB recommended)</li>
761
- </ul>
762
- </div>
763
- </div>
764
- """
765
-
766
- logging.info("βœ… AI Analysis completed successfully")
767
-
768
- # 4. Save analysis result with valid questionnaire_response_id
769
- try:
770
- if questionnaire_response_id:
771
- self.database_manager.save_analysis_result(questionnaire_response_id, analysis_result)
772
- logging.info("βœ… Analysis result saved to database")
773
- except Exception as db_error:
774
- logging.error(f"❌ Database save error: {db_error}")
775
- # Continue with display even if DB save fails
776
-
777
- # 5. Format comprehensive analysis results with all images
778
- formatted_analysis = self._format_comprehensive_analysis_results(
779
- analysis_result, wound_image, questionnaire_data_for_ai
780
- )
781
-
782
- return formatted_analysis
783
-
784
- except Exception as analysis_error:
785
- logging.error(f"❌ AI analysis exception: {analysis_error}", exc_info=True)
786
- return f"""
787
- <div class='status-error' style='padding: 30px; background: #fff5f5; border-left: 5px solid #e53e3e; margin: 20px 0; border-radius: 12px;'>
788
- <h3 style="color: #c53030; margin-top: 0;">❌ Analysis Processing Error</h3>
789
- <p style="color: #742a2a;">There was an unexpected error during wound analysis:</p>
790
- <div style='background: #fed7d7; padding: 15px; border-radius: 8px; margin: 15px 0;'>
791
- <code style='color: #742a2a; font-size: 0.9rem; word-break: break-word;'>{str(analysis_error)}</code>
792
- </div>
793
- <p style="color: #742a2a;"><strong>Next Steps:</strong></p>
794
- <ul style="color: #742a2a; margin: 10px 0;">
795
- <li>Refresh the page and try again</li>
796
- <li>Check your internet connection</li>
797
- <li>Try with a different wound image</li>
798
- <li>Contact system administrator if the problem persists</li>
799
- </ul>
800
- </div>
801
- """
802
-
803
  except Exception as e:
804
  logging.error(f"❌ Analysis handler error: {e}", exc_info=True)
805
- return f"""
806
- <div class='status-error' style='padding: 30px; background: #fff5f5; border-left: 5px solid #e53e3e; margin: 20px 0; border-radius: 12px;'>
807
- <h3 style="color: #c53030; margin-top: 0;">❌ System Error</h3>
808
- <p style="color: #742a2a;">A system error occurred while processing your request:</p>
809
- <div style='background: #fed7d7; padding: 15px; border-radius: 8px; margin: 15px 0;'>
810
- <code style='color: #742a2a; font-size: 0.9rem;'>{str(e)}</code>
811
- </div>
812
- <p style="color: #742a2a;">Please contact technical support if this issue continues.</p>
813
- </div>
814
- """
815
-
816
- def handle_logout():
817
- self.current_user = {}
818
- return {
819
- auth_panel: gr.update(visible=True),
820
- practitioner_panel: gr.update(visible=False)
821
- }
822
-
823
- def toggle_role_fields(role):
824
- if role == "organization":
825
- return {
826
- org_fields: gr.update(visible=True),
827
- prac_fields: gr.update(visible=False)
828
- }
829
- else:
830
- return {
831
- org_fields: gr.update(visible=False),
832
- prac_fields: gr.update(visible=True)
833
- }
834
-
835
  def load_patient_history():
836
  try:
837
  user_id = self.current_user.get('id', 1)
838
  if not user_id:
839
  return "<div class='status-error'>❌ Please login first.</div>"
840
-
841
  history_data = self.patient_history_manager.get_user_patient_history(user_id)
842
- formatted_history = self.patient_history_manager.format_history_for_display(history_data)
843
- return formatted_history
844
  except Exception as e:
845
  logging.error(f"Error loading patient history: {e}")
846
- return f"<div class='status-error'>❌ Error loading history: {str(e)}</div>"
847
-
848
- def search_specific_patient(patient_name):
849
  try:
850
- user_id = self.current_user.get('id', 1)
851
- if not user_id:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852
  return "<div class='status-error'>❌ Please login first.</div>"
853
-
854
- if not patient_name.strip():
855
  return "<div class='status-warning'>⚠️ Please enter a patient name to search.</div>"
856
-
857
- patient_data = self.patient_history_manager.search_patient_by_name(user_id, patient_name.strip())
858
- if patient_data:
859
- formatted_data = self.patient_history_manager.format_patient_data_for_display(patient_data)
860
- return formatted_data
861
- else:
862
- return f"<div class='status-warning'>⚠️ No records found for patient: {patient_name}</div>"
863
-
864
  except Exception as e:
865
  logging.error(f"Error searching patient: {e}")
866
- return f"<div class='status-error'>❌ Error searching patient: {str(e)}</div>"
867
-
868
- # Bind event handlers
869
  login_btn.click(
870
  handle_login,
871
  inputs=[login_username, login_password],
872
- outputs=[auth_panel, practitioner_panel, login_status]
873
  )
874
-
875
  signup_btn.click(
876
  handle_signup,
877
- inputs=[signup_username, signup_email, signup_password, signup_name, signup_role,
878
- org_name, phone, country_code, department, location, organization_dropdown],
 
 
879
  outputs=[signup_status]
880
  )
881
-
882
  signup_role.change(
883
  toggle_role_fields,
884
  inputs=[signup_role],
885
  outputs=[org_fields, prac_fields]
886
  )
887
-
888
  analyze_btn.click(
889
  handle_analysis,
890
- inputs=[patient_name, patient_age, patient_gender, wound_location, wound_duration,
891
- pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment,
892
- medical_history, medications, allergies, additional_notes, wound_image],
 
 
893
  outputs=[analysis_output]
894
  )
895
-
896
  logout_btn_prac.click(
897
  handle_logout,
898
  outputs=[auth_panel, practitioner_panel]
899
  )
900
-
901
  history_btn.click(
902
  load_patient_history,
903
  outputs=[patient_history_output]
904
  )
905
-
 
 
 
 
 
 
 
 
 
 
 
906
  search_patient_btn.click(
907
- search_specific_patient,
908
  inputs=[search_patient_name],
909
  outputs=[specific_patient_output]
910
  )
911
-
912
- return app
 
913
 
914
  def _format_comprehensive_analysis_results(self, analysis_result, image_url=None, questionnaire_data=None):
915
  """Format comprehensive analysis results with all visualization images from AIProcessor."""
 
376
  """
377
 
378
  def create_interface(self):
379
+ """Create the main Gradio interface with View Details + Search Specific Patient."""
380
+ import html
381
+ import gradio as gr
382
+ import os
383
+ import logging
384
+ from datetime import datetime
385
+
386
+ with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - SmartHeal AI Wound Care Assistant") as app:
387
+
388
+ # -------------------------- HEADER --------------------------
389
  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"
390
+
391
  gr.HTML(f"""
392
  <div class="medical-header">
393
  <img src="{logo_url}" class="logo" alt="SmartHeal Logo">
 
397
  </div>
398
  </div>
399
  """)
400
+
401
+ # Disclaimer
402
  gr.HTML("""
403
+ <div style="border: 2px solid #FF6B6B; background-color: #FFE5E5; padding: 15px; border-radius: 12px; margin: 10px 0;">
404
+ <h3 style="color: #D63031; margin-top: 0;">⚠️ IMPORTANT DISCLAIMER</h3>
405
+ <p><strong>This model is for testing and educational purposes only and is NOT a replacement for professional medical advice.</strong></p>
406
+ <p>Information generated may be inaccurate. Always consult a qualified healthcare provider for medical concerns.</p>
407
+ </div>
408
+ """)
409
+
410
+ # -------------------------- LAYOUT --------------------------
 
411
  with gr.Row():
412
+ # ---------------------- AUTH PANEL -----------------------
413
  with gr.Column(visible=True) as auth_panel:
414
  gr.HTML("""
415
  <div style="text-align: center; margin: 40px 0;">
416
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px; border-radius: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto;">
417
+ <h2 style="color: white; font-size: 2.2rem; margin-bottom: 10px; font-weight: 700;">πŸ₯ SmartHeal Access</h2>
418
+ <p style="color: rgba(255,255,255,0.95); font-size: 1rem; margin-bottom: 0;">Secure Healthcare Professional Portal</p>
419
  </div>
420
  </div>
421
  """)
422
+
423
  with gr.Tabs():
424
+ with gr.Tab("πŸ” Professional Login"):
425
+ login_username = gr.Textbox(label="πŸ‘€ Username", placeholder="Enter your username")
426
+ login_password = gr.Textbox(label="πŸ”’ Password", type="password", placeholder="Enter your secure password")
427
+ login_btn = gr.Button("πŸš€ Sign In to Dashboard", variant="primary")
428
+ login_status = gr.HTML("<div style='text-align:center;color:#718096;font-size:0.9rem;margin-top:8px;'>Enter your credentials to access the system</div>")
429
+
430
+ with gr.Tab("πŸ“ New Registration"):
431
+ signup_username = gr.Textbox(label="πŸ‘€ Username", placeholder="Choose a unique username")
432
+ signup_email = gr.Textbox(label="πŸ“§ Email Address", placeholder="Enter your professional email")
433
+ signup_password = gr.Textbox(label="πŸ”’ Password", type="password", placeholder="Create a strong password")
434
+ signup_name = gr.Textbox(label="πŸ‘¨β€βš•οΈ Full Name", placeholder="Enter your full professional name")
435
+ signup_role = gr.Radio(["practitioner", "organization"], label="πŸ₯ Account Type", value="practitioner")
436
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  with gr.Group(visible=False) as org_fields:
438
+ gr.HTML("<h4 style='color:#2d3748;margin: 12px 0 8px 0;'>🏒 Organization Details</h4>")
439
  org_name = gr.Textbox(label="Organization Name", placeholder="Enter organization name")
440
  phone = gr.Textbox(label="Phone Number", placeholder="Enter contact number")
441
  country_code = gr.Textbox(label="Country Code", placeholder="e.g., +1, +44")
442
  department = gr.Textbox(label="Department", placeholder="e.g., Emergency, Surgery")
443
  location = gr.Textbox(label="Location", placeholder="City, State/Province, Country")
444
+
 
445
  with gr.Group(visible=True) as prac_fields:
446
+ gr.HTML("<h4 style='color:#2d3748;margin: 12px 0 8px 0;'>πŸ₯ Affiliation</h4>")
447
+ organization_dropdown = gr.Dropdown(choices=self.get_organizations_dropdown(), label="Select Your Organization")
448
+
449
+ signup_btn = gr.Button("✨ Create Professional Account", variant="primary")
450
+ signup_status = gr.HTML("<div style='text-align:center;color:#718096;font-size:0.9rem;margin-top:8px;'>Fill in your details to create an account</div>")
451
+
452
+ # ------------------- PRACTITIONER PANEL -------------------
 
 
 
 
 
 
 
 
 
 
453
  with gr.Column(visible=False) as practitioner_panel:
454
+ gr.HTML('<div class="medical-card-title" style="font-weight:800;font-size:20px;">πŸ‘©β€βš•οΈ Practitioner Dashboard</div>')
 
455
  user_info = gr.HTML("")
456
  logout_btn_prac = gr.Button("πŸšͺ Logout", variant="secondary")
457
+
 
458
  with gr.Tabs():
459
+ # ------------- WOUND ANALYSIS TAB -------------
460
  with gr.Tab("πŸ”¬ Wound Analysis"):
461
  with gr.Row():
462
  with gr.Column(scale=1):
463
  gr.HTML("<h3>πŸ“‹ Patient Information</h3>")
464
  patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient's full name")
465
  patient_age = gr.Number(label="Age", value=30, minimum=0, maximum=120)
466
+ patient_gender = gr.Dropdown(choices=["Male", "Female", "Other"], label="Gender", value="Male")
467
+
 
 
 
 
468
  gr.HTML("<h3>🩹 Wound Information</h3>")
469
  wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left ankle, Right arm")
470
  wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks, 1 month")
471
+ pain_level = gr.Slider(minimum=0, maximum=10, value=5, step=1, label="Pain Level (0-10)")
472
+
 
 
 
473
  gr.HTML("<h3>βš•οΈ Clinical Assessment</h3>")
474
+ moisture_level = gr.Dropdown(choices=["Dry", "Moist", "Wet", "Saturated"], label="Moisture Level", value="Moist")
475
+ infection_signs = gr.Dropdown(choices=["None", "Mild", "Moderate", "Severe"], label="Signs of Infection", value="None")
476
+ diabetic_status = gr.Dropdown(choices=["Non-diabetic", "Type 1", "Type 2", "Gestational"], label="Diabetic Status", value="Non-diabetic")
477
+
 
 
 
 
 
 
 
 
 
 
 
 
478
  with gr.Column(scale=1):
479
  gr.HTML("<h3>πŸ“Έ Wound Image Upload</h3>")
480
+ wound_image = gr.Image(label="Upload Wound Image", type="filepath")
481
+
 
 
 
482
  gr.HTML("<h3>πŸ“ Medical History</h3>")
483
+ previous_treatment = gr.Textbox(label="Previous Treatment", placeholder="Describe any previous treatments...", lines=3)
484
+ medical_history = gr.Textbox(label="Medical History", placeholder="Relevant medical conditions, surgeries, etc...", lines=3)
485
+ medications = gr.Textbox(label="Current Medications", placeholder="List current medications...", lines=2)
486
+ allergies = gr.Textbox(label="Known Allergies", placeholder="List any known allergies...", lines=2)
487
+ additional_notes = gr.Textbox(label="Additional Notes", placeholder="Any additional clinical observations...", lines=3)
488
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  analyze_btn = gr.Button("πŸ”¬ Analyze Wound", variant="primary", size="lg", elem_id="analyze-btn")
490
  analysis_output = gr.HTML("")
491
+
492
+ # ------------- PATIENT HISTORY TAB -------------
493
  with gr.Tab("πŸ“‹ Patient History"):
494
  with gr.Row():
495
  with gr.Column(scale=2):
496
  gr.HTML("<h3>πŸ“Š Patient History Dashboard</h3>")
497
  history_btn = gr.Button("πŸ“‹ Load Patient History", variant="primary")
498
  patient_history_output = gr.HTML("")
499
+
500
  with gr.Column(scale=1):
501
+ # View Details (dropdown)
502
+ gr.HTML("<h3>πŸ”Ž View Details</h3>")
503
+ patient_selector = gr.Dropdown(
504
+ choices=[],
505
+ label="Select Patient",
506
+ interactive=True,
507
+ allow_custom_value=False,
508
+ info="Pick a patient to view wound progression"
509
  )
510
+ reload_patients_btn = gr.Button("πŸ”„ Refresh Patient List", variant="secondary")
511
+ view_details_btn = gr.Button("πŸ” View Details", variant="primary")
512
+ patient_details_output = gr.HTML("")
513
+
514
+ # Search Specific Patient (restored)
515
+ gr.HTML("<h3 style='margin-top:20px;'>πŸ” Search Specific Patient</h3>")
516
+ search_patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient name to search…")
517
+ search_patient_btn = gr.Button("πŸ”Ž Search Patient History", variant="secondary")
518
  specific_patient_output = gr.HTML("")
519
+
520
+ # --------------------- HELPERS & HANDLERS ---------------------
521
+
522
+ def _risk_chip_ui(level: str) -> str:
523
+ rl = (level or "Unknown").strip().lower()
524
+ bg, fg = "#f0f0f0", "#333333"
525
+ if rl.startswith("low"):
526
+ bg, fg = "#d4edda", "#155724"
527
+ elif rl.startswith("moderate"):
528
+ bg, fg = "#fff3cd", "#856404"
529
+ elif rl.startswith("high"):
530
+ bg, fg = "#f8d7da", "#721c24"
531
+ return f"<span style='background:{bg};color:{fg};padding:6px 10px;border-radius:999px;font-weight:700;font-size:12px;letter-spacing:.4px;text-transform:uppercase;'>{html.escape(level or 'Unknown')}</span>"
532
+
533
+ def _render_progression_timeline(rows):
534
+ if not rows:
535
+ return "<div class='status-warning'>No wound progression data found for this patient.</div>"
536
+
537
+ head = """
538
+ <div style="border:1px solid #e2e8f0;border-radius:16px;overflow:hidden;background:white;box-shadow:0 8px 24px rgba(0,0,0,.06);">
539
+ <div style="background:linear-gradient(135deg,#2563eb 0%,#1e3a8a 100%);color:white;padding:24px 28px;">
540
+ <h2 style="margin:0;font-size:22px;font-weight:800;letter-spacing:.2px;">🩺 Wound Progression</h2>
541
+ <p style="margin:6px 0 0 0;opacity:.95;">Chronological clinical snapshots with AI risk assessment</p>
542
+ </div>
543
+ <div style="padding:22px 22px 6px 22px">
544
+ """
545
+ items = []
546
+ for i, r in enumerate(rows, start=1):
547
+ dt = r.get("visit_date")
548
+ try:
549
+ dt_str = dt.strftime('%b %d, %Y β€’ %I:%M %p') if hasattr(dt, "strftime") else str(dt)
550
+ except Exception:
551
+ dt_str = str(dt)
552
+
553
+ risk = _risk_chip_ui(r.get("risk_level"))
554
+ wound_loc = r.get("wound_location") or "N/A"
555
+ moisture = r.get("moisture") or "β€”"
556
+ infection = r.get("infection") or "β€”"
557
+ pain = r.get("pain_level", "N/A")
558
+ summary = r.get("summary")
559
+ img = r.get("image_url")
560
+
561
+ summary_block = (
562
+ f"<div style='margin-top:12px;color:#0f172a;background:white;border:1px solid #e2e8f0;border-radius:10px;padding:12px;'><strong>Summary:</strong> {html.escape(str(summary))}</div>"
563
+ if summary else ""
564
+ )
565
+ img_block = (
566
+ f"<div style='margin-top:12px;'><img src='{html.escape(str(img))}' style='max-width:360px;border-radius:12px;border:1px solid #e2e8f0;box-shadow:0 6px 18px rgba(0,0,0,.06)'></div>"
567
+ if img else ""
568
+ )
569
+
570
+ card = f"""
571
+ <div style="display:grid;grid-template-columns:32px 1fr;gap:16px;position:relative;margin-bottom:22px;">
572
+ <div style="display:flex;align-items:center;justify-content:center;">
573
+ <div style="width:12px;height:12px;background:#2563eb;border:2px solid white;border-radius:999px;box-shadow:0 0 0 3px rgba(37,99,235,.15)"></div>
574
+ <div style="position:absolute;left:6px;top:24px;bottom:-10px;width:2px;background:linear-gradient(180deg, rgba(203,213,225,1), rgba(203,213,225,0));"></div>
575
+ </div>
576
+ <div style="border:1px solid #e2e8f0;border-radius:12px;padding:16px;background:#f9fafb;">
577
+ <div style="display:flex;flex-wrap:wrap;align-items:center;gap:10px;justify-content:space-between;">
578
+ <div style="font-weight:800;color:#0f172a;letter-spacing:.2px;">Visit #{i}</div>
579
+ <div style="color:#475569;font-weight:600;">{html.escape(dt_str)}</div>
580
+ </div>
581
+ <div style="margin-top:10px;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;">
582
+ <div style="background:white;border:1px solid #e2e8f0;border-radius:10px;padding:12px;">
583
+ <div style="font-size:12px;color:#64748b;font-weight:700;letter-spacing:.4px;text-transform:uppercase;">Location</div>
584
+ <div style="font-weight:700;color:#0f172a;">{html.escape(str(wound_loc))}</div>
585
+ </div>
586
+ <div style="background:white;border:1px solid #e2e8f0;border-radius:10px;padding:12px;">
587
+ <div style="font-size:12px;color:#64748b;font-weight:700;letter-spacing:.4px;text-transform:uppercase;">Pain</div>
588
+ <div style="font-weight:700;color:#0f172a;">{html.escape(str(pain))} / 10</div>
589
+ </div>
590
+ <div style="background:white;border:1px solid #e2e8f0;border-radius:10px;padding:12px;">
591
+ <div style="font-size:12px;color:#64748b;font-weight:700;letter-spacing:.4px;text-transform:uppercase;">Moisture</div>
592
+ <div style="font-weight:700;color:#0f172a;">{html.escape(str(moisture))}</div>
593
+ </div>
594
+ <div style="background:white;border:1px solid #e2e8f0;border-radius:10px;padding:12px;">
595
+ <div style="font-size:12px;color:#64748b;font-weight:700;letter-spacing:.4px;text-transform:uppercase;">Infection</div>
596
+ <div style="font-weight:700;color:#0f172a;">{html.escape(str(infection))}</div>
597
+ </div>
598
+ <div style="background:white;border:1px solid #e2e8f0;border-radius:10px;padding:12px;">
599
+ <div style="font-size:12px;color:#64748b;font-weight:700;letter-spacing:.4px;text-transform:uppercase;">AI Risk</div>
600
+ <div>{risk}</div>
601
+ </div>
602
+ </div>
603
+ {summary_block}
604
+ {img_block}
605
+ </div>
606
+ </div>
607
+ """
608
+ items.append(card)
609
+
610
+ tail = "</div></div>"
611
+ return head + "".join(items) + tail
612
+
613
+ # ---------- AUTH HANDLERS ----------
614
  def handle_login(username, password):
615
  user_data = self.auth_manager.authenticate_user(username, password)
616
  if user_data:
617
  self.current_user = user_data
618
+ patient_update = _load_patient_names()
619
  return {
620
  auth_panel: gr.update(visible=False),
621
  practitioner_panel: gr.update(visible=True),
622
+ login_status: "<div class='status-success'>βœ… Login successful! Welcome to SmartHeal</div>",
623
+ patient_selector: patient_update
624
  }
625
  else:
626
+ return {login_status: "<div class='status-error'>❌ Invalid credentials. Please try again.</div>"}
627
+
628
+ def handle_signup(username, email, password, name, role, org_name_val, phone_val, cc_val, dept_val, loc_val, org_dropdown_val):
 
 
629
  try:
630
  if role == "organization":
631
  org_data = {
632
+ 'org_name': org_name_val,
 
 
 
 
 
 
 
 
 
633
  'email': email,
634
+ 'phone': phone_val,
635
+ 'country_code': cc_val,
636
+ 'department': dept_val,
637
+ 'location': loc_val
638
  }
639
+ org_id = self.database_manager.create_organization(org_data)
640
+ user_data = {'username': username, 'email': email, 'password': password, 'name': name, 'role': role, 'org_id': org_id}
641
  else:
642
+ org_id = 1
643
+ user_data = {'username': username, 'email': email, 'password': password, 'name': name, 'role': role, 'org_id': org_id}
644
+
 
 
 
 
 
 
 
 
645
  if self.auth_manager.create_user(user_data):
646
+ return {signup_status: "<div class='status-success'>βœ… Account created successfully! Please login.</div>"}
 
 
647
  else:
648
+ return {signup_status: "<div class='status-error'>❌ Failed to create account. Username or email may already exist.</div>"}
 
 
649
  except Exception as e:
650
+ return {signup_status: f"<div class='status-error'>❌ Error: {html.escape(str(e))}</div>"}
651
+
652
+ def toggle_role_fields(role):
653
+ if role == "organization":
654
+ return {org_fields: gr.update(visible=True), prac_fields: gr.update(visible=False)}
655
+ return {org_fields: gr.update(visible=False), prac_fields: gr.update(visible=True)}
656
+
657
+ def handle_logout():
658
+ self.current_user = {}
659
+ return {auth_panel: gr.update(visible=True), practitioner_panel: gr.update(visible=False)}
660
+
661
+ # ---------- ANALYSIS HANDLER ----------
662
+ def handle_analysis(
663
+ _patient_name, _patient_age, _patient_gender, _wound_location, _wound_duration,
664
+ _pain_level, _moisture_level, _infection_signs, _diabetic_status, _previous_treatment,
665
+ _medical_history, _medications, _allergies, _additional_notes, _wound_image
666
+ ):
667
  try:
668
+ if not _wound_image:
669
  return "<div class='status-error'>❌ Please upload a wound image for analysis.</div>"
670
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
671
  questionnaire_data_for_db = {
672
+ 'user_id': self.current_user.get('id', 1),
673
+ 'patient_name': _patient_name,
674
+ 'patient_age': _patient_age,
675
+ 'patient_gender': _patient_gender,
676
+ 'wound_location': _wound_location,
677
+ 'wound_duration': _wound_duration,
678
+ 'pain_level': _pain_level,
679
+ 'moisture_level': _moisture_level,
680
+ 'infection_signs': _infection_signs,
681
+ 'diabetic_status': _diabetic_status,
682
+ 'previous_treatment': _previous_treatment,
683
+ 'medical_history': _medical_history,
684
+ 'medications': _medications,
685
+ 'allergies': _allergies,
686
+ 'additional_notes': _additional_notes
687
  }
688
+
689
+ response_id = self.database_manager.save_questionnaire(questionnaire_data_for_db)
690
+ if not response_id:
691
+ return "<div class='status-error'>❌ Could not save questionnaire (patient/profile). Check logs.</div>"
692
+
 
693
  questionnaire_data_for_ai = {
694
+ 'age': _patient_age,
695
+ 'diabetic': 'Yes' if _diabetic_status != 'Non-diabetic' else 'No',
696
+ 'allergies': _allergies,
697
+ 'date_of_injury': 'Unknown',
698
+ 'professional_care': 'Yes',
699
+ 'oozing_bleeding': 'Minor Oozing' if _infection_signs != 'None' else 'None',
700
+ 'infection': 'Yes' if _infection_signs != 'None' else 'No',
701
+ 'moisture': _moisture_level,
702
+ 'patient_name': _patient_name,
703
+ 'patient_gender': _patient_gender,
704
+ 'wound_location': _wound_location,
705
+ 'wound_duration': _wound_duration,
706
+ 'pain_level': _pain_level,
707
+ 'previous_treatment': _previous_treatment,
708
+ 'medical_history': _medical_history,
709
+ 'medications': _medications,
710
+ 'additional_notes': _additional_notes
 
711
  }
712
+
713
+ analysis_result = self.wound_analyzer.analyze_wound(_wound_image, questionnaire_data_for_ai)
714
+ if not analysis_result.get('success'):
715
+ err = analysis_result.get('error', 'Analysis failed')
716
+ return f"<div class='status-error'>❌ AI Analysis Error: {html.escape(str(err))}</div>"
717
+
718
  try:
719
+ self.database_manager.save_analysis_result(response_id, analysis_result)
720
+ except Exception as db_error:
721
+ logging.error(f"DB save (analysis_result) error: {db_error}")
722
+
723
+ formatted = self._format_comprehensive_analysis_results(analysis_result, _wound_image, questionnaire_data_for_ai)
724
+ return formatted
725
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  except Exception as e:
727
  logging.error(f"❌ Analysis handler error: {e}", exc_info=True)
728
+ return f"<div class='status-error'>❌ System Error: {html.escape(str(e))}</div>"
729
+
730
+ # ---------- HISTORY HELPERS ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
  def load_patient_history():
732
  try:
733
  user_id = self.current_user.get('id', 1)
734
  if not user_id:
735
  return "<div class='status-error'>❌ Please login first.</div>"
 
736
  history_data = self.patient_history_manager.get_user_patient_history(user_id)
737
+ formatted = self.patient_history_manager.format_history_for_display(history_data)
738
+ return formatted
739
  except Exception as e:
740
  logging.error(f"Error loading patient history: {e}")
741
+ return f"<div class='status-error'>❌ Error loading history: {html.escape(str(e))}</div>"
742
+
743
+ def _load_patient_names():
744
  try:
745
+ uid = self.current_user.get('id', 1)
746
+ plist = self.patient_history_manager.get_patient_list(uid) or []
747
+ names, seen = [], set()
748
+ for row in plist:
749
+ nm = row.get("patient_name")
750
+ if nm and nm not in seen:
751
+ names.append(nm); seen.add(nm)
752
+ if not names:
753
+ names = ["β€” No patients yet β€”"]
754
+ return gr.update(choices=names, value=(names[0] if names else None))
755
+ except Exception as e:
756
+ logging.error(f"load patients error: {e}")
757
+ return gr.update(choices=["β€” Error loading β€”"], value=None)
758
+
759
+ def view_details_action(selected_name):
760
+ try:
761
+ uid = self.current_user.get('id', 1)
762
+ if not selected_name or selected_name.startswith("β€”"):
763
+ return "<div class='status-warning'>Please select a valid patient.</div>"
764
+ rows = self.patient_history_manager.get_wound_progression(uid, selected_name)
765
+ return _render_progression_timeline(rows)
766
+ except Exception as e:
767
+ logging.error(f"view details error: {e}")
768
+ return f"<div class='status-error'>Error loading details: {html.escape(str(e))}</div>"
769
+
770
+ def search_specific_patient_action(name_text):
771
+ try:
772
+ uid = self.current_user.get('id', 1)
773
+ if not uid:
774
  return "<div class='status-error'>❌ Please login first.</div>"
775
+ if not name_text or not name_text.strip():
 
776
  return "<div class='status-warning'>⚠️ Please enter a patient name to search.</div>"
777
+ rows = self.patient_history_manager.search_patient_by_name(uid, name_text.strip())
778
+ if not rows:
779
+ return f"<div class='status-warning'>⚠️ No records found for patient: {html.escape(name_text.strip())}</div>"
780
+ return self.patient_history_manager.format_patient_data_for_display(rows)
 
 
 
 
781
  except Exception as e:
782
  logging.error(f"Error searching patient: {e}")
783
+ return f"<div class='status-error'>❌ Error searching patient: {html.escape(str(e))}</div>"
784
+
785
+ # --------------------------- BINDINGS ---------------------------
786
  login_btn.click(
787
  handle_login,
788
  inputs=[login_username, login_password],
789
+ outputs=[auth_panel, practitioner_panel, login_status, patient_selector]
790
  )
791
+
792
  signup_btn.click(
793
  handle_signup,
794
+ inputs=[
795
+ signup_username, signup_email, signup_password, signup_name, signup_role,
796
+ org_name, phone, country_code, department, location, organization_dropdown
797
+ ],
798
  outputs=[signup_status]
799
  )
800
+
801
  signup_role.change(
802
  toggle_role_fields,
803
  inputs=[signup_role],
804
  outputs=[org_fields, prac_fields]
805
  )
806
+
807
  analyze_btn.click(
808
  handle_analysis,
809
+ inputs=[
810
+ patient_name, patient_age, patient_gender, wound_location, wound_duration,
811
+ pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment,
812
+ medical_history, medications, allergies, additional_notes, wound_image
813
+ ],
814
  outputs=[analysis_output]
815
  )
816
+
817
  logout_btn_prac.click(
818
  handle_logout,
819
  outputs=[auth_panel, practitioner_panel]
820
  )
821
+
822
  history_btn.click(
823
  load_patient_history,
824
  outputs=[patient_history_output]
825
  )
826
+
827
+ reload_patients_btn.click(
828
+ _load_patient_names,
829
+ outputs=[patient_selector]
830
+ )
831
+
832
+ view_details_btn.click(
833
+ view_details_action,
834
+ inputs=[patient_selector],
835
+ outputs=[patient_details_output]
836
+ )
837
+
838
  search_patient_btn.click(
839
+ search_specific_patient_action,
840
  inputs=[search_patient_name],
841
  outputs=[specific_patient_output]
842
  )
843
+
844
+ return app
845
+
846
 
847
  def _format_comprehensive_analysis_results(self, analysis_result, image_url=None, questionnaire_data=None):
848
  """Format comprehensive analysis results with all visualization images from AIProcessor."""