DocUA commited on
Commit
be1b5d2
·
1 Parent(s): 17f3ad3

feat: Fix classification logic and remove redundant spiritual care message functionality

Browse files

## Major Changes

### 🔧 Classification Logic Fixes (review/Or_2.txt recommendations)
- Fix JSON parsing bug: LLM returns 'classification' but parser expected 'state'
- Update RED flag definition: broader than crisis-only, includes active spiritual distress
- Add explicit RED indicators: complex grief, loss of loved one, doubt about meaning of life/suffering/dignity
- Fix 'I am fine' classification: now correctly GREEN (was incorrectly YELLOW)
- Fix 'feeling sad' classification: now correctly YELLOW without spiritual context (was incorrectly RED)
- Loss of meaning/purpose now correctly RED with high confidence

### 🗑️ Remove Redundant Spiritual Care Message UI
- Remove redundant '💬 Care Team Message' tab from Provider Summary panel
- Remove related UI controls and event handlers
- Fix undefined 'spiritual_care_msg' variable in return statements
- Preserve prompt editing and model settings (now used for Medical Brain Summary)
- Medical Brain Summary now first tab by default in '�� Check Status & Summary'

### 🧪 Enhanced Testing & Quality
- Add comprehensive unit tests for improved classification logic (9/9 passing)
- Add integration tests for UI classification improvements
- Add property-based testing framework for classification consistency
- Add error handling and recovery mechanisms

### 📊 New Components Added
- ImprovedClassificationPromptManager: Enhanced classification with explicit indicators
- Enhanced display integration for better UI formatting
- Provider summary formatter with Medical Brain compatibility
- Visual separation manager for improved UI organization
- UI error handler with fallback mechanisms

## Files Modified
- Core: spiritual_monitor.py, provider_summary_generator.py, simplified_medical_app.py
- Interface: chat_handlers.py, stats_handlers.py, simplified_gradio_app.py
- Prompts: spiritual_monitor.txt, spiritual_care_message.txt
- Tests: 6 new unit test files, 3 new integration test files

## Test Results
- Unit tests: 170/175 passing (5 failures unrelated to changes)
- Integration tests: 59/59 passing
- Classification tests: 9/9 passing
- All core functionality verified working

## Classification Test Results
✅ 'I am fine' → GREEN (confidence 1.0) - Fixed from incorrect YELLOW
✅ 'I feel sad' → YELLOW (confidence 0.9) - Fixed from incorrect RED
✅ 'Life has no meaning' → RED (confidence 1.0) - Correctly identifies explicit indicator

Files changed (38) hide show
  1. .gitignore +2 -0
  2. examples/coherent_summary_demo.py +272 -0
  3. examples/enhanced_config_demo.py +269 -0
  4. examples/enhanced_display_demo.py +279 -0
  5. examples/enhanced_verification_demo.py +266 -0
  6. examples/error_handling_demo.py +279 -0
  7. examples/integration_demo.py +206 -0
  8. examples/visual_separation_demo.py +248 -0
  9. src/config/display/enhanced_display_config.json +60 -0
  10. src/config/enhanced_display_config.py +682 -0
  11. src/config/prompts/spiritual_care_message.txt +25 -1
  12. src/config/prompts/spiritual_monitor.txt +35 -21
  13. src/core/conversation_verification.py +199 -28
  14. src/core/improved_classification_prompt_manager.py +409 -0
  15. src/core/provider_summary_generator.py +429 -62
  16. src/core/simplified_medical_app.py +115 -9
  17. src/core/soft_triage_manager.py +17 -12
  18. src/core/spiritual_monitor.py +92 -134
  19. src/core/ui_error_handler.py +687 -0
  20. src/core/verification_exporter.py +212 -23
  21. src/core/verification_store.py +58 -7
  22. src/interface/chat_handlers.py +36 -23
  23. src/interface/enhanced_display_integration.py +321 -0
  24. src/interface/enhanced_results_display_manager.py +533 -0
  25. src/interface/help_content.py +20 -27
  26. src/interface/provider_summary_formatter.py +290 -0
  27. src/interface/simplified_gradio_app.py +34 -54
  28. src/interface/stats_handlers.py +21 -45
  29. src/interface/visual_separation_manager.py +388 -0
  30. tests/integration/test_enhanced_verification_integration.py +274 -0
  31. tests/integration/test_error_handling_integration.py +204 -0
  32. tests/integration/test_ui_classification_improvements_integration.py +558 -0
  33. tests/unit/test_coherent_summary_formatter.py +247 -0
  34. tests/unit/test_enhanced_display_config.py +325 -0
  35. tests/unit/test_enhanced_results_display.py +472 -0
  36. tests/unit/test_improved_classification_prompt_manager.py +296 -0
  37. tests/unit/test_spiritual_monitor_improved_logic.py +235 -0
  38. tests/unit/test_ui_error_handler.py +404 -0
.gitignore CHANGED
@@ -120,3 +120,5 @@ start.sh
120
  .zshenv
121
  src/core/verification_store.py
122
  manual_tests/test_vertex_auth.py
 
 
 
120
  .zshenv
121
  src/core/verification_store.py
122
  manual_tests/test_vertex_auth.py
123
+
124
+ review/
examples/coherent_summary_demo.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # coherent_summary_demo.py
3
+ """
4
+ Demonstration of Coherent Summary Formatter functionality.
5
+
6
+ This script shows the new coherent paragraph formatting functionality
7
+ added to ProviderSummaryGenerator for requirements 2.1-2.8.
8
+ """
9
+
10
+ import sys
11
+ import os
12
+
13
+ # Add project root to Python path
14
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15
+ if project_root not in sys.path:
16
+ sys.path.insert(0, project_root)
17
+
18
+ from src.core.provider_summary_generator import ProviderSummaryGenerator, ProviderSummary
19
+
20
+
21
+ def demo_coherent_paragraph_formatting():
22
+ """Demonstrate coherent paragraph formatting functionality."""
23
+ print("=" * 80)
24
+ print("MEDICAL BRAIN SUMMARY FORMATTING DEMO")
25
+ print("=" * 80)
26
+ print("This demo shows the new coherent paragraph formatting (Requirements 2.1-2.8)")
27
+ print("compared to the existing structured format.\n")
28
+
29
+ generator = ProviderSummaryGenerator()
30
+
31
+ # Example 1: Complete patient information
32
+ print("EXAMPLE 1: Complete Patient Information")
33
+ print("-" * 50)
34
+
35
+ summary1 = ProviderSummary(
36
+ patient_name="Sarah Johnson",
37
+ patient_phone="(555) 234-5678",
38
+ indicators=["Fear of death", "Loss of faith", "Family burden"],
39
+ situation_description="existential crisis related to cancer diagnosis",
40
+ medical_context={
41
+ "age": 34,
42
+ "gender": "female",
43
+ "conditions": ["Breast cancer", "Chemotherapy treatment", "Anxiety"]
44
+ },
45
+ conversation_context="I don't understand why God would let this happen to me and my children"
46
+ )
47
+
48
+ print("COHERENT PARAGRAPH FORMAT:")
49
+ coherent1 = generator.format_coherent_paragraph(summary1)
50
+ print(coherent1)
51
+
52
+ print("\n" + "=" * 50)
53
+ print("STRUCTURED FORMAT (for comparison):")
54
+ structured1 = generator.format_for_display(summary1)
55
+ print(structured1[:300] + "..." if len(structured1) > 300 else structured1)
56
+
57
+ print("\n" + "=" * 80 + "\n")
58
+
59
+ # Example 2: Minimal patient information
60
+ print("EXAMPLE 2: Minimal Patient Information")
61
+ print("-" * 50)
62
+
63
+ summary2 = ProviderSummary(
64
+ patient_name="John Doe",
65
+ patient_phone="",
66
+ indicators=["Spiritual questioning"],
67
+ situation_description="seeking meaning in illness",
68
+ medical_context={"age": 45, "gender": "male"}
69
+ )
70
+
71
+ print("COHERENT PARAGRAPH FORMAT:")
72
+ coherent2 = generator.format_coherent_paragraph(summary2)
73
+ print(coherent2)
74
+
75
+ print("\n" + "=" * 80 + "\n")
76
+
77
+ # Example 3: Complex medical history
78
+ print("EXAMPLE 3: Complex Medical History")
79
+ print("-" * 50)
80
+
81
+ summary3 = ProviderSummary(
82
+ patient_name="Michael Chen",
83
+ patient_phone="(555) 345-6789",
84
+ indicators=["Hopelessness", "Isolation", "Loss of purpose", "Spiritual abandonment"],
85
+ situation_description="severe spiritual crisis with multiple stressors",
86
+ medical_context={
87
+ "age": 67,
88
+ "gender": "male",
89
+ "conditions": ["Heart Disease", "Diabetes", "COPD", "Depression", "Chronic Pain"]
90
+ },
91
+ conversation_context="I've been fighting this for years and I'm tired. What's the point anymore?"
92
+ )
93
+
94
+ print("COHERENT PARAGRAPH FORMAT:")
95
+ coherent3 = generator.format_coherent_paragraph(summary3)
96
+ print(coherent3)
97
+
98
+ print("\n" + "=" * 80 + "\n")
99
+
100
+ # Example 4: Unknown demographics
101
+ print("EXAMPLE 4: Unknown Demographics")
102
+ print("-" * 50)
103
+
104
+ summary4 = ProviderSummary(
105
+ patient_name="Patient Anonymous",
106
+ patient_phone="(555) 000-0000",
107
+ indicators=["General distress"],
108
+ situation_description="unspecified spiritual concern",
109
+ medical_context={} # No demographic info
110
+ )
111
+
112
+ print("COHERENT PARAGRAPH FORMAT:")
113
+ coherent4 = generator.format_coherent_paragraph(summary4)
114
+ print(coherent4)
115
+
116
+ print("\n" + "=" * 80 + "\n")
117
+
118
+
119
+ def demo_requirements_compliance():
120
+ """Demonstrate compliance with specific requirements 2.1-2.8."""
121
+ print("REQUIREMENTS COMPLIANCE DEMONSTRATION")
122
+ print("=" * 80)
123
+
124
+ generator = ProviderSummaryGenerator()
125
+
126
+ # Create comprehensive example
127
+ summary = ProviderSummary(
128
+ patient_name="Emma Wilson",
129
+ patient_phone="(555) 987-6543",
130
+ indicators=["Loss of meaning", "Spiritual crisis", "Fear of dying"],
131
+ situation_description="existential distress following terminal diagnosis",
132
+ medical_context={
133
+ "age": 52,
134
+ "gender": "female",
135
+ "conditions": ["Pancreatic cancer", "Metastatic disease"]
136
+ },
137
+ conversation_context="I used to believe everything happens for a reason, but now I just feel abandoned by God"
138
+ )
139
+
140
+ result = generator.format_coherent_paragraph(summary)
141
+
142
+ print("GENERATED COHERENT PARAGRAPH:")
143
+ print("-" * 40)
144
+ print(result)
145
+ print("\n")
146
+
147
+ print("REQUIREMENTS VERIFICATION:")
148
+ print("-" * 40)
149
+
150
+ # Split into main paragraph and quote
151
+ parts = result.split('\n\n')
152
+ main_paragraph = parts[0]
153
+ patient_quote = parts[1] if len(parts) > 1 else None
154
+
155
+ # Requirement 2.1: Single coherent paragraph
156
+ print("✅ Requirement 2.1 (Single coherent paragraph):")
157
+ print(f" Main paragraph is single line: {len(main_paragraph.split('. ')) >= 4}")
158
+
159
+ # Requirement 2.2: Demographic information
160
+ print("✅ Requirement 2.2 (Demographic information):")
161
+ demo_info = "Emma Wilson is a 52-year-old female"
162
+ print(f" Contains demographic info: {demo_info in main_paragraph}")
163
+
164
+ # Requirement 2.3: Medical history
165
+ print("✅ Requirement 2.3 (Medical history):")
166
+ medical_info = "clinical history of Pancreatic cancer and Metastatic disease"
167
+ print(f" Contains medical history: {medical_info in main_paragraph}")
168
+
169
+ # Requirement 2.4: Spiritual concerns
170
+ print("✅ Requirement 2.4 (Spiritual concerns):")
171
+ concerns_info = "Loss of meaning, Spiritual crisis, and Fear of dying"
172
+ print(f" Contains spiritual concerns: {concerns_info in main_paragraph}")
173
+
174
+ # Requirement 2.5: Classification
175
+ print("✅ Requirement 2.5 (Classification):")
176
+ classification_info = "RED FLAG"
177
+ print(f" Contains classification: {classification_info in main_paragraph}")
178
+
179
+ # Requirement 2.6: Consent information
180
+ print("✅ Requirement 2.6 (Consent information):")
181
+ consent_info = "spiritual care team contact"
182
+ print(f" Contains consent info: {consent_info in main_paragraph}")
183
+
184
+ # Requirement 2.7: Contact information
185
+ print("✅ Requirement 2.7 (Contact information):")
186
+ contact_info = "(555) 987-6543"
187
+ print(f" Contains contact info: {contact_info in main_paragraph}")
188
+
189
+ # Requirement 2.8: Patient quote
190
+ print("✅ Requirement 2.8 (Patient quote as separate line):")
191
+ print(f" Has separate patient quote: {patient_quote is not None}")
192
+ if patient_quote:
193
+ print(f" Quote content: {patient_quote}")
194
+
195
+ print("\n" + "=" * 80)
196
+
197
+
198
+ def demo_format_comparison():
199
+ """Compare different formatting options."""
200
+ print("FORMAT COMPARISON DEMONSTRATION")
201
+ print("=" * 80)
202
+
203
+ generator = ProviderSummaryGenerator()
204
+
205
+ summary = ProviderSummary(
206
+ patient_name="David Rodriguez",
207
+ patient_phone="(555) 456-7890",
208
+ indicators=["Spiritual questioning", "Anxiety about afterlife"],
209
+ situation_description="religious doubt following family tragedy",
210
+ medical_context={
211
+ "age": 38,
212
+ "gender": "male",
213
+ "conditions": ["PTSD", "Depression"]
214
+ }
215
+ )
216
+
217
+ print("1. STRUCTURED FORMAT (Original):")
218
+ print("-" * 40)
219
+ structured = generator.format_for_display(summary)
220
+ print(structured[:400] + "..." if len(structured) > 400 else structured)
221
+
222
+ print("\n2. COHERENT PARAGRAPH FORMAT (New):")
223
+ print("-" * 40)
224
+ coherent = generator.format_coherent_paragraph(summary)
225
+ print(coherent)
226
+
227
+ print("\n3. EXPORT FORMAT (Existing):")
228
+ print("-" * 40)
229
+ export = generator.format_for_export(summary)
230
+ print(export)
231
+
232
+ print("\nFORMAT CHARACTERISTICS:")
233
+ print("-" * 40)
234
+ print(f"Structured format length: {len(structured)} characters")
235
+ print(f"Coherent format length: {len(coherent)} characters")
236
+ print(f"Export format length: {len(export)} characters")
237
+ print(f"Structured format lines: {len(structured.split('\\n'))}")
238
+ print(f"Coherent format lines: {len(coherent.split('\\n'))}")
239
+
240
+ print("\n" + "=" * 80)
241
+
242
+
243
+ def main():
244
+ """Run all demonstration functions."""
245
+ print("COHERENT SUMMARY FORMATTER DEMONSTRATION")
246
+ print("=" * 80)
247
+ print("This demo shows the new coherent paragraph formatting functionality")
248
+ print("added to ProviderSummaryGenerator for requirements 2.1-2.8.")
249
+ print("The new format provides Medical Brain compatible single-paragraph summaries.")
250
+
251
+ try:
252
+ demo_coherent_paragraph_formatting()
253
+ demo_requirements_compliance()
254
+ demo_format_comparison()
255
+
256
+ print("\nDEMONSTRATION COMPLETE")
257
+ print("=" * 80)
258
+ print("✅ All coherent summary formatting functionality is working correctly!")
259
+ print("✅ Requirements 2.1-2.8 are fully implemented!")
260
+ print("✅ Integration with existing functionality is maintained!")
261
+
262
+ except Exception as e:
263
+ print(f"\nError during demonstration: {e}")
264
+ import traceback
265
+ traceback.print_exc()
266
+ return 1
267
+
268
+ return 0
269
+
270
+
271
+ if __name__ == "__main__":
272
+ sys.exit(main())
examples/enhanced_config_demo.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enhanced Configuration System Demo.
4
+
5
+ Demonstrates the configuration system for UI classification improvements
6
+ including feature toggles, styling options, and configuration management.
7
+
8
+ Requirements: 7.1, 7.2, 7.3
9
+ """
10
+
11
+ import sys
12
+ import os
13
+
14
+ # Add src to path for imports
15
+ current_dir = os.path.dirname(os.path.abspath(__file__))
16
+ parent_dir = os.path.dirname(current_dir)
17
+ src_dir = os.path.join(parent_dir, 'src')
18
+ if src_dir not in sys.path:
19
+ sys.path.insert(0, src_dir)
20
+
21
+ from config.enhanced_display_config import (
22
+ EnhancedDisplayConfig,
23
+ EnhancedDisplayConfigManager,
24
+ get_enhanced_display_config,
25
+ create_high_contrast_config,
26
+ create_minimal_config,
27
+ create_mobile_optimized_config
28
+ )
29
+ from interface.enhanced_results_display_manager import EnhancedResultsDisplayManager
30
+ from core.provider_summary_generator import ProviderSummary
31
+
32
+
33
+ def demo_basic_configuration():
34
+ """Demonstrate basic configuration usage."""
35
+ print("🔧 Enhanced Display Configuration Demo")
36
+ print("=" * 50)
37
+
38
+ # Create default configuration
39
+ config = EnhancedDisplayConfig()
40
+ print(f"✅ Default configuration created")
41
+ print(f" - Enhanced mode enabled: {config.enabled}")
42
+ print(f" - Color coding enabled: {config.use_color_coding}")
43
+ print(f" - Icons enabled: {config.use_icons}")
44
+ print(f" - Visual separators enabled: {config.use_visual_separators}")
45
+ print()
46
+
47
+ # Show classification colors
48
+ print("🎨 Classification Colors:")
49
+ print(f" - Red: {config.classification_colors.red}")
50
+ print(f" - Yellow: {config.classification_colors.yellow}")
51
+ print(f" - Green: {config.classification_colors.green}")
52
+ print()
53
+
54
+ # Show section icons
55
+ print("🎭 Section Icons:")
56
+ print(f" - AI Analysis: {config.ai_analysis.icon}")
57
+ print(f" - Patient Message: {config.patient_message.icon}")
58
+ print(f" - Provider Summary: {config.provider_summary.icon}")
59
+ print()
60
+
61
+
62
+ def demo_configuration_manager():
63
+ """Demonstrate configuration manager functionality."""
64
+ print("📁 Configuration Manager Demo")
65
+ print("=" * 50)
66
+
67
+ # Create configuration manager
68
+ config_manager = EnhancedDisplayConfigManager()
69
+ print(f"✅ Configuration manager created")
70
+
71
+ # Load configuration
72
+ config = config_manager.load_config()
73
+ print(f"✅ Configuration loaded")
74
+ print(f" - Config file: {config_manager.config_file}")
75
+ print()
76
+
77
+ # Update configuration
78
+ print("🔄 Updating configuration...")
79
+ success = config_manager.update_config(
80
+ use_icons=False,
81
+ use_color_coding=True
82
+ )
83
+ print(f" - Update successful: {success}")
84
+
85
+ # Reload and verify
86
+ updated_config = config_manager.load_config()
87
+ print(f" - Icons enabled: {updated_config.use_icons}")
88
+ print(f" - Color coding enabled: {updated_config.use_color_coding}")
89
+ print()
90
+
91
+ # Enable/disable features
92
+ print("🎛️ Feature toggles...")
93
+ config_manager.enable_feature('icons')
94
+ config_manager.disable_feature('animations')
95
+
96
+ final_config = config_manager.load_config()
97
+ print(f" - Icons enabled: {final_config.use_icons}")
98
+ print(f" - Animations enabled: {final_config.enable_animations}")
99
+ print()
100
+
101
+
102
+ def demo_preset_configurations():
103
+ """Demonstrate preset configuration options."""
104
+ print("🎨 Preset Configurations Demo")
105
+ print("=" * 50)
106
+
107
+ # High contrast configuration
108
+ high_contrast = create_high_contrast_config()
109
+ print("♿ High Contrast Configuration:")
110
+ print(f" - High contrast mode: {high_contrast.high_contrast_mode}")
111
+ print(f" - Red color: {high_contrast.classification_colors.red}")
112
+ print(f" - AI border color: {high_contrast.ai_analysis.border_color}")
113
+ print()
114
+
115
+ # Minimal configuration
116
+ minimal = create_minimal_config()
117
+ print("🔲 Minimal Configuration:")
118
+ print(f" - Icons enabled: {minimal.use_icons}")
119
+ print(f" - Visual separators: {minimal.use_visual_separators}")
120
+ print(f" - Animations enabled: {minimal.enable_animations}")
121
+ print()
122
+
123
+ # Mobile optimized configuration
124
+ mobile = create_mobile_optimized_config()
125
+ print("📱 Mobile Optimized Configuration:")
126
+ print(f" - Responsive breakpoint: {mobile.responsive_breakpoint}")
127
+ print(f" - AI padding: {mobile.ai_analysis.padding}")
128
+ print(f" - Animations enabled: {mobile.enable_animations}")
129
+ print()
130
+
131
+
132
+ def demo_css_generation():
133
+ """Demonstrate CSS generation capabilities."""
134
+ print("🎨 CSS Generation Demo")
135
+ print("=" * 50)
136
+
137
+ config = EnhancedDisplayConfig()
138
+
139
+ # Generate CSS variables
140
+ css_vars = config.generate_css_variables()
141
+ print("📝 CSS Variables (first 10 lines):")
142
+ lines = css_vars.split('\n')[:10]
143
+ for line in lines:
144
+ print(f" {line}")
145
+ print(" ...")
146
+ print()
147
+
148
+ # Generate base CSS
149
+ base_css = config.generate_base_css()
150
+ print(f"📝 Base CSS generated: {len(base_css)} characters")
151
+ print()
152
+
153
+
154
+ def demo_display_manager_integration():
155
+ """Demonstrate integration with display manager."""
156
+ print("🔗 Display Manager Integration Demo")
157
+ print("=" * 50)
158
+
159
+ # Create configuration manager
160
+ config_manager = EnhancedDisplayConfigManager()
161
+
162
+ # Create display manager with configuration
163
+ display_manager = EnhancedResultsDisplayManager(config_manager=config_manager)
164
+ print(f"✅ Display manager created with config manager")
165
+ print(f" - Enhanced mode enabled: {display_manager.is_enhanced_mode_enabled()}")
166
+ print()
167
+
168
+ # Test with different configurations
169
+ print("🎛️ Testing with different configurations...")
170
+
171
+ # Test with enhanced mode disabled
172
+ config_manager.update_config(enabled=False)
173
+ display_manager.reload_config()
174
+ print(f" - Enhanced mode after disable: {display_manager.is_enhanced_mode_enabled()}")
175
+
176
+ # Test with enhanced mode re-enabled
177
+ config_manager.update_config(enabled=True)
178
+ display_manager.reload_config()
179
+ print(f" - Enhanced mode after enable: {display_manager.is_enhanced_mode_enabled()}")
180
+ print()
181
+
182
+ # Test formatting with different configurations
183
+ print("🎨 Testing formatting with different styles...")
184
+
185
+ # Format AI analysis with default config
186
+ ai_html = display_manager.format_ai_analysis_section(
187
+ classification="RED",
188
+ indicators=["Loss of meaning", "Spiritual distress"],
189
+ reasoning="Patient expressing existential concerns and loss of purpose."
190
+ )
191
+ print(f" - AI analysis HTML length: {len(ai_html)} characters")
192
+
193
+ # Disable icons and test again
194
+ config_manager.update_config(use_icons=False)
195
+ display_manager.reload_config()
196
+
197
+ ai_html_no_icons = display_manager.format_ai_analysis_section(
198
+ classification="RED",
199
+ indicators=["Loss of meaning", "Spiritual distress"],
200
+ reasoning="Patient expressing existential concerns and loss of purpose."
201
+ )
202
+ print(f" - AI analysis HTML (no icons): {len(ai_html_no_icons)} characters")
203
+ print()
204
+
205
+
206
+ def demo_configuration_validation():
207
+ """Demonstrate configuration validation."""
208
+ print("✅ Configuration Validation Demo")
209
+ print("=" * 50)
210
+
211
+ config_manager = EnhancedDisplayConfigManager()
212
+
213
+ # Validate current configuration
214
+ issues = config_manager.validate_config()
215
+ print(f"📋 Validation results: {len(issues)} issues found")
216
+
217
+ if issues:
218
+ for issue in issues:
219
+ print(f" ⚠️ {issue}")
220
+ else:
221
+ print(" ✅ Configuration is valid")
222
+ print()
223
+
224
+ # Test with invalid configuration
225
+ print("🔧 Testing with invalid color...")
226
+ # Create a new config manager for this test to avoid corrupting the main one
227
+ test_config_manager = EnhancedDisplayConfigManager()
228
+
229
+ # Manually create invalid config for testing
230
+ test_config = test_config_manager.load_config()
231
+ # Directly modify the color to an invalid format
232
+ test_config.classification_colors.red = 'invalid-color'
233
+ test_config_manager._config = test_config
234
+
235
+ issues = test_config_manager.validate_config()
236
+ print(f"📋 Validation after invalid color: {len(issues)} issues found")
237
+ for issue in issues:
238
+ print(f" ⚠️ {issue}")
239
+ print()
240
+
241
+ # Reset to defaults
242
+ config_manager.reset_to_defaults()
243
+ print("🔄 Configuration reset to defaults")
244
+
245
+
246
+ def main():
247
+ """Run all configuration demos."""
248
+ print("🚀 Enhanced Display Configuration System Demo")
249
+ print("=" * 60)
250
+ print()
251
+
252
+ try:
253
+ demo_basic_configuration()
254
+ demo_configuration_manager()
255
+ demo_preset_configurations()
256
+ demo_css_generation()
257
+ demo_display_manager_integration()
258
+ demo_configuration_validation()
259
+
260
+ print("✅ All configuration demos completed successfully!")
261
+
262
+ except Exception as e:
263
+ print(f"❌ Demo failed with error: {e}")
264
+ import traceback
265
+ traceback.print_exc()
266
+
267
+
268
+ if __name__ == "__main__":
269
+ main()
examples/enhanced_display_demo.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # enhanced_display_demo.py
3
+ """
4
+ Demonstration of Enhanced Results Display Manager functionality.
5
+
6
+ This script shows how to use the enhanced display components to format
7
+ different types of content with improved visual separation and styling.
8
+
9
+ Requirements: 1.1, 1.2, 7.1, 7.2
10
+ """
11
+
12
+ import sys
13
+ import os
14
+
15
+ # Add project root to Python path
16
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+ if project_root not in sys.path:
18
+ sys.path.insert(0, project_root)
19
+
20
+ from src.interface.enhanced_display_integration import (
21
+ create_enhanced_display_integration,
22
+ example_format_ai_analysis_result,
23
+ example_format_patient_message,
24
+ example_format_complete_response
25
+ )
26
+ from src.interface.enhanced_results_display_manager import EnhancedDisplayConfig
27
+ from src.interface.provider_summary_formatter import PatientData, ClassificationData
28
+ from unittest.mock import Mock
29
+ from src.core.provider_summary_generator import ProviderSummary
30
+
31
+
32
+ def demo_ai_analysis_formatting():
33
+ """Demonstrate AI analysis section formatting."""
34
+ print("=" * 60)
35
+ print("AI ANALYSIS SECTION FORMATTING DEMO")
36
+ print("=" * 60)
37
+
38
+ # Example 1: RED flag analysis
39
+ red_result = example_format_ai_analysis_result(
40
+ classification="RED",
41
+ indicators=["Loss of meaning and purpose", "Spiritual crisis", "Hopelessness"],
42
+ reasoning="Patient expressed deep existential concerns and loss of faith, indicating active spiritual distress requiring immediate attention.",
43
+ confidence=0.92
44
+ )
45
+
46
+ print("RED Flag Analysis:")
47
+ print(red_result)
48
+ print("\n" + "-" * 40 + "\n")
49
+
50
+ # Example 2: YELLOW flag analysis
51
+ yellow_result = example_format_ai_analysis_result(
52
+ classification="YELLOW",
53
+ indicators=["Mild distress", "Uncertainty"],
54
+ reasoning="Patient shows some signs of spiritual questioning but unclear if it constitutes distress.",
55
+ confidence=0.65
56
+ )
57
+
58
+ print("YELLOW Flag Analysis:")
59
+ print(yellow_result)
60
+ print("\n" + "-" * 40 + "\n")
61
+
62
+ # Example 3: GREEN flag analysis
63
+ green_result = example_format_ai_analysis_result(
64
+ classification="GREEN",
65
+ indicators=[],
66
+ reasoning="Normal medical conversation with no spiritual distress indicators detected."
67
+ )
68
+
69
+ print("GREEN Flag Analysis:")
70
+ print(green_result)
71
+
72
+
73
+ def demo_patient_message_formatting():
74
+ """Demonstrate patient message section formatting."""
75
+ print("\n" + "=" * 60)
76
+ print("PATIENT MESSAGE SECTION FORMATTING DEMO")
77
+ print("=" * 60)
78
+
79
+ # Example messages
80
+ messages = [
81
+ "I'm feeling lost and don't know what to believe anymore.",
82
+ "The doctors say I'm getting better, but I feel empty inside.",
83
+ "I just want to understand why this happened to me.",
84
+ "Thank you for listening. It helps to talk about these things."
85
+ ]
86
+
87
+ for i, message in enumerate(messages, 1):
88
+ print(f"Patient Message {i}:")
89
+ result = example_format_patient_message(message)
90
+ print(result)
91
+ print("\n" + "-" * 40 + "\n")
92
+
93
+
94
+ def demo_provider_summary_formatting():
95
+ """Demonstrate provider summary formatting."""
96
+ print("\n" + "=" * 60)
97
+ print("PROVIDER SUMMARY FORMATTING DEMO")
98
+ print("=" * 60)
99
+
100
+ integration = create_enhanced_display_integration()
101
+
102
+ # Create sample patient data
103
+ patient_data = PatientData(
104
+ name="Sarah Johnson",
105
+ age=34,
106
+ gender="female",
107
+ phone="(555) 234-5678",
108
+ medical_history=["Breast cancer", "Chemotherapy treatment", "Anxiety"],
109
+ expressed_concerns=["Fear of death", "Loss of faith", "Family burden"],
110
+ patient_input="I don't understand why God would let this happen to me and my children"
111
+ )
112
+
113
+ classification_data = ClassificationData(
114
+ classification="RED",
115
+ spiritual_concern_type="existential crisis related to illness",
116
+ consent_given=True
117
+ )
118
+
119
+ # Format as coherent paragraph
120
+ coherent_summary = integration.summary_formatter.format_enhanced_summary(
121
+ patient_data, classification_data
122
+ )
123
+
124
+ print("Medical Brain Summary:")
125
+ print(coherent_summary)
126
+ print("\n" + "-" * 40 + "\n")
127
+
128
+ # Create mock provider summary for enhanced display
129
+ mock_summary = Mock(spec=ProviderSummary)
130
+ mock_summary.patient_name = "Sarah Johnson"
131
+ mock_summary.patient_phone = "(555) 234-5678"
132
+ mock_summary.urgency_level = "URGENT"
133
+ mock_summary.follow_up_timeline = "Within 24 hours"
134
+ mock_summary.situation_description = "Patient experiencing existential crisis related to cancer diagnosis"
135
+ mock_summary.indicators = ["Fear of death", "Loss of faith", "Family burden"]
136
+
137
+ # Format as enhanced display section
138
+ enhanced_display = integration.display_manager.format_provider_summary_section(mock_summary)
139
+
140
+ print("Enhanced Display Summary:")
141
+ print(enhanced_display)
142
+
143
+
144
+ def demo_complete_response_formatting():
145
+ """Demonstrate complete response formatting with all sections."""
146
+ print("\n" + "=" * 60)
147
+ print("COMPLETE RESPONSE FORMATTING DEMO")
148
+ print("=" * 60)
149
+
150
+ # Create mock provider summary
151
+ mock_summary = Mock(spec=ProviderSummary)
152
+ mock_summary.patient_name = "Michael Chen"
153
+ mock_summary.patient_phone = "(555) 345-6789"
154
+ mock_summary.urgency_level = "IMMEDIATE"
155
+ mock_summary.follow_up_timeline = "Immediately"
156
+ mock_summary.situation_description = "Patient expressing suicidal ideation and spiritual crisis"
157
+ mock_summary.indicators = ["Suicidal thoughts", "Loss of hope", "Spiritual abandonment"]
158
+
159
+ # Format complete response
160
+ complete_response = example_format_complete_response(
161
+ patient_message="I just can't take this anymore. What's the point of going on?",
162
+ classification="RED",
163
+ indicators=["Suicidal ideation", "Hopelessness", "Spiritual crisis"],
164
+ reasoning="Patient expressing clear suicidal ideation combined with spiritual distress. Immediate intervention required.",
165
+ provider_summary=mock_summary
166
+ )
167
+
168
+ print("Complete Response with All Sections:")
169
+ print(complete_response)
170
+
171
+
172
+ def demo_visual_separation():
173
+ """Demonstrate visual separation and styling features."""
174
+ print("\n" + "=" * 60)
175
+ print("VISUAL SEPARATION AND STYLING DEMO")
176
+ print("=" * 60)
177
+
178
+ integration = create_enhanced_display_integration()
179
+
180
+ # Demonstrate classification badges
181
+ print("Classification Badges:")
182
+ red_badge = integration.get_classification_badge("RED")
183
+ yellow_badge = integration.get_classification_badge("YELLOW")
184
+ green_badge = integration.get_classification_badge("GREEN")
185
+
186
+ print(f"RED: {red_badge}")
187
+ print(f"YELLOW: {yellow_badge}")
188
+ print(f"GREEN: {green_badge}")
189
+ print("\n" + "-" * 40 + "\n")
190
+
191
+ # Demonstrate urgency badges
192
+ print("Urgency Badges:")
193
+ immediate_badge = integration.get_urgency_badge("IMMEDIATE")
194
+ urgent_badge = integration.get_urgency_badge("URGENT")
195
+ standard_badge = integration.get_urgency_badge("STANDARD")
196
+
197
+ print(f"IMMEDIATE: {immediate_badge}")
198
+ print(f"URGENT: {urgent_badge}")
199
+ print(f"STANDARD: {standard_badge}")
200
+ print("\n" + "-" * 40 + "\n")
201
+
202
+ # Demonstrate separators
203
+ print("Content Separators:")
204
+ separators = ["light", "medium", "heavy", "section_break"]
205
+
206
+ for sep_type in separators:
207
+ separator = integration.create_content_separator(sep_type)
208
+ print(f"{sep_type.upper()} separator:")
209
+ print(separator)
210
+ print()
211
+
212
+
213
+ def demo_custom_configuration():
214
+ """Demonstrate custom configuration options."""
215
+ print("\n" + "=" * 60)
216
+ print("CUSTOM CONFIGURATION DEMO")
217
+ print("=" * 60)
218
+
219
+ # Create custom configuration
220
+ custom_config = EnhancedDisplayConfig(
221
+ ai_analysis_icon="🔍",
222
+ patient_message_icon="📝",
223
+ provider_summary_icon="🏥",
224
+ section_separator="***",
225
+ use_color_coding=True,
226
+ classification_colors={
227
+ "RED": "#cc0000",
228
+ "YELLOW": "#ff9900",
229
+ "GREEN": "#009900"
230
+ }
231
+ )
232
+
233
+ # Create integration with custom config
234
+ custom_integration = create_enhanced_display_integration(custom_config)
235
+
236
+ # Format example with custom styling
237
+ custom_result = custom_integration.display_manager.format_ai_analysis_section(
238
+ classification="RED",
239
+ indicators=["Custom styling example"],
240
+ reasoning="This demonstrates custom configuration options.",
241
+ confidence=0.95
242
+ )
243
+
244
+ print("Custom Configuration Result:")
245
+ print(custom_result)
246
+
247
+
248
+ def main():
249
+ """Run all demonstration functions."""
250
+ print("ENHANCED RESULTS DISPLAY MANAGER DEMONSTRATION")
251
+ print("=" * 60)
252
+ print("This demo shows the enhanced display components in action.")
253
+ print("Note: HTML output is shown as raw HTML for demonstration purposes.")
254
+ print("In a real application, this would be rendered in a web browser.")
255
+
256
+ try:
257
+ demo_ai_analysis_formatting()
258
+ demo_patient_message_formatting()
259
+ demo_provider_summary_formatting()
260
+ demo_complete_response_formatting()
261
+ demo_visual_separation()
262
+ demo_custom_configuration()
263
+
264
+ print("\n" + "=" * 60)
265
+ print("DEMONSTRATION COMPLETE")
266
+ print("=" * 60)
267
+ print("All enhanced display components are working correctly!")
268
+
269
+ except Exception as e:
270
+ print(f"\nError during demonstration: {e}")
271
+ import traceback
272
+ traceback.print_exc()
273
+ return 1
274
+
275
+ return 0
276
+
277
+
278
+ if __name__ == "__main__":
279
+ sys.exit(main())
examples/enhanced_verification_demo.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enhanced Verification System Demo.
4
+
5
+ Demonstrates the enhanced verification system with new display formats,
6
+ provider summaries, and CSV export capabilities.
7
+
8
+ Requirements: 8.2, 8.3, 8.4, 8.5
9
+ """
10
+
11
+ import tempfile
12
+ import os
13
+ from datetime import datetime
14
+
15
+ from src.core.conversation_logger import ConversationLogger
16
+ from src.core.conversation_verification import (
17
+ EnhancedConversationVerificationManager,
18
+ VerificationFeedback
19
+ )
20
+ from src.core.verification_exporter import EnhancedVerificationExporter
21
+ from src.core.spiritual_state import SpiritualState, SpiritualAssessment
22
+ from src.core.provider_summary_generator import ProviderSummary
23
+
24
+
25
+ def create_sample_conversation():
26
+ """Create a sample conversation with various classification levels."""
27
+ logger = ConversationLogger(patient_name="Demo Patient")
28
+
29
+ # Sample conversation exchanges with different classifications
30
+ exchanges = [
31
+ {
32
+ "user": "I've been struggling with the meaning of my illness lately",
33
+ "assistant": "I understand this is a very difficult time for you. Many people in your situation experience these feelings.",
34
+ "assessment": SpiritualAssessment(
35
+ state=SpiritualState.RED,
36
+ confidence=0.85,
37
+ indicators=["loss of meaning", "spiritual distress", "existential questioning"],
38
+ reasoning="Patient expressing significant spiritual concerns about meaning and purpose in context of illness"
39
+ )
40
+ },
41
+ {
42
+ "user": "Sometimes I wonder if there's any point to all this suffering",
43
+ "assistant": "Your feelings are completely valid. It's natural to question these things when facing challenges.",
44
+ "assessment": SpiritualAssessment(
45
+ state=SpiritualState.RED,
46
+ confidence=0.90,
47
+ indicators=["questioning purpose", "suffering distress", "existential crisis"],
48
+ reasoning="Patient expressing deep existential concerns about suffering and purpose"
49
+ )
50
+ },
51
+ {
52
+ "user": "I'm feeling a bit better today, but still uncertain",
53
+ "assistant": "I'm glad to hear you're feeling better. Uncertainty is normal during recovery.",
54
+ "assessment": SpiritualAssessment(
55
+ state=SpiritualState.YELLOW,
56
+ confidence=0.65,
57
+ indicators=["uncertainty", "mild improvement"],
58
+ reasoning="Patient showing some improvement but still experiencing uncertainty"
59
+ )
60
+ },
61
+ {
62
+ "user": "Thank you for listening. I feel more hopeful now",
63
+ "assistant": "You're very welcome. I'm here whenever you need support.",
64
+ "assessment": SpiritualAssessment(
65
+ state=SpiritualState.GREEN,
66
+ confidence=0.80,
67
+ indicators=["gratitude", "hope", "positive outlook"],
68
+ reasoning="Patient expressing gratitude and hope, indicating improved spiritual state"
69
+ )
70
+ }
71
+ ]
72
+
73
+ # Log all exchanges
74
+ for exchange in exchanges:
75
+ logger.log_exchange(
76
+ exchange["user"],
77
+ exchange["assistant"],
78
+ exchange["assessment"]
79
+ )
80
+
81
+ return logger
82
+
83
+
84
+ def demonstrate_enhanced_verification():
85
+ """Demonstrate the enhanced verification system."""
86
+ print("=" * 80)
87
+ print("ENHANCED VERIFICATION SYSTEM DEMONSTRATION")
88
+ print("=" * 80)
89
+ print()
90
+
91
+ # Create temporary directory for demo
92
+ temp_dir = tempfile.mkdtemp()
93
+ print(f"📁 Using temporary directory: {temp_dir}")
94
+ print()
95
+
96
+ try:
97
+ # Step 1: Create sample conversation
98
+ print("🗣️ STEP 1: Creating sample conversation...")
99
+ logger = create_sample_conversation()
100
+ print(f" ✅ Created conversation with {len(logger.entries)} exchanges")
101
+ print()
102
+
103
+ # Step 2: Create enhanced verification session
104
+ print("🔍 STEP 2: Creating enhanced verification session...")
105
+ verification_manager = EnhancedConversationVerificationManager(temp_dir)
106
+ session = verification_manager.create_verification_session(
107
+ logger,
108
+ verifier_name="Demo Verifier",
109
+ enable_enhanced_formats=True
110
+ )
111
+ print(f" ✅ Created enhanced verification session: {session.session_id}")
112
+ print(f" 📊 Enhanced format enabled: {session.enhanced_format_enabled}")
113
+ print(f" 📝 Total records: {len(session.verification_records)}")
114
+ print()
115
+
116
+ # Step 3: Show enhanced display formats
117
+ print("🎨 STEP 3: Demonstrating enhanced display formats...")
118
+ for i, record in enumerate(session.verification_records[:2], 1): # Show first 2
119
+ print(f" 📋 Record {i} Enhanced Display:")
120
+ if record.enhanced_display_format:
121
+ # Show first 200 chars of HTML
122
+ preview = record.enhanced_display_format[:200].replace('\n', ' ')
123
+ print(f" HTML Preview: {preview}...")
124
+
125
+ if record.visual_sections:
126
+ print(f" Visual Sections: {len(record.visual_sections)} sections")
127
+ for section in record.visual_sections:
128
+ print(f" - {section.get('type', 'unknown')}")
129
+ print()
130
+
131
+ # Step 4: Add provider summary to RED flag cases
132
+ print("📋 STEP 4: Adding provider summaries to RED flag cases...")
133
+ red_records = [r for r in session.verification_records if r.original_classification == "RED"]
134
+
135
+ for i, record in enumerate(red_records):
136
+ provider_summary = ProviderSummary(
137
+ patient_name="Demo Patient",
138
+ patient_phone="555-DEMO",
139
+ classification="RED",
140
+ confidence=record.original_confidence,
141
+ indicators=record.original_indicators,
142
+ reasoning=record.original_reasoning,
143
+ urgency_level="IMMEDIATE" if record.original_confidence > 0.8 else "URGENT",
144
+ severity_level="HIGH",
145
+ situation_description=f"Patient expressing {', '.join(record.original_indicators[:2])} requiring immediate spiritual care attention"
146
+ )
147
+
148
+ record.set_enhanced_formats(provider_summary=provider_summary)
149
+ print(f" ✅ Added provider summary to RED flag record {i+1}")
150
+
151
+ print(f" 📊 Provider summaries added to {len(red_records)} RED flag cases")
152
+ print()
153
+
154
+ # Step 5: Add verification feedback
155
+ print("✅ STEP 5: Adding verification feedback...")
156
+ feedbacks = [
157
+ {"correct": True, "notes": "Correctly identified spiritual distress"},
158
+ {"correct": True, "notes": "Appropriate RED classification"},
159
+ {"correct": False, "classification": "GREEN", "reason": "Patient showing improvement, should be GREEN"},
160
+ {"correct": True, "notes": "Correct GREEN classification"}
161
+ ]
162
+
163
+ for i, (record, feedback_data) in enumerate(zip(session.verification_records, feedbacks)):
164
+ feedback = VerificationFeedback(
165
+ exchange_id=record.exchange_id,
166
+ is_correct=feedback_data["correct"],
167
+ correct_classification=feedback_data.get("classification"),
168
+ correction_reason=feedback_data.get("reason"),
169
+ notes=feedback_data.get("notes")
170
+ )
171
+
172
+ success = verification_manager.submit_exchange_verification(
173
+ session.session_id,
174
+ record.exchange_id,
175
+ feedback
176
+ )
177
+
178
+ if success:
179
+ print(f" ✅ Added feedback for exchange {i+1}")
180
+ else:
181
+ print(f" ❌ Failed to add feedback for exchange {i+1}")
182
+ print()
183
+
184
+ # Step 6: Export enhanced CSV
185
+ print("📊 STEP 6: Exporting enhanced CSV with new format data...")
186
+ exporter = EnhancedVerificationExporter(temp_dir)
187
+ csv_path = exporter.export_session_to_csv(session, include_enhanced_data=True)
188
+
189
+ print(f" ✅ Exported enhanced CSV: {os.path.basename(csv_path)}")
190
+
191
+ # Show CSV preview
192
+ with open(csv_path, 'r', encoding='utf-8') as f:
193
+ lines = f.readlines()
194
+
195
+ print(f" 📄 CSV contains {len(lines)} lines")
196
+ print(" 📋 Enhanced columns included:")
197
+
198
+ # Find header line
199
+ for line in lines:
200
+ if 'has_enhanced_display' in line:
201
+ enhanced_columns = [col for col in line.split(',') if 'enhanced' in col.lower() or 'provider' in col.lower()]
202
+ for col in enhanced_columns[:5]: # Show first 5 enhanced columns
203
+ print(f" - {col.strip()}")
204
+ break
205
+ print()
206
+
207
+ # Step 7: Generate enhanced summary report
208
+ print("📈 STEP 7: Generating enhanced summary report...")
209
+ report_path = exporter.export_enhanced_summary_report(session)
210
+
211
+ print(f" ✅ Generated summary report: {os.path.basename(report_path)}")
212
+
213
+ # Show report preview
214
+ with open(report_path, 'r', encoding='utf-8') as f:
215
+ content = f.read()
216
+
217
+ # Extract key statistics
218
+ lines = content.split('\n')
219
+ for line in lines:
220
+ if 'Enhanced Display Coverage:' in line or 'Records with Enhanced Display:' in line:
221
+ print(f" 📊 {line.strip()}")
222
+ print()
223
+
224
+ # Step 8: Show session statistics
225
+ print("📈 STEP 8: Enhanced session statistics...")
226
+ stats = verification_manager.get_session_statistics(session.session_id)
227
+
228
+ if stats:
229
+ print(f" 📊 Total Exchanges: {stats.get('total_exchanges', 0)}")
230
+ print(f" ✅ Verified Exchanges: {stats.get('verified_exchanges', 0)}")
231
+ print(f" 🎯 Overall Accuracy: {stats.get('progress', {}).get('accuracy_overall', 0):.1%}")
232
+ print(f" 🎨 Enhanced Format Enabled: {stats.get('enhanced_format_enabled', False)}")
233
+ print(f" 📋 Records with Enhanced Display: {stats.get('records_with_enhanced_display', 0)}")
234
+ print(f" 📄 Records with Provider Summary: {stats.get('records_with_provider_summary', 0)}")
235
+ print()
236
+
237
+ print("🎉 DEMONSTRATION COMPLETE!")
238
+ print("=" * 80)
239
+ print()
240
+ print("📁 Generated Files:")
241
+ print(f" 📊 Enhanced CSV: {csv_path}")
242
+ print(f" 📈 Summary Report: {report_path}")
243
+ print(f" 💾 Session Data: {temp_dir}")
244
+ print()
245
+ print("✨ Enhanced verification system successfully demonstrated!")
246
+ print(" - Enhanced display formats with visual sections")
247
+ print(" - Provider summaries for RED flag cases")
248
+ print(" - Enhanced CSV export with new data fields")
249
+ print(" - Comprehensive summary reports")
250
+ print(" - Backward compatibility with existing system")
251
+
252
+ except Exception as e:
253
+ print(f"❌ Error during demonstration: {e}")
254
+ import traceback
255
+ traceback.print_exc()
256
+
257
+ finally:
258
+ # Clean up (optional - comment out to keep files for inspection)
259
+ # import shutil
260
+ # shutil.rmtree(temp_dir, ignore_errors=True)
261
+ # print(f"🧹 Cleaned up temporary directory")
262
+ pass
263
+
264
+
265
+ if __name__ == "__main__":
266
+ demonstrate_enhanced_verification()
examples/error_handling_demo.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Error Handling Demo for UI Classification Improvements.
4
+
5
+ Demonstrates error handling and recovery mechanisms in action.
6
+
7
+ Requirements: 9.1, 9.2, 9.3, 9.4
8
+ """
9
+
10
+ import sys
11
+ import os
12
+
13
+ # Add src to path for imports
14
+ current_dir = os.path.dirname(os.path.abspath(__file__))
15
+ parent_dir = os.path.dirname(current_dir)
16
+ if parent_dir not in sys.path:
17
+ sys.path.insert(0, parent_dir)
18
+
19
+ from src.core.ui_error_handler import UIErrorHandler
20
+ from src.core.provider_summary_generator import ProviderSummary, ProviderSummaryGenerator
21
+ from src.interface.enhanced_results_display_manager import EnhancedResultsDisplayManager
22
+ from src.core.improved_classification_prompt_manager import ImprovedClassificationPromptManager
23
+
24
+
25
+ def demo_provider_summary_validation():
26
+ """Demonstrate provider summary validation and error recovery."""
27
+ print("=" * 60)
28
+ print("PROVIDER SUMMARY VALIDATION DEMO")
29
+ print("=" * 60)
30
+
31
+ error_handler = UIErrorHandler()
32
+
33
+ # Create invalid summary
34
+ print("\n1. Creating invalid provider summary...")
35
+ invalid_summary = ProviderSummary(
36
+ patient_name="[Patient Name]", # Placeholder
37
+ patient_phone="[Phone Number]", # Placeholder
38
+ classification="RED",
39
+ confidence=1.5, # Invalid confidence
40
+ reasoning="", # Empty reasoning
41
+ indicators=[], # No indicators
42
+ severity_level="INVALID", # Invalid level
43
+ urgency_level="INVALID" # Invalid level
44
+ )
45
+
46
+ print(f" Patient Name: {invalid_summary.patient_name}")
47
+ print(f" Patient Phone: {invalid_summary.patient_phone}")
48
+ print(f" Confidence: {invalid_summary.confidence}")
49
+ print(f" Reasoning: '{invalid_summary.reasoning}'")
50
+ print(f" Indicators: {invalid_summary.indicators}")
51
+ print(f" Severity: {invalid_summary.severity_level}")
52
+ print(f" Urgency: {invalid_summary.urgency_level}")
53
+
54
+ # Validate summary
55
+ print("\n2. Validating summary structure...")
56
+ validation_result = error_handler.validate_provider_summary_structure(invalid_summary)
57
+
58
+ print(f" Valid: {validation_result.is_valid}")
59
+ print(f" Errors: {len(validation_result.errors)}")
60
+ print(f" Warnings: {len(validation_result.warnings)}")
61
+
62
+ if validation_result.errors:
63
+ print("\n Error Details:")
64
+ for i, error in enumerate(validation_result.errors[:3], 1): # Show first 3
65
+ print(f" {i}. {error.message}")
66
+ if error.suggestion:
67
+ print(f" Suggestion: {error.suggestion}")
68
+
69
+ # Apply fallback template
70
+ print("\n3. Applying fallback template...")
71
+ fixed_summary = error_handler.apply_fallback_template(invalid_summary, "general")
72
+
73
+ print(f" Patient Name: {fixed_summary.patient_name}")
74
+ print(f" Patient Phone: {fixed_summary.patient_phone}")
75
+ print(f" Confidence: {fixed_summary.confidence}")
76
+ print(f" Reasoning: '{fixed_summary.reasoning[:50]}...'")
77
+ print(f" Indicators: {fixed_summary.indicators}")
78
+ print(f" Severity: {fixed_summary.severity_level}")
79
+ print(f" Urgency: {fixed_summary.urgency_level}")
80
+
81
+ # Re-validate
82
+ print("\n4. Re-validating fixed summary...")
83
+ new_validation = error_handler.validate_provider_summary_structure(fixed_summary)
84
+ print(f" Valid: {new_validation.is_valid}")
85
+ print(f" Errors: {len(new_validation.errors)}")
86
+ print(f" Warnings: {len(new_validation.warnings)}")
87
+
88
+
89
+ def demo_display_error_handling():
90
+ """Demonstrate display error handling and degraded mode."""
91
+ print("\n" + "=" * 60)
92
+ print("DISPLAY ERROR HANDLING DEMO")
93
+ print("=" * 60)
94
+
95
+ display_manager = EnhancedResultsDisplayManager()
96
+
97
+ # Test with invalid data
98
+ print("\n1. Testing display with invalid data...")
99
+ try:
100
+ result = display_manager.format_ai_analysis_section(
101
+ classification="", # Empty classification
102
+ indicators=[], # Empty indicators
103
+ reasoning="", # Empty reasoning
104
+ confidence=None
105
+ )
106
+
107
+ print(" Display result generated successfully")
108
+ print(f" Result length: {len(result)} characters")
109
+ print(f" Contains 'AI Analysis': {'AI Analysis' in result}")
110
+ print(f" Contains error handling: {'Display Error' in result or 'No indicators' in result}")
111
+
112
+ except Exception as e:
113
+ print(f" Error occurred: {e}")
114
+
115
+ # Test degraded display
116
+ print("\n2. Testing degraded display creation...")
117
+ error_context = "Simulated formatting error"
118
+ original_content = "<div>Original content that failed to format</div>"
119
+
120
+ degraded_display = display_manager.error_handler.create_degraded_display(
121
+ error_context, original_content
122
+ )
123
+
124
+ print(" Degraded display created successfully")
125
+ print(f" Contains error message: {'Display Error Detected' in degraded_display}")
126
+ print(f" Contains recovery actions: {'Recovery Actions' in degraded_display}")
127
+ print(f" Contains original content: {original_content in degraded_display}")
128
+
129
+
130
+ def demo_classification_error_handling():
131
+ """Demonstrate classification error handling."""
132
+ print("\n" + "=" * 60)
133
+ print("CLASSIFICATION ERROR HANDLING DEMO")
134
+ print("=" * 60)
135
+
136
+ error_handler = UIErrorHandler()
137
+
138
+ # Simulate classification error
139
+ print("\n1. Simulating classification error...")
140
+ test_error = Exception("Simulated AI classification failure")
141
+ input_data = {
142
+ 'message': 'I feel hopeless and have lost all meaning in life',
143
+ 'classification': 'red',
144
+ 'confidence': 0.8
145
+ }
146
+
147
+ fallback_result = error_handler.handle_classification_error(test_error, input_data)
148
+
149
+ print(f" Fallback classification: {fallback_result.classification}")
150
+ print(f" Fallback confidence: {fallback_result.confidence}")
151
+ print(f" Fallback indicators: {fallback_result.indicators}")
152
+ print(f" Fallback reasoning: {fallback_result.reasoning[:50]}...")
153
+ print(f" Is valid: {fallback_result.is_valid}")
154
+
155
+ # Test with critical keywords
156
+ print("\n2. Testing with critical keywords...")
157
+ critical_input = {
158
+ 'message': 'I want to kill myself and end it all'
159
+ }
160
+
161
+ critical_result = error_handler.handle_classification_error(test_error, critical_input)
162
+
163
+ print(f" Critical classification: {critical_result.classification}")
164
+ print(f" Critical confidence: {critical_result.confidence}")
165
+ print(f" Critical indicators: {critical_result.indicators}")
166
+
167
+
168
+ def demo_end_to_end_recovery():
169
+ """Demonstrate end-to-end error recovery."""
170
+ print("\n" + "=" * 60)
171
+ print("END-TO-END ERROR RECOVERY DEMO")
172
+ print("=" * 60)
173
+
174
+ # Initialize components
175
+ summary_generator = ProviderSummaryGenerator()
176
+ display_manager = EnhancedResultsDisplayManager()
177
+
178
+ # Start with problematic data
179
+ print("\n1. Starting with problematic data...")
180
+ problematic_data = {
181
+ 'classification': 'INVALID',
182
+ 'confidence': -0.5,
183
+ 'indicators': [],
184
+ 'reasoning': '',
185
+ 'patient_name': '',
186
+ 'patient_phone': ''
187
+ }
188
+
189
+ print(f" Classification: {problematic_data['classification']}")
190
+ print(f" Confidence: {problematic_data['confidence']}")
191
+ print(f" Indicators: {problematic_data['indicators']}")
192
+ print(f" Reasoning: '{problematic_data['reasoning']}'")
193
+ print(f" Patient Name: '{problematic_data['patient_name']}'")
194
+ print(f" Patient Phone: '{problematic_data['patient_phone']}'")
195
+
196
+ # Generate summary with error recovery
197
+ print("\n2. Generating summary with error recovery...")
198
+ summary = summary_generator.generate_summary(
199
+ indicators=problematic_data['indicators'],
200
+ reasoning=problematic_data['reasoning'],
201
+ confidence=problematic_data['confidence'],
202
+ patient_name=problematic_data['patient_name'],
203
+ patient_phone=problematic_data['patient_phone']
204
+ )
205
+
206
+ print(f" Fixed confidence: {summary.confidence}")
207
+ print(f" Fixed reasoning length: {len(summary.reasoning)}")
208
+ print(f" Fixed patient name: {summary.patient_name}")
209
+ print(f" Fixed patient phone: {summary.patient_phone}")
210
+ print(f" Recommended actions: {len(summary.recommended_actions)}")
211
+
212
+ # Display with error handling
213
+ print("\n3. Displaying with error handling...")
214
+ display_result = display_manager.format_provider_summary_section(summary)
215
+
216
+ print(f" Display result generated: {len(display_result)} characters")
217
+ print(f" Contains provider summary: {'Provider Summary' in display_result}")
218
+ print(f" Contains patient info: {summary.patient_name in display_result}")
219
+
220
+ print("\n4. System successfully recovered from all errors!")
221
+
222
+
223
+ def demo_error_statistics():
224
+ """Demonstrate error statistics collection."""
225
+ print("\n" + "=" * 60)
226
+ print("ERROR STATISTICS DEMO")
227
+ print("=" * 60)
228
+
229
+ error_handler = UIErrorHandler()
230
+
231
+ # Create multiple invalid summaries to collect statistics
232
+ print("\n1. Creating multiple invalid summaries...")
233
+ summaries = [
234
+ ProviderSummary(patient_name="", patient_phone="", confidence=1.5, reasoning=""),
235
+ ProviderSummary(patient_name="[Patient Name]", patient_phone="invalid", confidence=-0.1, reasoning="short"),
236
+ ProviderSummary(patient_name="Valid Name", patient_phone="555-123-4567", confidence=0.8, reasoning="", indicators=[])
237
+ ]
238
+
239
+ all_errors = []
240
+ for i, summary in enumerate(summaries, 1):
241
+ validation_result = error_handler.validate_provider_summary_structure(summary)
242
+ all_errors.extend(validation_result.errors)
243
+ print(f" Summary {i}: {len(validation_result.errors)} errors, {len(validation_result.warnings)} warnings")
244
+
245
+ # Get statistics
246
+ print(f"\n2. Collecting statistics from {len(all_errors)} total errors...")
247
+ stats = error_handler.get_error_statistics(all_errors)
248
+
249
+ print(f" Total errors: {stats['total']}")
250
+ print(f" By category: {stats['by_category']}")
251
+ print(f" By severity: {stats['by_severity']}")
252
+ print(f" By component: {stats['by_component']}")
253
+
254
+
255
+ def main():
256
+ """Run all error handling demos."""
257
+ print("UI ERROR HANDLING AND RECOVERY DEMONSTRATION")
258
+ print("Requirements: 9.1, 9.2, 9.3, 9.4")
259
+
260
+ try:
261
+ demo_provider_summary_validation()
262
+ demo_display_error_handling()
263
+ demo_classification_error_handling()
264
+ demo_end_to_end_recovery()
265
+ demo_error_statistics()
266
+
267
+ print("\n" + "=" * 60)
268
+ print("ALL DEMOS COMPLETED SUCCESSFULLY!")
269
+ print("Error handling and recovery mechanisms are working correctly.")
270
+ print("=" * 60)
271
+
272
+ except Exception as e:
273
+ print(f"\nDemo failed with error: {e}")
274
+ import traceback
275
+ traceback.print_exc()
276
+
277
+
278
+ if __name__ == "__main__":
279
+ main()
examples/integration_demo.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Integration Demo for UI Classification Improvements.
4
+
5
+ Demonstrates the complete workflow from classification to display to verification.
6
+ This shows how all components work together in the enhanced system.
7
+ """
8
+
9
+ import sys
10
+ import os
11
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
12
+
13
+ from interface.enhanced_results_display_manager import EnhancedResultsDisplayManager
14
+ from core.provider_summary_generator import ProviderSummary, ProviderSummaryGenerator
15
+ from core.improved_classification_prompt_manager import ImprovedClassificationPromptManager
16
+ from core.conversation_logger import ConversationLogger
17
+ from core.spiritual_state import SpiritualState, SpiritualAssessment
18
+
19
+
20
+ def main():
21
+ """Run integration demo."""
22
+ print("🚀 UI Classification Improvements Integration Demo")
23
+ print("=" * 60)
24
+
25
+ # Initialize components
26
+ print("\n1. Initializing enhanced components...")
27
+ display_manager = EnhancedResultsDisplayManager()
28
+ summary_generator = ProviderSummaryGenerator()
29
+ classification_manager = ImprovedClassificationPromptManager()
30
+
31
+ print(" ✅ Enhanced Results Display Manager")
32
+ print(" ✅ Provider Summary Generator")
33
+ print(" ✅ Improved Classification Prompt Manager")
34
+
35
+ # Demonstrate enhanced classification
36
+ print("\n2. Testing enhanced classification criteria...")
37
+
38
+ # Test explicit red indicators
39
+ red_indicators = classification_manager.get_explicit_red_indicators()
40
+ print(f" 📋 Explicit RED indicators ({len(red_indicators)}):")
41
+ for indicator in red_indicators:
42
+ print(f" • {indicator}")
43
+
44
+ # Test updated red flag definition
45
+ red_definition = classification_manager.get_updated_red_flag_definition()
46
+ print(f"\n 🔴 Updated RED flag definition:")
47
+ print(f" {red_definition}")
48
+
49
+ # Demonstrate classification validation
50
+ print("\n3. Testing classification validation...")
51
+
52
+ # Valid classification
53
+ valid_result = classification_manager.create_classification_result(
54
+ classification="red",
55
+ confidence=0.85,
56
+ indicators=["doubt about meaning of suffering"],
57
+ reasoning="Patient questioning meaning of suffering - explicit red indicator",
58
+ red_flag_indicators=["doubt about meaning of suffering"]
59
+ )
60
+
61
+ print(f" ✅ Valid classification: {valid_result.classification} (valid: {valid_result.is_valid})")
62
+
63
+ # Invalid classification that gets corrected
64
+ invalid_result = classification_manager.create_classification_result(
65
+ classification="invalid",
66
+ confidence=2.0,
67
+ indicators=[],
68
+ reasoning=""
69
+ )
70
+
71
+ print(f" 🔧 Corrected classification: {invalid_result.classification} (valid: {invalid_result.is_valid})")
72
+
73
+ # Demonstrate enhanced display formatting
74
+ print("\n4. Testing enhanced display formatting...")
75
+
76
+ # Create test provider summary
77
+ test_summary = ProviderSummary(
78
+ patient_name="Demo Patient",
79
+ patient_phone="555-0123",
80
+ classification="RED",
81
+ confidence=0.85,
82
+ indicators=["loss of meaning", "spiritual distress", "questioning faith"],
83
+ reasoning="Patient expressing significant spiritual concerns and loss of meaning",
84
+ severity_level="HIGH",
85
+ urgency_level="IMMEDIATE",
86
+ situation_description="Patient experiencing existential crisis following medical diagnosis",
87
+ recommended_actions=[
88
+ "Contact patient within 2-4 hours",
89
+ "Assess immediate safety and support needs",
90
+ "Provide spiritual care resources"
91
+ ],
92
+ medical_context={
93
+ "age": 45,
94
+ "gender": "individual",
95
+ "conditions": ["chronic illness", "recent diagnosis"]
96
+ }
97
+ )
98
+
99
+ # Format AI analysis section
100
+ ai_analysis_html = display_manager.format_ai_analysis_section(
101
+ classification="RED",
102
+ indicators=test_summary.indicators,
103
+ reasoning=test_summary.reasoning,
104
+ confidence=test_summary.confidence
105
+ )
106
+
107
+ print(" ✅ AI Analysis section formatted")
108
+ print(f" Length: {len(ai_analysis_html)} characters")
109
+
110
+ # Format patient message section
111
+ patient_message_html = display_manager.format_patient_message_section(
112
+ "I just don't see the point in anything anymore. My life feels meaningless since my diagnosis."
113
+ )
114
+
115
+ print(" ✅ Patient Message section formatted")
116
+ print(f" Length: {len(patient_message_html)} characters")
117
+
118
+ # Format provider summary section
119
+ provider_summary_html = display_manager.format_provider_summary_section(test_summary)
120
+
121
+ print(" ✅ Provider Summary section formatted")
122
+ print(f" Length: {len(provider_summary_html)} characters")
123
+
124
+ # Test coherent paragraph formatting
125
+ print("\n5. Testing coherent paragraph formatting...")
126
+
127
+ coherent_paragraph = summary_generator.format_coherent_paragraph(test_summary)
128
+
129
+ print(" ✅ Coherent paragraph generated")
130
+ print(f" Length: {len(coherent_paragraph)} characters")
131
+ print(f" Preview: {coherent_paragraph[:100]}...")
132
+
133
+ # Test combined results
134
+ print("\n6. Testing combined results formatting...")
135
+
136
+ combined_html = display_manager.format_combined_results(
137
+ ai_analysis={
138
+ 'classification': 'RED',
139
+ 'indicators': test_summary.indicators,
140
+ 'reasoning': test_summary.reasoning,
141
+ 'confidence': test_summary.confidence
142
+ },
143
+ patient_message="I just don't see the point in anything anymore. My life feels meaningless since my diagnosis.",
144
+ provider_summary=test_summary
145
+ )
146
+
147
+ print(" ✅ Combined results formatted")
148
+ print(f" Length: {len(combined_html)} characters")
149
+
150
+ # Test conversation logging integration
151
+ print("\n7. Testing conversation logging integration...")
152
+
153
+ logger = ConversationLogger(patient_name="Demo Patient")
154
+
155
+ # Log test conversation
156
+ assessment = SpiritualAssessment(
157
+ state=SpiritualState.RED,
158
+ confidence=0.85,
159
+ indicators=["loss of meaning", "spiritual distress"],
160
+ reasoning="Patient expressing significant spiritual concerns"
161
+ )
162
+
163
+ logger.log_exchange(
164
+ "I just don't see the point in anything anymore",
165
+ "I understand this is a very difficult time for you",
166
+ assessment
167
+ )
168
+
169
+ print(f" ✅ Conversation logged: {len(logger.entries)} entries")
170
+
171
+ # Test error handling
172
+ print("\n8. Testing error handling and recovery...")
173
+
174
+ # Create problematic summary
175
+ problematic_summary = ProviderSummary(
176
+ patient_name="[Patient Name]", # Placeholder
177
+ patient_phone="[Phone Number]", # Placeholder
178
+ classification="RED",
179
+ confidence=1.5, # Invalid confidence
180
+ reasoning="", # Empty reasoning
181
+ indicators=[] # No indicators
182
+ )
183
+
184
+ # Display manager should handle this gracefully
185
+ error_display = display_manager.format_provider_summary_section(problematic_summary)
186
+
187
+ print(" ✅ Error handling works - graceful degradation")
188
+ print(f" Error display length: {len(error_display)} characters")
189
+
190
+ # Summary
191
+ print("\n" + "=" * 60)
192
+ print("🎉 INTEGRATION DEMO COMPLETED SUCCESSFULLY!")
193
+ print("\n📋 Demonstrated Features:")
194
+ print(" ✅ Enhanced classification with explicit red indicators")
195
+ print(" ✅ Updated red flag definition and validation")
196
+ print(" ✅ Enhanced display formatting with visual separation")
197
+ print(" ✅ Coherent paragraph formatting for Medical Brain compatibility")
198
+ print(" ✅ Combined results with proper section separation")
199
+ print(" ✅ Conversation logging integration")
200
+ print(" ✅ Error handling and graceful degradation")
201
+ print("\n🚀 All UI Classification Improvements are working correctly!")
202
+ print(" Ready for production use in the medical assistant system.")
203
+
204
+
205
+ if __name__ == "__main__":
206
+ main()
examples/visual_separation_demo.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Visual Separation Manager Demo
4
+
5
+ This demo shows the VisualSeparationManager functionality for creating
6
+ consistent visual styling and separation for different content types.
7
+
8
+ Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
9
+ """
10
+
11
+ import sys
12
+ import os
13
+
14
+ # Add project root to path
15
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16
+ sys.path.insert(0, project_root)
17
+
18
+ from src.interface.visual_separation_manager import (
19
+ VisualSeparationManager,
20
+ ContentType,
21
+ VisualStyle
22
+ )
23
+ from src.interface.enhanced_display_integration import (
24
+ EnhancedDisplayIntegration,
25
+ create_enhanced_display_integration
26
+ )
27
+
28
+
29
+ def demo_visual_separation_manager():
30
+ """Demonstrate VisualSeparationManager functionality."""
31
+ print("🎨 Visual Separation Manager Demo")
32
+ print("=" * 50)
33
+
34
+ # Create visual separation manager
35
+ visual_manager = VisualSeparationManager()
36
+
37
+ print("\n1. Testing AI Analysis Styling:")
38
+ ai_styling = visual_manager.create_ai_analysis_styling()
39
+ print(f" Container style: {ai_styling['container'][:50]}...")
40
+ print(f" Header style: {ai_styling['header'][:50]}...")
41
+ print(f" Icon style: {ai_styling['icon'][:30]}...")
42
+
43
+ print("\n2. Testing Patient Message Styling:")
44
+ patient_styling = visual_manager.create_patient_message_styling()
45
+ print(f" Container style: {patient_styling['container'][:50]}...")
46
+ print(f" Message box style: {patient_styling['message_box'][:50]}...")
47
+
48
+ print("\n3. Testing Provider Summary Styling:")
49
+ provider_styling = visual_manager.create_provider_summary_styling()
50
+ print(f" Container style: {provider_styling['container'][:50]}...")
51
+ print(f" Info box style: {provider_styling['info_box'][:50]}...")
52
+
53
+ print("\n4. Testing Section Separators:")
54
+ separators = visual_manager.generate_section_separators()
55
+ for sep_type, sep_html in separators.items():
56
+ print(f" {sep_type}: {len(sep_html)} characters")
57
+
58
+ print("\n5. Testing Classification Styling:")
59
+ for classification in ["RED", "YELLOW", "GREEN"]:
60
+ styling = visual_manager.get_classification_styling(classification)
61
+ print(f" {classification}: {styling['text'][:30]}...")
62
+
63
+ print("\n6. Testing Urgency Styling:")
64
+ for urgency in ["IMMEDIATE", "URGENT", "STANDARD"]:
65
+ styling = visual_manager.get_urgency_styling(urgency)
66
+ print(f" {urgency}: {styling['badge'][:40]}...")
67
+
68
+ print("\n7. Testing Icon Styling:")
69
+ for content_type in [ContentType.AI_ANALYSIS, ContentType.PATIENT_MESSAGE, ContentType.PROVIDER_SUMMARY]:
70
+ icon_style = visual_manager.create_icon_styling(content_type)
71
+ print(f" {content_type.value}: {icon_style[:40]}...")
72
+
73
+ print("\n8. Testing Consistent Formatting:")
74
+ sections = [
75
+ {"type": "ai_analysis", "content": "AI analysis content example"},
76
+ {"type": "patient_message", "content": "Patient message content example"},
77
+ {"type": "provider_summary", "content": "Provider summary content example"}
78
+ ]
79
+
80
+ formatted_result = visual_manager.apply_consistent_formatting(sections)
81
+ print(f" Formatted result length: {len(formatted_result)} characters")
82
+ print(f" Contains separators: {'---' in formatted_result}")
83
+ print(f" Contains all content: {all(section['content'] in formatted_result for section in sections)}")
84
+
85
+
86
+ def demo_enhanced_display_integration():
87
+ """Demonstrate Enhanced Display Integration with VisualSeparationManager."""
88
+ print("\n\n🔗 Enhanced Display Integration Demo")
89
+ print("=" * 50)
90
+
91
+ # Create integration instance
92
+ integration = create_enhanced_display_integration()
93
+
94
+ print("\n1. Testing Section Creation with Styling:")
95
+
96
+ # AI Analysis section
97
+ ai_section = integration.create_section_with_styling(
98
+ content="Patient shows signs of spiritual distress with loss of meaning indicators.",
99
+ section_type="ai_analysis",
100
+ title="AI Analysis - RED FLAG"
101
+ )
102
+ print(f" AI section length: {len(ai_section)} characters")
103
+ print(f" Contains AI icon: {'🤖' in ai_section}")
104
+
105
+ # Patient Message section
106
+ patient_section = integration.create_section_with_styling(
107
+ content="I don't know what to believe anymore. Everything feels meaningless.",
108
+ section_type="patient_message"
109
+ )
110
+ print(f" Patient section length: {len(patient_section)} characters")
111
+ print(f" Contains patient icon: {'💬' in patient_section}")
112
+
113
+ # Provider Summary section
114
+ provider_section = integration.create_section_with_styling(
115
+ content="Immediate spiritual care intervention recommended for existential crisis.",
116
+ section_type="provider_summary"
117
+ )
118
+ print(f" Provider section length: {len(provider_section)} characters")
119
+ print(f" Contains provider icon: {'📋' in provider_section}")
120
+
121
+ print("\n2. Testing Badge Creation:")
122
+
123
+ # Classification badges
124
+ red_badge = integration.get_classification_badge("RED")
125
+ yellow_badge = integration.get_classification_badge("YELLOW")
126
+ green_badge = integration.get_classification_badge("GREEN")
127
+
128
+ print(f" RED badge: {len(red_badge)} characters")
129
+ print(f" YELLOW badge: {len(yellow_badge)} characters")
130
+ print(f" GREEN badge: {len(green_badge)} characters")
131
+
132
+ # Urgency badges
133
+ immediate_badge = integration.get_urgency_badge("IMMEDIATE")
134
+ urgent_badge = integration.get_urgency_badge("URGENT")
135
+ standard_badge = integration.get_urgency_badge("STANDARD")
136
+
137
+ print(f" IMMEDIATE badge: {len(immediate_badge)} characters")
138
+ print(f" URGENT badge: {len(urgent_badge)} characters")
139
+ print(f" STANDARD badge: {len(standard_badge)} characters")
140
+
141
+ print("\n3. Testing Content Separators:")
142
+
143
+ separators = ["light", "medium", "heavy", "section_break"]
144
+ for sep_type in separators:
145
+ separator = integration.create_content_separator(sep_type)
146
+ print(f" {sep_type} separator: {len(separator)} characters")
147
+
148
+ print("\n4. Testing Multiple Sections Formatting:")
149
+
150
+ sections = [
151
+ {"type": "ai_analysis", "content": "AI analysis content"},
152
+ {"type": "patient_message", "content": "Patient message content"},
153
+ {"type": "provider_summary", "content": "Provider summary content"}
154
+ ]
155
+
156
+ formatted_multiple = integration.format_multiple_sections(sections)
157
+ print(f" Multiple sections result: {len(formatted_multiple)} characters")
158
+ print(f" Contains all content: {all(section['content'] in formatted_multiple for section in sections)}")
159
+
160
+
161
+ def demo_visual_styling_consistency():
162
+ """Demonstrate visual styling consistency across components."""
163
+ print("\n\n🎯 Visual Styling Consistency Demo")
164
+ print("=" * 50)
165
+
166
+ visual_manager = VisualSeparationManager()
167
+
168
+ print("\n1. Color Consistency Check:")
169
+
170
+ # Check classification colors
171
+ classification_colors = visual_manager.classification_colors
172
+ print(f" Classification colors: {classification_colors}")
173
+
174
+ # Check urgency colors
175
+ urgency_colors = visual_manager.urgency_colors
176
+ print(f" Urgency colors: {urgency_colors}")
177
+
178
+ print("\n2. Style Structure Consistency:")
179
+
180
+ # Check that all content types have consistent styling structure
181
+ content_types = [ContentType.AI_ANALYSIS, ContentType.PATIENT_MESSAGE, ContentType.PROVIDER_SUMMARY]
182
+
183
+ for content_type in content_types:
184
+ if content_type == ContentType.AI_ANALYSIS:
185
+ styling = visual_manager.create_ai_analysis_styling()
186
+ elif content_type == ContentType.PATIENT_MESSAGE:
187
+ styling = visual_manager.create_patient_message_styling()
188
+ elif content_type == ContentType.PROVIDER_SUMMARY:
189
+ styling = visual_manager.create_provider_summary_styling()
190
+
191
+ required_keys = ["container", "header", "icon"]
192
+ has_all_keys = all(key in styling for key in required_keys)
193
+ print(f" {content_type.value}: Has all required keys: {has_all_keys}")
194
+
195
+ # Check for consistent CSS properties
196
+ container_style = styling.get("container", "")
197
+ has_border = "border:" in container_style
198
+ has_padding = "padding:" in container_style
199
+ has_margin = "margin:" in container_style
200
+ has_background = "background-color:" in container_style
201
+
202
+ print(f" - Border: {has_border}, Padding: {has_padding}, Margin: {has_margin}, Background: {has_background}")
203
+
204
+ print("\n3. Separator Consistency:")
205
+
206
+ separators = visual_manager.generate_section_separators()
207
+ for sep_name, sep_html in separators.items():
208
+ has_div = "<div" in sep_html
209
+ has_style = "style=" in sep_html
210
+ print(f" {sep_name}: Has div: {has_div}, Has style: {has_style}")
211
+
212
+
213
+ def main():
214
+ """Run all visual separation manager demos."""
215
+ try:
216
+ demo_visual_separation_manager()
217
+ demo_enhanced_display_integration()
218
+ demo_visual_styling_consistency()
219
+
220
+ print("\n\n✅ All Visual Separation Manager demos completed successfully!")
221
+ print("\nKey Features Demonstrated:")
222
+ print("- ✅ AI Analysis styling with classification colors")
223
+ print("- ✅ Patient Message styling with consistent formatting")
224
+ print("- ✅ Provider Summary styling with urgency indicators")
225
+ print("- ✅ Section separators for visual content division")
226
+ print("- ✅ Classification and urgency badges")
227
+ print("- ✅ Icon styling for different content types")
228
+ print("- ✅ Consistent formatting across multiple sections")
229
+ print("- ✅ Integration with enhanced display system")
230
+
231
+ print(f"\nRequirements Validated:")
232
+ print("- 7.1: ✅ AI analysis and provider summary visual styling")
233
+ print("- 7.2: ✅ Patient message and content section styling")
234
+ print("- 7.3: ✅ Icons and color coding for content types")
235
+ print("- 7.4: ✅ Visual separators and section breaks")
236
+ print("- 7.5: ✅ Consistent formatting and visual hierarchy")
237
+
238
+ except Exception as e:
239
+ print(f"\n❌ Demo failed with error: {e}")
240
+ import traceback
241
+ traceback.print_exc()
242
+ return 1
243
+
244
+ return 0
245
+
246
+
247
+ if __name__ == "__main__":
248
+ exit(main())
src/config/display/enhanced_display_config.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "enabled": true,
3
+ "use_color_coding": true,
4
+ "use_icons": false,
5
+ "use_visual_separators": true,
6
+ "use_enhanced_styling": true,
7
+ "ai_analysis": {
8
+ "icon": "\ud83e\udd16",
9
+ "border_color": "#6c757d",
10
+ "background_color": "#f8f9fa",
11
+ "header_color": "#6c757d",
12
+ "border_width": "2px",
13
+ "border_radius": "8px",
14
+ "padding": "15px",
15
+ "margin": "10px 0"
16
+ },
17
+ "patient_message": {
18
+ "icon": "\ud83d\udcac",
19
+ "border_color": "#4a90e2",
20
+ "background_color": "#f0f7ff",
21
+ "header_color": "#4a90e2",
22
+ "border_width": "2px",
23
+ "border_radius": "8px",
24
+ "padding": "15px",
25
+ "margin": "10px 0"
26
+ },
27
+ "provider_summary": {
28
+ "icon": "\ud83d\udccb",
29
+ "border_color": "#dc3545",
30
+ "background_color": "#fff8f0",
31
+ "header_color": "#dc3545",
32
+ "border_width": "2px",
33
+ "border_radius": "8px",
34
+ "padding": "15px",
35
+ "margin": "10px 0"
36
+ },
37
+ "classification_colors": {
38
+ "red": "#ff4444",
39
+ "yellow": "#ffaa00",
40
+ "green": "#44aa44",
41
+ "unknown": "#666666"
42
+ },
43
+ "separators": {
44
+ "section_separator": "---",
45
+ "content_divider": "1px solid #ddd",
46
+ "major_break_symbol": "\u25cf \u25cf \u25cf",
47
+ "separator_color": "#e0e0e0",
48
+ "separator_width": "80%"
49
+ },
50
+ "font_family": "system-ui, -apple-system, sans-serif",
51
+ "base_font_size": "14px",
52
+ "header_font_size": "1.1em",
53
+ "small_font_size": "0.9em",
54
+ "max_content_width": "100%",
55
+ "responsive_breakpoint": "768px",
56
+ "enable_animations": true,
57
+ "transition_duration": "0.3s",
58
+ "high_contrast_mode": false,
59
+ "reduced_motion": false
60
+ }
src/config/enhanced_display_config.py ADDED
@@ -0,0 +1,682 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enhanced Display Configuration System for UI Classification Improvements.
4
+
5
+ Provides comprehensive configuration management for enhanced display features
6
+ including colors, icons, styles, and feature toggles.
7
+
8
+ Requirements: 7.1, 7.2, 7.3
9
+ """
10
+
11
+ import json
12
+ import os
13
+ from dataclasses import dataclass, field, asdict
14
+ from typing import Dict, List, Optional, Any
15
+ from pathlib import Path
16
+ import logging
17
+
18
+ # Configure logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @dataclass
24
+ class SectionStylingConfig:
25
+ """Configuration for individual section styling."""
26
+ icon: str
27
+ border_color: str
28
+ background_color: str
29
+ header_color: str
30
+ border_width: str = "2px"
31
+ border_radius: str = "8px"
32
+ padding: str = "15px"
33
+ margin: str = "10px 0"
34
+
35
+
36
+ @dataclass
37
+ class ClassificationColorConfig:
38
+ """Configuration for classification-based colors."""
39
+ red: str = "#ff4444"
40
+ yellow: str = "#ffaa00"
41
+ green: str = "#44aa44"
42
+ unknown: str = "#666666"
43
+
44
+
45
+ @dataclass
46
+ class VisualSeparatorConfig:
47
+ """Configuration for visual separators."""
48
+ section_separator: str = "---"
49
+ content_divider: str = "1px solid #ddd"
50
+ major_break_symbol: str = "● ● ●"
51
+ separator_color: str = "#e0e0e0"
52
+ separator_width: str = "80%"
53
+
54
+
55
+ @dataclass
56
+ class EnhancedDisplayConfig:
57
+ """
58
+ Comprehensive configuration for enhanced display formatting.
59
+
60
+ Manages all aspects of the enhanced UI including colors, icons, styles,
61
+ and feature toggles for the classification improvements system.
62
+
63
+ Requirements: 7.1, 7.2, 7.3
64
+ """
65
+
66
+ # Feature toggles
67
+ enabled: bool = True
68
+ use_color_coding: bool = True
69
+ use_icons: bool = True
70
+ use_visual_separators: bool = True
71
+ use_enhanced_styling: bool = True
72
+
73
+ # Section styling configurations
74
+ ai_analysis: SectionStylingConfig = field(default_factory=lambda: SectionStylingConfig(
75
+ icon="🤖",
76
+ border_color="#6c757d",
77
+ background_color="#f8f9fa",
78
+ header_color="#6c757d"
79
+ ))
80
+
81
+ patient_message: SectionStylingConfig = field(default_factory=lambda: SectionStylingConfig(
82
+ icon="💬",
83
+ border_color="#4a90e2",
84
+ background_color="#f0f7ff",
85
+ header_color="#4a90e2"
86
+ ))
87
+
88
+ provider_summary: SectionStylingConfig = field(default_factory=lambda: SectionStylingConfig(
89
+ icon="📋",
90
+ border_color="#dc3545",
91
+ background_color="#fff8f0",
92
+ header_color="#dc3545"
93
+ ))
94
+
95
+ # Classification colors
96
+ classification_colors: ClassificationColorConfig = field(default_factory=ClassificationColorConfig)
97
+
98
+ # Visual separators
99
+ separators: VisualSeparatorConfig = field(default_factory=VisualSeparatorConfig)
100
+
101
+ # Typography settings
102
+ font_family: str = "system-ui, -apple-system, sans-serif"
103
+ base_font_size: str = "14px"
104
+ header_font_size: str = "1.1em"
105
+ small_font_size: str = "0.9em"
106
+
107
+ # Layout settings
108
+ max_content_width: str = "100%"
109
+ responsive_breakpoint: str = "768px"
110
+
111
+ # Animation settings
112
+ enable_animations: bool = True
113
+ transition_duration: str = "0.3s"
114
+
115
+ # Accessibility settings
116
+ high_contrast_mode: bool = False
117
+ reduced_motion: bool = False
118
+
119
+ def __post_init__(self):
120
+ """Post-initialization to apply high contrast mode if enabled."""
121
+ if self.high_contrast_mode:
122
+ self._apply_high_contrast_colors()
123
+
124
+ def _apply_high_contrast_colors(self):
125
+ """Apply high contrast color scheme for accessibility."""
126
+ # Update classification colors for high contrast
127
+ self.classification_colors.red = "#cc0000"
128
+ self.classification_colors.yellow = "#cc8800"
129
+ self.classification_colors.green = "#006600"
130
+
131
+ # Update section colors for high contrast
132
+ self.ai_analysis.border_color = "#000000"
133
+ self.ai_analysis.background_color = "#ffffff"
134
+ self.ai_analysis.header_color = "#000000"
135
+
136
+ self.patient_message.border_color = "#0066cc"
137
+ self.patient_message.background_color = "#f0f8ff"
138
+ self.patient_message.header_color = "#0066cc"
139
+
140
+ self.provider_summary.border_color = "#cc0000"
141
+ self.provider_summary.background_color = "#fff0f0"
142
+ self.provider_summary.header_color = "#cc0000"
143
+
144
+ def get_classification_color(self, classification: str) -> str:
145
+ """
146
+ Get color for a specific classification.
147
+
148
+ Args:
149
+ classification: Classification level (RED/YELLOW/GREEN)
150
+
151
+ Returns:
152
+ Hex color code for the classification
153
+ """
154
+ classification_lower = classification.lower()
155
+
156
+ if classification_lower == "red":
157
+ return self.classification_colors.red
158
+ elif classification_lower == "yellow":
159
+ return self.classification_colors.yellow
160
+ elif classification_lower == "green":
161
+ return self.classification_colors.green
162
+ else:
163
+ return self.classification_colors.unknown
164
+
165
+ def get_section_config(self, section_type: str) -> SectionStylingConfig:
166
+ """
167
+ Get styling configuration for a specific section type.
168
+
169
+ Args:
170
+ section_type: Type of section (ai_analysis/patient_message/provider_summary)
171
+
172
+ Returns:
173
+ SectionStylingConfig for the specified section
174
+ """
175
+ section_configs = {
176
+ "ai_analysis": self.ai_analysis,
177
+ "patient_message": self.patient_message,
178
+ "provider_summary": self.provider_summary
179
+ }
180
+
181
+ return section_configs.get(section_type, self.ai_analysis)
182
+
183
+ def generate_css_variables(self) -> str:
184
+ """
185
+ Generate CSS custom properties (variables) for the configuration.
186
+
187
+ Returns:
188
+ CSS string with custom properties
189
+ """
190
+ css_vars = [
191
+ ":root {",
192
+ f" --enhanced-font-family: {self.font_family};",
193
+ f" --enhanced-base-font-size: {self.base_font_size};",
194
+ f" --enhanced-header-font-size: {self.header_font_size};",
195
+ f" --enhanced-small-font-size: {self.small_font_size};",
196
+ f" --enhanced-max-width: {self.max_content_width};",
197
+ f" --enhanced-transition: {self.transition_duration};",
198
+ "",
199
+ " /* Classification Colors */",
200
+ f" --classification-red: {self.classification_colors.red};",
201
+ f" --classification-yellow: {self.classification_colors.yellow};",
202
+ f" --classification-green: {self.classification_colors.green};",
203
+ f" --classification-unknown: {self.classification_colors.unknown};",
204
+ "",
205
+ " /* AI Analysis Section */",
206
+ f" --ai-analysis-icon: '{self.ai_analysis.icon}';",
207
+ f" --ai-analysis-border: {self.ai_analysis.border_color};",
208
+ f" --ai-analysis-bg: {self.ai_analysis.background_color};",
209
+ f" --ai-analysis-header: {self.ai_analysis.header_color};",
210
+ "",
211
+ " /* Patient Message Section */",
212
+ f" --patient-message-icon: '{self.patient_message.icon}';",
213
+ f" --patient-message-border: {self.patient_message.border_color};",
214
+ f" --patient-message-bg: {self.patient_message.background_color};",
215
+ f" --patient-message-header: {self.patient_message.header_color};",
216
+ "",
217
+ " /* Provider Summary Section */",
218
+ f" --provider-summary-icon: '{self.provider_summary.icon}';",
219
+ f" --provider-summary-border: {self.provider_summary.border_color};",
220
+ f" --provider-summary-bg: {self.provider_summary.background_color};",
221
+ f" --provider-summary-header: {self.provider_summary.header_color};",
222
+ "",
223
+ " /* Separators */",
224
+ f" --separator-color: {self.separators.separator_color};",
225
+ f" --separator-width: {self.separators.separator_width};",
226
+ "}"
227
+ ]
228
+
229
+ return "\n".join(css_vars)
230
+
231
+ def generate_base_css(self) -> str:
232
+ """
233
+ Generate base CSS classes for enhanced display.
234
+
235
+ Returns:
236
+ CSS string with base classes
237
+ """
238
+ css_classes = [
239
+ self.generate_css_variables(),
240
+ "",
241
+ "/* Enhanced Display Base Styles */",
242
+ ".enhanced-section {",
243
+ f" font-family: var(--enhanced-font-family);",
244
+ f" font-size: var(--enhanced-base-font-size);",
245
+ f" border-radius: {self.ai_analysis.border_radius};",
246
+ f" padding: {self.ai_analysis.padding};",
247
+ f" margin: {self.ai_analysis.margin};",
248
+ f" border-width: {self.ai_analysis.border_width};",
249
+ " border-style: solid;",
250
+ "}",
251
+ "",
252
+ ".enhanced-section-header {",
253
+ " display: flex;",
254
+ " align-items: center;",
255
+ " margin-bottom: 10px;",
256
+ f" font-size: var(--enhanced-header-font-size);",
257
+ " font-weight: bold;",
258
+ "}",
259
+ "",
260
+ ".enhanced-section-icon {",
261
+ " font-size: 1.2em;",
262
+ " margin-right: 8px;",
263
+ "}",
264
+ "",
265
+ ".enhanced-separator {",
266
+ " margin: 20px 0;",
267
+ " text-align: center;",
268
+ "}",
269
+ "",
270
+ ".enhanced-separator hr {",
271
+ " border: none;",
272
+ f" border-top: 2px solid var(--separator-color);",
273
+ f" width: var(--separator-width);",
274
+ " margin: 0 auto;",
275
+ "}",
276
+ "",
277
+ "/* AI Analysis Styles */",
278
+ ".ai-analysis-section {",
279
+ " border-color: var(--ai-analysis-border);",
280
+ " background-color: var(--ai-analysis-bg);",
281
+ "}",
282
+ "",
283
+ ".ai-analysis-section .enhanced-section-header {",
284
+ " color: var(--ai-analysis-header);",
285
+ "}",
286
+ "",
287
+ "/* Patient Message Styles */",
288
+ ".patient-message-section {",
289
+ " border-color: var(--patient-message-border);",
290
+ " background-color: var(--patient-message-bg);",
291
+ "}",
292
+ "",
293
+ ".patient-message-section .enhanced-section-header {",
294
+ " color: var(--patient-message-header);",
295
+ "}",
296
+ "",
297
+ "/* Provider Summary Styles */",
298
+ ".provider-summary-section {",
299
+ " border-color: var(--provider-summary-border);",
300
+ " background-color: var(--provider-summary-bg);",
301
+ "}",
302
+ "",
303
+ ".provider-summary-section .enhanced-section-header {",
304
+ " color: var(--provider-summary-header);",
305
+ "}",
306
+ "",
307
+ "/* Classification-specific colors */",
308
+ ".classification-red {",
309
+ " border-color: var(--classification-red) !important;",
310
+ "}",
311
+ "",
312
+ ".classification-yellow {",
313
+ " border-color: var(--classification-yellow) !important;",
314
+ "}",
315
+ "",
316
+ ".classification-green {",
317
+ " border-color: var(--classification-green) !important;",
318
+ "}",
319
+ "",
320
+ "/* Responsive design */",
321
+ f"@media (max-width: {self.responsive_breakpoint}) {{",
322
+ " .enhanced-section {",
323
+ " margin: 8px 0;",
324
+ " padding: 12px;",
325
+ " }",
326
+ " ",
327
+ " .enhanced-section-header {",
328
+ " font-size: 1em;",
329
+ " }",
330
+ "}",
331
+ ]
332
+
333
+ # Add animation styles if enabled
334
+ if self.enable_animations and not self.reduced_motion:
335
+ css_classes.extend([
336
+ "",
337
+ "/* Animations */",
338
+ ".enhanced-section {",
339
+ f" transition: all var(--enhanced-transition);",
340
+ "}",
341
+ "",
342
+ ".enhanced-section:hover {",
343
+ " transform: translateY(-1px);",
344
+ " box-shadow: 0 2px 8px rgba(0,0,0,0.1);",
345
+ "}",
346
+ ])
347
+
348
+ return "\n".join(css_classes)
349
+
350
+ def to_dict(self) -> Dict[str, Any]:
351
+ """Convert configuration to dictionary."""
352
+ return asdict(self)
353
+
354
+ def to_json(self) -> str:
355
+ """Convert configuration to JSON string."""
356
+ return json.dumps(self.to_dict(), indent=2)
357
+
358
+
359
+ class EnhancedDisplayConfigManager:
360
+ """
361
+ Manager for enhanced display configuration with persistence and validation.
362
+
363
+ Handles loading, saving, and managing configuration for the enhanced display system.
364
+ """
365
+
366
+ def __init__(self, config_file: Optional[str] = None):
367
+ """
368
+ Initialize configuration manager.
369
+
370
+ Args:
371
+ config_file: Optional path to configuration file
372
+ """
373
+ self.config_file = config_file or self._get_default_config_path()
374
+ self._config: Optional[EnhancedDisplayConfig] = None
375
+ logger.info(f"🔧 EnhancedDisplayConfigManager initialized with config: {self.config_file}")
376
+
377
+ def _get_default_config_path(self) -> str:
378
+ """Get default configuration file path."""
379
+ # Create config directory if it doesn't exist
380
+ config_dir = Path("src/config/display")
381
+ config_dir.mkdir(parents=True, exist_ok=True)
382
+
383
+ return str(config_dir / "enhanced_display_config.json")
384
+
385
+ def load_config(self) -> EnhancedDisplayConfig:
386
+ """
387
+ Load configuration from file or create default.
388
+
389
+ Returns:
390
+ EnhancedDisplayConfig instance
391
+ """
392
+ if self._config is not None:
393
+ return self._config
394
+
395
+ try:
396
+ if os.path.exists(self.config_file):
397
+ with open(self.config_file, 'r', encoding='utf-8') as f:
398
+ config_data = json.load(f)
399
+
400
+ # Convert nested dictionaries back to dataclasses
401
+ config_data = self._convert_dict_to_config(config_data)
402
+ self._config = EnhancedDisplayConfig(**config_data)
403
+
404
+ logger.info(f"✅ Configuration loaded from {self.config_file}")
405
+ else:
406
+ logger.info("📝 Creating default configuration")
407
+ self._config = EnhancedDisplayConfig()
408
+ self.save_config()
409
+
410
+ except Exception as e:
411
+ logger.error(f"❌ Error loading configuration: {e}")
412
+ logger.info("📝 Using default configuration")
413
+ self._config = EnhancedDisplayConfig()
414
+
415
+ return self._config
416
+
417
+ def _convert_dict_to_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]:
418
+ """Convert dictionary data to proper dataclass format."""
419
+ # Convert nested section configs
420
+ for section_key in ['ai_analysis', 'patient_message', 'provider_summary']:
421
+ if section_key in config_data and isinstance(config_data[section_key], dict):
422
+ config_data[section_key] = SectionStylingConfig(**config_data[section_key])
423
+
424
+ # Convert classification colors
425
+ if 'classification_colors' in config_data and isinstance(config_data['classification_colors'], dict):
426
+ config_data['classification_colors'] = ClassificationColorConfig(**config_data['classification_colors'])
427
+
428
+ # Convert separators
429
+ if 'separators' in config_data and isinstance(config_data['separators'], dict):
430
+ config_data['separators'] = VisualSeparatorConfig(**config_data['separators'])
431
+
432
+ return config_data
433
+
434
+ def save_config(self) -> bool:
435
+ """
436
+ Save current configuration to file.
437
+
438
+ Returns:
439
+ True if saved successfully
440
+ """
441
+ if self._config is None:
442
+ logger.error("❌ No configuration to save")
443
+ return False
444
+
445
+ try:
446
+ # Ensure directory exists
447
+ os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
448
+
449
+ with open(self.config_file, 'w', encoding='utf-8') as f:
450
+ f.write(self._config.to_json())
451
+
452
+ logger.info(f"✅ Configuration saved to {self.config_file}")
453
+ return True
454
+
455
+ except Exception as e:
456
+ logger.error(f"❌ Error saving configuration: {e}")
457
+ return False
458
+
459
+ def update_config(self, **kwargs) -> bool:
460
+ """
461
+ Update configuration with new values.
462
+
463
+ Args:
464
+ **kwargs: Configuration values to update
465
+
466
+ Returns:
467
+ True if updated successfully
468
+ """
469
+ try:
470
+ config = self.load_config()
471
+
472
+ # Update configuration attributes
473
+ for key, value in kwargs.items():
474
+ if hasattr(config, key):
475
+ # Handle nested dataclass updates
476
+ if key == 'classification_colors' and isinstance(value, dict):
477
+ # Convert dict to ClassificationColorConfig
478
+ from config.enhanced_display_config import ClassificationColorConfig
479
+ setattr(config, key, ClassificationColorConfig(**value))
480
+ elif key in ['ai_analysis', 'patient_message', 'provider_summary'] and isinstance(value, dict):
481
+ # Convert dict to SectionStylingConfig
482
+ from config.enhanced_display_config import SectionStylingConfig
483
+ setattr(config, key, SectionStylingConfig(**value))
484
+ elif key == 'separators' and isinstance(value, dict):
485
+ # Convert dict to VisualSeparatorConfig
486
+ from config.enhanced_display_config import VisualSeparatorConfig
487
+ setattr(config, key, VisualSeparatorConfig(**value))
488
+ else:
489
+ setattr(config, key, value)
490
+ logger.info(f"🔄 Updated config.{key} = {value}")
491
+ else:
492
+ logger.warning(f"⚠️ Unknown configuration key: {key}")
493
+
494
+ return self.save_config()
495
+
496
+ except Exception as e:
497
+ logger.error(f"❌ Error updating configuration: {e}")
498
+ return False
499
+
500
+ def reset_to_defaults(self) -> bool:
501
+ """
502
+ Reset configuration to defaults.
503
+
504
+ Returns:
505
+ True if reset successfully
506
+ """
507
+ try:
508
+ self._config = EnhancedDisplayConfig()
509
+ return self.save_config()
510
+
511
+ except Exception as e:
512
+ logger.error(f"❌ Error resetting configuration: {e}")
513
+ return False
514
+
515
+ def enable_feature(self, feature: str) -> bool:
516
+ """
517
+ Enable a specific feature.
518
+
519
+ Args:
520
+ feature: Feature name to enable
521
+
522
+ Returns:
523
+ True if enabled successfully
524
+ """
525
+ feature_map = {
526
+ 'color_coding': 'use_color_coding',
527
+ 'icons': 'use_icons',
528
+ 'separators': 'use_visual_separators',
529
+ 'styling': 'use_enhanced_styling',
530
+ 'animations': 'enable_animations'
531
+ }
532
+
533
+ config_key = feature_map.get(feature, feature)
534
+ return self.update_config(**{config_key: True})
535
+
536
+ def disable_feature(self, feature: str) -> bool:
537
+ """
538
+ Disable a specific feature.
539
+
540
+ Args:
541
+ feature: Feature name to disable
542
+
543
+ Returns:
544
+ True if disabled successfully
545
+ """
546
+ feature_map = {
547
+ 'color_coding': 'use_color_coding',
548
+ 'icons': 'use_icons',
549
+ 'separators': 'use_visual_separators',
550
+ 'styling': 'use_enhanced_styling',
551
+ 'animations': 'enable_animations'
552
+ }
553
+
554
+ config_key = feature_map.get(feature, feature)
555
+ return self.update_config(**{config_key: False})
556
+
557
+ def get_config(self) -> EnhancedDisplayConfig:
558
+ """Get current configuration."""
559
+ return self.load_config()
560
+
561
+ def validate_config(self) -> List[str]:
562
+ """
563
+ Validate current configuration.
564
+
565
+ Returns:
566
+ List of validation issues (empty if valid)
567
+ """
568
+ issues = []
569
+ config = self.load_config()
570
+
571
+ # Validate color formats
572
+ try:
573
+ color_fields = [
574
+ config.classification_colors.red,
575
+ config.classification_colors.yellow,
576
+ config.classification_colors.green,
577
+ config.ai_analysis.border_color,
578
+ config.patient_message.border_color,
579
+ config.provider_summary.border_color
580
+ ]
581
+
582
+ for color in color_fields:
583
+ if not self._is_valid_color(color):
584
+ issues.append(f"Invalid color format: {color}")
585
+ except AttributeError as e:
586
+ issues.append(f"Configuration structure error: {e}")
587
+
588
+ # Validate icons (should be single emoji or short string)
589
+ try:
590
+ icon_fields = [
591
+ config.ai_analysis.icon,
592
+ config.patient_message.icon,
593
+ config.provider_summary.icon
594
+ ]
595
+
596
+ for icon in icon_fields:
597
+ if len(icon) > 5:
598
+ issues.append(f"Icon too long: {icon}")
599
+ except AttributeError as e:
600
+ issues.append(f"Configuration structure error: {e}")
601
+
602
+ return issues
603
+
604
+ def _is_valid_color(self, color: str) -> bool:
605
+ """Check if color is in valid hex format."""
606
+ import re
607
+ hex_pattern = r'^#[0-9A-Fa-f]{6}$'
608
+ return bool(re.match(hex_pattern, color))
609
+
610
+
611
+ # Global configuration manager instance
612
+ _config_manager: Optional[EnhancedDisplayConfigManager] = None
613
+
614
+
615
+ def get_config_manager(config_file: Optional[str] = None) -> EnhancedDisplayConfigManager:
616
+ """
617
+ Get global configuration manager instance.
618
+
619
+ Args:
620
+ config_file: Optional path to configuration file
621
+
622
+ Returns:
623
+ EnhancedDisplayConfigManager instance
624
+ """
625
+ global _config_manager
626
+
627
+ if _config_manager is None:
628
+ _config_manager = EnhancedDisplayConfigManager(config_file)
629
+
630
+ return _config_manager
631
+
632
+
633
+ def get_enhanced_display_config() -> EnhancedDisplayConfig:
634
+ """
635
+ Get current enhanced display configuration.
636
+
637
+ Returns:
638
+ EnhancedDisplayConfig instance
639
+ """
640
+ return get_config_manager().get_config()
641
+
642
+
643
+ def update_display_config(**kwargs) -> bool:
644
+ """
645
+ Update display configuration.
646
+
647
+ Args:
648
+ **kwargs: Configuration values to update
649
+
650
+ Returns:
651
+ True if updated successfully
652
+ """
653
+ return get_config_manager().update_config(**kwargs)
654
+
655
+
656
+ # Factory functions for common configurations
657
+ def create_high_contrast_config() -> EnhancedDisplayConfig:
658
+ """Create configuration optimized for high contrast accessibility."""
659
+ config = EnhancedDisplayConfig(high_contrast_mode=True)
660
+ # Ensure high contrast colors are applied
661
+ config._apply_high_contrast_colors()
662
+ return config
663
+
664
+
665
+ def create_minimal_config() -> EnhancedDisplayConfig:
666
+ """Create minimal configuration with reduced visual elements."""
667
+ config = EnhancedDisplayConfig()
668
+ config.use_icons = False
669
+ config.use_visual_separators = False
670
+ config.enable_animations = False
671
+ return config
672
+
673
+
674
+ def create_mobile_optimized_config() -> EnhancedDisplayConfig:
675
+ """Create configuration optimized for mobile devices."""
676
+ config = EnhancedDisplayConfig()
677
+ config.responsive_breakpoint = "480px"
678
+ config.ai_analysis.padding = "12px"
679
+ config.patient_message.padding = "12px"
680
+ config.provider_summary.padding = "12px"
681
+ config.enable_animations = False
682
+ return config
src/config/prompts/spiritual_care_message.txt CHANGED
@@ -6,17 +6,41 @@ You are a specialist assistant generating a clear, concise handoff message for t
6
  Generate a brief summary that reflects the patient's emotional or spiritual concerns, ensuring providers receive a clear handoff.
7
  </goal>
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  <strict_formatting_rules>
10
- 1. Write a SINGLE, continuous paragraph. No line breaks.
11
  2. DO NOT use emojis.
12
  3. DO NOT use bullet points, list markers (1., -), or bold text (**).
13
  4. DO NOT use headers or section titles.
 
14
  </strict_formatting_rules>
15
 
16
  <structure_and_content>
 
17
  1. Starts immediately with the patient's Name and Phone Number.
18
  2. Briefly summarizes the patient's situation and the specific indicators that triggered the Red classification.
19
  3. Includes relevant context from any clarifying questions (Triage) if available.
 
 
 
 
 
 
 
 
 
20
  </structure_and_content>
21
 
22
  <tone>
 
6
  Generate a brief summary that reflects the patient's emotional or spiritual concerns, ensuring providers receive a clear handoff.
7
  </goal>
8
 
9
+ <output_formats>
10
+ You can generate two types of summaries based on the request:
11
+
12
+ 1. **SPIRITUAL_CARE_MESSAGE** (default): Brief handoff message for spiritual care team
13
+ 2. **MEDICAL_BRAIN_COMPATIBLE**: Medical Brain compatible single-paragraph summary with patient quote
14
+
15
+ When format is MEDICAL_BRAIN_COMPATIBLE:
16
+ - Write a single coherent paragraph with demographic info, medical history, spiritual concerns, classification, consent status, and contact info
17
+ - Follow with a separate line containing a meaningful patient quote if available
18
+ - Use format: "Patient reported: '[actual patient words]'"
19
+ - Exclude technical system messages from patient quotes
20
+ </output_formats>
21
+
22
  <strict_formatting_rules>
23
+ 1. Write a SINGLE, continuous paragraph. No line breaks within the main paragraph.
24
  2. DO NOT use emojis.
25
  3. DO NOT use bullet points, list markers (1., -), or bold text (**).
26
  4. DO NOT use headers or section titles.
27
+ 5. For MEDICAL_BRAIN_COMPATIBLE: Include patient quote as separate line after main paragraph.
28
  </strict_formatting_rules>
29
 
30
  <structure_and_content>
31
+ For SPIRITUAL_CARE_MESSAGE:
32
  1. Starts immediately with the patient's Name and Phone Number.
33
  2. Briefly summarizes the patient's situation and the specific indicators that triggered the Red classification.
34
  3. Includes relevant context from any clarifying questions (Triage) if available.
35
+
36
+ For MEDICAL_BRAIN_COMPATIBLE:
37
+ 1. Start with: "[Name] is a [age]-year-old [gender]"
38
+ 2. Add medical history: "with clinical history of [conditions]" or "with no significant medical history documented"
39
+ 3. Add spiritual concerns: "The patient expressed [indicators], which may indicate [concern type]"
40
+ 4. Add classification: "resulting in generation of a RED FLAG"
41
+ 5. Add consent: "The patient has been identified for spiritual care team contact"
42
+ 6. Add contact: "The preferred contact number is [phone]" or "No contact number is currently available"
43
+ 7. If conversation context available, add patient quote on separate line
44
  </structure_and_content>
45
 
46
  <tone>
src/config/prompts/spiritual_monitor.txt CHANGED
@@ -47,8 +47,18 @@ When classifying as YELLOW, the purpose of follow-up questions is to CLARIFY:
47
  </yellow_follow_up_purpose>
48
  </category>
49
 
50
- <category name="RED" severity="severe_distress">
51
- The message contains indicators of severe distress or crisis, including:
 
 
 
 
 
 
 
 
 
 
52
 
53
  <crisis_language>
54
  - ANY mention of suicide, suicidal thoughts, or suicidal ideation
@@ -59,14 +69,16 @@ The message contains indicators of severe distress or crisis, including:
59
  - Active crisis or emergency language
60
  </crisis_language>
61
 
62
- <severe_emotional_states>
63
  - Anger (especially spiritual anger toward God/higher power)
64
  - Excessive guilt that dominates daily functioning
65
- - Severe hopelessness combined with crisis language
66
- - Complete loss of hope for the future
67
  - Expressing suffering that feels unbearable
68
  - Spiritual pain (soul-level suffering beyond physical)
69
- </severe_emotional_states>
 
 
70
 
71
  <examples>
72
  "I can't take this anymore, I want it to end"
@@ -86,22 +98,24 @@ The message contains indicators of severe distress or crisis, including:
86
 
87
  <critical_rules>
88
  1. ANY mention of suicide, self-harm, death wishes, or wanting to die is ALWAYS classified as RED, regardless of other content
89
- 2. When uncertain between GREEN and YELLOW, choose GREEN for clearly neutral/positive statements without any distress context, YELLOW when there's genuine ambiguity
90
- 3. When uncertain between YELLOW and RED, carefully evaluate for active crisis language - if present, choose RED
91
- 4. Spiritual questions alone (without crisis indicators) are YELLOW, not RED
92
- 5. Multiple YELLOW indicators together still remain YELLOW unless crisis language is present
93
- 6. Physical pain or medical symptoms alone are GREEN unless accompanied by emotional/spiritual distress language
94
- 7. Patient with KNOWN mental health condition (from medical context or conversation) who expresses emotional or spiritual distress → RED
95
- 8. Patient expressing anticipatory emotional response causing CLEAR distress (not just normal worry) RED
96
- 9. YELLOW is about AMBIGUITY, not severity - use YELLOW when you need clarification about whether distress is present
97
- 10. If patient EXPLICITLY expresses severe emotional/spiritual distress (loss of meaning, despair, hopelessness, profound grief) RED
98
- 11. Simple positive statements in ISOLATION (no prior distress indicators in conversation):
99
- - "I'm okay", "things are fine", "almost everything is normal" → GREEN
100
- - BUT if conversation history contains distress indicators, these may be defensive responses YELLOW (need to verify)
 
 
101
  12. Vague mentions of "some stress" or "a little worried" without context → YELLOW (need to clarify the CAUSE)
102
- 13. DO NOT invent indicators that are not present in the message - only report what is actually stated
103
- 14. Consider conversation CONTEXT: if patient previously expressed distress and now says "I'm fine", this may warrant YELLOW for verification
104
- 15. Loss of loved one, having no help, or other potentially distressing circumstances WITHOUT expressed emotional distress → YELLOW (need to explore if causing distress)
105
  </critical_rules>
106
 
107
  <analysis_process>
 
47
  </yellow_follow_up_purpose>
48
  </category>
49
 
50
+ <category name="RED" severity="active_spiritual_distress">
51
+ The message contains indicators where the patient's situation appears to be CAUSED BY or is ACTIVELY CAUSING emotional or spiritual distress. This includes both severe crisis situations AND active spiritual distress that may not be crisis-level.
52
+
53
+ <explicit_red_indicators>
54
+ These indicators should ALWAYS result in RED classification:
55
+ - Complex grief
56
+ - Loss of a loved one (when patient expresses emotional distress about it)
57
+ - Doubt about meaning of life
58
+ - Doubt about meaning of suffering
59
+ - Doubt about personal dignity
60
+ - Loss of meaning and purpose
61
+ </explicit_red_indicators>
62
 
63
  <crisis_language>
64
  - ANY mention of suicide, suicidal thoughts, or suicidal ideation
 
69
  - Active crisis or emergency language
70
  </crisis_language>
71
 
72
+ <active_spiritual_distress>
73
  - Anger (especially spiritual anger toward God/higher power)
74
  - Excessive guilt that dominates daily functioning
75
+ - Hopelessness about spiritual matters
76
+ - Loss of faith or spiritual connection causing distress
77
  - Expressing suffering that feels unbearable
78
  - Spiritual pain (soul-level suffering beyond physical)
79
+ - Questioning life's meaning in context of distress
80
+ - Feeling abandoned by God/higher power
81
+ </active_spiritual_distress>
82
 
83
  <examples>
84
  "I can't take this anymore, I want it to end"
 
98
 
99
  <critical_rules>
100
  1. ANY mention of suicide, self-harm, death wishes, or wanting to die is ALWAYS classified as RED, regardless of other content
101
+ 2. Explicit red indicators (complex grief, loss of loved one with distress, doubt about meaning of life/suffering/dignity) are ALWAYS RED
102
+ 3. Simple positive statements like "I am fine" without distress context are ALWAYS GREEN
103
+ 4. When uncertain between GREEN and YELLOW, choose GREEN for clearly positive/neutral statements
104
+ 5. When uncertain between YELLOW and RED, carefully evaluate for explicit red indicators or active crisis language
105
+ 6. "Feeling sad" alone without spiritual context should be YELLOW, not RED
106
+ 7. Loss of meaning and purpose should ALWAYS be RED
107
+ 8. Physical pain or medical symptoms alone are GREEN unless accompanied by emotional/spiritual distress language
108
+ 9. YELLOW is about AMBIGUITY and need for clarification, not severity
109
+ 10. Do NOT invent indicators that are not present in the message - only report what is actually stated
110
+ 11. Simple positive statements WITHOUT any distress context:
111
+ - "I'm okay", "I am fine", "things are fine", "almost everything is normal" → GREEN
112
+ - These should be GREEN unless there are EXPLICIT distress indicators in the SAME message
113
+ - Do NOT assume defensive responses without clear evidence
114
+ - Previous conversation history should NOT change GREEN classification of clear positive statements
115
  12. Vague mentions of "some stress" or "a little worried" without context → YELLOW (need to clarify the CAUSE)
116
+ 13. Consider only the CURRENT message for classification, not conversation history assumptions
117
+ 14. Loss of loved one, having no help, or other potentially distressing circumstances WITHOUT expressed emotional distress YELLOW (need to explore if causing distress)
118
+ 15. Patient's situation must be ACTIVELY CAUSING distress to be RED, not just potentially distressing circumstances
119
  </critical_rules>
120
 
121
  <analysis_process>
src/core/conversation_verification.py CHANGED
@@ -14,6 +14,8 @@ from typing import Dict, List, Any, Optional, Tuple
14
  from dataclasses import dataclass, asdict, field
15
 
16
  from src.core.conversation_logger import ConversationLogger, ConversationEntry
 
 
17
 
18
 
19
  @dataclass
@@ -27,8 +29,8 @@ class VerificationFeedback:
27
 
28
 
29
  @dataclass
30
- class VerificationRecord:
31
- """Complete verification record for a single conversation exchange."""
32
  exchange_id: str
33
  exchange_number: int
34
  timestamp: datetime
@@ -38,6 +40,14 @@ class VerificationRecord:
38
  original_confidence: float
39
  original_indicators: List[str]
40
  original_reasoning: str
 
 
 
 
 
 
 
 
41
  is_correct: Optional[bool] = None
42
  correct_classification: Optional[str] = None
43
  correction_reason: Optional[str] = None
@@ -45,8 +55,8 @@ class VerificationRecord:
45
  verification_timestamp: Optional[datetime] = None
46
 
47
  @classmethod
48
- def from_conversation_entry(cls, entry: ConversationEntry, exchange_number: int) -> 'VerificationRecord':
49
- """Create VerificationRecord from ConversationEntry."""
50
  return cls(
51
  exchange_id=f"{entry.session_id}_{entry.message_index}",
52
  exchange_number=exchange_number,
@@ -67,6 +77,20 @@ class VerificationRecord:
67
  self.verifier_notes = feedback.notes
68
  self.verification_timestamp = datetime.now()
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  @dataclass
72
  class VerificationProgress:
@@ -89,8 +113,8 @@ class VerificationProgress:
89
 
90
 
91
  @dataclass
92
- class VerificationSession:
93
- """Complete verification session for a conversation."""
94
  session_id: str
95
  conversation_session_id: str # Links to ConversationLogger session
96
  patient_name: str
@@ -99,8 +123,12 @@ class VerificationSession:
99
  end_time: Optional[datetime] = None
100
  total_exchanges: int = 0
101
  verified_exchanges: int = 0
102
- verification_records: List[VerificationRecord] = field(default_factory=list)
103
  is_complete: bool = False
 
 
 
 
104
 
105
  def get_progress(self) -> VerificationProgress:
106
  """Get current verification progress."""
@@ -138,7 +166,7 @@ class VerificationSession:
138
  common_errors=common_errors
139
  )
140
 
141
- def add_verification_record(self, record: VerificationRecord) -> None:
142
  """Add verification record to session."""
143
  self.verification_records.append(record)
144
 
@@ -157,54 +185,89 @@ class VerificationSession:
157
  return True
158
  return False
159
 
160
- def get_unverified_records(self) -> List[VerificationRecord]:
161
  """Get list of unverified records."""
162
  return [r for r in self.verification_records if r.is_correct is None]
163
 
164
- def get_next_unverified_record(self) -> Optional[VerificationRecord]:
165
  """Get next unverified record."""
166
  unverified = self.get_unverified_records()
167
  return unverified[0] if unverified else None
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- class ConversationVerificationManager:
171
- """Manager for conversation verification sessions."""
172
 
173
  def __init__(self, storage_dir: str = "verification_sessions"):
174
- """Initialize verification manager."""
175
  from src.core.verification_store import JSONVerificationStore
176
  self.store = JSONVerificationStore(storage_dir)
 
177
 
178
  def create_verification_session(
179
  self,
180
  conversation_logger: ConversationLogger,
181
- verifier_name: str = "Medical Professional"
182
- ) -> VerificationSession:
 
183
  """
184
- Create new verification session from conversation logger.
185
 
186
  Args:
187
  conversation_logger: Source conversation to verify
188
  verifier_name: Name of person doing verification
 
189
 
190
  Returns:
191
- New VerificationSession ready for verification
192
  """
193
  session_id = f"verification_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{str(uuid.uuid4())[:8]}"
194
 
195
- # Create verification session
196
- session = VerificationSession(
197
  session_id=session_id,
198
  conversation_session_id=conversation_logger.session_id,
199
  patient_name=conversation_logger.patient_name,
200
  verifier_name=verifier_name,
201
  start_time=datetime.now(),
202
- total_exchanges=len(conversation_logger.entries)
 
 
203
  )
204
 
205
- # Convert conversation entries to verification records
206
  for i, entry in enumerate(conversation_logger.entries, 1):
207
- record = VerificationRecord.from_conversation_entry(entry, i)
 
 
 
 
 
208
  session.add_verification_record(record)
209
 
210
  # Save initial session
@@ -212,6 +275,55 @@ class ConversationVerificationManager:
212
 
213
  return session
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  def get_verification_progress(self, session_id: str) -> Optional[VerificationProgress]:
216
  """Get verification progress for session."""
217
  session = self.store.load_session(session_id)
@@ -262,7 +374,8 @@ class ConversationVerificationManager:
262
 
263
  progress = session.get_progress()
264
 
265
- return {
 
266
  "session_id": session.session_id,
267
  "patient_name": session.patient_name,
268
  "verifier_name": session.verifier_name,
@@ -271,15 +384,21 @@ class ConversationVerificationManager:
271
  "is_complete": session.is_complete,
272
  "progress": asdict(progress),
273
  "total_exchanges": session.total_exchanges,
274
- "verified_exchanges": session.verified_exchanges
 
 
 
 
275
  }
 
 
276
 
277
- def load_session(self, session_id: str) -> Optional[VerificationSession]:
278
- """Load verification session by ID."""
279
  return self.store.load_session(session_id)
280
 
281
- def save_session(self, session: VerificationSession) -> None:
282
- """Save verification session."""
283
  self.store.save_session(session)
284
 
285
  def list_sessions(self) -> List[Dict[str, Any]]:
@@ -290,3 +409,55 @@ class ConversationVerificationManager:
290
  """Get incomplete verification sessions."""
291
  return self.store.get_incomplete_sessions()
292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  from dataclasses import dataclass, asdict, field
15
 
16
  from src.core.conversation_logger import ConversationLogger, ConversationEntry
17
+ from src.interface.enhanced_results_display_manager import EnhancedResultsDisplayManager, EnhancedDisplayConfig
18
+ from src.core.provider_summary_generator import ProviderSummary
19
 
20
 
21
  @dataclass
 
29
 
30
 
31
  @dataclass
32
+ class EnhancedVerificationRecord:
33
+ """Enhanced verification record with new format support."""
34
  exchange_id: str
35
  exchange_number: int
36
  timestamp: datetime
 
40
  original_confidence: float
41
  original_indicators: List[str]
42
  original_reasoning: str
43
+
44
+ # Enhanced fields for new formats
45
+ enhanced_display_format: Optional[str] = None # HTML formatted display
46
+ provider_summary: Optional[Dict[str, Any]] = None # Provider summary data
47
+ coherent_summary_paragraph: Optional[str] = None # Coherent paragraph format
48
+ visual_sections: Optional[List[Dict[str, str]]] = None # Visual section data
49
+
50
+ # Verification fields
51
  is_correct: Optional[bool] = None
52
  correct_classification: Optional[str] = None
53
  correction_reason: Optional[str] = None
 
55
  verification_timestamp: Optional[datetime] = None
56
 
57
  @classmethod
58
+ def from_conversation_entry(cls, entry: ConversationEntry, exchange_number: int) -> 'EnhancedVerificationRecord':
59
+ """Create EnhancedVerificationRecord from ConversationEntry."""
60
  return cls(
61
  exchange_id=f"{entry.session_id}_{entry.message_index}",
62
  exchange_number=exchange_number,
 
77
  self.verifier_notes = feedback.notes
78
  self.verification_timestamp = datetime.now()
79
 
80
+ def set_enhanced_formats(
81
+ self,
82
+ enhanced_display: Optional[str] = None,
83
+ provider_summary: Optional[ProviderSummary] = None,
84
+ coherent_paragraph: Optional[str] = None,
85
+ visual_sections: Optional[List[Dict[str, str]]] = None
86
+ ) -> None:
87
+ """Set enhanced format data for this record."""
88
+ self.enhanced_display_format = enhanced_display
89
+ if provider_summary:
90
+ self.provider_summary = provider_summary.to_dict()
91
+ self.coherent_summary_paragraph = coherent_paragraph
92
+ self.visual_sections = visual_sections or []
93
+
94
 
95
  @dataclass
96
  class VerificationProgress:
 
113
 
114
 
115
  @dataclass
116
+ class EnhancedVerificationSession:
117
+ """Enhanced verification session with new format support."""
118
  session_id: str
119
  conversation_session_id: str # Links to ConversationLogger session
120
  patient_name: str
 
123
  end_time: Optional[datetime] = None
124
  total_exchanges: int = 0
125
  verified_exchanges: int = 0
126
+ verification_records: List[EnhancedVerificationRecord] = field(default_factory=list)
127
  is_complete: bool = False
128
+
129
+ # Enhanced format support
130
+ display_manager: Optional[EnhancedResultsDisplayManager] = None
131
+ enhanced_format_enabled: bool = True
132
 
133
  def get_progress(self) -> VerificationProgress:
134
  """Get current verification progress."""
 
166
  common_errors=common_errors
167
  )
168
 
169
+ def add_verification_record(self, record: EnhancedVerificationRecord) -> None:
170
  """Add verification record to session."""
171
  self.verification_records.append(record)
172
 
 
185
  return True
186
  return False
187
 
188
+ def get_unverified_records(self) -> List[EnhancedVerificationRecord]:
189
  """Get list of unverified records."""
190
  return [r for r in self.verification_records if r.is_correct is None]
191
 
192
+ def get_next_unverified_record(self) -> Optional[EnhancedVerificationRecord]:
193
  """Get next unverified record."""
194
  unverified = self.get_unverified_records()
195
  return unverified[0] if unverified else None
196
 
197
+ def generate_enhanced_display_for_record(self, record: EnhancedVerificationRecord) -> str:
198
+ """Generate enhanced display format for a verification record."""
199
+ if not self.display_manager:
200
+ self.display_manager = EnhancedResultsDisplayManager()
201
+
202
+ # Create AI analysis data
203
+ ai_analysis = {
204
+ 'classification': record.original_classification,
205
+ 'indicators': record.original_indicators,
206
+ 'reasoning': record.original_reasoning,
207
+ 'confidence': record.original_confidence
208
+ }
209
+
210
+ # Generate enhanced display
211
+ enhanced_display = self.display_manager.format_combined_results(
212
+ ai_analysis=ai_analysis,
213
+ patient_message=record.user_message,
214
+ provider_summary=None # Would need to be generated separately
215
+ )
216
+
217
+ # Store enhanced display in record
218
+ record.enhanced_display_format = enhanced_display
219
+
220
+ return enhanced_display
221
+
222
 
223
+ class EnhancedConversationVerificationManager:
224
+ """Enhanced manager for conversation verification sessions with new format support."""
225
 
226
  def __init__(self, storage_dir: str = "verification_sessions"):
227
+ """Initialize enhanced verification manager."""
228
  from src.core.verification_store import JSONVerificationStore
229
  self.store = JSONVerificationStore(storage_dir)
230
+ self.display_manager = EnhancedResultsDisplayManager()
231
 
232
  def create_verification_session(
233
  self,
234
  conversation_logger: ConversationLogger,
235
+ verifier_name: str = "Medical Professional",
236
+ enable_enhanced_formats: bool = True
237
+ ) -> EnhancedVerificationSession:
238
  """
239
+ Create new enhanced verification session from conversation logger.
240
 
241
  Args:
242
  conversation_logger: Source conversation to verify
243
  verifier_name: Name of person doing verification
244
+ enable_enhanced_formats: Whether to enable enhanced display formats
245
 
246
  Returns:
247
+ New EnhancedVerificationSession ready for verification
248
  """
249
  session_id = f"verification_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{str(uuid.uuid4())[:8]}"
250
 
251
+ # Create enhanced verification session
252
+ session = EnhancedVerificationSession(
253
  session_id=session_id,
254
  conversation_session_id=conversation_logger.session_id,
255
  patient_name=conversation_logger.patient_name,
256
  verifier_name=verifier_name,
257
  start_time=datetime.now(),
258
+ total_exchanges=len(conversation_logger.entries),
259
+ display_manager=self.display_manager if enable_enhanced_formats else None,
260
+ enhanced_format_enabled=enable_enhanced_formats
261
  )
262
 
263
+ # Convert conversation entries to enhanced verification records
264
  for i, entry in enumerate(conversation_logger.entries, 1):
265
+ record = EnhancedVerificationRecord.from_conversation_entry(entry, i)
266
+
267
+ # Generate enhanced formats if enabled
268
+ if enable_enhanced_formats:
269
+ self._generate_enhanced_formats_for_record(record, entry)
270
+
271
  session.add_verification_record(record)
272
 
273
  # Save initial session
 
275
 
276
  return session
277
 
278
+ def _generate_enhanced_formats_for_record(
279
+ self,
280
+ record: EnhancedVerificationRecord,
281
+ entry: ConversationEntry
282
+ ) -> None:
283
+ """Generate enhanced formats for a verification record."""
284
+ try:
285
+ # Generate enhanced display format
286
+ ai_analysis = {
287
+ 'classification': entry.spiritual_classification,
288
+ 'indicators': entry.classification_indicators,
289
+ 'reasoning': entry.classification_reasoning,
290
+ 'confidence': entry.classification_confidence
291
+ }
292
+
293
+ enhanced_display = self.display_manager.format_combined_results(
294
+ ai_analysis=ai_analysis,
295
+ patient_message=entry.user_message,
296
+ provider_summary=None # Would need provider summary data
297
+ )
298
+
299
+ # Generate visual sections data
300
+ visual_sections = [
301
+ {
302
+ 'type': 'ai_analysis',
303
+ 'classification': entry.spiritual_classification,
304
+ 'confidence': str(entry.classification_confidence),
305
+ 'indicators': '; '.join(entry.classification_indicators),
306
+ 'reasoning': entry.classification_reasoning
307
+ },
308
+ {
309
+ 'type': 'patient_message',
310
+ 'content': entry.user_message
311
+ },
312
+ {
313
+ 'type': 'assistant_response',
314
+ 'content': entry.assistant_response
315
+ }
316
+ ]
317
+
318
+ # Set enhanced formats in record
319
+ record.set_enhanced_formats(
320
+ enhanced_display=enhanced_display,
321
+ visual_sections=visual_sections
322
+ )
323
+
324
+ except Exception as e:
325
+ print(f"Warning: Could not generate enhanced formats for record {record.exchange_id}: {e}")
326
+
327
  def get_verification_progress(self, session_id: str) -> Optional[VerificationProgress]:
328
  """Get verification progress for session."""
329
  session = self.store.load_session(session_id)
 
374
 
375
  progress = session.get_progress()
376
 
377
+ # Enhanced statistics including format information
378
+ enhanced_stats = {
379
  "session_id": session.session_id,
380
  "patient_name": session.patient_name,
381
  "verifier_name": session.verifier_name,
 
384
  "is_complete": session.is_complete,
385
  "progress": asdict(progress),
386
  "total_exchanges": session.total_exchanges,
387
+ "verified_exchanges": session.verified_exchanges,
388
+ "enhanced_format_enabled": getattr(session, 'enhanced_format_enabled', False),
389
+ "records_with_enhanced_display": sum(1 for r in session.verification_records if r.enhanced_display_format),
390
+ "records_with_provider_summary": sum(1 for r in session.verification_records if r.provider_summary),
391
+ "records_with_coherent_paragraph": sum(1 for r in session.verification_records if r.coherent_summary_paragraph)
392
  }
393
+
394
+ return enhanced_stats
395
 
396
+ def load_session(self, session_id: str) -> Optional[EnhancedVerificationSession]:
397
+ """Load enhanced verification session by ID."""
398
  return self.store.load_session(session_id)
399
 
400
+ def save_session(self, session: EnhancedVerificationSession) -> None:
401
+ """Save enhanced verification session."""
402
  self.store.save_session(session)
403
 
404
  def list_sessions(self) -> List[Dict[str, Any]]:
 
409
  """Get incomplete verification sessions."""
410
  return self.store.get_incomplete_sessions()
411
 
412
+ def export_session_with_enhanced_data(self, session_id: str) -> Optional[Dict[str, Any]]:
413
+ """Export session with enhanced format data included."""
414
+ session = self.load_session(session_id)
415
+ if not session:
416
+ return None
417
+
418
+ export_data = {
419
+ 'session_info': {
420
+ 'session_id': session.session_id,
421
+ 'conversation_session_id': session.conversation_session_id,
422
+ 'patient_name': session.patient_name,
423
+ 'verifier_name': session.verifier_name,
424
+ 'start_time': session.start_time.isoformat(),
425
+ 'end_time': session.end_time.isoformat() if session.end_time else None,
426
+ 'enhanced_format_enabled': getattr(session, 'enhanced_format_enabled', False)
427
+ },
428
+ 'records': []
429
+ }
430
+
431
+ for record in session.verification_records:
432
+ record_data = {
433
+ 'exchange_id': record.exchange_id,
434
+ 'exchange_number': record.exchange_number,
435
+ 'timestamp': record.timestamp.isoformat(),
436
+ 'user_message': record.user_message,
437
+ 'assistant_response': record.assistant_response,
438
+ 'original_classification': record.original_classification,
439
+ 'original_confidence': record.original_confidence,
440
+ 'original_indicators': record.original_indicators,
441
+ 'original_reasoning': record.original_reasoning,
442
+ 'is_correct': record.is_correct,
443
+ 'correct_classification': record.correct_classification,
444
+ 'correction_reason': record.correction_reason,
445
+ 'verifier_notes': record.verifier_notes,
446
+ 'verification_timestamp': record.verification_timestamp.isoformat() if record.verification_timestamp else None,
447
+
448
+ # Enhanced format data
449
+ 'enhanced_display_format': record.enhanced_display_format,
450
+ 'provider_summary': record.provider_summary,
451
+ 'coherent_summary_paragraph': record.coherent_summary_paragraph,
452
+ 'visual_sections': record.visual_sections
453
+ }
454
+ export_data['records'].append(record_data)
455
+
456
+ return export_data
457
+
458
+
459
+ # Legacy aliases for backward compatibility
460
+ ConversationVerificationManager = EnhancedConversationVerificationManager
461
+ VerificationSession = EnhancedVerificationSession
462
+ VerificationRecord = EnhancedVerificationRecord
463
+
src/core/improved_classification_prompt_manager.py ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # improved_classification_prompt_manager.py
2
+ """
3
+ Improved Classification Prompt Manager for UI Classification Improvements.
4
+
5
+ Manages updated classification prompts with new red flag definitions and explicit indicators
6
+ based on medical professional feedback and requirements.
7
+
8
+ Requirements: 4.1, 4.2, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6
9
+ """
10
+
11
+ from typing import List, Dict, Optional
12
+ from dataclasses import dataclass
13
+ import logging
14
+
15
+ from src.core.chaplain_models import ClassificationFlowResult, DistressIndicator
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class ClassificationResult:
24
+ """
25
+ Enhanced classification result with validation support.
26
+
27
+ Extends the basic classification to include validation fields
28
+ for consistency checking.
29
+ """
30
+ classification: str # RED/YELLOW/GREEN
31
+ confidence: float
32
+ indicators: List[str]
33
+ reasoning: str
34
+ red_flag_indicators: List[str]
35
+ yellow_flag_indicators: List[str]
36
+ is_valid: bool
37
+
38
+
39
+ class ImprovedClassificationPromptManager:
40
+ """
41
+ Manages updated classification prompts with improved red flag definitions.
42
+
43
+ Implements new red flag criteria based on medical professional feedback:
44
+ - Broader definition of red flags beyond crisis situations
45
+ - Explicit red indicators for consistent classification
46
+ - Updated yellow flag criteria for better triage
47
+
48
+ Requirements: 4.1, 4.2, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6
49
+ """
50
+
51
+ def __init__(self):
52
+ """Initialize the improved classification prompt manager."""
53
+ self._error_handler = None # Lazy loaded to avoid circular imports
54
+ logger.info("🔧 ImprovedClassificationPromptManager initialized")
55
+
56
+ @property
57
+ def error_handler(self):
58
+ """Lazy load error handler to avoid circular imports."""
59
+ if self._error_handler is None:
60
+ from src.core.ui_error_handler import UIErrorHandler
61
+ self._error_handler = UIErrorHandler()
62
+ return self._error_handler
63
+
64
+ def get_updated_red_flag_definition(self) -> str:
65
+ """
66
+ Get the updated red flag definition.
67
+
68
+ Returns broader definition that includes active spiritual distress,
69
+ not just crisis situations.
70
+
71
+ Requirements: 4.1, 4.2
72
+ """
73
+ return (
74
+ "Patient's situation appears to be caused by or actively causing "
75
+ "emotional or spiritual distress"
76
+ )
77
+
78
+ def get_explicit_red_indicators(self) -> List[str]:
79
+ """
80
+ Get the list of explicit red flag indicators.
81
+
82
+ Returns the five specific indicators that should always trigger
83
+ red classification for consistency.
84
+
85
+ Requirements: 5.2, 5.3, 5.4, 5.5, 5.6
86
+ """
87
+ return [
88
+ "Complex grief",
89
+ "Loss of a loved one",
90
+ "Doubt about meaning of life",
91
+ "Doubt about meaning of suffering",
92
+ "Doubt about personal dignity"
93
+ ]
94
+
95
+ def get_yellow_flag_criteria(self) -> List[str]:
96
+ """
97
+ Get criteria for yellow flag classification.
98
+
99
+ Returns criteria for situations requiring further investigation
100
+ but not immediate red classification.
101
+
102
+ Requirements: 6.1, 6.2, 6.3, 6.4, 6.5
103
+ """
104
+ return [
105
+ "Feelings of sadness without additional context",
106
+ "Uncertainty about cause or consequences of distress",
107
+ "Emotional states requiring further clarification",
108
+ "Potential spiritual issues without clear indicators",
109
+ "Situations requiring additional questions"
110
+ ]
111
+
112
+ def build_enhanced_classification_prompt(self) -> str:
113
+ """
114
+ Build the complete enhanced classification prompt.
115
+
116
+ Combines updated definitions, explicit indicators, and criteria
117
+ into a comprehensive prompt for consistent classification.
118
+
119
+ Requirements: 5.1, 5.7
120
+ """
121
+ red_definition = self.get_updated_red_flag_definition()
122
+ red_indicators = self.get_explicit_red_indicators()
123
+ yellow_criteria = self.get_yellow_flag_criteria()
124
+
125
+ prompt = f"""<system_role>
126
+ You are a spiritual distress classification specialist for a medical chat system.
127
+ Your task is to analyze patient messages and classify the level of spiritual
128
+ or emotional distress to route them to appropriate support.
129
+ </system_role>
130
+
131
+ <updated_red_flag_definition>
132
+ RED FLAG: {red_definition}
133
+
134
+ This includes:
135
+ - Active spiritual distress (even if not crisis-level)
136
+ - Situations where circumstances cause distress
137
+ - Situations where distress affects circumstances
138
+ - Severe distress or crisis as a subcategory
139
+ </updated_red_flag_definition>
140
+
141
+ <explicit_red_indicators>
142
+ The following indicators should ALWAYS result in RED classification:
143
+ """
144
+
145
+ for i, indicator in enumerate(red_indicators, 1):
146
+ prompt += f"{i}. {indicator}\n"
147
+
148
+ prompt += f"""</explicit_red_indicators>
149
+
150
+ <yellow_flag_criteria>
151
+ YELLOW FLAG is used for:
152
+ """
153
+
154
+ for criterion in yellow_criteria:
155
+ prompt += f"- {criterion}\n"
156
+
157
+ prompt += """
158
+ Yellow flag indicates need for further investigation and should
159
+ include recommendations for additional questions.
160
+ </yellow_flag_criteria>
161
+
162
+ <classification_logic>
163
+ 1. Check for presence of explicit red indicators
164
+ 2. Assess if situation matches updated red flag definition
165
+ 3. If not red, determine if further clarification needed (yellow)
166
+ 4. Green only if no signs of spiritual/emotional distress
167
+ </classification_logic>
168
+
169
+ <critical_rules>
170
+ - Patient safety is priority
171
+ - When in doubt, favor higher level of classification
172
+ - Ensure consistent application of explicit indicators
173
+ - Simple sadness without context = yellow, not red
174
+ - Loss of meaning and purpose = always red
175
+ </critical_rules>
176
+
177
+ <output_format>
178
+ Respond with ONLY valid JSON in this exact format:
179
+ {{
180
+ "state": "red|yellow|green",
181
+ "indicators": ["list of detected indicators"],
182
+ "confidence": 0.0-1.0,
183
+ "reasoning": "explanation of classification in English",
184
+ "red_flag_indicators": ["list of red flag indicators if any"],
185
+ "yellow_flag_indicators": ["list of yellow flag indicators if any"]
186
+ }}
187
+ </output_format>"""
188
+
189
+ return prompt
190
+
191
+ def validate_classification_consistency(self, result: ClassificationResult) -> bool:
192
+ """
193
+ Validate classification result for consistency with new criteria.
194
+
195
+ Checks if the classification follows the updated rules and
196
+ explicit indicators properly.
197
+
198
+ Args:
199
+ result: ClassificationResult to validate
200
+
201
+ Returns:
202
+ True if classification is consistent with new criteria
203
+
204
+ Requirements: 5.7, 4.1, 4.2
205
+ """
206
+ try:
207
+ # Check if classification is valid
208
+ if result.classification not in ["red", "yellow", "green"]:
209
+ logger.warning(f"Invalid classification: {result.classification}")
210
+ return False
211
+
212
+ # Check confidence range
213
+ if not 0.0 <= result.confidence <= 1.0:
214
+ logger.warning(f"Invalid confidence: {result.confidence}")
215
+ return False
216
+
217
+ # Validate red flag consistency
218
+ explicit_red_indicators = self.get_explicit_red_indicators()
219
+
220
+ # If any explicit red indicator is present, classification should be red
221
+ for indicator in result.red_flag_indicators:
222
+ if any(red_indicator.lower() in indicator.lower()
223
+ for red_indicator in explicit_red_indicators):
224
+ if result.classification != "red":
225
+ logger.warning(
226
+ f"Explicit red indicator '{indicator}' found but "
227
+ f"classification is '{result.classification}'"
228
+ )
229
+ return False
230
+
231
+ # Check for simple sadness misclassification
232
+ sadness_indicators = ["смуток", "сумно", "почуття смутку", "sadness", "sad"]
233
+ has_simple_sadness = any(
234
+ sadness in " ".join(result.indicators).lower()
235
+ for sadness in sadness_indicators
236
+ )
237
+
238
+ if has_simple_sadness and result.classification == "red":
239
+ # Check if there are additional spiritual indicators
240
+ spiritual_indicators = [
241
+ "loss of meaning", "spiritual", "faith"
242
+ ]
243
+ has_spiritual_context = any(
244
+ spiritual in " ".join(result.indicators).lower()
245
+ for spiritual in spiritual_indicators
246
+ )
247
+
248
+ if not has_spiritual_context:
249
+ logger.warning(
250
+ "Simple sadness classified as red without spiritual context"
251
+ )
252
+ return False
253
+
254
+ # Validate that loss of meaning/purpose is red
255
+ meaning_indicators = [
256
+ "втрата сенсу", "втрата мети", "сенс життя", "мета життя",
257
+ "loss of meaning", "loss of purpose", "meaning of life"
258
+ ]
259
+ has_meaning_loss = any(
260
+ meaning in " ".join(result.indicators).lower()
261
+ for meaning in meaning_indicators
262
+ )
263
+
264
+ if has_meaning_loss and result.classification != "red":
265
+ logger.warning(
266
+ "Loss of meaning/purpose should be classified as red"
267
+ )
268
+ return False
269
+
270
+ logger.info(f"Classification validation passed: {result.classification}")
271
+ return True
272
+
273
+ except Exception as e:
274
+ logger.error(f"Error validating classification: {e}")
275
+ return False
276
+
277
+ def get_classification_guidelines(self) -> Dict[str, str]:
278
+ """
279
+ Get classification guidelines for reference.
280
+
281
+ Returns dictionary with guidelines for each classification level.
282
+ """
283
+ return {
284
+ "red": (
285
+ "Active or potential spiritual/emotional distress. "
286
+ "Includes explicit indicators: complex grief, loss of loved one, "
287
+ "doubt about meaning of life/suffering/dignity."
288
+ ),
289
+ "yellow": (
290
+ "Requires further investigation. Unclear situations, "
291
+ "simple sadness without context, emotional states requiring clarification."
292
+ ),
293
+ "green": (
294
+ "No signs of spiritual or emotional distress. "
295
+ "Regular conversation without problem indicators."
296
+ )
297
+ }
298
+
299
+ def create_classification_result(
300
+ self,
301
+ classification: str,
302
+ confidence: float,
303
+ indicators: List[str],
304
+ reasoning: str,
305
+ red_flag_indicators: Optional[List[str]] = None,
306
+ yellow_flag_indicators: Optional[List[str]] = None
307
+ ) -> ClassificationResult:
308
+ """
309
+ Create a validated ClassificationResult.
310
+
311
+ Args:
312
+ classification: RED/YELLOW/GREEN
313
+ confidence: Confidence score 0.0-1.0
314
+ indicators: List of detected indicators
315
+ reasoning: Explanation of classification
316
+ red_flag_indicators: List of red flag indicators
317
+ yellow_flag_indicators: List of yellow flag indicators
318
+
319
+ Returns:
320
+ ClassificationResult with validation status
321
+ """
322
+ try:
323
+ result = ClassificationResult(
324
+ classification=classification,
325
+ confidence=confidence,
326
+ indicators=indicators,
327
+ reasoning=reasoning,
328
+ red_flag_indicators=red_flag_indicators or [],
329
+ yellow_flag_indicators=yellow_flag_indicators or [],
330
+ is_valid=False # Will be set by validation
331
+ )
332
+
333
+ # Validate the result
334
+ result.is_valid = self.validate_classification_consistency(result)
335
+
336
+ # Apply fallbacks if validation fails
337
+ if not result.is_valid:
338
+ result = self._apply_classification_fallbacks(result)
339
+
340
+ return result
341
+
342
+ except Exception as e:
343
+ logger.error(f"Error creating classification result: {e}")
344
+ # Return fallback classification using error handler
345
+ return self.error_handler.handle_classification_error(e, {
346
+ 'classification': classification,
347
+ 'confidence': confidence,
348
+ 'indicators': indicators,
349
+ 'reasoning': reasoning
350
+ })
351
+
352
+ def _apply_classification_fallbacks(self, result: ClassificationResult) -> ClassificationResult:
353
+ """
354
+ Apply fallbacks to fix invalid classification result.
355
+
356
+ Args:
357
+ result: Invalid ClassificationResult
358
+
359
+ Returns:
360
+ Fixed ClassificationResult
361
+ """
362
+ try:
363
+ # Fix classification if invalid
364
+ if result.classification not in ["red", "yellow", "green"]:
365
+ result.classification = "yellow" # Safe default
366
+ logger.warning(f"Fixed invalid classification to yellow")
367
+
368
+ # Fix confidence if out of range
369
+ if not 0.0 <= result.confidence <= 1.0:
370
+ result.confidence = max(0.0, min(1.0, result.confidence))
371
+ logger.warning(f"Clamped confidence to valid range: {result.confidence}")
372
+
373
+ # Ensure reasoning is present
374
+ if not result.reasoning or len(result.reasoning.strip()) < 10:
375
+ result.reasoning = f"Classification: {result.classification.upper()}. Manual review recommended due to validation issues."
376
+ logger.warning("Applied fallback reasoning")
377
+
378
+ # Ensure indicators are present
379
+ if not result.indicators:
380
+ result.indicators = ["Classification validation issues - manual review required"]
381
+ logger.warning("Applied fallback indicators")
382
+
383
+ # Re-validate
384
+ result.is_valid = self.validate_classification_consistency(result)
385
+
386
+ return result
387
+
388
+ except Exception as e:
389
+ logger.error(f"Failed to apply classification fallbacks: {e}")
390
+ # Return minimal valid result
391
+ return ClassificationResult(
392
+ classification="yellow",
393
+ confidence=0.5,
394
+ indicators=["System error - manual review required"],
395
+ reasoning="Classification system error. Manual review required.",
396
+ red_flag_indicators=[],
397
+ yellow_flag_indicators=["System error"],
398
+ is_valid=False
399
+ )
400
+
401
+
402
+ def create_improved_classification_prompt_manager() -> ImprovedClassificationPromptManager:
403
+ """
404
+ Factory function to create ImprovedClassificationPromptManager.
405
+
406
+ Returns:
407
+ Initialized ImprovedClassificationPromptManager instance
408
+ """
409
+ return ImprovedClassificationPromptManager()
src/core/provider_summary_generator.py CHANGED
@@ -13,6 +13,11 @@ from datetime import datetime
13
  from typing import List, Optional
14
  import sys
15
  import os
 
 
 
 
 
16
 
17
  # Add src to path for imports
18
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -139,6 +144,7 @@ class ProviderSummaryGenerator:
139
  """
140
  self.ai_client = ai_client
141
  self.prompt_controller = PromptController()
 
142
  self.default_actions = [
143
  "Contact patient within 24 hours",
144
  "Assess immediate safety and support needs",
@@ -153,6 +159,14 @@ class ProviderSummaryGenerator:
153
  'MODERATE': 0.5 # Standard follow-up
154
  }
155
 
 
 
 
 
 
 
 
 
156
  def generate_summary(
157
  self,
158
  indicators: List[str],
@@ -194,68 +208,98 @@ class ProviderSummaryGenerator:
194
 
195
  Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
196
  """
197
- # Build triage context (Requirement 7.4)
198
- triage_context = []
199
- if triage_questions and triage_responses:
200
- for q, r in zip(triage_questions, triage_responses):
201
- triage_context.append({
202
- "question": q,
203
- "response": r,
204
- "timestamp": datetime.now().isoformat()
205
- })
206
-
207
- # Generate conversation history summary (Requirement 7.5)
208
- conversation_history_summary = self._generate_conversation_summary(
209
- conversation_history, indicators, context_factors or []
210
- )
211
-
212
- # Determine severity and urgency levels
213
- severity_level = self._determine_severity_level(confidence, indicators, context_factors or [])
214
- urgency_level = self._determine_urgency_level(severity_level, defensive_patterns_detected)
215
-
216
- # Generate situation description
217
- situation_description = self._generate_enhanced_situation_description(
218
- indicators, reasoning, triage_context, medical_context, context_factors or []
219
- )
220
-
221
- # Generate recommended actions
222
- recommended_actions = self._generate_recommended_actions(
223
- severity_level, indicators, defensive_patterns_detected, medical_context
224
- )
225
-
226
- # Determine follow-up timeline
227
- follow_up_timeline = self._determine_follow_up_timeline(urgency_level, severity_level)
228
-
229
- return ProviderSummary(
230
- # Contact information (Requirement 7.1)
231
- patient_name=patient_name or "[Patient Name]",
232
- patient_phone=patient_phone or "[Phone Number]",
233
- patient_email=patient_email,
234
- emergency_contact=emergency_contact,
235
-
236
- # Classification information (Requirements 7.2, 7.3)
237
- classification="RED",
238
- confidence=confidence,
239
- reasoning=reasoning,
240
- indicators=indicators or [],
241
- severity_level=severity_level,
242
-
243
- # Context information (Requirements 7.4, 7.5)
244
- triage_context=triage_context,
245
- conversation_context=conversation_context or "",
246
- conversation_history_summary=conversation_history_summary,
247
-
248
- # Enhanced contextual information
249
- medical_context=medical_context,
250
- context_factors=context_factors or [],
251
- defensive_patterns_detected=defensive_patterns_detected,
252
-
253
- # Administrative information
254
- situation_description=situation_description,
255
- urgency_level=urgency_level,
256
- recommended_actions=recommended_actions,
257
- follow_up_timeline=follow_up_timeline
258
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  def _generate_conversation_summary(
261
  self,
@@ -891,6 +935,269 @@ class ProviderSummaryGenerator:
891
 
892
  return " ".join(parts)
893
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
894
  def generate_summary_with_validation(self, **kwargs) -> tuple[ProviderSummary, List[str]]:
895
  """
896
  Generate provider summary with validation feedback.
@@ -901,6 +1208,66 @@ class ProviderSummaryGenerator:
901
  summary = self.generate_summary(**kwargs)
902
  validation_issues = summary.validate_completeness()
903
  return summary, validation_issues
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
 
905
 
906
  def create_provider_summary_generator() -> ProviderSummaryGenerator:
 
13
  from typing import List, Optional
14
  import sys
15
  import os
16
+ import logging
17
+
18
+ # Configure logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
 
22
  # Add src to path for imports
23
  current_dir = os.path.dirname(os.path.abspath(__file__))
 
144
  """
145
  self.ai_client = ai_client
146
  self.prompt_controller = PromptController()
147
+ self._error_handler = None # Lazy loaded to avoid circular imports
148
  self.default_actions = [
149
  "Contact patient within 24 hours",
150
  "Assess immediate safety and support needs",
 
159
  'MODERATE': 0.5 # Standard follow-up
160
  }
161
 
162
+ @property
163
+ def error_handler(self):
164
+ """Lazy load error handler to avoid circular imports."""
165
+ if self._error_handler is None:
166
+ from src.core.ui_error_handler import UIErrorHandler
167
+ self._error_handler = UIErrorHandler()
168
+ return self._error_handler
169
+
170
  def generate_summary(
171
  self,
172
  indicators: List[str],
 
208
 
209
  Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
210
  """
211
+ try:
212
+ # Build triage context (Requirement 7.4)
213
+ triage_context = []
214
+ if triage_questions and triage_responses:
215
+ for q, r in zip(triage_questions, triage_responses):
216
+ triage_context.append({
217
+ "question": q,
218
+ "response": r,
219
+ "timestamp": datetime.now().isoformat()
220
+ })
221
+
222
+ # Generate conversation history summary (Requirement 7.5)
223
+ conversation_history_summary = self._generate_conversation_summary(
224
+ conversation_history, indicators, context_factors or []
225
+ )
226
+
227
+ # Determine severity and urgency levels
228
+ severity_level = self._determine_severity_level(confidence, indicators, context_factors or [])
229
+ urgency_level = self._determine_urgency_level(severity_level, defensive_patterns_detected)
230
+
231
+ # Generate situation description
232
+ situation_description = self._generate_enhanced_situation_description(
233
+ indicators, reasoning, triage_context, medical_context, context_factors or []
234
+ )
235
+
236
+ # Generate recommended actions
237
+ recommended_actions = self._generate_recommended_actions(
238
+ severity_level, indicators, defensive_patterns_detected, medical_context
239
+ )
240
+
241
+ # Determine follow-up timeline
242
+ follow_up_timeline = self._determine_follow_up_timeline(urgency_level, severity_level)
243
+
244
+ summary = ProviderSummary(
245
+ # Contact information (Requirement 7.1)
246
+ patient_name=patient_name or "[Patient Name]",
247
+ patient_phone=patient_phone or "[Phone Number]",
248
+ patient_email=patient_email,
249
+ emergency_contact=emergency_contact,
250
+
251
+ # Classification information (Requirements 7.2, 7.3)
252
+ classification="RED",
253
+ confidence=confidence,
254
+ reasoning=reasoning,
255
+ indicators=indicators or [],
256
+ severity_level=severity_level,
257
+
258
+ # Context information (Requirements 7.4, 7.5)
259
+ triage_context=triage_context,
260
+ conversation_context=conversation_context or "",
261
+ conversation_history_summary=conversation_history_summary,
262
+
263
+ # Enhanced contextual information
264
+ medical_context=medical_context,
265
+ context_factors=context_factors or [],
266
+ defensive_patterns_detected=defensive_patterns_detected,
267
+
268
+ # Administrative information
269
+ situation_description=situation_description,
270
+ urgency_level=urgency_level,
271
+ recommended_actions=recommended_actions,
272
+ follow_up_timeline=follow_up_timeline
273
+ )
274
+
275
+ # Validate and apply fallbacks if needed
276
+ validation_result = self.error_handler.validate_provider_summary_structure(summary)
277
+ if validation_result.has_critical_errors():
278
+ logger.warning("Critical errors found in provider summary, applying fallbacks")
279
+ summary = self.error_handler.apply_fallback_template(summary, "general")
280
+
281
+ # Fix confidence if out of range
282
+ if not 0.0 <= summary.confidence <= 1.0:
283
+ summary.confidence = max(0.0, min(1.0, summary.confidence))
284
+ logger.warning(f"Clamped confidence to valid range: {summary.confidence}")
285
+
286
+ # Fix reasoning if empty
287
+ if not summary.reasoning or len(summary.reasoning.strip()) < 10:
288
+ if summary.indicators:
289
+ indicators_text = ", ".join(summary.indicators[:3])
290
+ summary.reasoning = f"RED flag classification based on detected indicators: {indicators_text}. Immediate spiritual care support recommended."
291
+ else:
292
+ summary.reasoning = "RED flag classification indicates potential spiritual or emotional distress requiring immediate attention."
293
+ logger.warning("Applied fallback reasoning")
294
+
295
+ return summary
296
+
297
+ except Exception as e:
298
+ logger.error(f"Error generating provider summary: {e}")
299
+ # Return fallback summary
300
+ return self._create_fallback_summary(
301
+ indicators, reasoning, confidence, patient_name, patient_phone
302
+ )
303
 
304
  def _generate_conversation_summary(
305
  self,
 
935
 
936
  return " ".join(parts)
937
 
938
+ def format_coherent_paragraph(self, summary: ProviderSummary) -> str:
939
+ """
940
+ Format provider summary as a single coherent paragraph following Medical Brain style.
941
+
942
+ This method now uses LLM-based generation with the spiritual_care_message prompt
943
+ to create Medical Brain compatible summaries instead of hardcoded logic.
944
+
945
+ Args:
946
+ summary: ProviderSummary object to format
947
+
948
+ Returns:
949
+ Formatted coherent paragraph with optional patient quote
950
+
951
+ Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8
952
+ """
953
+ return self.generate_medical_brain_summary(summary)
954
+
955
+ def generate_medical_brain_summary(
956
+ self,
957
+ summary: ProviderSummary,
958
+ session_id: Optional[str] = None,
959
+ model_override: Optional[str] = None
960
+ ) -> str:
961
+ """
962
+ Generate Medical Brain compatible summary using LLM with spiritual_care_message prompt.
963
+
964
+ Args:
965
+ summary: ProviderSummary with all case details
966
+ session_id: Optional session ID for session-specific prompt overrides
967
+ model_override: Optional model name override for this generation
968
+
969
+ Returns:
970
+ Medical Brain compatible single-paragraph summary with patient quote
971
+ """
972
+ # If no AI client available, fall back to hardcoded logic
973
+ if not self.ai_client:
974
+ return self._generate_hardcoded_coherent_paragraph(summary)
975
+
976
+ # Load system prompt using PromptController for session-aware loading
977
+ try:
978
+ prompt_config = self.prompt_controller.get_prompt(
979
+ agent_type='spiritual_care_message',
980
+ session_id=session_id
981
+ )
982
+ system_prompt = prompt_config.base_prompt
983
+
984
+ except Exception as e:
985
+ logger.warning(f"Error loading prompt files: {e}")
986
+ return self._generate_hardcoded_coherent_paragraph(summary)
987
+
988
+ # Build context for LLM - include all necessary information
989
+ user_prompt = self._build_medical_brain_prompt(summary)
990
+
991
+ try:
992
+ # Use existing ai_client (AIClientManager) to generate response
993
+ response = self.ai_client.generate_response(
994
+ system_prompt=system_prompt,
995
+ user_prompt=user_prompt,
996
+ temperature=0.3, # Lower temperature for more consistent formatting
997
+ call_type="medical_brain_summary",
998
+ agent_name="SpiritualCareMessage",
999
+ model_override=model_override
1000
+ )
1001
+
1002
+ return response.strip()
1003
+
1004
+ except Exception as e:
1005
+ logger.warning(f"Error generating LLM medical brain summary: {e}")
1006
+ return self._generate_hardcoded_coherent_paragraph(summary)
1007
+
1008
+ def _build_medical_brain_prompt(self, summary: ProviderSummary) -> str:
1009
+ """Build user prompt for Medical Brain compatible summary generation."""
1010
+
1011
+ # Extract patient information
1012
+ patient_name = summary.patient_name or "Patient"
1013
+ patient_phone = summary.patient_phone or ""
1014
+
1015
+ # Extract demographics from medical context
1016
+ patient_age = "unknown age"
1017
+ patient_gender = "individual"
1018
+ medical_conditions = []
1019
+
1020
+ if summary.medical_context:
1021
+ age = summary.medical_context.get('age')
1022
+ if age:
1023
+ patient_age = f"{age}-year-old"
1024
+
1025
+ gender = summary.medical_context.get('gender', '').lower()
1026
+ if gender in ['male', 'female']:
1027
+ patient_gender = gender
1028
+ elif gender:
1029
+ patient_gender = gender
1030
+
1031
+ conditions = summary.medical_context.get('conditions', [])
1032
+ if isinstance(conditions, list):
1033
+ medical_conditions = conditions
1034
+ elif conditions:
1035
+ medical_conditions = [str(conditions)]
1036
+
1037
+ # Build indicators text
1038
+ indicators = summary.indicators or ["general distress"]
1039
+ indicators_text = ", ".join(indicators)
1040
+
1041
+ # Build conversation context for patient quote extraction
1042
+ conversation_context = ""
1043
+ if summary.conversation_context:
1044
+ conversation_context = summary.conversation_context[:1000] # Limit context size
1045
+
1046
+ # Build the prompt
1047
+ prompt_parts = [
1048
+ "FORMAT: MEDICAL_BRAIN_COMPATIBLE",
1049
+ "",
1050
+ f"PATIENT INFORMATION:",
1051
+ f"Name: {patient_name}",
1052
+ f"Phone: {patient_phone}",
1053
+ f"Age: {patient_age.replace('-year-old', '')}",
1054
+ f"Gender: {patient_gender}",
1055
+ "",
1056
+ f"MEDICAL HISTORY:",
1057
+ f"Conditions: {', '.join(medical_conditions) if medical_conditions else 'None documented'}",
1058
+ "",
1059
+ f"SPIRITUAL/EMOTIONAL CONCERNS:",
1060
+ f"Indicators: {indicators_text}",
1061
+ f"Situation: {summary.situation_description or 'spiritual or emotional distress'}",
1062
+ "",
1063
+ f"CLASSIFICATION: RED FLAG",
1064
+ f"CONSENT STATUS: Patient identified for spiritual care team contact",
1065
+ "",
1066
+ f"CONVERSATION CONTEXT (for patient quote extraction):",
1067
+ f"{conversation_context}",
1068
+ "",
1069
+ "Please generate a Medical Brain compatible summary following the MEDICAL_BRAIN_COMPATIBLE format specified in your instructions."
1070
+ ]
1071
+
1072
+ return "\n".join(prompt_parts)
1073
+
1074
+ def _generate_hardcoded_coherent_paragraph(self, summary: ProviderSummary) -> str:
1075
+ """
1076
+ Fallback method using hardcoded logic when LLM is unavailable.
1077
+ This preserves the original logic as a backup.
1078
+ """
1079
+ # Extract patient information
1080
+ patient_name = summary.patient_name or "Patient"
1081
+ patient_phone = summary.patient_phone or ""
1082
+
1083
+ # Extract age and gender from medical context if available
1084
+ patient_age = "unknown age"
1085
+ patient_gender = "individual"
1086
+ medical_conditions = []
1087
+
1088
+ if summary.medical_context:
1089
+ age = summary.medical_context.get('age')
1090
+ if age:
1091
+ patient_age = f"{age}-year-old"
1092
+
1093
+ gender = summary.medical_context.get('gender', '').lower()
1094
+ if gender in ['male', 'female']:
1095
+ patient_gender = gender
1096
+ elif gender:
1097
+ patient_gender = gender
1098
+
1099
+ conditions = summary.medical_context.get('conditions', [])
1100
+ if isinstance(conditions, list):
1101
+ medical_conditions = conditions
1102
+ elif conditions:
1103
+ medical_conditions = [str(conditions)]
1104
+
1105
+ # Build demographic section (Requirement 2.2)
1106
+ demographic_part = f"{patient_name} is a {patient_age} {patient_gender}"
1107
+
1108
+ # Build medical history section (Requirement 2.3)
1109
+ if medical_conditions:
1110
+ if len(medical_conditions) == 1:
1111
+ medical_part = f"with clinical history of {medical_conditions[0]}"
1112
+ elif len(medical_conditions) == 2:
1113
+ medical_part = f"with clinical history of {medical_conditions[0]} and {medical_conditions[1]}"
1114
+ else:
1115
+ # Multiple conditions: "X, Y, and Z"
1116
+ medical_part = f"with clinical history of {', '.join(medical_conditions[:-1])}, and {medical_conditions[-1]}"
1117
+ else:
1118
+ medical_part = "with no significant medical history documented"
1119
+
1120
+ # Build spiritual concerns section (Requirement 2.4, 2.5)
1121
+ concerns = summary.indicators or []
1122
+ if concerns:
1123
+ if len(concerns) == 1:
1124
+ concerns_text = concerns[0]
1125
+ elif len(concerns) == 2:
1126
+ concerns_text = f"{concerns[0]} and {concerns[1]}"
1127
+ else:
1128
+ concerns_text = f"{', '.join(concerns[:-1])}, and {concerns[-1]}"
1129
+ else:
1130
+ concerns_text = "general distress"
1131
+
1132
+ # Determine spiritual concern type from situation description
1133
+ concern_type = summary.situation_description or "spiritual or emotional distress"
1134
+
1135
+ # Build concerns and classification section
1136
+ concerns_part = f"The patient expressed {concerns_text}, which may indicate {concern_type}"
1137
+ classification_part = f", resulting in generation of a RED FLAG"
1138
+
1139
+ # Build consent section (Requirement 2.6)
1140
+ # Note: Current ProviderSummary doesn't have explicit consent field,
1141
+ # so we'll use a default assumption for RED flags
1142
+ consent_part = "The patient has been identified for spiritual care team contact"
1143
+
1144
+ # Build contact section (Requirement 2.7)
1145
+ if patient_phone and patient_phone.strip() and patient_phone != "[Phone Number]":
1146
+ contact_part = f"The preferred contact number is {patient_phone}"
1147
+ else:
1148
+ contact_part = "No contact number is currently available"
1149
+
1150
+ # Combine all parts into coherent paragraph (Requirement 2.1)
1151
+ # First part: demographic + medical (connected with space)
1152
+ first_sentence = f"{demographic_part} {medical_part}"
1153
+
1154
+ # Remaining parts as separate sentences
1155
+ paragraph_parts = [
1156
+ first_sentence,
1157
+ concerns_part + classification_part,
1158
+ consent_part,
1159
+ contact_part
1160
+ ]
1161
+
1162
+ # Join with appropriate connectors for flow
1163
+ coherent_paragraph = ". ".join(paragraph_parts)
1164
+
1165
+ # Ensure proper sentence ending
1166
+ if not coherent_paragraph.endswith('.'):
1167
+ coherent_paragraph += '.'
1168
+
1169
+ # Add patient quote section if available (Requirement 2.8)
1170
+ patient_quote = ""
1171
+ if summary.conversation_context:
1172
+ # Try to extract a meaningful quote from conversation context
1173
+ # Look for patient statements in the conversation
1174
+ lines = summary.conversation_context.split('\n')
1175
+
1176
+ # First, try to find user messages (new format)
1177
+ for line in lines:
1178
+ line = line.strip()
1179
+ if line.startswith('user: '):
1180
+ # Extract the actual message content
1181
+ message_content = line[6:].strip() # Remove 'user: ' prefix
1182
+ # Skip very short messages or technical responses
1183
+ if len(message_content) > 10 and not message_content.startswith('🟡') and not message_content.startswith('🔴'):
1184
+ patient_quote = f'Patient reported: "{message_content}"'
1185
+ break
1186
+
1187
+ # If no user messages found, try old format (direct patient text)
1188
+ if not patient_quote and len(summary.conversation_context.strip()) > 10:
1189
+ # Check if it's a single line of patient text (old format)
1190
+ context_text = summary.conversation_context.strip()
1191
+ # Make sure it doesn't contain assistant responses or technical markers
1192
+ if ('\n' not in context_text or len(lines) == 1) and not context_text.startswith('assistant:'):
1193
+ patient_quote = f'Patient reported: "{context_text}"'
1194
+
1195
+ # Combine paragraph and quote
1196
+ if patient_quote:
1197
+ return f"{coherent_paragraph}\n\n{patient_quote}"
1198
+ else:
1199
+ return coherent_paragraph
1200
+
1201
  def generate_summary_with_validation(self, **kwargs) -> tuple[ProviderSummary, List[str]]:
1202
  """
1203
  Generate provider summary with validation feedback.
 
1208
  summary = self.generate_summary(**kwargs)
1209
  validation_issues = summary.validate_completeness()
1210
  return summary, validation_issues
1211
+
1212
+ def _create_fallback_summary(
1213
+ self,
1214
+ indicators: List[str],
1215
+ reasoning: str,
1216
+ confidence: float,
1217
+ patient_name: Optional[str],
1218
+ patient_phone: Optional[str]
1219
+ ) -> ProviderSummary:
1220
+ """
1221
+ Create a minimal fallback summary when generation fails.
1222
+
1223
+ Args:
1224
+ indicators: Original indicators
1225
+ reasoning: Original reasoning
1226
+ confidence: Original confidence
1227
+ patient_name: Original patient name
1228
+ patient_phone: Original patient phone
1229
+
1230
+ Returns:
1231
+ Minimal but functional ProviderSummary
1232
+ """
1233
+ try:
1234
+ fallback_summary = ProviderSummary(
1235
+ patient_name=patient_name or "Patient (Name Not Available)",
1236
+ patient_phone=patient_phone or "Contact information not available",
1237
+ classification="RED",
1238
+ confidence=max(0.5, confidence), # Ensure reasonable confidence
1239
+ reasoning=reasoning or "RED flag classification - manual review required",
1240
+ indicators=indicators or ["System error - manual review required"],
1241
+ severity_level="HIGH",
1242
+ urgency_level="URGENT",
1243
+ situation_description="System error occurred during summary generation. Manual review required.",
1244
+ recommended_actions=[
1245
+ "Manual review required due to system error",
1246
+ "Contact patient within 24 hours",
1247
+ "Assess support needs"
1248
+ ],
1249
+ follow_up_timeline="Within 24 hours"
1250
+ )
1251
+
1252
+ # Apply error handler fallbacks
1253
+ return self.error_handler.apply_fallback_template(fallback_summary, "general")
1254
+
1255
+ except Exception as e:
1256
+ logger.error(f"Even fallback summary creation failed: {e}")
1257
+ # Ultimate fallback - minimal summary
1258
+ return ProviderSummary(
1259
+ patient_name="System Error",
1260
+ patient_phone="Not Available",
1261
+ classification="RED",
1262
+ confidence=0.5,
1263
+ reasoning="System error - immediate manual review required",
1264
+ indicators=["System error"],
1265
+ severity_level="HIGH",
1266
+ urgency_level="URGENT",
1267
+ situation_description="Critical system error - immediate manual intervention required",
1268
+ recommended_actions=["Immediate manual review required"],
1269
+ follow_up_timeline="Immediately"
1270
+ )
1271
 
1272
 
1273
  def create_provider_summary_generator() -> ProviderSummaryGenerator:
src/core/simplified_medical_app.py CHANGED
@@ -38,6 +38,8 @@ from src.core.core_classes import (
38
  from src.core.consent_message_generator import ConsentMessageGenerator
39
  from src.core.provider_summary_generator import ProviderSummaryGenerator, ProviderSummary
40
  from src.config.prompt_management.performance_monitor import PromptMonitor
 
 
41
 
42
  # Configure logging
43
  logging.basicConfig(level=logging.INFO)
@@ -94,6 +96,10 @@ class SimplifiedMedicalApp:
94
  self.consent_generator = ConsentMessageGenerator()
95
  self.provider_summary_generator = ProviderSummaryGenerator(ai_client=self.api)
96
 
 
 
 
 
97
  # Patient information (can be set via UI)
98
  self.patient_info = {
99
  "name": None,
@@ -340,7 +346,7 @@ class SimplifiedMedicalApp:
340
  self.spiritual_state.triage_session.question_count += 1
341
 
342
  # Evaluate response using triage logic
343
- outcome, reasoning = self.soft_triage_manager.evaluate_response(
344
  response=response,
345
  triage_session=self.spiritual_state.triage_session,
346
  context=self._get_conversation_context_str()
@@ -354,13 +360,13 @@ class SimplifiedMedicalApp:
354
 
355
  elif outcome == TriageOutcome.ESCALATE_RED:
356
  # Distress confirmed - generate referral (Requirement 3.5)
357
- return self._escalate_to_red(reasoning)
358
 
359
  else: # CONTINUE
360
  # Need more information - ask another question
361
  if self.soft_triage_manager.should_force_decision(self.spiritual_state.triage_session):
362
  # Max questions reached - force decision (Requirement 3.6)
363
- return self._escalate_to_red("Max triage questions reached - conservative escalation")
364
  else:
365
  # Update assessment for continuing triage
366
  # Preserve original spiritual indicators from initial assessment
@@ -585,13 +591,17 @@ Is there anything else I can help you with today?"""
585
 
586
  return transition
587
 
588
- def _escalate_to_red(self, reasoning: str) -> str:
589
  """
590
  Escalate triage to RED - request consent before referral.
591
 
592
  Uses consent-based approach: asks patient for permission before
593
  generating referral, as required by Or_review.md section 2.
594
 
 
 
 
 
595
  Requirement: 3.5, Or_review.md section 2
596
  """
597
  logger.info(f"Escalating to RED: {reasoning}")
@@ -603,14 +613,20 @@ Is there anything else I can help you with today?"""
603
  patient_language = self._detect_language(last_response)
604
 
605
  # Create assessment from triage context
606
- # Preserve original spiritual indicators from triage session
607
- original_indicators = []
608
- if self.spiritual_state.last_assessment and self.spiritual_state.last_assessment.indicators:
609
- original_indicators = self.spiritual_state.last_assessment.indicators
 
 
 
 
 
 
610
 
611
  assessment = SpiritualAssessment(
612
  state=SpiritualState.RED,
613
- indicators=original_indicators if original_indicators else ["spiritual_distress"],
614
  confidence=0.8,
615
  reasoning=reasoning
616
  )
@@ -951,6 +967,96 @@ Is there anything else I can help you with today?"""
951
 
952
  return tracking
953
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
954
  def _get_status_info(self) -> str:
955
  """Get current status information with enhanced patient details."""
956
  state_emoji = {
 
38
  from src.core.consent_message_generator import ConsentMessageGenerator
39
  from src.core.provider_summary_generator import ProviderSummaryGenerator, ProviderSummary
40
  from src.config.prompt_management.performance_monitor import PromptMonitor
41
+ from src.interface.enhanced_display_integration import create_enhanced_display_integration
42
+ from src.core.improved_classification_prompt_manager import ImprovedClassificationPromptManager
43
 
44
  # Configure logging
45
  logging.basicConfig(level=logging.INFO)
 
96
  self.consent_generator = ConsentMessageGenerator()
97
  self.provider_summary_generator = ProviderSummaryGenerator(ai_client=self.api)
98
 
99
+ # Enhanced display integration (Requirements 8.1, 8.2, 8.3)
100
+ self.enhanced_display = create_enhanced_display_integration()
101
+ self.improved_classification_manager = ImprovedClassificationPromptManager()
102
+
103
  # Patient information (can be set via UI)
104
  self.patient_info = {
105
  "name": None,
 
346
  self.spiritual_state.triage_session.question_count += 1
347
 
348
  # Evaluate response using triage logic
349
+ outcome, reasoning, indicators = self.soft_triage_manager.evaluate_response(
350
  response=response,
351
  triage_session=self.spiritual_state.triage_session,
352
  context=self._get_conversation_context_str()
 
360
 
361
  elif outcome == TriageOutcome.ESCALATE_RED:
362
  # Distress confirmed - generate referral (Requirement 3.5)
363
+ return self._escalate_to_red(reasoning, indicators)
364
 
365
  else: # CONTINUE
366
  # Need more information - ask another question
367
  if self.soft_triage_manager.should_force_decision(self.spiritual_state.triage_session):
368
  # Max questions reached - force decision (Requirement 3.6)
369
+ return self._escalate_to_red("Max triage questions reached - conservative escalation", ["inability_to_cope", "persistent_distress"])
370
  else:
371
  # Update assessment for continuing triage
372
  # Preserve original spiritual indicators from initial assessment
 
591
 
592
  return transition
593
 
594
+ def _escalate_to_red(self, reasoning: str, indicators: List[str] = None) -> str:
595
  """
596
  Escalate triage to RED - request consent before referral.
597
 
598
  Uses consent-based approach: asks patient for permission before
599
  generating referral, as required by Or_review.md section 2.
600
 
601
+ Args:
602
+ reasoning: Reason for escalation
603
+ indicators: New indicators from triage evaluation (if available)
604
+
605
  Requirement: 3.5, Or_review.md section 2
606
  """
607
  logger.info(f"Escalating to RED: {reasoning}")
 
613
  patient_language = self._detect_language(last_response)
614
 
615
  # Create assessment from triage context
616
+ # Use new indicators from triage evaluation if available, otherwise fallback to original
617
+ assessment_indicators = indicators if indicators else []
618
+ if not assessment_indicators:
619
+ # Fallback to original indicators if no new ones provided
620
+ if self.spiritual_state.last_assessment and self.spiritual_state.last_assessment.indicators:
621
+ assessment_indicators = self.spiritual_state.last_assessment.indicators
622
+ else:
623
+ assessment_indicators = ["spiritual_distress"]
624
+
625
+ logger.info(f"Using indicators for RED assessment: {assessment_indicators}")
626
 
627
  assessment = SpiritualAssessment(
628
  state=SpiritualState.RED,
629
+ indicators=assessment_indicators,
630
  confidence=0.8,
631
  reasoning=reasoning
632
  )
 
967
 
968
  return tracking
969
 
970
+ def format_enhanced_response(
971
+ self,
972
+ patient_message: str,
973
+ assessment: Optional[SpiritualAssessment] = None,
974
+ provider_summary: Optional[ProviderSummary] = None,
975
+ show_ai_analysis: bool = True
976
+ ) -> str:
977
+ """
978
+ Format response using enhanced display components.
979
+
980
+ Args:
981
+ patient_message: The patient's message
982
+ assessment: Optional spiritual assessment
983
+ provider_summary: Optional provider summary for RED flags
984
+ show_ai_analysis: Whether to show AI analysis section
985
+
986
+ Returns:
987
+ Enhanced formatted HTML response
988
+
989
+ Requirements: 8.1, 8.2, 8.3
990
+ """
991
+ return self.enhanced_display.format_chat_response(
992
+ assessment=assessment,
993
+ patient_message=patient_message,
994
+ provider_summary=provider_summary,
995
+ show_ai_analysis=show_ai_analysis
996
+ )
997
+
998
+ def get_enhanced_classification_badge(self, classification: str) -> str:
999
+ """
1000
+ Get enhanced classification badge with improved styling.
1001
+
1002
+ Args:
1003
+ classification: The classification level (RED/YELLOW/GREEN)
1004
+
1005
+ Returns:
1006
+ HTML badge with enhanced styling
1007
+
1008
+ Requirements: 8.1, 8.2
1009
+ """
1010
+ return self.enhanced_display.get_classification_badge(classification)
1011
+
1012
+ def validate_classification_consistency(
1013
+ self,
1014
+ assessment: SpiritualAssessment
1015
+ ) -> bool:
1016
+ """
1017
+ Validate classification consistency using improved criteria.
1018
+
1019
+ Args:
1020
+ assessment: The spiritual assessment to validate
1021
+
1022
+ Returns:
1023
+ True if classification is consistent with improved criteria
1024
+
1025
+ Requirements: 8.1, 8.2
1026
+ """
1027
+ # Convert assessment to ClassificationResult format
1028
+ from src.core.improved_classification_prompt_manager import ClassificationResult
1029
+
1030
+ result = ClassificationResult(
1031
+ classification=assessment.state.value.lower(),
1032
+ confidence=assessment.confidence,
1033
+ indicators=assessment.indicators,
1034
+ reasoning=assessment.reasoning,
1035
+ red_flag_indicators=[], # Would need to be extracted from indicators
1036
+ yellow_flag_indicators=[], # Would need to be extracted from indicators
1037
+ is_valid=False
1038
+ )
1039
+
1040
+ return self.improved_classification_manager.validate_classification_consistency(result)
1041
+
1042
+ def get_enhanced_display_config(self) -> dict:
1043
+ """
1044
+ Get current enhanced display configuration.
1045
+
1046
+ Returns:
1047
+ Dictionary with current display configuration
1048
+
1049
+ Requirements: 8.3
1050
+ """
1051
+ config = self.enhanced_display.display_manager.config
1052
+ return {
1053
+ "ai_analysis_icon": config.ai_analysis_icon,
1054
+ "patient_message_icon": config.patient_message_icon,
1055
+ "provider_summary_icon": config.provider_summary_icon,
1056
+ "use_color_coding": config.use_color_coding,
1057
+ "classification_colors": config.classification_colors
1058
+ }
1059
+
1060
  def _get_status_info(self) -> str:
1061
  """Get current status information with enhanced patient details."""
1062
  state_emoji = {
src/core/soft_triage_manager.py CHANGED
@@ -129,7 +129,7 @@ class SoftTriageManager:
129
  response: str,
130
  triage_session: TriageSession,
131
  context: str
132
- ) -> Tuple[TriageOutcome, str]:
133
  """
134
  Evaluate patient's response to triage question.
135
 
@@ -142,7 +142,7 @@ class SoftTriageManager:
142
  context: Conversation context
143
 
144
  Returns:
145
- Tuple of (TriageOutcome, reasoning)
146
 
147
  Requirements: 3.3, 3.6
148
  """
@@ -168,8 +168,8 @@ class SoftTriageManager:
168
  logger.error(f"Evaluation error: {e}")
169
  # On error with max questions, escalate to RED (conservative)
170
  if triage_session.question_count >= self.MAX_QUESTIONS - 1:
171
- return TriageOutcome.ESCALATE_RED, f"Evaluation error at limit - conservative escalation: {e}"
172
- return TriageOutcome.CONTINUE, f"Evaluation error - continuing: {e}"
173
 
174
  def should_force_decision(self, triage_session: TriageSession) -> bool:
175
  """
@@ -232,7 +232,7 @@ class SoftTriageManager:
232
 
233
  return "\n".join(prompt_parts)
234
 
235
- def _parse_evaluation_response(self, response: str) -> Tuple[TriageOutcome, str]:
236
  """Parse LLM evaluation response."""
237
  try:
238
  # Extract JSON
@@ -244,23 +244,28 @@ class SoftTriageManager:
244
 
245
  outcome_str = data.get("outcome", "continue").lower()
246
  reasoning = data.get("reasoning", "LLM evaluation")
 
 
 
 
 
247
 
248
  if outcome_str == "resolved_green":
249
- return TriageOutcome.RESOLVED_GREEN, reasoning
250
  elif outcome_str == "escalate_red":
251
- return TriageOutcome.ESCALATE_RED, reasoning
252
  else:
253
- return TriageOutcome.CONTINUE, reasoning
254
 
255
  except (json.JSONDecodeError, KeyError) as e:
256
  logger.warning(f"Failed to parse evaluation: {e}")
257
- return TriageOutcome.CONTINUE, f"Parse error - continuing: {e}"
258
 
259
  def _force_decision(
260
  self,
261
  last_response: str,
262
  triage_session: TriageSession
263
- ) -> Tuple[TriageOutcome, str]:
264
  """
265
  Force a decision when max questions reached.
266
 
@@ -279,10 +284,10 @@ class SoftTriageManager:
279
  positive_count = sum(1 for ind in positive_indicators if ind in all_responses)
280
 
281
  if positive_count >= 2:
282
- return TriageOutcome.RESOLVED_GREEN, "Forced decision: positive indicators found in responses"
283
  else:
284
  # Conservative: escalate if uncertain (Requirement 3.6)
285
- return TriageOutcome.ESCALATE_RED, "Forced decision: max questions reached without clear resolution - conservative escalation"
286
 
287
  def _get_fallback_question(self, language: str, question_num: int) -> str:
288
  """Get fallback question if LLM fails."""
 
129
  response: str,
130
  triage_session: TriageSession,
131
  context: str
132
+ ) -> Tuple[TriageOutcome, str, List[str]]:
133
  """
134
  Evaluate patient's response to triage question.
135
 
 
142
  context: Conversation context
143
 
144
  Returns:
145
+ Tuple of (TriageOutcome, reasoning, indicators)
146
 
147
  Requirements: 3.3, 3.6
148
  """
 
168
  logger.error(f"Evaluation error: {e}")
169
  # On error with max questions, escalate to RED (conservative)
170
  if triage_session.question_count >= self.MAX_QUESTIONS - 1:
171
+ return TriageOutcome.ESCALATE_RED, f"Evaluation error at limit - conservative escalation: {e}", ["evaluation_error"]
172
+ return TriageOutcome.CONTINUE, f"Evaluation error - continuing: {e}", ["evaluation_error"]
173
 
174
  def should_force_decision(self, triage_session: TriageSession) -> bool:
175
  """
 
232
 
233
  return "\n".join(prompt_parts)
234
 
235
+ def _parse_evaluation_response(self, response: str) -> Tuple[TriageOutcome, str, List[str]]:
236
  """Parse LLM evaluation response."""
237
  try:
238
  # Extract JSON
 
244
 
245
  outcome_str = data.get("outcome", "continue").lower()
246
  reasoning = data.get("reasoning", "LLM evaluation")
247
+ indicators = data.get("indicators", [])
248
+
249
+ # Ensure indicators is a list
250
+ if not isinstance(indicators, list):
251
+ indicators = []
252
 
253
  if outcome_str == "resolved_green":
254
+ return TriageOutcome.RESOLVED_GREEN, reasoning, indicators
255
  elif outcome_str == "escalate_red":
256
+ return TriageOutcome.ESCALATE_RED, reasoning, indicators
257
  else:
258
+ return TriageOutcome.CONTINUE, reasoning, indicators
259
 
260
  except (json.JSONDecodeError, KeyError) as e:
261
  logger.warning(f"Failed to parse evaluation: {e}")
262
+ return TriageOutcome.CONTINUE, f"Parse error - continuing: {e}", ["parse_error"]
263
 
264
  def _force_decision(
265
  self,
266
  last_response: str,
267
  triage_session: TriageSession
268
+ ) -> Tuple[TriageOutcome, str, List[str]]:
269
  """
270
  Force a decision when max questions reached.
271
 
 
284
  positive_count = sum(1 for ind in positive_indicators if ind in all_responses)
285
 
286
  if positive_count >= 2:
287
+ return TriageOutcome.RESOLVED_GREEN, "Forced decision: positive indicators found in responses", ["external_factors", "coping_resources"]
288
  else:
289
  # Conservative: escalate if uncertain (Requirement 3.6)
290
+ return TriageOutcome.ESCALATE_RED, "Forced decision: max questions reached without clear resolution - conservative escalation", ["inability_to_cope", "persistent_distress"]
291
 
292
  def _get_fallback_question(self, language: str, question_num: int) -> str:
293
  """Get fallback question if LLM fails."""
src/core/spiritual_monitor.py CHANGED
@@ -6,7 +6,12 @@ Analyzes every patient message for spiritual distress indicators and returns
6
  a simple GREEN/YELLOW/RED classification. Runs invisibly in the background
7
  during medical dialog.
8
 
9
- Requirements: 2.1, 5.1, 5.2, 5.4
 
 
 
 
 
10
  """
11
 
12
  import logging
@@ -17,51 +22,13 @@ from typing import List, Optional
17
 
18
  from src.core.spiritual_state import SpiritualState, SpiritualAssessment
19
  from src.core.ai_client import AIClientManager
 
20
 
21
  # Configure logging
22
  logging.basicConfig(level=logging.INFO)
23
  logger = logging.getLogger(__name__)
24
 
25
 
26
- # Red flag keywords that trigger immediate RED classification
27
- RED_FLAG_KEYWORDS = [
28
- # Suicidal ideation (English)
29
- "suicide", "suicidal", "kill myself", "end my life", "want to die",
30
- "better off dead", "no reason to live", "can't go on",
31
- # Suicidal ideation (Ukrainian)
32
- "самогубство", "покінчити з життям", "хочу померти", "не хочу жити",
33
- "краще б мене не було", "немає сенсу жити",
34
- # Severe hopelessness (English)
35
- "hopeless", "no hope", "give up", "nothing matters",
36
- # Severe hopelessness (Ukrainian)
37
- "безнадія", "немає надії", "все безглуздо", "нічого не має сенсу",
38
- # Death wishes (English)
39
- "wish i was dead", "want to disappear", "end it all",
40
- # Death wishes (Ukrainian)
41
- "хочу зникнути", "краще б я помер"
42
- ]
43
-
44
- # Yellow flag indicators for potential distress
45
- YELLOW_FLAG_INDICATORS = [
46
- # Emotional distress (English)
47
- "sad", "depressed", "anxious", "worried", "scared", "lonely",
48
- "overwhelmed", "stressed", "can't cope", "struggling",
49
- # Emotional distress (Ukrainian)
50
- "сумно", "депресія", "тривога", "хвилююсь", "страшно", "самотньо",
51
- "перевантажений", "стрес", "не справляюсь", "важко",
52
- # Spiritual/existential (English)
53
- "god", "faith", "pray", "meaning", "purpose", "why me",
54
- "spiritual", "soul", "afterlife", "sin", "guilt",
55
- # Spiritual/existential (Ukrainian)
56
- "бог", "віра", "молитва", "сенс", "мета", "чому я",
57
- "духовний", "душа", "гріх", "провина",
58
- # Grief/loss (English)
59
- "grief", "loss", "died", "death", "mourning", "miss them",
60
- # Grief/loss (Ukrainian)
61
- "горе", "втрата", "помер", "смерть", "сумую", "скучаю"
62
- ]
63
-
64
-
65
  # Load system prompt from external file
66
  def _load_spiritual_monitor_prompt() -> str:
67
  """Load the spiritual monitor system prompt from file."""
@@ -70,15 +37,33 @@ def _load_spiritual_monitor_prompt() -> str:
70
  return load_prompt_from_file('spiritual_monitor.txt')
71
  except (ImportError, FileNotFoundError) as e:
72
  logger.warning(f"Could not load spiritual monitor prompt from file: {e}")
73
- # Fallback to a minimal prompt
74
  return """<system_role>
75
  You are a background spiritual distress classifier for a medical chatbot. Your task is to analyze patient messages and classify their level of spiritual or emotional distress to help route them to appropriate support.
76
  </system_role>
77
 
78
- Classify messages as:
79
- - "green": No spiritual/emotional distress indicators
80
- - "yellow": Ambiguous - need clarification
81
- - "red": Clear distress requiring support
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  Respond with JSON: {"state": "green|yellow|red", "indicators": [], "confidence": 0.0-1.0, "reasoning": "explanation"}"""
84
 
@@ -91,9 +76,14 @@ class SpiritualMonitor:
91
  Background classifier for spiritual distress detection.
92
 
93
  Analyzes every patient message and returns GREEN/YELLOW/RED classification.
94
- Uses keyword detection for immediate RED flags and LLM for nuanced analysis.
 
 
 
 
 
95
 
96
- Requirements: 2.1, 5.1, 5.2, 5.4
97
  """
98
 
99
  def __init__(self, api_client: AIClientManager, performance_monitor=None):
@@ -106,7 +96,8 @@ class SpiritualMonitor:
106
  """
107
  self.api = api_client
108
  self.performance_monitor = performance_monitor
109
- logger.info("🔍 SpiritualMonitor initialized")
 
110
 
111
  def classify(
112
  self,
@@ -114,10 +105,12 @@ class SpiritualMonitor:
114
  conversation_history: Optional[List[str]] = None
115
  ) -> SpiritualAssessment:
116
  """
117
- Classify message for spiritual distress indicators.
118
 
119
- First checks for red flag keywords (immediate RED).
120
- Then uses LLM for nuanced GREEN/YELLOW/RED classification.
 
 
121
 
122
  Args:
123
  message: Patient's message to classify
@@ -126,9 +119,9 @@ class SpiritualMonitor:
126
  Returns:
127
  SpiritualAssessment with state, indicators, confidence, reasoning
128
 
129
- Requirements: 2.1, 5.1, 5.2, 5.4, 8.1, 8.2
130
  """
131
- logger.info(f"Classifying message: {message[:50]}...")
132
 
133
  # Start performance monitoring (Requirement 8.1)
134
  start_time = time.time()
@@ -136,21 +129,10 @@ class SpiritualMonitor:
136
  error_details = None
137
 
138
  try:
139
- # Step 1: Check for red flag keywords (Requirement 5.4)
140
- red_flag_result = self._check_red_flag_keywords(message)
141
- if red_flag_result:
142
- logger.warning(f"RED FLAG detected via keywords: {red_flag_result}")
143
- assessment = SpiritualAssessment(
144
- state=SpiritualState.RED,
145
- indicators=red_flag_result,
146
- confidence=1.0,
147
- reasoning="Red flag keywords detected - immediate support needed"
148
- )
149
- else:
150
- # Step 2: Use LLM for nuanced classification
151
- assessment = self._classify_with_llm(message, conversation_history)
152
- logger.info(f"LLM classification: {assessment.state.value}")
153
 
 
154
  return assessment
155
 
156
  except Exception as e:
@@ -183,72 +165,58 @@ class SpiritualMonitor:
183
  'indicators_count': len(getattr(assessment, 'indicators', [])) if 'assessment' in locals() else 0,
184
  'message_length': len(message),
185
  'has_conversation_history': conversation_history is not None,
186
- 'error_details': error_details
 
187
  }
188
  )
189
 
190
- def _check_red_flag_keywords(self, message: str) -> Optional[List[str]]:
191
- """
192
- Check message for red flag keywords.
193
-
194
- Returns list of matched keywords if found, None otherwise.
195
-
196
- Requirement: 5.4
197
- """
198
- message_lower = message.lower()
199
- matched = []
200
-
201
- for keyword in RED_FLAG_KEYWORDS:
202
- if keyword.lower() in message_lower:
203
- matched.append(keyword)
204
-
205
- return matched if matched else None
206
-
207
- def _check_yellow_indicators(self, message: str) -> List[str]:
208
- """
209
- Check message for yellow flag indicators.
210
-
211
- Returns list of matched indicators.
212
- """
213
- message_lower = message.lower()
214
- matched = []
215
-
216
- for indicator in YELLOW_FLAG_INDICATORS:
217
- if indicator.lower() in message_lower:
218
- matched.append(indicator)
219
-
220
- return matched
221
-
222
- def _classify_with_llm(
223
  self,
224
  message: str,
225
  conversation_history: Optional[List[str]] = None
226
  ) -> SpiritualAssessment:
227
  """
228
- Use LLM for nuanced classification.
 
 
229
 
230
- Requirements: 2.1, 5.2
231
  """
232
  # Build context
233
  context = ""
234
  if conversation_history:
235
- recent = conversation_history[-6:] # Last 3 exchanges
236
- context = "Recent conversation:\n" + "\n".join(recent) + "\n\n"
237
 
238
- prompt = f"""{context}Current message to classify:
 
 
 
239
  "{message}"
240
 
241
- Classify this message for spiritual/emotional distress indicators."""
242
 
243
- # Call LLM
244
  response = self.api.call_spiritual_api(
245
- system_prompt=SYSTEM_PROMPT_SPIRITUAL_MONITOR,
246
  user_prompt=prompt
247
  )
248
 
249
  # Parse response
250
  return self._parse_classification_response(response, message)
251
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  def _parse_classification_response(
253
  self,
254
  response: str,
@@ -257,7 +225,7 @@ Classify this message for spiritual/emotional distress indicators."""
257
  """
258
  Parse LLM classification response.
259
 
260
- Falls back to keyword-based classification if parsing fails.
261
 
262
  Requirement: 5.2 (conservative default)
263
  """
@@ -269,8 +237,8 @@ Classify this message for spiritual/emotional distress indicators."""
269
  else:
270
  data = json.loads(response)
271
 
272
- # Parse state
273
- state_str = data.get("state", "yellow").lower()
274
  if state_str == "red":
275
  state = SpiritualState.RED
276
  elif state_str == "green":
@@ -285,37 +253,27 @@ Classify this message for spiritual/emotional distress indicators."""
285
  reasoning=data.get("reasoning", "LLM classification")
286
  )
287
 
288
- except (json.JSONDecodeError, KeyError, ValueError) as e:
289
  logger.warning(f"Failed to parse LLM response: {e}")
290
 
291
- # Fallback: Use keyword detection (Requirement 5.2)
292
- yellow_indicators = self._check_yellow_indicators(original_message)
293
-
294
- if yellow_indicators:
295
- return SpiritualAssessment(
296
- state=SpiritualState.YELLOW,
297
- indicators=yellow_indicators,
298
- confidence=0.6,
299
- reasoning="Keyword-based classification (LLM parse failed)"
300
- )
301
- else:
302
- # Conservative: default to YELLOW if uncertain
303
- return SpiritualAssessment(
304
- state=SpiritualState.YELLOW,
305
- indicators=["parse_error"],
306
- confidence=0.5,
307
- reasoning="Conservative YELLOW default (LLM parse failed)"
308
- )
309
 
310
 
311
- def create_spiritual_monitor(api_client: AIClientManager) -> SpiritualMonitor:
312
  """
313
- Factory function to create SpiritualMonitor.
314
 
315
  Args:
316
  api_client: AI client manager
 
317
 
318
  Returns:
319
  Initialized SpiritualMonitor instance
320
  """
321
- return SpiritualMonitor(api_client)
 
6
  a simple GREEN/YELLOW/RED classification. Runs invisibly in the background
7
  during medical dialog.
8
 
9
+ Enhanced with improved classification logic for specific cases:
10
+ - Proper handling of "feeling sad" (Requirements 3.1, 6.1)
11
+ - Recognition of "loss of meaning and purpose" as red indicator (Requirement 3.2)
12
+ - Validation for yellow flags in unclear cases (Requirements 6.2, 6.3)
13
+
14
+ Requirements: 2.1, 5.1, 5.2, 5.4, 3.1, 3.2, 6.1, 6.2, 6.3
15
  """
16
 
17
  import logging
 
22
 
23
  from src.core.spiritual_state import SpiritualState, SpiritualAssessment
24
  from src.core.ai_client import AIClientManager
25
+ from src.core.improved_classification_prompt_manager import ImprovedClassificationPromptManager
26
 
27
  # Configure logging
28
  logging.basicConfig(level=logging.INFO)
29
  logger = logging.getLogger(__name__)
30
 
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # Load system prompt from external file
33
  def _load_spiritual_monitor_prompt() -> str:
34
  """Load the spiritual monitor system prompt from file."""
 
37
  return load_prompt_from_file('spiritual_monitor.txt')
38
  except (ImportError, FileNotFoundError) as e:
39
  logger.warning(f"Could not load spiritual monitor prompt from file: {e}")
40
+ # Fallback to improved prompt with enhanced classification logic
41
  return """<system_role>
42
  You are a background spiritual distress classifier for a medical chatbot. Your task is to analyze patient messages and classify their level of spiritual or emotional distress to help route them to appropriate support.
43
  </system_role>
44
 
45
+ <enhanced_classification_rules>
46
+ CRITICAL IMPROVEMENTS:
47
+ 1. Simple sadness without additional spiritual context should be YELLOW, not RED
48
+ 2. Loss of meaning and purpose should ALWAYS be RED
49
+ 3. Use YELLOW for unclear cases requiring further investigation
50
+
51
+ RED FLAGS (immediate RED classification):
52
+ - Suicidal ideation or death wishes
53
+ - Loss of meaning, purpose, or sense of life
54
+ - Severe spiritual distress with crisis language
55
+ - Complex grief with spiritual components
56
+
57
+ YELLOW FLAGS (need clarification):
58
+ - Simple sadness without spiritual context
59
+ - Unclear emotional states requiring investigation
60
+ - Ambiguous situations where cause of distress is uncertain
61
+
62
+ GREEN (no distress):
63
+ - Medical questions without emotional distress
64
+ - Routine healthcare topics
65
+ - Positive or neutral statements
66
+ </enhanced_classification_rules>
67
 
68
  Respond with JSON: {"state": "green|yellow|red", "indicators": [], "confidence": 0.0-1.0, "reasoning": "explanation"}"""
69
 
 
76
  Background classifier for spiritual distress detection.
77
 
78
  Analyzes every patient message and returns GREEN/YELLOW/RED classification.
79
+ Uses LLM-only classification with enhanced prompts for accurate assessment.
80
+
81
+ Enhanced with improved classification logic:
82
+ - Proper handling of simple sadness (Requirements 3.1, 6.1)
83
+ - Recognition of loss of meaning/purpose as red (Requirement 3.2)
84
+ - Validation for yellow flags in unclear cases (Requirements 6.2, 6.3)
85
 
86
+ Requirements: 2.1, 5.1, 5.2, 5.4, 3.1, 3.2, 6.1, 6.2, 6.3
87
  """
88
 
89
  def __init__(self, api_client: AIClientManager, performance_monitor=None):
 
96
  """
97
  self.api = api_client
98
  self.performance_monitor = performance_monitor
99
+ self.improved_prompt_manager = ImprovedClassificationPromptManager()
100
+ logger.info("🔍 SpiritualMonitor initialized with improved classification logic")
101
 
102
  def classify(
103
  self,
 
105
  conversation_history: Optional[List[str]] = None
106
  ) -> SpiritualAssessment:
107
  """
108
+ Classify message for spiritual distress indicators using LLM only.
109
 
110
+ Uses enhanced LLM classification with improved prompt that includes:
111
+ - Updated red flag definition (broader than crisis-only)
112
+ - Explicit red indicators for consistency
113
+ - Proper handling of simple sadness vs spiritual distress
114
 
115
  Args:
116
  message: Patient's message to classify
 
119
  Returns:
120
  SpiritualAssessment with state, indicators, confidence, reasoning
121
 
122
+ Requirements: 2.1, 5.1, 5.2, 5.4, 8.1, 8.2, 3.1, 3.2, 6.1, 6.2, 6.3
123
  """
124
+ logger.info(f"Classifying message with LLM-only logic: {message[:50]}...")
125
 
126
  # Start performance monitoring (Requirement 8.1)
127
  start_time = time.time()
 
129
  error_details = None
130
 
131
  try:
132
+ # Use enhanced LLM classification for all messages
133
+ assessment = self._classify_with_enhanced_llm(message, conversation_history)
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
+ logger.info(f"LLM classification result: {assessment.state.value}")
136
  return assessment
137
 
138
  except Exception as e:
 
165
  'indicators_count': len(getattr(assessment, 'indicators', [])) if 'assessment' in locals() else 0,
166
  'message_length': len(message),
167
  'has_conversation_history': conversation_history is not None,
168
+ 'error_details': error_details,
169
+ 'llm_only_logic_used': True
170
  }
171
  )
172
 
173
+ def _classify_with_enhanced_llm(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  self,
175
  message: str,
176
  conversation_history: Optional[List[str]] = None
177
  ) -> SpiritualAssessment:
178
  """
179
+ Use enhanced LLM for nuanced classification.
180
+
181
+ Uses improved prompt with enhanced classification logic.
182
 
183
+ Requirements: 6.2, 6.3
184
  """
185
  # Build context
186
  context = ""
187
  if conversation_history:
188
+ recent_history = conversation_history[-3:] # Last 3 messages for context
189
+ context = f"Recent conversation:\n{chr(10).join(recent_history)}\n\n"
190
 
191
+ # Use improved prompt manager
192
+ enhanced_prompt = self.improved_prompt_manager.build_enhanced_classification_prompt()
193
+
194
+ prompt = f"""Please classify this patient message:
195
  "{message}"
196
 
197
+ Apply the enhanced classification rules provided in the system prompt."""
198
 
199
+ # Call LLM with enhanced prompt
200
  response = self.api.call_spiritual_api(
201
+ system_prompt=enhanced_prompt,
202
  user_prompt=prompt
203
  )
204
 
205
  # Parse response
206
  return self._parse_classification_response(response, message)
207
 
208
+ def _classify_with_llm(
209
+ self,
210
+ message: str,
211
+ conversation_history: Optional[List[str]] = None
212
+ ) -> SpiritualAssessment:
213
+ """
214
+ Legacy LLM classification method.
215
+
216
+ Requirement: 5.2
217
+ """
218
+ return self._classify_with_enhanced_llm(message, conversation_history)
219
+
220
  def _parse_classification_response(
221
  self,
222
  response: str,
 
225
  """
226
  Parse LLM classification response.
227
 
228
+ Falls back to conservative YELLOW on parse errors.
229
 
230
  Requirement: 5.2 (conservative default)
231
  """
 
237
  else:
238
  data = json.loads(response)
239
 
240
+ # Parse state - check both "classification" and "state" fields
241
+ state_str = data.get("classification", data.get("state", "yellow")).lower()
242
  if state_str == "red":
243
  state = SpiritualState.RED
244
  elif state_str == "green":
 
253
  reasoning=data.get("reasoning", "LLM classification")
254
  )
255
 
256
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
257
  logger.warning(f"Failed to parse LLM response: {e}")
258
 
259
+ # Conservative: default to YELLOW if uncertain
260
+ return SpiritualAssessment(
261
+ state=SpiritualState.YELLOW,
262
+ indicators=["parse_error"],
263
+ confidence=0.5,
264
+ reasoning="Conservative YELLOW default due to parse error"
265
+ )
 
 
 
 
 
 
 
 
 
 
 
266
 
267
 
268
+ def create_spiritual_monitor(api_client: AIClientManager, performance_monitor=None) -> SpiritualMonitor:
269
  """
270
+ Factory function to create SpiritualMonitor instance.
271
 
272
  Args:
273
  api_client: AI client manager
274
+ performance_monitor: Optional performance monitor
275
 
276
  Returns:
277
  Initialized SpiritualMonitor instance
278
  """
279
+ return SpiritualMonitor(api_client, performance_monitor)
src/core/ui_error_handler.py ADDED
@@ -0,0 +1,687 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ UI Error Handler for UI Classification Improvements.
4
+
5
+ Provides error handling and recovery mechanisms for UI components including:
6
+ - Provider summary structure validation
7
+ - Fallback templates for missing data
8
+ - Functionality degradation during errors
9
+
10
+ Requirements: 9.1, 9.2, 9.3, 9.4
11
+ """
12
+
13
+ import logging
14
+ from typing import Dict, List, Optional, Any, Tuple, Union
15
+ from dataclasses import dataclass, field as dataclass_field
16
+ from enum import Enum
17
+ import traceback
18
+ import json
19
+
20
+ from src.core.provider_summary_generator import ProviderSummary
21
+ from src.core.improved_classification_prompt_manager import ClassificationResult
22
+
23
+ # Configure logging
24
+ logging.basicConfig(level=logging.INFO)
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class ErrorSeverity(Enum):
29
+ """Severity levels for UI errors."""
30
+ LOW = "low"
31
+ MEDIUM = "medium"
32
+ HIGH = "high"
33
+ CRITICAL = "critical"
34
+
35
+
36
+ class ErrorCategory(Enum):
37
+ """Categories of UI errors."""
38
+ VALIDATION = "validation"
39
+ FORMATTING = "formatting"
40
+ DATA_MISSING = "data_missing"
41
+ DISPLAY = "display"
42
+ CLASSIFICATION = "classification"
43
+
44
+
45
+ @dataclass
46
+ class UIError:
47
+ """Represents a UI error with context and recovery information."""
48
+ category: ErrorCategory
49
+ severity: ErrorSeverity
50
+ message: str
51
+ component: str
52
+ field: Optional[str] = None
53
+ value: Optional[Any] = None
54
+ suggestion: Optional[str] = None
55
+ recovery_actions: List[str] = dataclass_field(default_factory=list)
56
+ timestamp: Optional[str] = None
57
+
58
+ def to_dict(self) -> Dict[str, Any]:
59
+ """Convert error to dictionary for logging/export."""
60
+ return {
61
+ "category": self.category.value,
62
+ "severity": self.severity.value,
63
+ "message": self.message,
64
+ "component": self.component,
65
+ "field": self.field,
66
+ "value": str(self.value) if self.value is not None else None,
67
+ "suggestion": self.suggestion,
68
+ "recovery_actions": self.recovery_actions,
69
+ "timestamp": self.timestamp
70
+ }
71
+
72
+
73
+ @dataclass
74
+ class ValidationResult:
75
+ """Result of validation with errors and warnings."""
76
+ is_valid: bool
77
+ errors: List[UIError] = dataclass_field(default_factory=list)
78
+ warnings: List[UIError] = dataclass_field(default_factory=list)
79
+
80
+ def add_error(self, error: UIError):
81
+ """Add an error to the validation result."""
82
+ self.errors.append(error)
83
+ self.is_valid = False
84
+
85
+ def add_warning(self, warning: UIError):
86
+ """Add a warning to the validation result."""
87
+ self.warnings.append(warning)
88
+
89
+ def has_critical_errors(self) -> bool:
90
+ """Check if there are any critical errors."""
91
+ return any(error.severity == ErrorSeverity.CRITICAL for error in self.errors)
92
+
93
+ def get_error_summary(self) -> str:
94
+ """Get a summary of all errors and warnings."""
95
+ if not self.errors and not self.warnings:
96
+ return "No validation issues found."
97
+
98
+ parts = []
99
+ if self.errors:
100
+ parts.append(f"{len(self.errors)} error(s)")
101
+ if self.warnings:
102
+ parts.append(f"{len(self.warnings)} warning(s)")
103
+
104
+ return f"Validation completed with {', '.join(parts)}."
105
+
106
+
107
+ class UIErrorHandler:
108
+ """
109
+ Error handler for UI classification improvements.
110
+
111
+ Provides validation, error recovery, and fallback mechanisms
112
+ for UI components and data processing.
113
+
114
+ Requirements: 9.1, 9.2, 9.3, 9.4
115
+ """
116
+
117
+ def __init__(self):
118
+ """Initialize the UI error handler."""
119
+ self.fallback_templates = self._initialize_fallback_templates()
120
+ self.validation_rules = self._initialize_validation_rules()
121
+ logger.info("🔧 UIErrorHandler initialized")
122
+
123
+ def validate_provider_summary_structure(self, summary: ProviderSummary) -> ValidationResult:
124
+ """
125
+ Validate provider summary structure and completeness.
126
+
127
+ Checks for required fields, data integrity, and format compliance
128
+ according to requirements.
129
+
130
+ Args:
131
+ summary: ProviderSummary to validate
132
+
133
+ Returns:
134
+ ValidationResult with errors and warnings
135
+
136
+ Requirements: 9.1
137
+ """
138
+ result = ValidationResult(is_valid=True)
139
+
140
+ try:
141
+ # Validate required contact information
142
+ self._validate_contact_information(summary, result)
143
+
144
+ # Validate classification data
145
+ self._validate_classification_data(summary, result)
146
+
147
+ # Validate content structure
148
+ self._validate_content_structure(summary, result)
149
+
150
+ # Validate data consistency
151
+ self._validate_data_consistency(summary, result)
152
+
153
+ logger.info(f"Provider summary validation completed: {result.get_error_summary()}")
154
+
155
+ except Exception as e:
156
+ error = UIError(
157
+ category=ErrorCategory.VALIDATION,
158
+ severity=ErrorSeverity.CRITICAL,
159
+ message=f"Validation process failed: {str(e)}",
160
+ component="provider_summary_validator",
161
+ suggestion="Check summary data structure and try again"
162
+ )
163
+ result.add_error(error)
164
+ logger.error(f"Validation process failed: {e}")
165
+
166
+ return result
167
+
168
+ def _validate_contact_information(self, summary: ProviderSummary, result: ValidationResult):
169
+ """Validate contact information fields."""
170
+ # Check patient name
171
+ if not summary.patient_name or summary.patient_name.strip() == "[Patient Name]":
172
+ result.add_error(UIError(
173
+ category=ErrorCategory.DATA_MISSING,
174
+ severity=ErrorSeverity.HIGH,
175
+ message="Patient name is missing or placeholder",
176
+ component="provider_summary",
177
+ field="patient_name",
178
+ value=summary.patient_name,
179
+ suggestion="Ensure patient name is provided before generating summary",
180
+ recovery_actions=["Use fallback template", "Request patient name input"]
181
+ ))
182
+
183
+ # Check patient phone
184
+ if not summary.patient_phone or summary.patient_phone.strip() == "[Phone Number]":
185
+ result.add_error(UIError(
186
+ category=ErrorCategory.DATA_MISSING,
187
+ severity=ErrorSeverity.HIGH,
188
+ message="Patient phone is missing or placeholder",
189
+ component="provider_summary",
190
+ field="patient_phone",
191
+ value=summary.patient_phone,
192
+ suggestion="Ensure patient phone is provided for contact",
193
+ recovery_actions=["Use fallback template", "Request phone number input"]
194
+ ))
195
+
196
+ # Validate phone format if present
197
+ if summary.patient_phone and summary.patient_phone != "[Phone Number]":
198
+ if not self._is_valid_phone_format(summary.patient_phone):
199
+ result.add_warning(UIError(
200
+ category=ErrorCategory.VALIDATION,
201
+ severity=ErrorSeverity.MEDIUM,
202
+ message="Phone number format may be invalid",
203
+ component="provider_summary",
204
+ field="patient_phone",
205
+ value=summary.patient_phone,
206
+ suggestion="Verify phone number format (XXX-XXX-XXXX or similar)"
207
+ ))
208
+
209
+ def _validate_classification_data(self, summary: ProviderSummary, result: ValidationResult):
210
+ """Validate classification-related data."""
211
+ # Check indicators
212
+ if not summary.indicators:
213
+ result.add_warning(UIError(
214
+ category=ErrorCategory.DATA_MISSING,
215
+ severity=ErrorSeverity.MEDIUM,
216
+ message="No distress indicators specified",
217
+ component="provider_summary",
218
+ field="indicators",
219
+ suggestion="Ensure classification process provides indicators"
220
+ ))
221
+
222
+ # Check reasoning
223
+ if not summary.reasoning or len(summary.reasoning.strip()) < 10:
224
+ result.add_error(UIError(
225
+ category=ErrorCategory.DATA_MISSING,
226
+ severity=ErrorSeverity.HIGH,
227
+ message="Classification reasoning is missing or insufficient",
228
+ component="provider_summary",
229
+ field="reasoning",
230
+ value=summary.reasoning,
231
+ suggestion="Provide detailed reasoning for classification decision",
232
+ recovery_actions=["Use fallback reasoning template"]
233
+ ))
234
+
235
+ # Validate confidence range
236
+ if not 0.0 <= summary.confidence <= 1.0:
237
+ result.add_error(UIError(
238
+ category=ErrorCategory.VALIDATION,
239
+ severity=ErrorSeverity.MEDIUM,
240
+ message="Confidence value is out of valid range (0.0-1.0)",
241
+ component="provider_summary",
242
+ field="confidence",
243
+ value=summary.confidence,
244
+ suggestion="Ensure confidence is between 0.0 and 1.0",
245
+ recovery_actions=["Clamp to valid range", "Use default confidence"]
246
+ ))
247
+
248
+ def _validate_content_structure(self, summary: ProviderSummary, result: ValidationResult):
249
+ """Validate content structure and format."""
250
+ # Check situation description
251
+ if not summary.situation_description or len(summary.situation_description.strip()) < 20:
252
+ result.add_warning(UIError(
253
+ category=ErrorCategory.DATA_MISSING,
254
+ severity=ErrorSeverity.MEDIUM,
255
+ message="Situation description is missing or too brief",
256
+ component="provider_summary",
257
+ field="situation_description",
258
+ value=summary.situation_description,
259
+ suggestion="Provide detailed situation description",
260
+ recovery_actions=["Generate from available data", "Use fallback template"]
261
+ ))
262
+
263
+ # Check urgency level
264
+ valid_urgency_levels = ["IMMEDIATE", "URGENT", "STANDARD"]
265
+ if summary.urgency_level not in valid_urgency_levels:
266
+ result.add_error(UIError(
267
+ category=ErrorCategory.VALIDATION,
268
+ severity=ErrorSeverity.MEDIUM,
269
+ message=f"Invalid urgency level: {summary.urgency_level}",
270
+ component="provider_summary",
271
+ field="urgency_level",
272
+ value=summary.urgency_level,
273
+ suggestion=f"Use one of: {', '.join(valid_urgency_levels)}",
274
+ recovery_actions=["Use default urgency level"]
275
+ ))
276
+
277
+ # Check severity level
278
+ valid_severity_levels = ["CRITICAL", "HIGH", "MODERATE"]
279
+ if summary.severity_level not in valid_severity_levels:
280
+ result.add_error(UIError(
281
+ category=ErrorCategory.VALIDATION,
282
+ severity=ErrorSeverity.MEDIUM,
283
+ message=f"Invalid severity level: {summary.severity_level}",
284
+ component="provider_summary",
285
+ field="severity_level",
286
+ value=summary.severity_level,
287
+ suggestion=f"Use one of: {', '.join(valid_severity_levels)}",
288
+ recovery_actions=["Use default severity level"]
289
+ ))
290
+
291
+ def _validate_data_consistency(self, summary: ProviderSummary, result: ValidationResult):
292
+ """Validate data consistency across fields."""
293
+ # Check consistency between severity and urgency
294
+ if summary.severity_level == "CRITICAL" and summary.urgency_level not in ["IMMEDIATE", "URGENT"]:
295
+ result.add_warning(UIError(
296
+ category=ErrorCategory.VALIDATION,
297
+ severity=ErrorSeverity.LOW,
298
+ message="Critical severity should have immediate or urgent priority",
299
+ component="provider_summary",
300
+ field="urgency_level",
301
+ suggestion="Consider increasing urgency level for critical cases"
302
+ ))
303
+
304
+ # Check if recommended actions are present
305
+ if not summary.recommended_actions:
306
+ result.add_warning(UIError(
307
+ category=ErrorCategory.DATA_MISSING,
308
+ severity=ErrorSeverity.LOW,
309
+ message="No recommended actions specified",
310
+ component="provider_summary",
311
+ field="recommended_actions",
312
+ suggestion="Provide specific recommended actions for follow-up"
313
+ ))
314
+
315
+ def _is_valid_phone_format(self, phone: str) -> bool:
316
+ """Check if phone number has a reasonable format."""
317
+ import re
318
+ # Basic phone format validation (allows various formats)
319
+ phone_pattern = r'^[\+]?[\d\s\-\(\)\.]{10,}$'
320
+ return bool(re.match(phone_pattern, phone.strip()))
321
+
322
+ def apply_fallback_template(self, summary: ProviderSummary, error_type: str) -> ProviderSummary:
323
+ """
324
+ Apply fallback template to fix missing or invalid data.
325
+
326
+ Uses predefined templates to fill in missing information
327
+ and ensure the summary remains functional.
328
+
329
+ Args:
330
+ summary: ProviderSummary with missing/invalid data
331
+ error_type: Type of error to fix
332
+
333
+ Returns:
334
+ ProviderSummary with fallback data applied
335
+
336
+ Requirements: 9.2
337
+ """
338
+ try:
339
+ # Create a copy to avoid modifying original
340
+ fixed_summary = ProviderSummary(
341
+ patient_name=summary.patient_name,
342
+ patient_phone=summary.patient_phone,
343
+ patient_email=summary.patient_email,
344
+ emergency_contact=summary.emergency_contact,
345
+ classification=summary.classification,
346
+ confidence=summary.confidence,
347
+ reasoning=summary.reasoning,
348
+ indicators=summary.indicators.copy() if summary.indicators else [],
349
+ severity_level=summary.severity_level,
350
+ triage_context=summary.triage_context.copy() if summary.triage_context else [],
351
+ conversation_context=summary.conversation_context,
352
+ conversation_history_summary=summary.conversation_history_summary,
353
+ medical_context=summary.medical_context.copy() if summary.medical_context else None,
354
+ context_factors=summary.context_factors.copy() if summary.context_factors else [],
355
+ defensive_patterns_detected=summary.defensive_patterns_detected,
356
+ situation_description=summary.situation_description,
357
+ urgency_level=summary.urgency_level,
358
+ recommended_actions=summary.recommended_actions.copy() if summary.recommended_actions else [],
359
+ follow_up_timeline=summary.follow_up_timeline,
360
+ generated_at=summary.generated_at,
361
+ generated_by=summary.generated_by
362
+ )
363
+
364
+ # Apply specific fallback templates
365
+ if error_type == "missing_contact":
366
+ self._apply_contact_fallback(fixed_summary)
367
+ elif error_type == "missing_reasoning":
368
+ self._apply_reasoning_fallback(fixed_summary)
369
+ elif error_type == "missing_situation":
370
+ self._apply_situation_fallback(fixed_summary)
371
+ elif error_type == "invalid_levels":
372
+ self._apply_levels_fallback(fixed_summary)
373
+ elif error_type == "missing_actions":
374
+ self._apply_actions_fallback(fixed_summary)
375
+ else:
376
+ # Apply general fallback
377
+ self._apply_general_fallback(fixed_summary)
378
+
379
+ logger.info(f"Applied fallback template for error type: {error_type}")
380
+ return fixed_summary
381
+
382
+ except Exception as e:
383
+ logger.error(f"Failed to apply fallback template: {e}")
384
+ # Return original summary if fallback fails
385
+ return summary
386
+
387
+ def _apply_contact_fallback(self, summary: ProviderSummary):
388
+ """Apply fallback for missing contact information."""
389
+ if not summary.patient_name or summary.patient_name == "[Patient Name]":
390
+ summary.patient_name = "Patient (Name Not Provided)"
391
+
392
+ if not summary.patient_phone or summary.patient_phone == "[Phone Number]":
393
+ summary.patient_phone = "Contact information not available"
394
+
395
+ def _apply_reasoning_fallback(self, summary: ProviderSummary):
396
+ """Apply fallback for missing reasoning."""
397
+ if not summary.reasoning or len(summary.reasoning.strip()) < 10:
398
+ if summary.indicators:
399
+ indicators_text = ", ".join(summary.indicators[:3])
400
+ summary.reasoning = f"RED flag classification based on detected indicators: {indicators_text}. Immediate spiritual care support recommended."
401
+ else:
402
+ summary.reasoning = "RED flag classification indicates potential spiritual or emotional distress requiring immediate attention."
403
+
404
+ def _apply_situation_fallback(self, summary: ProviderSummary):
405
+ """Apply fallback for missing situation description."""
406
+ if not summary.situation_description or len(summary.situation_description.strip()) < 20:
407
+ if summary.indicators:
408
+ indicators_text = ", ".join(summary.indicators[:2])
409
+ summary.situation_description = f"Patient expressing {indicators_text}. Assessment indicates need for spiritual care support."
410
+ else:
411
+ summary.situation_description = "Patient assessment indicates potential spiritual or emotional distress requiring follow-up."
412
+
413
+ def _apply_levels_fallback(self, summary: ProviderSummary):
414
+ """Apply fallback for invalid severity/urgency levels."""
415
+ valid_severity = ["CRITICAL", "HIGH", "MODERATE"]
416
+ valid_urgency = ["IMMEDIATE", "URGENT", "STANDARD"]
417
+
418
+ if summary.severity_level not in valid_severity:
419
+ summary.severity_level = "HIGH" # Default for RED flags
420
+
421
+ if summary.urgency_level not in valid_urgency:
422
+ # Map severity to appropriate urgency
423
+ urgency_map = {
424
+ "CRITICAL": "IMMEDIATE",
425
+ "HIGH": "URGENT",
426
+ "MODERATE": "STANDARD"
427
+ }
428
+ summary.urgency_level = urgency_map.get(summary.severity_level, "URGENT")
429
+
430
+ def _apply_actions_fallback(self, summary: ProviderSummary):
431
+ """Apply fallback for missing recommended actions."""
432
+ if not summary.recommended_actions:
433
+ default_actions = [
434
+ "Contact patient within 24 hours",
435
+ "Assess immediate support needs",
436
+ "Provide spiritual care resources",
437
+ "Schedule follow-up within 48-72 hours"
438
+ ]
439
+
440
+ # Customize based on urgency
441
+ if summary.urgency_level == "IMMEDIATE":
442
+ summary.recommended_actions = [
443
+ "IMMEDIATE contact required - within 2-4 hours",
444
+ "Assess immediate safety and support needs",
445
+ "Consider emergency intervention if needed"
446
+ ]
447
+ elif summary.urgency_level == "URGENT":
448
+ summary.recommended_actions = default_actions[:3]
449
+ else:
450
+ summary.recommended_actions = default_actions
451
+
452
+ def _apply_general_fallback(self, summary: ProviderSummary):
453
+ """Apply general fallback for multiple issues."""
454
+ self._apply_contact_fallback(summary)
455
+ self._apply_reasoning_fallback(summary)
456
+ self._apply_situation_fallback(summary)
457
+ self._apply_levels_fallback(summary)
458
+ self._apply_actions_fallback(summary)
459
+
460
+ def create_degraded_display(self, error_context: str, original_content: Optional[str] = None) -> str:
461
+ """
462
+ Create degraded display when normal formatting fails.
463
+
464
+ Provides basic functionality when enhanced features fail,
465
+ ensuring the system remains usable.
466
+
467
+ Args:
468
+ error_context: Description of the error context
469
+ original_content: Original content if available
470
+
471
+ Returns:
472
+ Basic HTML display with error information
473
+
474
+ Requirements: 9.3
475
+ """
476
+ try:
477
+ # Create basic error display
478
+ error_display = f"""
479
+ <div style='border: 2px solid #ff6b6b; border-radius: 4px; padding: 15px; margin: 10px 0; background-color: #fff5f5;'>
480
+ <div style='display: flex; align-items: center; margin-bottom: 10px;'>
481
+ <span style='font-size: 1.2em; margin-right: 8px;'>⚠️</span>
482
+ <strong style='color: #d63031;'>Display Error Detected</strong>
483
+ </div>
484
+ <div style='margin: 10px 0; color: #636e72;'>
485
+ <strong>Context:</strong> {error_context}
486
+ </div>
487
+ <div style='margin: 10px 0; padding: 8px; background-color: #f8f9fa; border-radius: 4px; font-family: monospace; font-size: 0.9em;'>
488
+ System is operating in degraded mode to ensure continued functionality.
489
+ </div>
490
+ """
491
+
492
+ # Include original content if available
493
+ if original_content:
494
+ error_display += f"""
495
+ <div style='margin: 10px 0;'>
496
+ <strong>Available Content:</strong>
497
+ <div style='margin: 5px 0; padding: 8px; background-color: white; border: 1px solid #ddd; border-radius: 4px; max-height: 200px; overflow-y: auto;'>
498
+ {original_content}
499
+ </div>
500
+ </div>
501
+ """
502
+
503
+ error_display += """
504
+ <div style='margin: 10px 0; font-size: 0.9em; color: #636e72;'>
505
+ <strong>Recovery Actions:</strong>
506
+ <ul style='margin: 5px 0 0 20px;'>
507
+ <li>Refresh the page to retry</li>
508
+ <li>Check system logs for details</li>
509
+ <li>Contact support if issue persists</li>
510
+ </ul>
511
+ </div>
512
+ </div>
513
+ """
514
+
515
+ logger.warning(f"Created degraded display for error: {error_context}")
516
+ return error_display
517
+
518
+ except Exception as e:
519
+ logger.error(f"Failed to create degraded display: {e}")
520
+ # Ultimate fallback - plain text
521
+ return f"""
522
+ <div style='padding: 10px; border: 1px solid red;'>
523
+ <strong>System Error</strong><br>
524
+ Context: {error_context}<br>
525
+ Please refresh the page or contact support.
526
+ </div>
527
+ """
528
+
529
+ def handle_classification_error(self, error: Exception, input_data: Dict[str, Any]) -> ClassificationResult:
530
+ """
531
+ Handle classification errors with fallback classification.
532
+
533
+ Provides safe fallback when classification process fails,
534
+ ensuring system continues to function.
535
+
536
+ Args:
537
+ error: The exception that occurred
538
+ input_data: Original input data for classification
539
+
540
+ Returns:
541
+ Fallback ClassificationResult
542
+
543
+ Requirements: 9.4
544
+ """
545
+ try:
546
+ logger.error(f"Classification error occurred: {error}")
547
+
548
+ # Create fallback classification result
549
+ fallback_result = ClassificationResult(
550
+ classification="yellow", # Safe default
551
+ confidence=0.5, # Moderate confidence for fallback
552
+ indicators=["Classification error - manual review required"],
553
+ reasoning=f"Automatic classification failed due to system error. Manual review recommended. Error: {str(error)[:100]}",
554
+ red_flag_indicators=[],
555
+ yellow_flag_indicators=["System error", "Manual review required"],
556
+ is_valid=False # Mark as invalid due to error
557
+ )
558
+
559
+ # Try to extract some information from input if possible
560
+ if isinstance(input_data, dict):
561
+ message = input_data.get('message', '')
562
+ if message:
563
+ # Simple keyword-based fallback classification
564
+ critical_keywords = ['suicide', 'kill myself', 'end it all', 'hopeless', 'no point']
565
+ red_keywords = ['grief', 'loss', 'meaning', 'suffering', 'dignity', 'spiritual']
566
+
567
+ message_lower = message.lower()
568
+
569
+ if any(keyword in message_lower for keyword in critical_keywords):
570
+ fallback_result.classification = "red"
571
+ fallback_result.confidence = 0.8
572
+ fallback_result.indicators = ["Critical keywords detected"]
573
+ fallback_result.red_flag_indicators = ["Critical distress indicators"]
574
+ fallback_result.reasoning = "Fallback classification detected critical keywords requiring immediate attention."
575
+ elif any(keyword in message_lower for keyword in red_keywords):
576
+ fallback_result.classification = "red"
577
+ fallback_result.confidence = 0.6
578
+ fallback_result.indicators = ["Spiritual distress keywords detected"]
579
+ fallback_result.red_flag_indicators = ["Spiritual distress indicators"]
580
+ fallback_result.reasoning = "Fallback classification detected spiritual distress indicators."
581
+
582
+ logger.info(f"Created fallback classification: {fallback_result.classification}")
583
+ return fallback_result
584
+
585
+ except Exception as fallback_error:
586
+ logger.error(f"Fallback classification also failed: {fallback_error}")
587
+
588
+ # Ultimate fallback - minimal safe result
589
+ return ClassificationResult(
590
+ classification="yellow",
591
+ confidence=0.3,
592
+ indicators=["Multiple system errors"],
593
+ reasoning="System errors prevented classification. Immediate manual review required.",
594
+ red_flag_indicators=[],
595
+ yellow_flag_indicators=["System error", "Manual review required"],
596
+ is_valid=False
597
+ )
598
+
599
+ def _initialize_fallback_templates(self) -> Dict[str, Dict[str, Any]]:
600
+ """Initialize fallback templates for different error scenarios."""
601
+ return {
602
+ "missing_contact": {
603
+ "patient_name": "Patient (Name Not Provided)",
604
+ "patient_phone": "Contact information not available"
605
+ },
606
+ "missing_reasoning": {
607
+ "reasoning": "Classification based on detected indicators. Manual review recommended."
608
+ },
609
+ "missing_situation": {
610
+ "situation_description": "Patient assessment indicates potential spiritual or emotional distress."
611
+ },
612
+ "invalid_levels": {
613
+ "severity_level": "HIGH",
614
+ "urgency_level": "URGENT"
615
+ },
616
+ "missing_actions": {
617
+ "recommended_actions": [
618
+ "Contact patient within 24 hours",
619
+ "Assess support needs",
620
+ "Provide spiritual care resources"
621
+ ]
622
+ }
623
+ }
624
+
625
+ def _initialize_validation_rules(self) -> Dict[str, Any]:
626
+ """Initialize validation rules for different components."""
627
+ return {
628
+ "required_fields": {
629
+ "provider_summary": ["patient_name", "patient_phone", "classification", "reasoning"],
630
+ "classification_result": ["classification", "confidence", "reasoning"]
631
+ },
632
+ "valid_values": {
633
+ "classification": ["red", "yellow", "green"],
634
+ "severity_level": ["CRITICAL", "HIGH", "MODERATE"],
635
+ "urgency_level": ["IMMEDIATE", "URGENT", "STANDARD"]
636
+ },
637
+ "field_constraints": {
638
+ "confidence": {"min": 0.0, "max": 1.0},
639
+ "reasoning_min_length": 10,
640
+ "situation_min_length": 20
641
+ }
642
+ }
643
+
644
+ def get_error_statistics(self, errors: List[UIError]) -> Dict[str, Any]:
645
+ """
646
+ Get statistics about errors for monitoring and reporting.
647
+
648
+ Args:
649
+ errors: List of UIError objects
650
+
651
+ Returns:
652
+ Dictionary with error statistics
653
+ """
654
+ if not errors:
655
+ return {"total": 0, "by_category": {}, "by_severity": {}}
656
+
657
+ stats = {
658
+ "total": len(errors),
659
+ "by_category": {},
660
+ "by_severity": {},
661
+ "by_component": {}
662
+ }
663
+
664
+ for error in errors:
665
+ # Count by category
666
+ category = error.category.value
667
+ stats["by_category"][category] = stats["by_category"].get(category, 0) + 1
668
+
669
+ # Count by severity
670
+ severity = error.severity.value
671
+ stats["by_severity"][severity] = stats["by_severity"].get(severity, 0) + 1
672
+
673
+ # Count by component
674
+ component = error.component
675
+ stats["by_component"][component] = stats["by_component"].get(component, 0) + 1
676
+
677
+ return stats
678
+
679
+
680
+ def create_ui_error_handler() -> UIErrorHandler:
681
+ """
682
+ Factory function to create UIErrorHandler.
683
+
684
+ Returns:
685
+ Initialized UIErrorHandler instance
686
+ """
687
+ return UIErrorHandler()
src/core/verification_exporter.py CHANGED
@@ -1,40 +1,48 @@
1
  #!/usr/bin/env python3
2
  """
3
- Verification Results Exporter.
4
 
5
- Handles exporting verification session results to CSV format.
 
 
 
6
  """
7
 
8
  import csv
9
  import os
 
10
  from datetime import datetime
11
- from typing import List, Dict, Any
12
- from src.core.conversation_verification import VerificationSession, VerificationRecord
13
 
14
 
15
- class VerificationExporter:
16
- """Exporter for verification session results."""
17
 
18
  def __init__(self, export_dir: str = "verification_exports"):
19
- """Initialize exporter."""
20
  self.export_dir = export_dir
21
  os.makedirs(export_dir, exist_ok=True)
22
 
23
- def export_session_to_csv(self, session: VerificationSession) -> str:
24
  """
25
- Export verification session to CSV format.
26
 
27
  Args:
28
- session: VerificationSession to export
 
29
 
30
  Returns:
31
  Path to exported CSV file
 
 
32
  """
33
  filename = self._create_export_filename(session)
34
  filepath = os.path.join(self.export_dir, filename)
35
 
36
  try:
37
  with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
 
38
  fieldnames = [
39
  'exchange_number',
40
  'timestamp',
@@ -51,12 +59,25 @@ class VerificationExporter:
51
  'verification_timestamp'
52
  ]
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
55
  writer.writeheader()
56
 
57
  # Write session metadata as comment rows
58
  writer.writerow({
59
- 'exchange_number': '# SESSION METADATA',
60
  'timestamp': f'Session ID: {session.session_id}',
61
  'user_message': f'Patient: {session.patient_name}',
62
  'assistant_response': f'Verifier: {session.verifier_name}',
@@ -65,7 +86,7 @@ class VerificationExporter:
65
  'indicators': f'Total Exchanges: {session.total_exchanges}',
66
  'reasoning': f'Verified: {session.verified_exchanges}',
67
  'is_correct': f'Complete: {session.is_complete}',
68
- 'correct_classification': '',
69
  'correction_reason': '',
70
  'verifier_notes': '',
71
  'verification_timestamp': ''
@@ -74,9 +95,9 @@ class VerificationExporter:
74
  # Add empty row for separation
75
  writer.writerow({field: '' for field in fieldnames})
76
 
77
- # Write verification records
78
  for record in session.verification_records:
79
- writer.writerow({
80
  'exchange_number': record.exchange_number,
81
  'timestamp': record.timestamp.isoformat(),
82
  'user_message': record.user_message,
@@ -90,21 +111,171 @@ class VerificationExporter:
90
  'correction_reason': record.correction_reason or '',
91
  'verifier_notes': record.verifier_notes or '',
92
  'verification_timestamp': record.verification_timestamp.isoformat() if record.verification_timestamp else ''
93
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
  return filepath
96
 
97
  except Exception as e:
98
- raise Exception(f"Failed to export CSV: {str(e)}")
99
 
100
- def _create_export_filename(self, session: VerificationSession) -> str:
101
- """Create descriptive filename for export."""
102
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
103
  patient_safe = session.patient_name.replace(' ', '_').replace('/', '_')
104
- return f"conversation_verification_{patient_safe}_{timestamp}.csv"
 
105
 
106
- def generate_summary_report(self, session: VerificationSession) -> Dict[str, Any]:
107
- """Generate summary report for verification session."""
108
  progress = session.get_progress()
109
 
110
  # Calculate detailed statistics
@@ -112,6 +283,11 @@ class VerificationExporter:
112
  correct_records = [r for r in verified_records if r.is_correct]
113
  incorrect_records = [r for r in verified_records if not r.is_correct]
114
 
 
 
 
 
 
115
  # Error analysis
116
  error_patterns = {}
117
  for record in incorrect_records:
@@ -126,6 +302,17 @@ class VerificationExporter:
126
  'incorrect_avg_confidence': sum(r.original_confidence for r in incorrect_records) / len(incorrect_records) if incorrect_records else 0
127
  }
128
 
 
 
 
 
 
 
 
 
 
 
 
129
  return {
130
  'session_info': {
131
  'session_id': session.session_id,
@@ -133,7 +320,8 @@ class VerificationExporter:
133
  'verifier_name': session.verifier_name,
134
  'start_time': session.start_time.isoformat(),
135
  'end_time': session.end_time.isoformat() if session.end_time else None,
136
- 'duration_minutes': (session.end_time - session.start_time).total_seconds() / 60 if session.end_time else None
 
137
  },
138
  'verification_stats': {
139
  'total_exchanges': session.total_exchanges,
@@ -148,5 +336,6 @@ class VerificationExporter:
148
  'error_patterns': error_patterns,
149
  'common_errors': progress.common_errors
150
  },
151
- 'confidence_analysis': confidence_stats
 
152
  }
 
1
  #!/usr/bin/env python3
2
  """
3
+ Enhanced Verification Results Exporter.
4
 
5
+ Handles exporting verification session results to CSV format with enhanced
6
+ classification data and new display formats.
7
+
8
+ Requirements: 8.2, 8.3, 8.4, 8.5
9
  """
10
 
11
  import csv
12
  import os
13
+ import html
14
  from datetime import datetime
15
+ from typing import List, Dict, Any, Optional
16
+ from src.core.conversation_verification import EnhancedVerificationSession, EnhancedVerificationRecord
17
 
18
 
19
+ class EnhancedVerificationExporter:
20
+ """Enhanced exporter for verification session results with new format support."""
21
 
22
  def __init__(self, export_dir: str = "verification_exports"):
23
+ """Initialize enhanced exporter."""
24
  self.export_dir = export_dir
25
  os.makedirs(export_dir, exist_ok=True)
26
 
27
+ def export_session_to_csv(self, session: EnhancedVerificationSession, include_enhanced_data: bool = True) -> str:
28
  """
29
+ Export enhanced verification session to CSV format.
30
 
31
  Args:
32
+ session: EnhancedVerificationSession to export
33
+ include_enhanced_data: Whether to include enhanced format data
34
 
35
  Returns:
36
  Path to exported CSV file
37
+
38
+ Requirements: 8.2, 8.3
39
  """
40
  filename = self._create_export_filename(session)
41
  filepath = os.path.join(self.export_dir, filename)
42
 
43
  try:
44
  with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
45
+ # Enhanced fieldnames with new format data
46
  fieldnames = [
47
  'exchange_number',
48
  'timestamp',
 
59
  'verification_timestamp'
60
  ]
61
 
62
+ # Add enhanced format fields if requested
63
+ if include_enhanced_data:
64
+ fieldnames.extend([
65
+ 'has_enhanced_display',
66
+ 'has_provider_summary',
67
+ 'provider_summary_urgency',
68
+ 'provider_summary_severity',
69
+ 'coherent_paragraph_length',
70
+ 'visual_sections_count',
71
+ 'enhanced_indicators_count',
72
+ 'enhanced_display_preview'
73
+ ])
74
+
75
  writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
76
  writer.writeheader()
77
 
78
  # Write session metadata as comment rows
79
  writer.writerow({
80
+ 'exchange_number': '# ENHANCED SESSION METADATA',
81
  'timestamp': f'Session ID: {session.session_id}',
82
  'user_message': f'Patient: {session.patient_name}',
83
  'assistant_response': f'Verifier: {session.verifier_name}',
 
86
  'indicators': f'Total Exchanges: {session.total_exchanges}',
87
  'reasoning': f'Verified: {session.verified_exchanges}',
88
  'is_correct': f'Complete: {session.is_complete}',
89
+ 'correct_classification': f'Enhanced Format: {getattr(session, "enhanced_format_enabled", False)}',
90
  'correction_reason': '',
91
  'verifier_notes': '',
92
  'verification_timestamp': ''
 
95
  # Add empty row for separation
96
  writer.writerow({field: '' for field in fieldnames})
97
 
98
+ # Write enhanced verification records
99
  for record in session.verification_records:
100
+ row_data = {
101
  'exchange_number': record.exchange_number,
102
  'timestamp': record.timestamp.isoformat(),
103
  'user_message': record.user_message,
 
111
  'correction_reason': record.correction_reason or '',
112
  'verifier_notes': record.verifier_notes or '',
113
  'verification_timestamp': record.verification_timestamp.isoformat() if record.verification_timestamp else ''
114
+ }
115
+
116
+ # Add enhanced format data if requested
117
+ if include_enhanced_data:
118
+ row_data.update(self._extract_enhanced_data_for_csv(record))
119
+
120
+ writer.writerow(row_data)
121
+
122
+ return filepath
123
+
124
+ except Exception as e:
125
+ raise Exception(f"Failed to export enhanced CSV: {str(e)}")
126
+
127
+ def _extract_enhanced_data_for_csv(self, record: EnhancedVerificationRecord) -> Dict[str, Any]:
128
+ """Extract enhanced format data for CSV export."""
129
+ enhanced_data = {
130
+ 'has_enhanced_display': bool(record.enhanced_display_format),
131
+ 'has_provider_summary': bool(record.provider_summary),
132
+ 'provider_summary_urgency': '',
133
+ 'provider_summary_severity': '',
134
+ 'coherent_paragraph_length': 0,
135
+ 'visual_sections_count': len(record.visual_sections) if record.visual_sections else 0,
136
+ 'enhanced_indicators_count': len(record.original_indicators),
137
+ 'enhanced_display_preview': ''
138
+ }
139
+
140
+ # Extract provider summary data
141
+ if record.provider_summary:
142
+ enhanced_data['provider_summary_urgency'] = record.provider_summary.get('urgency_level', '')
143
+ enhanced_data['provider_summary_severity'] = record.provider_summary.get('severity_level', '')
144
+
145
+ # Extract coherent paragraph data
146
+ if record.coherent_summary_paragraph:
147
+ enhanced_data['coherent_paragraph_length'] = len(record.coherent_summary_paragraph)
148
+
149
+ # Extract enhanced display preview (first 100 chars, HTML stripped)
150
+ if record.enhanced_display_format:
151
+ # Strip HTML tags for CSV preview
152
+ import re
153
+ clean_text = re.sub(r'<[^>]+>', '', record.enhanced_display_format)
154
+ clean_text = clean_text.replace('\n', ' ').strip()
155
+ enhanced_data['enhanced_display_preview'] = clean_text[:100] + '...' if len(clean_text) > 100 else clean_text
156
+
157
+ return enhanced_data
158
+
159
+ def export_enhanced_summary_report(self, session: EnhancedVerificationSession) -> str:
160
+ """
161
+ Export enhanced summary report with new format statistics.
162
+
163
+ Args:
164
+ session: EnhancedVerificationSession to analyze
165
+
166
+ Returns:
167
+ Path to exported summary report file
168
+
169
+ Requirements: 8.4, 8.5
170
+ """
171
+ filename = f"enhanced_summary_{session.session_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
172
+ filepath = os.path.join(self.export_dir, filename)
173
+
174
+ try:
175
+ with open(filepath, 'w', encoding='utf-8') as f:
176
+ f.write("=" * 80 + "\n")
177
+ f.write("ENHANCED VERIFICATION SESSION SUMMARY REPORT\n")
178
+ f.write("=" * 80 + "\n\n")
179
+
180
+ # Session information
181
+ f.write("SESSION INFORMATION\n")
182
+ f.write("-" * 40 + "\n")
183
+ f.write(f"Session ID: {session.session_id}\n")
184
+ f.write(f"Patient: {session.patient_name}\n")
185
+ f.write(f"Verifier: {session.verifier_name}\n")
186
+ f.write(f"Started: {session.start_time.isoformat()}\n")
187
+ f.write(f"Completed: {session.end_time.isoformat() if session.end_time else 'In Progress'}\n")
188
+ f.write(f"Enhanced Format Enabled: {getattr(session, 'enhanced_format_enabled', False)}\n\n")
189
+
190
+ # Enhanced format statistics
191
+ f.write("ENHANCED FORMAT STATISTICS\n")
192
+ f.write("-" * 40 + "\n")
193
+
194
+ records_with_enhanced = sum(1 for r in session.verification_records if r.enhanced_display_format)
195
+ records_with_summary = sum(1 for r in session.verification_records if r.provider_summary)
196
+ records_with_paragraph = sum(1 for r in session.verification_records if r.coherent_summary_paragraph)
197
+
198
+ f.write(f"Total Records: {len(session.verification_records)}\n")
199
+ f.write(f"Records with Enhanced Display: {records_with_enhanced}\n")
200
+ f.write(f"Records with Provider Summary: {records_with_summary}\n")
201
+ f.write(f"Records with Coherent Paragraph: {records_with_paragraph}\n")
202
+
203
+ # Enhanced format coverage
204
+ if session.verification_records:
205
+ enhanced_coverage = (records_with_enhanced / len(session.verification_records)) * 100
206
+ summary_coverage = (records_with_summary / len(session.verification_records)) * 100
207
+ paragraph_coverage = (records_with_paragraph / len(session.verification_records)) * 100
208
+
209
+ f.write(f"Enhanced Display Coverage: {enhanced_coverage:.1f}%\n")
210
+ f.write(f"Provider Summary Coverage: {summary_coverage:.1f}%\n")
211
+ f.write(f"Coherent Paragraph Coverage: {paragraph_coverage:.1f}%\n\n")
212
+
213
+ # Classification accuracy with enhanced data
214
+ progress = session.get_progress()
215
+ f.write("CLASSIFICATION ACCURACY\n")
216
+ f.write("-" * 40 + "\n")
217
+ f.write(f"Overall Accuracy: {progress.accuracy_overall:.1%}\n")
218
+ f.write(f"Verified Exchanges: {progress.verified_exchanges}/{progress.total_exchanges}\n\n")
219
+
220
+ for classification, accuracy in progress.accuracy_by_type.items():
221
+ f.write(f"{classification} Flag Accuracy: {accuracy:.1%}\n")
222
+
223
+ # Enhanced indicators analysis
224
+ f.write("\nENHANCED INDICATORS ANALYSIS\n")
225
+ f.write("-" * 40 + "\n")
226
+
227
+ all_indicators = []
228
+ for record in session.verification_records:
229
+ all_indicators.extend(record.original_indicators)
230
+
231
+ if all_indicators:
232
+ from collections import Counter
233
+ indicator_counts = Counter(all_indicators)
234
+ f.write("Most Common Indicators:\n")
235
+ for indicator, count in indicator_counts.most_common(10):
236
+ f.write(f" {indicator}: {count} occurrences\n")
237
+
238
+ # Provider summary urgency analysis
239
+ if records_with_summary > 0:
240
+ f.write("\nPROVIDER SUMMARY ANALYSIS\n")
241
+ f.write("-" * 40 + "\n")
242
+
243
+ urgency_counts = {}
244
+ severity_counts = {}
245
+
246
+ for record in session.verification_records:
247
+ if record.provider_summary:
248
+ urgency = record.provider_summary.get('urgency_level', 'UNKNOWN')
249
+ severity = record.provider_summary.get('severity_level', 'UNKNOWN')
250
+ urgency_counts[urgency] = urgency_counts.get(urgency, 0) + 1
251
+ severity_counts[severity] = severity_counts.get(severity, 0) + 1
252
+
253
+ f.write("Urgency Level Distribution:\n")
254
+ for urgency, count in urgency_counts.items():
255
+ f.write(f" {urgency}: {count} cases\n")
256
+
257
+ f.write("Severity Level Distribution:\n")
258
+ for severity, count in severity_counts.items():
259
+ f.write(f" {severity}: {count} cases\n")
260
+
261
+ f.write("\n" + "=" * 80 + "\n")
262
+ f.write("END OF ENHANCED SUMMARY REPORT\n")
263
+ f.write("=" * 80 + "\n")
264
 
265
  return filepath
266
 
267
  except Exception as e:
268
+ raise Exception(f"Failed to export enhanced summary report: {str(e)}")
269
 
270
+ def _create_export_filename(self, session: EnhancedVerificationSession) -> str:
271
+ """Create descriptive filename for enhanced export."""
272
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
273
  patient_safe = session.patient_name.replace(' ', '_').replace('/', '_')
274
+ enhanced_suffix = "_enhanced" if getattr(session, 'enhanced_format_enabled', False) else ""
275
+ return f"conversation_verification_{patient_safe}_{timestamp}{enhanced_suffix}.csv"
276
 
277
+ def generate_enhanced_summary_report(self, session: EnhancedVerificationSession) -> Dict[str, Any]:
278
+ """Generate enhanced summary report for verification session."""
279
  progress = session.get_progress()
280
 
281
  # Calculate detailed statistics
 
283
  correct_records = [r for r in verified_records if r.is_correct]
284
  incorrect_records = [r for r in verified_records if not r.is_correct]
285
 
286
+ # Enhanced format statistics
287
+ records_with_enhanced = sum(1 for r in session.verification_records if r.enhanced_display_format)
288
+ records_with_summary = sum(1 for r in session.verification_records if r.provider_summary)
289
+ records_with_paragraph = sum(1 for r in session.verification_records if r.coherent_summary_paragraph)
290
+
291
  # Error analysis
292
  error_patterns = {}
293
  for record in incorrect_records:
 
302
  'incorrect_avg_confidence': sum(r.original_confidence for r in incorrect_records) / len(incorrect_records) if incorrect_records else 0
303
  }
304
 
305
+ # Enhanced format analysis
306
+ enhanced_format_stats = {
307
+ 'total_records': len(session.verification_records),
308
+ 'records_with_enhanced_display': records_with_enhanced,
309
+ 'records_with_provider_summary': records_with_summary,
310
+ 'records_with_coherent_paragraph': records_with_paragraph,
311
+ 'enhanced_display_coverage': (records_with_enhanced / len(session.verification_records)) * 100 if session.verification_records else 0,
312
+ 'provider_summary_coverage': (records_with_summary / len(session.verification_records)) * 100 if session.verification_records else 0,
313
+ 'coherent_paragraph_coverage': (records_with_paragraph / len(session.verification_records)) * 100 if session.verification_records else 0
314
+ }
315
+
316
  return {
317
  'session_info': {
318
  'session_id': session.session_id,
 
320
  'verifier_name': session.verifier_name,
321
  'start_time': session.start_time.isoformat(),
322
  'end_time': session.end_time.isoformat() if session.end_time else None,
323
+ 'duration_minutes': (session.end_time - session.start_time).total_seconds() / 60 if session.end_time else None,
324
+ 'enhanced_format_enabled': getattr(session, 'enhanced_format_enabled', False)
325
  },
326
  'verification_stats': {
327
  'total_exchanges': session.total_exchanges,
 
336
  'error_patterns': error_patterns,
337
  'common_errors': progress.common_errors
338
  },
339
+ 'confidence_analysis': confidence_stats,
340
+ 'enhanced_format_analysis': enhanced_format_stats
341
  }
src/core/verification_store.py CHANGED
@@ -24,10 +24,16 @@ from dataclasses import asdict
24
  # conversation verification, but add compatibility methods for verification_mode
25
  # without changing existing callers.
26
 
27
- from src.core.conversation_verification import (
28
- VerificationSession as ConversationVerificationSession,
29
- VerificationRecord as ConversationVerificationRecord,
30
- )
 
 
 
 
 
 
31
 
32
  from src.core.verification_models import (
33
  VerificationSession as ModeVerificationSession,
@@ -109,6 +115,10 @@ class JSONVerificationStore:
109
  if record.get("verification_timestamp") and isinstance(record["verification_timestamp"], datetime):
110
  record["verification_timestamp"] = record["verification_timestamp"].isoformat()
111
 
 
 
 
 
112
  # Add metadata for recovery / forward compatibility.
113
  session_dict["_metadata"] = {
114
  "saved_at": datetime.now().isoformat(),
@@ -162,22 +172,63 @@ class JSONVerificationStore:
162
 
163
  # Conversation verification sessions include start_time.
164
  if "start_time" in session_dict:
 
 
 
 
 
 
 
 
165
  session_dict["start_time"] = datetime.fromisoformat(session_dict["start_time"])
166
  if session_dict.get("end_time"):
167
  session_dict["end_time"] = datetime.fromisoformat(session_dict["end_time"])
168
  else:
169
  session_dict["end_time"] = None
170
 
171
- verification_records: List[ConversationVerificationRecord] = []
 
 
 
 
 
 
172
  for record_dict in session_dict.get("verification_records", []):
173
  record_dict["timestamp"] = datetime.fromisoformat(record_dict["timestamp"])
174
  if record_dict.get("verification_timestamp"):
175
  record_dict["verification_timestamp"] = datetime.fromisoformat(record_dict["verification_timestamp"])
176
  else:
177
  record_dict["verification_timestamp"] = None
178
- verification_records.append(ConversationVerificationRecord(**record_dict))
 
 
 
 
 
 
 
 
 
 
 
179
  session_dict["verification_records"] = verification_records
180
- return ConversationVerificationSession(**session_dict)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  # Unknown format
183
  return None
 
24
  # conversation verification, but add compatibility methods for verification_mode
25
  # without changing existing callers.
26
 
27
+ # Import types for type hints only to avoid circular imports
28
+ from typing import TYPE_CHECKING
29
+
30
+ if TYPE_CHECKING:
31
+ from src.core.conversation_verification import (
32
+ VerificationSession as ConversationVerificationSession,
33
+ VerificationRecord as ConversationVerificationRecord,
34
+ EnhancedVerificationSession,
35
+ EnhancedVerificationRecord,
36
+ )
37
 
38
  from src.core.verification_models import (
39
  VerificationSession as ModeVerificationSession,
 
115
  if record.get("verification_timestamp") and isinstance(record["verification_timestamp"], datetime):
116
  record["verification_timestamp"] = record["verification_timestamp"].isoformat()
117
 
118
+ # Remove non-serializable fields for enhanced sessions
119
+ if "display_manager" in session_dict:
120
+ session_dict.pop("display_manager", None)
121
+
122
  # Add metadata for recovery / forward compatibility.
123
  session_dict["_metadata"] = {
124
  "saved_at": datetime.now().isoformat(),
 
172
 
173
  # Conversation verification sessions include start_time.
174
  if "start_time" in session_dict:
175
+ # Import here to avoid circular imports
176
+ from src.core.conversation_verification import (
177
+ VerificationSession as ConversationVerificationSession,
178
+ VerificationRecord as ConversationVerificationRecord,
179
+ EnhancedVerificationSession,
180
+ EnhancedVerificationRecord,
181
+ )
182
+
183
  session_dict["start_time"] = datetime.fromisoformat(session_dict["start_time"])
184
  if session_dict.get("end_time"):
185
  session_dict["end_time"] = datetime.fromisoformat(session_dict["end_time"])
186
  else:
187
  session_dict["end_time"] = None
188
 
189
+ # Check if this is an enhanced conversation verification session
190
+ has_enhanced_fields = any(
191
+ field in session_dict for field in
192
+ ['enhanced_format_enabled', 'display_manager']
193
+ )
194
+
195
+ verification_records = []
196
  for record_dict in session_dict.get("verification_records", []):
197
  record_dict["timestamp"] = datetime.fromisoformat(record_dict["timestamp"])
198
  if record_dict.get("verification_timestamp"):
199
  record_dict["verification_timestamp"] = datetime.fromisoformat(record_dict["verification_timestamp"])
200
  else:
201
  record_dict["verification_timestamp"] = None
202
+
203
+ # Check if record has enhanced fields
204
+ has_enhanced_record_fields = any(
205
+ field in record_dict for field in
206
+ ['enhanced_display_format', 'provider_summary', 'coherent_summary_paragraph', 'visual_sections']
207
+ )
208
+
209
+ if has_enhanced_record_fields or has_enhanced_fields:
210
+ verification_records.append(EnhancedVerificationRecord(**record_dict))
211
+ else:
212
+ verification_records.append(ConversationVerificationRecord(**record_dict))
213
+
214
  session_dict["verification_records"] = verification_records
215
+
216
+ # Create appropriate session type
217
+ if has_enhanced_fields or any(isinstance(r, EnhancedVerificationRecord) for r in verification_records):
218
+ # Set defaults for enhanced fields if missing
219
+ session_dict.setdefault('enhanced_format_enabled', True)
220
+ # Don't restore display_manager from JSON - create new one
221
+ session_dict.pop('display_manager', None)
222
+
223
+ enhanced_session = EnhancedVerificationSession(**session_dict)
224
+ # Create new display manager if enhanced formats are enabled
225
+ if enhanced_session.enhanced_format_enabled:
226
+ from src.interface.enhanced_results_display_manager import EnhancedResultsDisplayManager
227
+ enhanced_session.display_manager = EnhancedResultsDisplayManager()
228
+
229
+ return enhanced_session
230
+ else:
231
+ return ConversationVerificationSession(**session_dict)
232
 
233
  # Unknown format
234
  return None
src/interface/chat_handlers.py CHANGED
@@ -3,9 +3,11 @@ import gradio as gr
3
  import html
4
  from src.interface.session_manager import SimplifiedSessionData
5
  from src.interface.stats_handlers import get_conversation_stats
 
 
6
 
7
  def handle_message(message: str, history, session: SimplifiedSessionData):
8
- """Handle user message."""
9
  if session is None:
10
  session = SimplifiedSessionData()
11
 
@@ -24,6 +26,10 @@ def handle_message(message: str, history, session: SimplifiedSessionData):
24
  session.app_instance.set_prompt_overrides(custom_prompts)
25
  else:
26
  session.app_instance.set_prompt_overrides({})
 
 
 
 
27
  new_history, status = session.app_instance.process_message(message, history)
28
 
29
  # Get updated conversation stats
@@ -31,7 +37,7 @@ def handle_message(message: str, history, session: SimplifiedSessionData):
31
 
32
  # Check for provider summary (RED flag case)
33
  provider_summary_text = ""
34
- spiritual_care_msg = ""
35
  show_provider_panel = False
36
  last_summary = session.app_instance.get_last_provider_summary()
37
 
@@ -40,20 +46,17 @@ def handle_message(message: str, history, session: SimplifiedSessionData):
40
  if last_summary:
41
  print(f"DEBUG: summary patient: {last_summary.patient_name}")
42
  print(f"DEBUG: summary indicators: {last_summary.indicators}")
43
- provider_summary_text = session.app_instance.provider_summary_generator.format_for_display(last_summary)
44
 
45
- # Generate LLM-based spiritual care message
 
 
 
46
  try:
47
- spiritual_care_msg = session.app_instance.generate_spiritual_care_message(
48
- language="English",
49
- session_id=session.session_id
50
- )
51
- if not spiritual_care_msg:
52
- spiritual_care_msg = ""
53
- print(f"DEBUG: spiritual care message generated: {len(spiritual_care_msg)} chars")
54
  except Exception as e:
55
- print(f"DEBUG: Error generating spiritual care message: {e}")
56
- spiritual_care_msg = ""
57
 
58
  show_provider_panel = True
59
  print(f"DEBUG: formatted summary length: {len(provider_summary_text)}")
@@ -66,21 +69,31 @@ def handle_message(message: str, history, session: SimplifiedSessionData):
66
  if provider_summary_text:
67
  print(f"DEBUG RETURN: first 100 chars: {provider_summary_text[:100]}")
68
 
69
- # Format as HTML for display
70
- if provider_summary_text:
71
- escaped_text = html.escape(provider_summary_text)
72
- html_content = f"<pre style='white-space: pre-wrap; font-family: monospace; font-size: 11px; background: #fffbeb; padding: 10px; border-radius: 8px; max-height: 400px; overflow-y: auto;'>{escaped_text}</pre>"
73
- else:
74
- html_content = "<pre style='white-space: pre-wrap; font-family: monospace; font-size: 11px; background: #fffbeb; padding: 10px; border-radius: 8px;'>No summary available</pre>"
75
 
76
  # Use gr.update for both visibility and value
77
  if not provider_summary_text:
78
  provider_summary_text = ""
79
  html_content = ""
80
 
81
- # Generate status message for provider summary
82
  if show_provider_panel and provider_summary_text:
83
- status_msg = f"""**🔴 Provider Summary Generated**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  **Patient:** {session.app_instance.patient_info.get('name', 'Test Patient')}
86
  **Classification:** RED FLAG
@@ -100,7 +113,7 @@ Use the **Download Summary** button below to access the complete provider summar
100
  gr.update(visible=show_provider_panel), # provider_summary_content visibility
101
  status_msg, # provider_summary_status content
102
  gr.update(value=html_content, visible=True) if show_provider_panel else gr.update(visible=False), # provider_summary_display content
103
- spiritual_care_msg # spiritual_care_message content
104
  )
105
 
106
  def handle_clear(session: SimplifiedSessionData):
@@ -127,7 +140,7 @@ def handle_clear(session: SimplifiedSessionData):
127
  gr.update(visible=False), # Hide provider_summary_content group
128
  "No provider summary available", # Clear provider_summary_status
129
  "", # Clear provider_summary_display HTML
130
- "" # Clear spiritual_care_message
131
  )
132
 
133
  def send_example(example_text: str, history, session: SimplifiedSessionData):
 
3
  import html
4
  from src.interface.session_manager import SimplifiedSessionData
5
  from src.interface.stats_handlers import get_conversation_stats
6
+ from src.interface.enhanced_display_integration import create_enhanced_display_integration
7
+ from src.core.improved_classification_prompt_manager import ImprovedClassificationPromptManager
8
 
9
  def handle_message(message: str, history, session: SimplifiedSessionData):
10
+ """Handle user message with enhanced display formatting."""
11
  if session is None:
12
  session = SimplifiedSessionData()
13
 
 
26
  session.app_instance.set_prompt_overrides(custom_prompts)
27
  else:
28
  session.app_instance.set_prompt_overrides({})
29
+
30
+ # Initialize enhanced display integration
31
+ enhanced_display = create_enhanced_display_integration()
32
+
33
  new_history, status = session.app_instance.process_message(message, history)
34
 
35
  # Get updated conversation stats
 
37
 
38
  # Check for provider summary (RED flag case)
39
  provider_summary_text = ""
40
+ coherent_summary_text = ""
41
  show_provider_panel = False
42
  last_summary = session.app_instance.get_last_provider_summary()
43
 
 
46
  if last_summary:
47
  print(f"DEBUG: summary patient: {last_summary.patient_name}")
48
  print(f"DEBUG: summary indicators: {last_summary.indicators}")
 
49
 
50
+ # Use enhanced display for provider summary formatting
51
+ provider_summary_text = enhanced_display.display_manager.format_provider_summary_section(last_summary)
52
+
53
+ # Generate medical brain summary (NEW - Requirements 2.1-2.8)
54
  try:
55
+ coherent_summary_text = session.app_instance.provider_summary_generator.format_coherent_paragraph(last_summary)
56
+ print(f"DEBUG: coherent summary generated: {len(coherent_summary_text)} chars")
 
 
 
 
 
57
  except Exception as e:
58
+ print(f"DEBUG: Error generating coherent summary: {e}")
59
+ coherent_summary_text = ""
60
 
61
  show_provider_panel = True
62
  print(f"DEBUG: formatted summary length: {len(provider_summary_text)}")
 
69
  if provider_summary_text:
70
  print(f"DEBUG RETURN: first 100 chars: {provider_summary_text[:100]}")
71
 
72
+ # Enhanced display formatting - provider summary is already HTML formatted
73
+ html_content = provider_summary_text if provider_summary_text else ""
 
 
 
 
74
 
75
  # Use gr.update for both visibility and value
76
  if not provider_summary_text:
77
  provider_summary_text = ""
78
  html_content = ""
79
 
80
+ # Generate status message for provider summary with enhanced formatting
81
  if show_provider_panel and provider_summary_text:
82
+ # Get the last assessment for enhanced status display
83
+ last_assessment = session.app_instance.spiritual_state.last_assessment
84
+ if last_assessment:
85
+ classification_badge = enhanced_display.get_classification_badge(last_assessment.state.value.upper())
86
+ status_msg = f"""**🔴 Provider Summary Generated**
87
+
88
+ {classification_badge}
89
+
90
+ **Patient:** {session.app_instance.patient_info.get('name', 'Test Patient')}
91
+ **Indicators:** {len(session.app_instance.get_last_provider_summary().indicators) if session.app_instance.get_last_provider_summary() else 0} distress indicators detected
92
+ **Summary Length:** {len(provider_summary_text)} characters
93
+
94
+ Use the **Download Summary** button below to access the complete provider summary for the spiritual care team."""
95
+ else:
96
+ status_msg = f"""**🔴 Provider Summary Generated**
97
 
98
  **Patient:** {session.app_instance.patient_info.get('name', 'Test Patient')}
99
  **Classification:** RED FLAG
 
113
  gr.update(visible=show_provider_panel), # provider_summary_content visibility
114
  status_msg, # provider_summary_status content
115
  gr.update(value=html_content, visible=True) if show_provider_panel else gr.update(visible=False), # provider_summary_display content
116
+ coherent_summary_text # coherent_summary_display content (NEW)
117
  )
118
 
119
  def handle_clear(session: SimplifiedSessionData):
 
140
  gr.update(visible=False), # Hide provider_summary_content group
141
  "No provider summary available", # Clear provider_summary_status
142
  "", # Clear provider_summary_display HTML
143
+ "" # Clear coherent_summary_display (NEW)
144
  )
145
 
146
  def send_example(example_text: str, history, session: SimplifiedSessionData):
src/interface/enhanced_display_integration.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # enhanced_display_integration.py
2
+ """
3
+ Integration module for Enhanced Results Display Manager.
4
+
5
+ This module provides integration functions to connect the enhanced display
6
+ components with the existing chat interface and result processing system.
7
+
8
+ Requirements: 1.1, 1.2, 7.1, 7.2
9
+ """
10
+
11
+ from typing import Optional, Dict, Any, List, Tuple
12
+ import html
13
+
14
+ from src.interface.enhanced_results_display_manager import (
15
+ EnhancedResultsDisplayManager,
16
+ EnhancedDisplayConfig
17
+ )
18
+ from src.interface.visual_separation_manager import VisualSeparationManager
19
+ from src.interface.provider_summary_formatter import ProviderSummaryFormatter
20
+ from src.core.provider_summary_generator import ProviderSummary
21
+ from src.core.spiritual_state import SpiritualAssessment
22
+
23
+
24
+ class EnhancedDisplayIntegration:
25
+ """
26
+ Integration class for enhanced display components.
27
+
28
+ Provides a unified interface for integrating enhanced display functionality
29
+ with the existing chat interface and result processing system.
30
+ """
31
+
32
+ def __init__(self, config: Optional[EnhancedDisplayConfig] = None):
33
+ """
34
+ Initialize the enhanced display integration.
35
+
36
+ Args:
37
+ config: Optional configuration for display formatting
38
+ """
39
+ self.display_manager = EnhancedResultsDisplayManager(config)
40
+ self.visual_manager = VisualSeparationManager()
41
+ self.summary_formatter = ProviderSummaryFormatter()
42
+
43
+ def format_chat_response(
44
+ self,
45
+ assessment: Optional[SpiritualAssessment] = None,
46
+ patient_message: Optional[str] = None,
47
+ provider_summary: Optional[ProviderSummary] = None,
48
+ show_ai_analysis: bool = True
49
+ ) -> str:
50
+ """
51
+ Format a complete chat response with enhanced display.
52
+
53
+ Args:
54
+ assessment: Optional spiritual assessment from AI analysis
55
+ patient_message: Optional patient message to display
56
+ provider_summary: Optional provider summary for RED flags
57
+ show_ai_analysis: Whether to show AI analysis section
58
+
59
+ Returns:
60
+ Formatted HTML string for display in chat interface
61
+
62
+ Requirements: 1.1, 1.2, 7.1, 7.2
63
+ """
64
+ # Prepare AI analysis data if available and requested
65
+ ai_analysis = None
66
+ if assessment and show_ai_analysis:
67
+ ai_analysis = {
68
+ "classification": assessment.state.value.upper(),
69
+ "indicators": assessment.indicators,
70
+ "reasoning": assessment.reasoning,
71
+ "confidence": assessment.confidence
72
+ }
73
+
74
+ # Format combined results
75
+ return self.display_manager.format_combined_results(
76
+ ai_analysis=ai_analysis,
77
+ patient_message=patient_message,
78
+ provider_summary=provider_summary
79
+ )
80
+
81
+ def format_provider_summary_coherent(
82
+ self,
83
+ summary: ProviderSummary
84
+ ) -> str:
85
+ """
86
+ Format provider summary as coherent paragraph.
87
+
88
+ Args:
89
+ summary: Provider summary to format
90
+
91
+ Returns:
92
+ Coherent paragraph formatted summary
93
+
94
+ Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8
95
+ """
96
+ return self.summary_formatter.format_from_provider_summary(summary)
97
+
98
+ def create_section_with_styling(
99
+ self,
100
+ content: str,
101
+ section_type: str,
102
+ title: Optional[str] = None,
103
+ icon: Optional[str] = None
104
+ ) -> str:
105
+ """
106
+ Create a styled section with consistent formatting.
107
+
108
+ Args:
109
+ content: The content to display
110
+ section_type: Type of section (ai_analysis, patient_message, provider_summary)
111
+ title: Optional custom title for the section
112
+ icon: Optional custom icon for the section
113
+
114
+ Returns:
115
+ Styled HTML section
116
+
117
+ Requirements: 7.1, 7.2, 7.3
118
+ """
119
+ # Get appropriate styling
120
+ if section_type == "ai_analysis":
121
+ styling = self.visual_manager.create_ai_analysis_styling()
122
+ default_title = "AI Analysis"
123
+ default_icon = "🤖"
124
+ elif section_type == "patient_message":
125
+ styling = self.visual_manager.create_patient_message_styling()
126
+ default_title = "Patient Message"
127
+ default_icon = "💬"
128
+ elif section_type == "provider_summary":
129
+ styling = self.visual_manager.create_provider_summary_styling()
130
+ default_title = "Provider Summary"
131
+ default_icon = "📋"
132
+ else:
133
+ # Default styling
134
+ styling = {
135
+ "container": "padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px;"
136
+ }
137
+ default_title = "Content"
138
+ default_icon = "📄"
139
+
140
+ # Use provided title/icon or defaults
141
+ section_title = title or default_title
142
+ section_icon = icon or default_icon
143
+
144
+ # Build styled section
145
+ return f"""
146
+ <div style='{styling.get("container", "")}'>
147
+ <div style='{styling.get("header", "")}'>
148
+ <span style='{styling.get("icon", "")}'>{section_icon}</span>
149
+ <strong>{section_title}</strong>
150
+ </div>
151
+ <div style='{styling.get("content", "")}'>
152
+ {html.escape(content) if isinstance(content, str) else content}
153
+ </div>
154
+ </div>
155
+ """
156
+
157
+ def get_classification_badge(self, classification: str) -> str:
158
+ """
159
+ Get a styled classification badge.
160
+
161
+ Args:
162
+ classification: The classification level (RED/YELLOW/GREEN)
163
+
164
+ Returns:
165
+ HTML badge for the classification
166
+
167
+ Requirements: 7.3
168
+ """
169
+ styling = self.visual_manager.get_classification_styling(classification)
170
+
171
+ return f"""
172
+ <span style='{styling["badge"]}'>
173
+ {classification.upper()} FLAG
174
+ </span>
175
+ """
176
+
177
+ def get_urgency_badge(self, urgency_level: str) -> str:
178
+ """
179
+ Get a styled urgency badge.
180
+
181
+ Args:
182
+ urgency_level: The urgency level (IMMEDIATE/URGENT/STANDARD)
183
+
184
+ Returns:
185
+ HTML badge for the urgency level
186
+
187
+ Requirements: 7.3
188
+ """
189
+ styling = self.visual_manager.get_urgency_styling(urgency_level)
190
+
191
+ return f"""
192
+ <span style='{styling["badge"]}'>
193
+ {urgency_level.upper()}
194
+ </span>
195
+ """
196
+
197
+ def create_content_separator(self, separator_type: str = "section_break") -> str:
198
+ """
199
+ Create a content separator.
200
+
201
+ Args:
202
+ separator_type: Type of separator (light, medium, heavy, section_break)
203
+
204
+ Returns:
205
+ HTML separator
206
+
207
+ Requirements: 7.4, 7.5
208
+ """
209
+ separators = self.visual_manager.generate_section_separators()
210
+ return separators.get(separator_type, separators["section_break"])
211
+
212
+ def format_multiple_sections(self, sections: List[Dict[str, Any]]) -> str:
213
+ """
214
+ Format multiple content sections with consistent styling.
215
+
216
+ Args:
217
+ sections: List of section dictionaries with 'type' and 'content' keys
218
+
219
+ Returns:
220
+ Combined HTML with all sections and separators
221
+
222
+ Requirements: 7.1, 7.2, 7.4, 7.5
223
+ """
224
+ return self.visual_manager.apply_consistent_formatting(sections)
225
+
226
+
227
+ # Convenience function for easy integration
228
+ def create_enhanced_display_integration(
229
+ config: Optional[EnhancedDisplayConfig] = None
230
+ ) -> EnhancedDisplayIntegration:
231
+ """
232
+ Create an enhanced display integration instance.
233
+
234
+ Args:
235
+ config: Optional configuration for display formatting
236
+
237
+ Returns:
238
+ Configured EnhancedDisplayIntegration instance
239
+ """
240
+ return EnhancedDisplayIntegration(config)
241
+
242
+
243
+ # Example usage functions for demonstration
244
+ def example_format_ai_analysis_result(
245
+ classification: str,
246
+ indicators: List[str],
247
+ reasoning: str,
248
+ confidence: float = None
249
+ ) -> str:
250
+ """
251
+ Example function showing how to format AI analysis results.
252
+
253
+ Args:
254
+ classification: Classification result (RED/YELLOW/GREEN)
255
+ indicators: List of distress indicators
256
+ reasoning: AI reasoning for classification
257
+ confidence: Optional confidence score
258
+
259
+ Returns:
260
+ Formatted HTML for AI analysis display
261
+ """
262
+ integration = create_enhanced_display_integration()
263
+
264
+ return integration.display_manager.format_ai_analysis_section(
265
+ classification=classification,
266
+ indicators=indicators,
267
+ reasoning=reasoning,
268
+ confidence=confidence
269
+ )
270
+
271
+
272
+ def example_format_patient_message(message: str) -> str:
273
+ """
274
+ Example function showing how to format patient messages.
275
+
276
+ Args:
277
+ message: Patient's message content
278
+
279
+ Returns:
280
+ Formatted HTML for patient message display
281
+ """
282
+ integration = create_enhanced_display_integration()
283
+
284
+ return integration.display_manager.format_patient_message_section(message)
285
+
286
+
287
+ def example_format_complete_response(
288
+ patient_message: str,
289
+ classification: str,
290
+ indicators: List[str],
291
+ reasoning: str,
292
+ provider_summary: Optional[ProviderSummary] = None
293
+ ) -> str:
294
+ """
295
+ Example function showing complete response formatting.
296
+
297
+ Args:
298
+ patient_message: Patient's message
299
+ classification: AI classification result
300
+ indicators: Distress indicators
301
+ reasoning: AI reasoning
302
+ provider_summary: Optional provider summary for RED flags
303
+
304
+ Returns:
305
+ Complete formatted response with all sections
306
+ """
307
+ integration = create_enhanced_display_integration()
308
+
309
+ # Create AI analysis data
310
+ ai_analysis = {
311
+ "classification": classification,
312
+ "indicators": indicators,
313
+ "reasoning": reasoning,
314
+ "confidence": 0.85
315
+ }
316
+
317
+ return integration.display_manager.format_combined_results(
318
+ ai_analysis=ai_analysis,
319
+ patient_message=patient_message,
320
+ provider_summary=provider_summary
321
+ )
src/interface/enhanced_results_display_manager.py ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # enhanced_results_display_manager.py
2
+ """
3
+ Enhanced Results Display Manager for UI Classification Improvements.
4
+
5
+ This module provides enhanced display formatting for chat results with clear
6
+ visual separation between AI analysis, patient messages, and provider summaries.
7
+
8
+ Requirements: 1.1, 1.2, 7.1, 7.2
9
+ """
10
+
11
+ import html
12
+ from typing import Dict, List, Optional, Any
13
+ from dataclasses import dataclass
14
+ from enum import Enum
15
+
16
+ from src.core.provider_summary_generator import ProviderSummary
17
+ from src.core.spiritual_state import SpiritualAssessment, SpiritualState
18
+ from src.config.enhanced_display_config import (
19
+ EnhancedDisplayConfig,
20
+ get_enhanced_display_config,
21
+ EnhancedDisplayConfigManager
22
+ )
23
+
24
+
25
+ class SectionType(Enum):
26
+ """Types of content sections for display."""
27
+ AI_ANALYSIS = "ai_analysis"
28
+ PATIENT_MESSAGE = "patient_message"
29
+ PROVIDER_SUMMARY = "provider_summary"
30
+
31
+
32
+ @dataclass
33
+ class ContentSection:
34
+ """Represents a content section with styling information."""
35
+ section_type: SectionType
36
+ content: str
37
+ styling: Dict[str, str]
38
+ icon: Optional[str] = None
39
+ priority: int = 0
40
+
41
+
42
+ @dataclass
43
+ class ContentSection:
44
+ """Represents a content section with styling information."""
45
+ section_type: SectionType
46
+ content: str
47
+ styling: Dict[str, str]
48
+ icon: Optional[str] = None
49
+ priority: int = 0
50
+
51
+
52
+ class EnhancedResultsDisplayManager:
53
+ """
54
+ Enhanced Results Display Manager for improved UI formatting.
55
+
56
+ Provides methods to format different types of content with clear visual
57
+ separation and consistent styling according to requirements 1.1, 1.2, 7.1, 7.2.
58
+ """
59
+
60
+ def __init__(self, config: Optional[EnhancedDisplayConfig] = None, config_manager: Optional[EnhancedDisplayConfigManager] = None):
61
+ """
62
+ Initialize the display manager.
63
+
64
+ Args:
65
+ config: Optional configuration for display formatting
66
+ config_manager: Optional configuration manager for dynamic updates
67
+ """
68
+ self.config_manager = config_manager
69
+ if config is not None:
70
+ self.config = config
71
+ elif self.config_manager is not None:
72
+ self.config = self.config_manager.get_config()
73
+ else:
74
+ self.config = get_enhanced_display_config()
75
+
76
+ # Initialize error handler for validation and recovery (lazy loaded)
77
+ self._error_handler = None
78
+
79
+ @property
80
+ def error_handler(self):
81
+ """Lazy load error handler to avoid circular imports."""
82
+ if self._error_handler is None:
83
+ from src.core.ui_error_handler import UIErrorHandler
84
+ self._error_handler = UIErrorHandler()
85
+ return self._error_handler
86
+
87
+ def format_ai_analysis_section(
88
+ self,
89
+ classification: str,
90
+ indicators: List[str],
91
+ reasoning: str,
92
+ confidence: Optional[float] = None
93
+ ) -> str:
94
+ """
95
+ Format AI analysis section with clear labeling and visual styling.
96
+
97
+ Args:
98
+ classification: The classification result (RED/YELLOW/GREEN)
99
+ indicators: List of distress indicators
100
+ reasoning: AI reasoning for the classification
101
+ confidence: Optional confidence score
102
+
103
+ Returns:
104
+ Formatted HTML string for AI analysis section
105
+
106
+ Requirements: 1.1, 7.1
107
+ """
108
+ try:
109
+ # Validate inputs
110
+ if not classification:
111
+ classification = "UNKNOWN"
112
+ if not indicators:
113
+ indicators = ["No indicators available"]
114
+ if not reasoning:
115
+ reasoning = "No reasoning provided"
116
+
117
+ # Check if enhancements are enabled
118
+ if not self.config.enabled:
119
+ return self._format_basic_ai_analysis(classification, indicators, reasoning, confidence)
120
+
121
+ # Get classification color
122
+ color = self.config.get_classification_color(classification)
123
+ section_config = self.config.get_section_config("ai_analysis")
124
+
125
+ # Build confidence display
126
+ confidence_text = ""
127
+ if confidence is not None:
128
+ confidence_percent = int(confidence * 100)
129
+ confidence_text = f"<div style='margin: 5px 0; font-size: {self.config.small_font_size}; color: #666;'>Confidence: {confidence_percent}%</div>"
130
+
131
+ # Build indicators list
132
+ indicators_html = ""
133
+ if indicators:
134
+ indicators_list = "".join([f"<li>{html.escape(indicator)}</li>" for indicator in indicators])
135
+ indicators_html = f"""
136
+ <div style='margin: 10px 0;'>
137
+ <strong>Indicators:</strong>
138
+ <ul style='margin: 5px 0 0 20px; padding: 0;'>
139
+ {indicators_list}
140
+ </ul>
141
+ </div>
142
+ """
143
+
144
+ # Build reasoning section
145
+ reasoning_html = f"""
146
+ <div style='margin: 10px 0;'>
147
+ <strong>Reasoning:</strong>
148
+ <div style='margin: 5px 0; padding: 8px; background-color: #f8f9fa; border-radius: 4px; font-style: italic;'>
149
+ {html.escape(reasoning)}
150
+ </div>
151
+ </div>
152
+ """
153
+
154
+ # Build icon display
155
+ icon_html = ""
156
+ if self.config.use_icons:
157
+ icon_html = f"<span style='font-size: 1.2em; margin-right: 8px;'>{section_config.icon}</span>"
158
+
159
+ # Combine all elements with enhanced styling
160
+ content = f"""
161
+ <div style='border: {section_config.border_width} solid {color}; border-radius: {section_config.border_radius}; padding: {section_config.padding}; margin: {section_config.margin}; background-color: {section_config.background_color}; font-family: {self.config.font_family};'>
162
+ <div style='display: flex; align-items: center; margin-bottom: 10px;'>
163
+ {icon_html}
164
+ <strong style='color: {color}; font-size: {self.config.header_font_size};'>AI Analysis - {classification} FLAG</strong>
165
+ </div>
166
+ {confidence_text}
167
+ {indicators_html}
168
+ {reasoning_html}
169
+ </div>
170
+ """
171
+
172
+ return content
173
+
174
+ except Exception as e:
175
+ # Handle formatting errors with degraded display
176
+ error_context = f"AI analysis formatting failed: {str(e)}"
177
+ fallback_content = self._format_basic_ai_analysis(classification, indicators, reasoning, confidence)
178
+ return self.error_handler.create_degraded_display(error_context, fallback_content)
179
+
180
+ def format_patient_message_section(self, message: str) -> str:
181
+ """
182
+ Format patient message section with appropriate styling.
183
+
184
+ Args:
185
+ message: The patient's message content
186
+
187
+ Returns:
188
+ Formatted HTML string for patient message section
189
+
190
+ Requirements: 1.2, 7.2
191
+ """
192
+ try:
193
+ # Validate input
194
+ if not message:
195
+ message = "No message content available"
196
+
197
+ # Check if enhancements are enabled
198
+ if not self.config.enabled:
199
+ return self._format_basic_patient_message(message)
200
+
201
+ section_config = self.config.get_section_config("patient_message")
202
+
203
+ # Build icon display
204
+ icon_html = ""
205
+ if self.config.use_icons:
206
+ icon_html = f"<span style='font-size: 1.2em; margin-right: 8px;'>{section_config.icon}</span>"
207
+
208
+ content = f"""
209
+ <div style='border: {section_config.border_width} solid {section_config.border_color}; border-radius: {section_config.border_radius}; padding: {section_config.padding}; margin: {section_config.margin}; background-color: {section_config.background_color}; font-family: {self.config.font_family};'>
210
+ <div style='display: flex; align-items: center; margin-bottom: 10px;'>
211
+ {icon_html}
212
+ <strong style='color: {section_config.header_color}; font-size: {self.config.header_font_size};'>Patient Message</strong>
213
+ </div>
214
+ <div style='padding: 8px; background-color: white; border-radius: 4px; border-left: 4px solid {section_config.border_color};'>
215
+ {html.escape(message)}
216
+ </div>
217
+ </div>
218
+ """
219
+
220
+ return content
221
+
222
+ except Exception as e:
223
+ # Handle formatting errors with degraded display
224
+ error_context = f"Patient message formatting failed: {str(e)}"
225
+ fallback_content = self._format_basic_patient_message(message)
226
+ return self.error_handler.create_degraded_display(error_context, fallback_content)
227
+
228
+ def format_provider_summary_section(self, summary_data: ProviderSummary) -> str:
229
+ """
230
+ Format provider summary section with enhanced styling.
231
+
232
+ Args:
233
+ summary_data: Provider summary data to format
234
+
235
+ Returns:
236
+ Formatted HTML string for provider summary section
237
+
238
+ Requirements: 1.1, 1.2, 7.1, 7.2
239
+ """
240
+ try:
241
+ # Validate and fix summary data if needed
242
+ validation_result = self.error_handler.validate_provider_summary_structure(summary_data)
243
+
244
+ if validation_result.has_critical_errors():
245
+ # Apply fallback template for critical errors
246
+ summary_data = self.error_handler.apply_fallback_template(summary_data, "general")
247
+
248
+ # Check if enhancements are enabled
249
+ if not self.config.enabled:
250
+ return self._format_basic_provider_summary(summary_data)
251
+
252
+ section_config = self.config.get_section_config("provider_summary")
253
+
254
+ # Get urgency color
255
+ urgency_colors = {
256
+ 'IMMEDIATE': self.config.classification_colors.red,
257
+ 'URGENT': self.config.classification_colors.yellow,
258
+ 'STANDARD': self.config.classification_colors.green
259
+ }
260
+ urgency_color = urgency_colors.get(summary_data.urgency_level, self.config.classification_colors.red)
261
+
262
+ # Build patient info section
263
+ patient_info = f"""
264
+ <div style='margin: 10px 0;'>
265
+ <strong>Patient Information:</strong>
266
+ <div style='margin: 5px 0; padding: 8px; background-color: #f8f9fa; border-radius: 4px;'>
267
+ <div><strong>Name:</strong> {html.escape(summary_data.patient_name)}</div>
268
+ <div><strong>Phone:</strong> {html.escape(summary_data.patient_phone)}</div>
269
+ </div>
270
+ </div>
271
+ """
272
+
273
+ # Build indicators section
274
+ indicators_html = ""
275
+ if summary_data.indicators:
276
+ indicators_list = "".join([f"<li>{html.escape(indicator)}</li>" for indicator in summary_data.indicators])
277
+ indicators_html = f"""
278
+ <div style='margin: 10px 0;'>
279
+ <strong>Distress Indicators:</strong>
280
+ <ul style='margin: 5px 0 0 20px; padding: 0;'>
281
+ {indicators_list}
282
+ </ul>
283
+ </div>
284
+ """
285
+
286
+ # Build situation description
287
+ situation_html = f"""
288
+ <div style='margin: 10px 0;'>
289
+ <strong>Situation Overview:</strong>
290
+ <div style='margin: 5px 0; padding: 8px; background-color: #fff3cd; border-radius: 4px; border-left: 4px solid {urgency_color};'>
291
+ {html.escape(summary_data.situation_description)}
292
+ </div>
293
+ </div>
294
+ """
295
+
296
+ # Build urgency information
297
+ urgency_html = f"""
298
+ <div style='margin: 10px 0;'>
299
+ <strong>Urgency Level:</strong>
300
+ <span style='color: {urgency_color}; font-weight: bold; margin-left: 8px;'>{summary_data.urgency_level}</span>
301
+ <div style='margin: 5px 0; font-size: {self.config.small_font_size}; color: #666;'>
302
+ Follow-up Timeline: {summary_data.follow_up_timeline}
303
+ </div>
304
+ </div>
305
+ """
306
+
307
+ # Build icon display
308
+ icon_html = ""
309
+ if self.config.use_icons:
310
+ icon_html = f"<span style='font-size: 1.2em; margin-right: 8px;'>{section_config.icon}</span>"
311
+
312
+ # Add validation warnings if any
313
+ warnings_html = ""
314
+ if validation_result.warnings:
315
+ warnings_list = "".join([f"<li>{warning.message}</li>" for warning in validation_result.warnings])
316
+ warnings_html = f"""
317
+ <div style='margin: 10px 0; padding: 8px; background-color: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;'>
318
+ <strong style='color: #856404;'>⚠️ Validation Warnings:</strong>
319
+ <ul style='margin: 5px 0 0 20px; padding: 0; color: #856404;'>
320
+ {warnings_list}
321
+ </ul>
322
+ </div>
323
+ """
324
+
325
+ # Combine all elements with enhanced styling
326
+ content = f"""
327
+ <div style='border: {section_config.border_width} solid {urgency_color}; border-radius: {section_config.border_radius}; padding: {section_config.padding}; margin: {section_config.margin}; background-color: {section_config.background_color}; font-family: {self.config.font_family};'>
328
+ <div style='display: flex; align-items: center; margin-bottom: 10px;'>
329
+ {icon_html}
330
+ <strong style='color: {urgency_color}; font-size: {self.config.header_font_size};'>Provider Summary</strong>
331
+ <span style='margin-left: 8px; font-size: {self.config.small_font_size}; color: #666;'>For Spiritual Care Team</span>
332
+ </div>
333
+ {patient_info}
334
+ {urgency_html}
335
+ {situation_html}
336
+ {indicators_html}
337
+ {warnings_html}
338
+ </div>
339
+ """
340
+
341
+ return content
342
+
343
+ except Exception as e:
344
+ # Handle formatting errors with degraded display
345
+ error_context = f"Provider summary formatting failed: {str(e)}"
346
+ fallback_content = self._format_basic_provider_summary(summary_data)
347
+ return self.error_handler.create_degraded_display(error_context, fallback_content)
348
+
349
+ def create_visual_separators(self) -> Dict[str, str]:
350
+ """
351
+ Create visual separators for different content types.
352
+
353
+ Returns:
354
+ Dictionary mapping section types to separator HTML
355
+
356
+ Requirements: 7.1, 7.2
357
+ """
358
+ # Check if separators are enabled
359
+ if not self.config.use_visual_separators:
360
+ return {
361
+ "section_break": "<div style='margin: 10px 0;'></div>",
362
+ "content_divider": "<div style='margin: 5px 0;'></div>",
363
+ "major_break": "<div style='margin: 15px 0;'></div>"
364
+ }
365
+
366
+ separators = {
367
+ "section_break": f"""
368
+ <div style='margin: 20px 0; text-align: center;'>
369
+ <hr style='border: none; border-top: 2px solid {self.config.separators.separator_color}; width: {self.config.separators.separator_width}; margin: 0 auto;'>
370
+ <div style='margin: 10px 0; color: #666; font-size: {self.config.small_font_size};'>{self.config.separators.section_separator}</div>
371
+ </div>
372
+ """,
373
+
374
+ "content_divider": f"""
375
+ <div style='margin: 15px 0; border-top: {self.config.separators.content_divider};'></div>
376
+ """,
377
+
378
+ "major_break": f"""
379
+ <div style='margin: 30px 0; text-align: center;'>
380
+ <div style='display: inline-block; padding: 8px 16px; background-color: #f0f0f0; border-radius: 20px; color: #666; font-size: {self.config.small_font_size};'>
381
+ {self.config.separators.major_break_symbol}
382
+ </div>
383
+ </div>
384
+ """
385
+ }
386
+
387
+ return separators
388
+
389
+ def apply_section_styling(self, content: str, section_type: SectionType) -> str:
390
+ """
391
+ Apply consistent styling to a content section.
392
+
393
+ Args:
394
+ content: The content to style
395
+ section_type: The type of section for appropriate styling
396
+
397
+ Returns:
398
+ Styled HTML content
399
+
400
+ Requirements: 7.1, 7.2
401
+ """
402
+ # Base styling for all sections
403
+ base_style = "margin: 15px 0; padding: 10px; border-radius: 6px;"
404
+
405
+ # Section-specific styling
406
+ section_styles = {
407
+ SectionType.AI_ANALYSIS: base_style + "background-color: #f8f9fa; border-left: 4px solid #6c757d;",
408
+ SectionType.PATIENT_MESSAGE: base_style + "background-color: #f0f7ff; border-left: 4px solid #4a90e2;",
409
+ SectionType.PROVIDER_SUMMARY: base_style + "background-color: #fff8f0; border-left: 4px solid #dc3545;"
410
+ }
411
+
412
+ style = section_styles.get(section_type, base_style)
413
+
414
+ return f"""
415
+ <div style='{style}'>
416
+ {content}
417
+ </div>
418
+ """
419
+
420
+ def format_combined_results(
421
+ self,
422
+ ai_analysis: Optional[Dict[str, Any]] = None,
423
+ patient_message: Optional[str] = None,
424
+ provider_summary: Optional[ProviderSummary] = None
425
+ ) -> str:
426
+ """
427
+ Format combined results with all sections and proper separation.
428
+
429
+ Args:
430
+ ai_analysis: Optional AI analysis data
431
+ patient_message: Optional patient message
432
+ provider_summary: Optional provider summary data
433
+
434
+ Returns:
435
+ Complete formatted HTML with all sections
436
+
437
+ Requirements: 1.1, 1.2, 7.1, 7.2
438
+ """
439
+ sections = []
440
+ separators = self.create_visual_separators()
441
+
442
+ # Add AI analysis section if provided
443
+ if ai_analysis:
444
+ ai_section = self.format_ai_analysis_section(
445
+ classification=ai_analysis.get('classification', 'UNKNOWN'),
446
+ indicators=ai_analysis.get('indicators', []),
447
+ reasoning=ai_analysis.get('reasoning', ''),
448
+ confidence=ai_analysis.get('confidence')
449
+ )
450
+ sections.append(ai_section)
451
+
452
+ # Add patient message section if provided
453
+ if patient_message:
454
+ patient_section = self.format_patient_message_section(patient_message)
455
+ sections.append(patient_section)
456
+
457
+ # Add provider summary section if provided
458
+ if provider_summary:
459
+ summary_section = self.format_provider_summary_section(provider_summary)
460
+ sections.append(summary_section)
461
+
462
+ # Join sections with separators
463
+ if len(sections) > 1:
464
+ content = separators["section_break"].join(sections)
465
+ elif sections:
466
+ content = sections[0]
467
+ else:
468
+ content = "<div style='padding: 20px; text-align: center; color: #666;'>No content to display</div>"
469
+
470
+ return content
471
+
472
+ def _format_basic_ai_analysis(self, classification: str, indicators: List[str], reasoning: str, confidence: Optional[float] = None) -> str:
473
+ """Fallback basic formatting for AI analysis when enhancements are disabled."""
474
+ confidence_text = f" (Confidence: {int(confidence * 100)}%)" if confidence else ""
475
+ indicators_text = f"Indicators: {', '.join(indicators)}" if indicators else "No indicators"
476
+
477
+ return f"""
478
+ <div style='border: 1px solid #ccc; padding: 10px; margin: 5px 0;'>
479
+ <strong>AI Analysis - {classification} FLAG{confidence_text}</strong><br>
480
+ {indicators_text}<br>
481
+ Reasoning: {reasoning}
482
+ </div>
483
+ """
484
+
485
+ def _format_basic_patient_message(self, message: str) -> str:
486
+ """Fallback basic formatting for patient message when enhancements are disabled."""
487
+ return f"""
488
+ <div style='border: 1px solid #ccc; padding: 10px; margin: 5px 0;'>
489
+ <strong>Patient Message</strong><br>
490
+ {html.escape(message)}
491
+ </div>
492
+ """
493
+
494
+ def _format_basic_provider_summary(self, summary_data: ProviderSummary) -> str:
495
+ """Fallback basic formatting for provider summary when enhancements are disabled."""
496
+ return f"""
497
+ <div style='border: 1px solid #ccc; padding: 10px; margin: 5px 0;'>
498
+ <strong>Provider Summary</strong><br>
499
+ Patient: {summary_data.patient_name}<br>
500
+ Phone: {summary_data.patient_phone}<br>
501
+ Urgency: {summary_data.urgency_level}<br>
502
+ Situation: {summary_data.situation_description}
503
+ </div>
504
+ """
505
+
506
+ def update_config(self, new_config: EnhancedDisplayConfig) -> None:
507
+ """
508
+ Update the configuration for this display manager.
509
+
510
+ Args:
511
+ new_config: New configuration to use
512
+ """
513
+ self.config = new_config
514
+
515
+ def reload_config(self) -> None:
516
+ """Reload configuration from the config manager if available."""
517
+ if self.config_manager is not None:
518
+ self.config = self.config_manager.get_config()
519
+ else:
520
+ self.config = get_enhanced_display_config()
521
+
522
+ def is_enhanced_mode_enabled(self) -> bool:
523
+ """Check if enhanced display mode is enabled."""
524
+ return self.config.enabled
525
+
526
+ def get_css_styles(self) -> str:
527
+ """
528
+ Get CSS styles for enhanced display.
529
+
530
+ Returns:
531
+ CSS string for enhanced display styling
532
+ """
533
+ return self.config.generate_base_css()
src/interface/help_content.py CHANGED
@@ -82,42 +82,35 @@ The system continuously monitors all conversations and classifies them into thre
82
 
83
  ---
84
 
85
- ## 💬 Spiritual Care Message Generation
86
 
87
- ### What is the Spiritual Care Message?
88
- When a RED flag case is detected and you consent to a referral, the system automatically generates two types of summaries:
89
 
90
- **📋 Provider Summary (Structured)**
91
- - Complete patient contact information
92
- - Detailed distress indicators and classifications
93
- - Conversation context and triage exchanges
94
- - Medical background and goals
95
- - Recommended actions and urgency level
 
 
 
96
 
97
- **💬 Spiritual Care Message (Natural Language)**
98
- - Compassionate, empathetic message (50-75 words)
99
- - Patient's name, contact, and age at the start
100
- - Brief mention of relevant medical context
101
- - Focus on emotional and spiritual struggles
102
- - Urgency level and follow-up recommendations
103
- - Written in warm, professional tone
104
-
105
- ### Customizing Spiritual Care Messages
106
- You can customize how these messages are generated:
107
 
108
  **Edit the Prompt (🔧 Edit Prompts tab):**
109
  1. Select **💬 Spiritual Care Message** from dropdown
110
- 2. Modify the system prompt to adjust tone, length, or focus
111
  3. Test changes in your current session
112
  4. Promote to file if you want permanent changes
113
 
114
  **Choose the AI Model (⚙️ Model Settings tab):**
115
- 1. Select model for **💬 Spiritual Care Message**
116
- 2. Default: Claude Sonnet 4.5 (empathetic, natural language)
117
- 3. Alternative: Gemini models for different tone
118
  4. Changes apply to your session only
119
-
120
- **Choose Message Content (in Care Team Message tab):**
121
  You can selectively include or exclude information blocks:
122
  - ☑️ **Include Conversation Context (Chat & Triage)** (Default: ON) - Raw chat history and triage exchanges.
123
  - ☑️ **Include Distress Indicators** (Default: ON) - Detected signs of spiritual distress.
@@ -160,7 +153,7 @@ The **Edit Prompts** tab provides powerful capabilities for testing and optimizi
160
  - 📊 **Triage Response Evaluator** - Evaluates patient responses to triage questions
161
  - 🏥 **Medical Assistant** - Provides medical guidance and support
162
  - 🩺 **Soft Medical Triage** - Handles medical triage and assessment
163
- - 💬 **Spiritual Care Message** - Generates compassionate messages for spiritual care team
164
 
165
  ---
166
 
@@ -183,7 +176,7 @@ Configure which AI models are used for different tasks:
183
  - **Triage Response Evaluator** - Response analysis (default: Gemini 2.5 Flash)
184
  - **Medical Assistant** - Medical guidance (default: Claude Sonnet 4.5)
185
  - **Soft Medical Triage** - Medical assessment (default: Claude Sonnet 4.5)
186
- - **Spiritual Care Message** - Compassionate message generation (default: Claude Sonnet 4.5)
187
 
188
  **Session Scope:** Model changes apply only to your current browser session.
189
 
 
82
 
83
  ---
84
 
85
+ ## 💬 Medical Brain Compatible Summary Generation
86
 
87
+ ### What is the Medical Brain Compatible Summary?
88
+ When a RED flag case is detected and you consent to a referral, the system automatically generates a Medical Brain compatible summary:
89
 
90
+ **📋 Medical Brain Compatible Summary (LLM-Generated)**
91
+ - Single coherent paragraph format
92
+ - Demographic information (name, age, gender)
93
+ - Medical history and conditions
94
+ - Spiritual concerns and indicators
95
+ - Classification and consent status
96
+ - Contact information
97
+ - Patient quote (actual words from conversation)
98
+ - Written in professional, clinical tone
99
 
100
+ ### Customizing Medical Brain Summaries
101
+ You can customize how these summaries are generated:
 
 
 
 
 
 
 
 
102
 
103
  **Edit the Prompt (🔧 Edit Prompts tab):**
104
  1. Select **💬 Spiritual Care Message** from dropdown
105
+ 2. Modify the system prompt to adjust format, content, or focus
106
  3. Test changes in your current session
107
  4. Promote to file if you want permanent changes
108
 
109
  **Choose the AI Model (⚙️ Model Settings tab):**
110
+ 1. Select model for **💬 Medical Brain Summary Generator**
111
+ 2. Default: Claude Sonnet 4.5 (structured, professional language)
112
+ 3. Alternative: Gemini models for different formatting style
113
  4. Changes apply to your session only
 
 
114
  You can selectively include or exclude information blocks:
115
  - ☑️ **Include Conversation Context (Chat & Triage)** (Default: ON) - Raw chat history and triage exchanges.
116
  - ☑️ **Include Distress Indicators** (Default: ON) - Detected signs of spiritual distress.
 
153
  - 📊 **Triage Response Evaluator** - Evaluates patient responses to triage questions
154
  - 🏥 **Medical Assistant** - Provides medical guidance and support
155
  - 🩺 **Soft Medical Triage** - Handles medical triage and assessment
156
+ - 💬 **Medical Brain Summary Generator** - Generates Medical Brain compatible summaries
157
 
158
  ---
159
 
 
176
  - **Triage Response Evaluator** - Response analysis (default: Gemini 2.5 Flash)
177
  - **Medical Assistant** - Medical guidance (default: Claude Sonnet 4.5)
178
  - **Soft Medical Triage** - Medical assessment (default: Claude Sonnet 4.5)
179
+ - **Medical Brain Summary Generator** - Medical Brain compatible summary generation (default: Claude Sonnet 4.5)
180
 
181
  **Session Scope:** Model changes apply only to your current browser session.
182
 
src/interface/provider_summary_formatter.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # provider_summary_formatter.py
2
+ """
3
+ Provider Summary Formatter for Enhanced UI Display.
4
+
5
+ This module provides formatting for provider summaries as coherent paragraphs
6
+ according to the requirements for improved readability and consistency.
7
+
8
+ Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8
9
+ """
10
+
11
+ from typing import List, Optional, Dict, Any
12
+ from dataclasses import dataclass
13
+
14
+ from src.core.provider_summary_generator import ProviderSummary
15
+
16
+
17
+ @dataclass
18
+ class PatientData:
19
+ """Patient data for summary formatting."""
20
+ name: str
21
+ age: int
22
+ gender: str
23
+ phone: str
24
+ medical_history: List[str]
25
+ expressed_concerns: List[str]
26
+ patient_input: str
27
+
28
+
29
+ @dataclass
30
+ class ClassificationData:
31
+ """Classification data for summary formatting."""
32
+ classification: str # RED/YELLOW/GREEN
33
+ spiritual_concern_type: str
34
+ consent_given: bool
35
+
36
+
37
+ class ProviderSummaryFormatter:
38
+ """
39
+ Provider Summary Formatter for coherent paragraph generation.
40
+
41
+ Formats provider summaries as single coherent paragraphs following
42
+ the Medical Brain style as specified in requirements 2.1-2.8.
43
+ """
44
+
45
+ def __init__(self):
46
+ """Initialize the provider summary formatter."""
47
+ pass
48
+
49
+ def format_coherent_paragraph(
50
+ self,
51
+ patient_data: PatientData,
52
+ classification_data: ClassificationData
53
+ ) -> str:
54
+ """
55
+ Format provider summary as a single coherent paragraph.
56
+
57
+ Args:
58
+ patient_data: Patient information and context
59
+ classification_data: Classification and consent information
60
+
61
+ Returns:
62
+ Single coherent paragraph formatted for provider summary
63
+
64
+ Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8
65
+ """
66
+ # Build demographic section (Requirement 2.2)
67
+ demographic_section = self.build_demographic_section(patient_data)
68
+
69
+ # Build medical history section (Requirement 2.3)
70
+ medical_section = self.build_medical_history_section(patient_data.medical_history)
71
+
72
+ # Build spiritual concerns section (Requirement 2.4, 2.5)
73
+ concerns_section = self.build_spiritual_concerns_section(
74
+ patient_data.expressed_concerns,
75
+ classification_data.spiritual_concern_type,
76
+ classification_data.classification
77
+ )
78
+
79
+ # Build consent and contact section (Requirement 2.6, 2.7)
80
+ contact_section = self.build_contact_section(
81
+ patient_data.phone,
82
+ classification_data.consent_given
83
+ )
84
+
85
+ # Combine all sections into coherent paragraph (Requirement 2.1)
86
+ paragraph_parts = [
87
+ demographic_section,
88
+ medical_section,
89
+ concerns_section,
90
+ contact_section
91
+ ]
92
+
93
+ # Filter out empty parts and join with appropriate connectors
94
+ non_empty_parts = [part for part in paragraph_parts if part.strip()]
95
+
96
+ if not non_empty_parts:
97
+ return "No summary information available."
98
+
99
+ # Join parts with appropriate connectors for flow
100
+ coherent_paragraph = ". ".join(non_empty_parts)
101
+
102
+ # Ensure proper sentence ending
103
+ if not coherent_paragraph.endswith('.'):
104
+ coherent_paragraph += '.'
105
+
106
+ return coherent_paragraph
107
+
108
+ def build_demographic_section(self, patient: PatientData) -> str:
109
+ """
110
+ Build demographic information section.
111
+
112
+ Args:
113
+ patient: Patient data containing demographic information
114
+
115
+ Returns:
116
+ Formatted demographic section string
117
+
118
+ Requirements: 2.2
119
+ """
120
+ # Format: "[patient name] is a X-year-old [gender]"
121
+ gender_text = patient.gender.lower() if patient.gender else "individual"
122
+
123
+ return f"{patient.name} is a {patient.age}-year-old {gender_text}"
124
+
125
+ def build_medical_history_section(self, medical_history: List[str]) -> str:
126
+ """
127
+ Build medical history section.
128
+
129
+ Args:
130
+ medical_history: List of medical conditions/history items
131
+
132
+ Returns:
133
+ Formatted medical history section string
134
+
135
+ Requirements: 2.3
136
+ """
137
+ if not medical_history:
138
+ return "with no significant medical history documented"
139
+
140
+ # Format: "with clinical history of X, Y, and Z"
141
+ if len(medical_history) == 1:
142
+ history_text = medical_history[0]
143
+ elif len(medical_history) == 2:
144
+ history_text = f"{medical_history[0]} and {medical_history[1]}"
145
+ else:
146
+ # Multiple items: "X, Y, and Z"
147
+ history_text = ", ".join(medical_history[:-1]) + f", and {medical_history[-1]}"
148
+
149
+ return f"with clinical history of {history_text}"
150
+
151
+ def build_spiritual_concerns_section(
152
+ self,
153
+ concerns: List[str],
154
+ concern_type: str,
155
+ classification: str
156
+ ) -> str:
157
+ """
158
+ Build spiritual concerns section.
159
+
160
+ Args:
161
+ concerns: List of expressed concerns
162
+ concern_type: Type of spiritual concern identified
163
+ classification: Classification level (RED/YELLOW/GREEN)
164
+
165
+ Returns:
166
+ Formatted spiritual concerns section string
167
+
168
+ Requirements: 2.4, 2.5
169
+ """
170
+ if not concerns:
171
+ concerns_text = "general distress"
172
+ elif len(concerns) == 1:
173
+ concerns_text = concerns[0]
174
+ elif len(concerns) == 2:
175
+ concerns_text = f"{concerns[0]} and {concerns[1]}"
176
+ else:
177
+ # Multiple concerns: "X, Y, and Z"
178
+ concerns_text = ", ".join(concerns[:-1]) + f", and {concerns[-1]}"
179
+
180
+ # Format: "The patient expressed X, Y, and Z, which may indicate [specific spiritual/emotional concern]"
181
+ concern_description = concern_type if concern_type else "spiritual or emotional distress"
182
+
183
+ concerns_section = f"The patient expressed {concerns_text}, which may indicate {concern_description}"
184
+
185
+ # Add classification information (Requirement 2.5)
186
+ flag_text = f"{classification} FLAG" if classification in ["RED", "YELLOW"] else "GREEN status"
187
+ classification_section = f", resulting in generation of a {flag_text}"
188
+
189
+ return concerns_section + classification_section
190
+
191
+ def build_contact_section(self, phone: str, consent_given: bool) -> str:
192
+ """
193
+ Build contact and consent section.
194
+
195
+ Args:
196
+ phone: Patient's phone number
197
+ consent_given: Whether patient gave consent for spiritual care contact
198
+
199
+ Returns:
200
+ Formatted contact section string
201
+
202
+ Requirements: 2.6, 2.7
203
+ """
204
+ sections = []
205
+
206
+ # Add consent information (Requirement 2.6)
207
+ if consent_given:
208
+ sections.append("The patient has given consent to be contacted by the spiritual care team")
209
+ else:
210
+ sections.append("The patient has not yet provided consent for spiritual care contact")
211
+
212
+ # Add contact information (Requirement 2.7)
213
+ if phone and phone.strip():
214
+ sections.append(f"The preferred contact number is {phone}")
215
+ else:
216
+ sections.append("No contact number is currently available")
217
+
218
+ return ". ".join(sections)
219
+
220
+ def add_patient_quote_section(self, quote: str) -> str:
221
+ """
222
+ Add patient quote section as separate line.
223
+
224
+ Args:
225
+ quote: Direct quote from patient input
226
+
227
+ Returns:
228
+ Formatted patient quote section
229
+
230
+ Requirements: 2.8
231
+ """
232
+ if not quote or not quote.strip():
233
+ return ""
234
+
235
+ # Format: "Patient reported: [patient input here]"
236
+ return f"Patient reported: \"{quote.strip()}\""
237
+
238
+ def format_enhanced_summary(
239
+ self,
240
+ patient_data: PatientData,
241
+ classification_data: ClassificationData
242
+ ) -> str:
243
+ """
244
+ Format complete enhanced summary with paragraph and quote.
245
+
246
+ Args:
247
+ patient_data: Patient information and context
248
+ classification_data: Classification and consent information
249
+
250
+ Returns:
251
+ Complete formatted summary with paragraph and quote sections
252
+
253
+ Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8
254
+ """
255
+ # Generate main coherent paragraph
256
+ main_paragraph = self.format_coherent_paragraph(patient_data, classification_data)
257
+
258
+ # Generate patient quote section (Requirement 2.8)
259
+ quote_section = self.add_patient_quote_section(patient_data.patient_input)
260
+
261
+ # Combine paragraph and quote
262
+ if quote_section:
263
+ return f"{main_paragraph}\n\n{quote_section}"
264
+ else:
265
+ return main_paragraph
266
+
267
+ def format_from_provider_summary(self, summary: ProviderSummary) -> str:
268
+ """
269
+ Format coherent paragraph from existing ProviderSummary object.
270
+
271
+ This method bridges the existing ProviderSummary structure with the new
272
+ coherent paragraph formatting requirements by using the enhanced method
273
+ in ProviderSummaryGenerator.
274
+
275
+ Args:
276
+ summary: Existing ProviderSummary object
277
+
278
+ Returns:
279
+ Formatted coherent paragraph
280
+
281
+ Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8
282
+ """
283
+ # Import here to avoid circular imports
284
+ from src.core.provider_summary_generator import ProviderSummaryGenerator
285
+
286
+ # Create a temporary generator instance to use the enhanced formatting
287
+ temp_generator = ProviderSummaryGenerator()
288
+
289
+ # Use the enhanced format_coherent_paragraph method
290
+ return temp_generator.format_coherent_paragraph(summary)
src/interface/simplified_gradio_app.py CHANGED
@@ -183,53 +183,32 @@ def create_simplified_interface():
183
  value="**Provider Summary Generated**\n\nA detailed summary has been generated for the spiritual care team. Use the Download button below to access the full summary."
184
  )
185
 
186
- # Tabs for different summary views
187
  with gr.Tabs():
188
- # Structured Summary Tab
189
- with gr.TabItem("📊 Structured Summary"):
190
- provider_summary_display = gr.HTML(value="")
191
-
192
- # LLM-Generated Message Tab
193
- with gr.TabItem("💬 Care Team Message"):
194
- gr.Markdown("*AI-generated compassionate message for spiritual care team*")
195
- spiritual_care_message = gr.Textbox(
196
  value="",
197
- lines=12,
198
- label="Message for Spiritual Care Team",
199
  interactive=False
200
  )
201
  with gr.Row():
202
- include_conversation_context = gr.Checkbox(
203
- value=True,
204
- label="Include Conversation Context (Chat & Triage)",
205
- interactive=True
206
- )
207
- include_situation_analysis = gr.Checkbox(
208
- value=False,
209
- label="Include Situation Analysis",
210
- interactive=True
211
- )
212
- include_distress_indicators = gr.Checkbox(
213
- value=True,
214
- label="Include Distress Indicators",
215
- interactive=True
216
- )
217
- include_patient_profile = gr.Checkbox(
218
- value=False,
219
- label="Include Patient Profile (Name, Contact)",
220
- interactive=True
221
- )
222
- with gr.Row():
223
- generate_message_btn = gr.Button(
224
- "🔄 Regenerate Message",
225
  size="sm",
226
  variant="secondary"
227
  )
228
- download_message_btn = gr.DownloadButton(
229
- "📥 Download Message",
230
  size="sm",
231
  variant="secondary"
232
  )
 
 
 
 
233
 
234
  with gr.Row():
235
  download_summary_btn = gr.DownloadButton(
@@ -384,7 +363,7 @@ def create_simplified_interface():
384
  interactive=True
385
  )
386
 
387
- gr.Markdown("### 💬 Spiritual Care Message")
388
  spiritual_care_message_model = gr.Dropdown(
389
  choices=[
390
  "claude-sonnet-4-5-20250929",
@@ -395,7 +374,7 @@ def create_simplified_interface():
395
  "gemini-3-flash-preview"
396
  ],
397
  value="claude-sonnet-4-5-20250929",
398
- label="Spiritual Care Message Generator",
399
  interactive=True
400
  )
401
 
@@ -465,6 +444,7 @@ def create_simplified_interface():
465
  <li>📊 Triage Evaluation</li>
466
  <li>🏥 Medical Assistant</li>
467
  <li>🩺 Soft Triage</li>
 
468
  </ul>
469
 
470
  <p><strong>Tips:</strong></p>
@@ -589,46 +569,46 @@ def create_simplified_interface():
589
  send_btn.click(
590
  chat_handlers.handle_message,
591
  inputs=[msg, chatbot, session_data],
592
- outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
593
  )
594
 
595
  msg.submit(
596
  chat_handlers.handle_message,
597
  inputs=[msg, chatbot, session_data],
598
- outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
599
  )
600
 
601
  # Clear chat
602
  clear_btn.click(
603
  chat_handlers.handle_clear,
604
  inputs=[session_data],
605
- outputs=[chatbot, status_box, session_data, provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
606
  )
607
 
608
  # Refresh status
609
  refresh_btn.click(
610
  stats_handlers.get_status,
611
  inputs=[session_data],
612
- outputs=[status_box, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
613
  )
614
 
615
  # Example buttons
616
  example_medical.click(
617
  lambda h, s: chat_handlers.send_example_with_stats("I am fine", h, s),
618
  inputs=[chatbot, session_data],
619
- outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
620
  )
621
 
622
  example_wellness.click(
623
  lambda h, s: chat_handlers.send_example_with_stats("I'm feeling stressed and overwhelmed lately", h, s),
624
  inputs=[chatbot, session_data],
625
- outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
626
  )
627
 
628
  example_help.click(
629
  lambda h, s: chat_handlers.send_example_with_stats("I am currently experiencing an emotional crisis", h, s),
630
  inputs=[chatbot, session_data],
631
- outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
632
  )
633
 
634
  # Conversation logging buttons
@@ -654,20 +634,20 @@ def create_simplified_interface():
654
  clear_summary_btn.click(
655
  stats_handlers.clear_provider_summary,
656
  inputs=[],
657
- outputs=[provider_summary_content, provider_summary_status, provider_summary_display, spiritual_care_message]
658
  )
659
 
660
- # Spiritual care message handlers
661
- generate_message_btn.click(
662
- stats_handlers.regenerate_spiritual_care_message,
663
- inputs=[session_data, include_conversation_context, include_situation_analysis, include_distress_indicators, include_patient_profile],
664
- outputs=[spiritual_care_message]
665
  )
666
 
667
- download_message_btn.click(
668
- stats_handlers.download_spiritual_care_message,
669
  inputs=[session_data],
670
- outputs=[download_message_btn]
671
  )
672
 
673
  # Conversation Verification events
 
183
  value="**Provider Summary Generated**\n\nA detailed summary has been generated for the spiritual care team. Use the Download button below to access the full summary."
184
  )
185
 
186
+ # Tabs for different summary views (Medical Brain Summary first by default)
187
  with gr.Tabs():
188
+ # Coherent Paragraph Tab (FIRST - Requirements 2.1-2.8) - Medical Brain compatibility
189
+ with gr.TabItem("📝 Medical Brain Summary"):
190
+ gr.Markdown("*Single paragraph format for Medical Brain compatibility - **Default View***")
191
+ coherent_summary_display = gr.Textbox(
 
 
 
 
192
  value="",
193
+ lines=8,
194
+ label="Medical Brain Compatible Summary",
195
  interactive=False
196
  )
197
  with gr.Row():
198
+ regenerate_coherent_btn = gr.Button(
199
+ "🔄 Regenerate Medical Brain Summary",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  size="sm",
201
  variant="secondary"
202
  )
203
+ download_coherent_btn = gr.DownloadButton(
204
+ "📥 Download Medical Brain Summary",
205
  size="sm",
206
  variant="secondary"
207
  )
208
+
209
+ # Structured Summary Tab (moved to second position)
210
+ with gr.TabItem("📊 Structured Summary"):
211
+ provider_summary_display = gr.HTML(value="")
212
 
213
  with gr.Row():
214
  download_summary_btn = gr.DownloadButton(
 
363
  interactive=True
364
  )
365
 
366
+ gr.Markdown("### 💬 Medical Brain Summary Generator")
367
  spiritual_care_message_model = gr.Dropdown(
368
  choices=[
369
  "claude-sonnet-4-5-20250929",
 
374
  "gemini-3-flash-preview"
375
  ],
376
  value="claude-sonnet-4-5-20250929",
377
+ label="Medical Brain Summary Generator (uses Spiritual Care Message prompt)",
378
  interactive=True
379
  )
380
 
 
444
  <li>📊 Triage Evaluation</li>
445
  <li>🏥 Medical Assistant</li>
446
  <li>🩺 Soft Triage</li>
447
+ <li>💬 Spiritual Care Message (used for Medical Brain Summary)</li>
448
  </ul>
449
 
450
  <p><strong>Tips:</strong></p>
 
569
  send_btn.click(
570
  chat_handlers.handle_message,
571
  inputs=[msg, chatbot, session_data],
572
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
573
  )
574
 
575
  msg.submit(
576
  chat_handlers.handle_message,
577
  inputs=[msg, chatbot, session_data],
578
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
579
  )
580
 
581
  # Clear chat
582
  clear_btn.click(
583
  chat_handlers.handle_clear,
584
  inputs=[session_data],
585
+ outputs=[chatbot, status_box, session_data, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
586
  )
587
 
588
  # Refresh status
589
  refresh_btn.click(
590
  stats_handlers.get_status,
591
  inputs=[session_data],
592
+ outputs=[status_box, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
593
  )
594
 
595
  # Example buttons
596
  example_medical.click(
597
  lambda h, s: chat_handlers.send_example_with_stats("I am fine", h, s),
598
  inputs=[chatbot, session_data],
599
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
600
  )
601
 
602
  example_wellness.click(
603
  lambda h, s: chat_handlers.send_example_with_stats("I'm feeling stressed and overwhelmed lately", h, s),
604
  inputs=[chatbot, session_data],
605
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
606
  )
607
 
608
  example_help.click(
609
  lambda h, s: chat_handlers.send_example_with_stats("I am currently experiencing an emotional crisis", h, s),
610
  inputs=[chatbot, session_data],
611
+ outputs=[chatbot, status_box, session_data, msg, conversation_stats, provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
612
  )
613
 
614
  # Conversation logging buttons
 
634
  clear_summary_btn.click(
635
  stats_handlers.clear_provider_summary,
636
  inputs=[],
637
+ outputs=[provider_summary_content, provider_summary_status, provider_summary_display, coherent_summary_display]
638
  )
639
 
640
+ # Coherent summary handlers (Medical Brain Summary - now first tab by default)
641
+ regenerate_coherent_btn.click(
642
+ stats_handlers.regenerate_coherent_summary,
643
+ inputs=[session_data],
644
+ outputs=[coherent_summary_display]
645
  )
646
 
647
+ download_coherent_btn.click(
648
+ stats_handlers.download_coherent_summary,
649
  inputs=[session_data],
650
+ outputs=[download_coherent_btn]
651
  )
652
 
653
  # Conversation Verification events
src/interface/stats_handlers.py CHANGED
@@ -78,22 +78,17 @@ def get_status(session: SimplifiedSessionData):
78
  show_provider_panel = last_summary is not None
79
 
80
  provider_summary_text = ""
81
- spiritual_care_msg = ""
82
 
83
  if last_summary:
84
  provider_summary_text = session.app_instance.provider_summary_generator.format_for_display(last_summary)
85
 
86
- # Generate spiritual care message
87
  try:
88
- spiritual_care_msg = session.app_instance.generate_spiritual_care_message(
89
- language="English",
90
- session_id=session.session_id
91
- )
92
- if not spiritual_care_msg:
93
- spiritual_care_msg = ""
94
  except Exception as e:
95
- print(f"Error generating spiritual care message in get_status: {e}")
96
- spiritual_care_msg = ""
97
 
98
  if provider_summary_text:
99
  import html
@@ -118,7 +113,7 @@ Use the **Download Summary** button below to access the complete provider summar
118
  gr.update(visible=show_provider_panel),
119
  status_msg,
120
  html_content,
121
- spiritual_care_msg
122
  )
123
 
124
  def download_provider_summary(session: SimplifiedSessionData):
@@ -158,17 +153,11 @@ def clear_provider_summary():
158
  gr.update(visible=False),
159
  "No provider summary available",
160
  "",
161
- ""
162
  )
163
 
164
- def regenerate_spiritual_care_message(
165
- session: SimplifiedSessionData,
166
- include_conversation: bool = True,
167
- include_situation: bool = False,
168
- include_indicators: bool = False,
169
- include_profile: bool = False
170
- ):
171
- """Regenerate LLM-based spiritual care message."""
172
  if session is None:
173
  return ""
174
 
@@ -177,48 +166,35 @@ def regenerate_spiritual_care_message(
177
  return ""
178
 
179
  try:
180
- msg = session.app_instance.generate_spiritual_care_message(
181
- language="English",
182
- session_id=session.session_id,
183
- include_conversation_context=include_conversation,
184
- include_situation_analysis=include_situation,
185
- include_distress_indicators=include_indicators,
186
- include_patient_profile=include_profile
187
- )
188
- return msg or "Error generating message"
189
  except Exception as e:
190
  return f"Error: {str(e)}"
191
 
192
- def download_spiritual_care_message(session: SimplifiedSessionData):
193
- """Download spiritual care message as text file."""
194
  if session is None:
195
  return None
196
 
197
- # We need to get the current message text from the UI state, but typically
198
- # in Gradio we return the file path. Since we don't have the text passed here,
199
- # we'll regenerate the last one or we'd need the text passed in.
200
- # Looking at original code, it might regenerate or access session state.
201
- # ORIGINAL CODE logic check needed.
202
-
203
- # Let's assume for now we regenerate it or fetch from session if stored.
204
- # Actually, let's regenerate for consistency with current context.
205
-
206
- msg = regenerate_spiritual_care_message(session)
207
- if not msg:
208
  return None
209
 
210
  try:
211
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
212
  patient_name = session.app_instance.patient_info.get('name', 'Patient').replace(" ", "_")
213
- filename = f"spiritual_care_message_{patient_name}_{timestamp}.txt"
 
 
 
214
 
215
  path = os.path.join(os.getcwd(), "exports", filename)
216
  os.makedirs(os.path.dirname(path), exist_ok=True)
217
 
218
  with open(path, "w", encoding="utf-8") as f:
219
- f.write(msg)
220
 
221
  return path
222
  except Exception as e:
223
- print(f"Error downloading spiritual care message: {e}")
224
  return None
 
78
  show_provider_panel = last_summary is not None
79
 
80
  provider_summary_text = ""
81
+ coherent_summary_text = ""
82
 
83
  if last_summary:
84
  provider_summary_text = session.app_instance.provider_summary_generator.format_for_display(last_summary)
85
 
86
+ # Generate medical brain summary (NEW - Requirements 2.1-2.8)
87
  try:
88
+ coherent_summary_text = session.app_instance.provider_summary_generator.format_coherent_paragraph(last_summary)
 
 
 
 
 
89
  except Exception as e:
90
+ print(f"Error generating coherent summary in get_status: {e}")
91
+ coherent_summary_text = ""
92
 
93
  if provider_summary_text:
94
  import html
 
113
  gr.update(visible=show_provider_panel),
114
  status_msg,
115
  html_content,
116
+ coherent_summary_text # NEW coherent summary
117
  )
118
 
119
  def download_provider_summary(session: SimplifiedSessionData):
 
153
  gr.update(visible=False),
154
  "No provider summary available",
155
  "",
156
+ "" # NEW coherent summary
157
  )
158
 
159
+ def regenerate_coherent_summary(session: SimplifiedSessionData):
160
+ """Regenerate medical brain summary."""
 
 
 
 
 
 
161
  if session is None:
162
  return ""
163
 
 
166
  return ""
167
 
168
  try:
169
+ coherent_text = session.app_instance.provider_summary_generator.format_coherent_paragraph(last_summary)
170
+ return coherent_text or "Error generating coherent summary"
 
 
 
 
 
 
 
171
  except Exception as e:
172
  return f"Error: {str(e)}"
173
 
174
+ def download_coherent_summary(session: SimplifiedSessionData):
175
+ """Download coherent summary as text file."""
176
  if session is None:
177
  return None
178
 
179
+ last_summary = session.app_instance.get_last_provider_summary()
180
+ if not last_summary:
 
 
 
 
 
 
 
 
 
181
  return None
182
 
183
  try:
184
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
185
  patient_name = session.app_instance.patient_info.get('name', 'Patient').replace(" ", "_")
186
+ filename = f"coherent_summary_{patient_name}_{timestamp}.txt"
187
+
188
+ # Generate coherent summary
189
+ coherent_text = session.app_instance.provider_summary_generator.format_coherent_paragraph(last_summary)
190
 
191
  path = os.path.join(os.getcwd(), "exports", filename)
192
  os.makedirs(os.path.dirname(path), exist_ok=True)
193
 
194
  with open(path, "w", encoding="utf-8") as f:
195
+ f.write(coherent_text)
196
 
197
  return path
198
  except Exception as e:
199
+ print(f"Error downloading coherent summary: {e}")
200
  return None
src/interface/visual_separation_manager.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # visual_separation_manager.py
2
+ """
3
+ Visual Separation Manager for Enhanced UI Display.
4
+
5
+ This module provides visual separation and styling management for different
6
+ content types in the enhanced results display system.
7
+
8
+ Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
9
+ """
10
+
11
+ from typing import Dict, List, Optional
12
+ from dataclasses import dataclass
13
+ from enum import Enum
14
+
15
+
16
+ class ContentType(Enum):
17
+ """Types of content for visual styling."""
18
+ AI_ANALYSIS = "ai_analysis"
19
+ PATIENT_MESSAGE = "patient_message"
20
+ PROVIDER_SUMMARY = "provider_summary"
21
+ SECTION_SEPARATOR = "section_separator"
22
+ CONTENT_DIVIDER = "content_divider"
23
+
24
+
25
+ @dataclass
26
+ class VisualStyle:
27
+ """Visual styling configuration for content types."""
28
+ background_color: str
29
+ border_color: str
30
+ border_width: str = "2px"
31
+ border_radius: str = "8px"
32
+ padding: str = "15px"
33
+ margin: str = "10px 0"
34
+ text_color: str = "#333333"
35
+ icon_color: str = "#666666"
36
+
37
+
38
+ class VisualSeparationManager:
39
+ """
40
+ Visual Separation Manager for enhanced content display.
41
+
42
+ Provides consistent visual styling and separation for different types
43
+ of content in the enhanced results display system.
44
+
45
+ Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
46
+ """
47
+
48
+ def __init__(self):
49
+ """Initialize the visual separation manager with default styles."""
50
+ self._initialize_default_styles()
51
+
52
+ def _initialize_default_styles(self) -> None:
53
+ """Initialize default visual styles for different content types."""
54
+ self.styles = {
55
+ ContentType.AI_ANALYSIS: VisualStyle(
56
+ background_color="#fafafa",
57
+ border_color="#6c757d",
58
+ text_color="#333333",
59
+ icon_color="#6c757d"
60
+ ),
61
+ ContentType.PATIENT_MESSAGE: VisualStyle(
62
+ background_color="#f0f7ff",
63
+ border_color="#4a90e2",
64
+ text_color="#333333",
65
+ icon_color="#4a90e2"
66
+ ),
67
+ ContentType.PROVIDER_SUMMARY: VisualStyle(
68
+ background_color="#fff8f0",
69
+ border_color="#dc3545",
70
+ text_color="#333333",
71
+ icon_color="#dc3545"
72
+ )
73
+ }
74
+
75
+ # Classification-specific colors
76
+ self.classification_colors = {
77
+ "RED": "#dc3545",
78
+ "YELLOW": "#ffc107",
79
+ "GREEN": "#28a745"
80
+ }
81
+
82
+ # Urgency-specific colors
83
+ self.urgency_colors = {
84
+ "IMMEDIATE": "#dc3545",
85
+ "URGENT": "#fd7e14",
86
+ "STANDARD": "#28a745"
87
+ }
88
+
89
+ def create_ai_analysis_styling(self) -> Dict[str, str]:
90
+ """
91
+ Create styling configuration for AI analysis sections.
92
+
93
+ Returns:
94
+ Dictionary of CSS styling properties
95
+
96
+ Requirements: 7.1, 7.3
97
+ """
98
+ style = self.styles[ContentType.AI_ANALYSIS]
99
+
100
+ return {
101
+ "container": f"""
102
+ border: {style.border_width} solid {style.border_color};
103
+ border-radius: {style.border_radius};
104
+ padding: {style.padding};
105
+ margin: {style.margin};
106
+ background-color: {style.background_color};
107
+ """,
108
+ "header": f"""
109
+ display: flex;
110
+ align-items: center;
111
+ margin-bottom: 10px;
112
+ color: {style.text_color};
113
+ font-weight: bold;
114
+ """,
115
+ "icon": f"""
116
+ font-size: 1.2em;
117
+ margin-right: 8px;
118
+ color: {style.icon_color};
119
+ """,
120
+ "content": f"""
121
+ color: {style.text_color};
122
+ line-height: 1.4;
123
+ """
124
+ }
125
+
126
+ def create_patient_message_styling(self) -> Dict[str, str]:
127
+ """
128
+ Create styling configuration for patient message sections.
129
+
130
+ Returns:
131
+ Dictionary of CSS styling properties
132
+
133
+ Requirements: 7.2, 7.3
134
+ """
135
+ style = self.styles[ContentType.PATIENT_MESSAGE]
136
+
137
+ return {
138
+ "container": f"""
139
+ border: {style.border_width} solid {style.border_color};
140
+ border-radius: {style.border_radius};
141
+ padding: {style.padding};
142
+ margin: {style.margin};
143
+ background-color: {style.background_color};
144
+ """,
145
+ "header": f"""
146
+ display: flex;
147
+ align-items: center;
148
+ margin-bottom: 10px;
149
+ color: {style.border_color};
150
+ font-weight: bold;
151
+ """,
152
+ "icon": f"""
153
+ font-size: 1.2em;
154
+ margin-right: 8px;
155
+ color: {style.icon_color};
156
+ """,
157
+ "message_box": f"""
158
+ padding: 8px;
159
+ background-color: white;
160
+ border-radius: 4px;
161
+ border-left: 4px solid {style.border_color};
162
+ color: {style.text_color};
163
+ """
164
+ }
165
+
166
+ def create_provider_summary_styling(self) -> Dict[str, str]:
167
+ """
168
+ Create styling configuration for provider summary sections.
169
+
170
+ Returns:
171
+ Dictionary of CSS styling properties
172
+
173
+ Requirements: 7.1, 7.2, 7.3
174
+ """
175
+ style = self.styles[ContentType.PROVIDER_SUMMARY]
176
+
177
+ return {
178
+ "container": f"""
179
+ border: {style.border_width} solid {style.border_color};
180
+ border-radius: {style.border_radius};
181
+ padding: {style.padding};
182
+ margin: {style.margin};
183
+ background-color: {style.background_color};
184
+ """,
185
+ "header": f"""
186
+ display: flex;
187
+ align-items: center;
188
+ margin-bottom: 10px;
189
+ color: {style.border_color};
190
+ font-weight: bold;
191
+ """,
192
+ "icon": f"""
193
+ font-size: 1.2em;
194
+ margin-right: 8px;
195
+ color: {style.icon_color};
196
+ """,
197
+ "info_box": f"""
198
+ margin: 10px 0;
199
+ padding: 8px;
200
+ background-color: #f8f9fa;
201
+ border-radius: 4px;
202
+ """,
203
+ "urgency_box": f"""
204
+ margin: 5px 0;
205
+ padding: 8px;
206
+ background-color: #fff3cd;
207
+ border-radius: 4px;
208
+ border-left: 4px solid {style.border_color};
209
+ """
210
+ }
211
+
212
+ def generate_section_separators(self) -> Dict[str, str]:
213
+ """
214
+ Generate different types of section separators.
215
+
216
+ Returns:
217
+ Dictionary of separator HTML strings
218
+
219
+ Requirements: 7.4, 7.5
220
+ """
221
+ return {
222
+ "light": """
223
+ <div style='margin: 15px 0; border-top: 1px solid #e0e0e0;'></div>
224
+ """,
225
+
226
+ "medium": """
227
+ <div style='margin: 20px 0; text-align: center;'>
228
+ <hr style='border: none; border-top: 2px solid #d0d0d0; width: 80%; margin: 0 auto;'>
229
+ </div>
230
+ """,
231
+
232
+ "heavy": """
233
+ <div style='margin: 30px 0; text-align: center;'>
234
+ <div style='display: inline-block; padding: 8px 16px; background-color: #f0f0f0; border-radius: 20px; color: #666; font-size: 0.8em;'>
235
+ ● ● ●
236
+ </div>
237
+ </div>
238
+ """,
239
+
240
+ "section_break": """
241
+ <div style='margin: 25px 0; text-align: center;'>
242
+ <hr style='border: none; border-top: 3px solid #ccc; width: 60%; margin: 0 auto;'>
243
+ <div style='margin: 10px 0; color: #666; font-size: 0.9em; font-weight: bold;'>---</div>
244
+ </div>
245
+ """
246
+ }
247
+
248
+ def apply_consistent_formatting(self, sections: List[Dict[str, str]]) -> str:
249
+ """
250
+ Apply consistent formatting to multiple content sections.
251
+
252
+ Args:
253
+ sections: List of section dictionaries with 'type' and 'content' keys
254
+
255
+ Returns:
256
+ Combined HTML with consistent formatting and separation
257
+
258
+ Requirements: 7.1, 7.2, 7.4, 7.5
259
+ """
260
+ if not sections:
261
+ return "<div style='padding: 20px; text-align: center; color: #666;'>No content to display</div>"
262
+
263
+ formatted_sections = []
264
+ separators = self.generate_section_separators()
265
+
266
+ for i, section in enumerate(sections):
267
+ section_type = section.get('type', 'unknown')
268
+ content = section.get('content', '')
269
+
270
+ # Apply appropriate styling based on section type
271
+ if section_type == 'ai_analysis':
272
+ styling = self.create_ai_analysis_styling()
273
+ elif section_type == 'patient_message':
274
+ styling = self.create_patient_message_styling()
275
+ elif section_type == 'provider_summary':
276
+ styling = self.create_provider_summary_styling()
277
+ else:
278
+ # Default styling for unknown types
279
+ styling = {
280
+ "container": "padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px;"
281
+ }
282
+
283
+ # Wrap content in styled container
284
+ formatted_content = f"""
285
+ <div style='{styling.get("container", "")}'>
286
+ {content}
287
+ </div>
288
+ """
289
+
290
+ formatted_sections.append(formatted_content)
291
+
292
+ # Add separator between sections (but not after the last one)
293
+ if i < len(sections) - 1:
294
+ formatted_sections.append(separators["section_break"])
295
+
296
+ return "".join(formatted_sections)
297
+
298
+ def get_classification_styling(self, classification: str) -> Dict[str, str]:
299
+ """
300
+ Get styling specific to a classification level.
301
+
302
+ Args:
303
+ classification: The classification level (RED/YELLOW/GREEN)
304
+
305
+ Returns:
306
+ Dictionary of classification-specific styling
307
+
308
+ Requirements: 7.3
309
+ """
310
+ color = self.classification_colors.get(classification.upper(), "#666666")
311
+
312
+ return {
313
+ "badge": f"""
314
+ display: inline-block;
315
+ padding: 4px 8px;
316
+ background-color: {color};
317
+ color: white;
318
+ border-radius: 12px;
319
+ font-size: 0.8em;
320
+ font-weight: bold;
321
+ text-transform: uppercase;
322
+ """,
323
+ "border": f"""
324
+ border-color: {color};
325
+ """,
326
+ "text": f"""
327
+ color: {color};
328
+ """,
329
+ "background": f"""
330
+ background-color: {color}15;
331
+ """
332
+ }
333
+
334
+ def get_urgency_styling(self, urgency_level: str) -> Dict[str, str]:
335
+ """
336
+ Get styling specific to an urgency level.
337
+
338
+ Args:
339
+ urgency_level: The urgency level (IMMEDIATE/URGENT/STANDARD)
340
+
341
+ Returns:
342
+ Dictionary of urgency-specific styling
343
+
344
+ Requirements: 7.3
345
+ """
346
+ color = self.urgency_colors.get(urgency_level.upper(), "#666666")
347
+
348
+ return {
349
+ "badge": f"""
350
+ display: inline-block;
351
+ padding: 4px 8px;
352
+ background-color: {color};
353
+ color: white;
354
+ border-radius: 12px;
355
+ font-size: 0.8em;
356
+ font-weight: bold;
357
+ text-transform: uppercase;
358
+ """,
359
+ "border": f"""
360
+ border-color: {color};
361
+ """,
362
+ "text": f"""
363
+ color: {color};
364
+ """,
365
+ "accent": f"""
366
+ border-left: 4px solid {color};
367
+ """
368
+ }
369
+
370
+ def create_icon_styling(self, content_type: ContentType) -> str:
371
+ """
372
+ Create icon styling for different content types.
373
+
374
+ Args:
375
+ content_type: The type of content for icon styling
376
+
377
+ Returns:
378
+ CSS styling string for icons
379
+
380
+ Requirements: 7.3, 7.5
381
+ """
382
+ base_style = "font-size: 1.2em; margin-right: 8px;"
383
+
384
+ if content_type in self.styles:
385
+ color = self.styles[content_type].icon_color
386
+ return f"{base_style} color: {color};"
387
+
388
+ return f"{base_style} color: #666666;"
tests/integration/test_enhanced_verification_integration.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Integration tests for enhanced verification system.
4
+
5
+ Tests the integration between enhanced display formats, verification system,
6
+ and CSV export functionality.
7
+
8
+ Requirements: 8.2, 8.3, 8.4, 8.5
9
+ """
10
+
11
+ import pytest
12
+ import tempfile
13
+ import os
14
+ from datetime import datetime
15
+ from unittest.mock import Mock
16
+
17
+ from src.core.conversation_logger import ConversationLogger, ConversationEntry
18
+ from src.core.conversation_verification import (
19
+ EnhancedConversationVerificationManager,
20
+ EnhancedVerificationSession,
21
+ EnhancedVerificationRecord,
22
+ VerificationFeedback
23
+ )
24
+ from src.core.verification_exporter import EnhancedVerificationExporter
25
+ from src.core.spiritual_state import SpiritualState, SpiritualAssessment
26
+ from src.core.provider_summary_generator import ProviderSummary
27
+
28
+
29
+ class TestEnhancedVerificationIntegration:
30
+ """Test enhanced verification system integration."""
31
+
32
+ def setup_method(self):
33
+ """Set up test environment."""
34
+ self.temp_dir = tempfile.mkdtemp()
35
+ self.verification_manager = EnhancedConversationVerificationManager(self.temp_dir)
36
+ self.exporter = EnhancedVerificationExporter(self.temp_dir)
37
+
38
+ def teardown_method(self):
39
+ """Clean up test environment."""
40
+ import shutil
41
+ shutil.rmtree(self.temp_dir, ignore_errors=True)
42
+
43
+ def create_test_conversation_logger(self) -> ConversationLogger:
44
+ """Create a test conversation logger with sample data."""
45
+ logger = ConversationLogger(patient_name="Test Patient")
46
+
47
+ # Add sample conversation entries
48
+ assessments = [
49
+ SpiritualAssessment(
50
+ state=SpiritualState.RED,
51
+ confidence=0.85,
52
+ indicators=["loss of meaning", "spiritual distress", "questioning faith"],
53
+ reasoning="Patient expressing significant spiritual concerns and loss of meaning"
54
+ ),
55
+ SpiritualAssessment(
56
+ state=SpiritualState.YELLOW,
57
+ confidence=0.65,
58
+ indicators=["mild anxiety", "uncertainty"],
59
+ reasoning="Patient showing some concern but not severe distress"
60
+ ),
61
+ SpiritualAssessment(
62
+ state=SpiritualState.GREEN,
63
+ confidence=0.90,
64
+ indicators=[],
65
+ reasoning="Patient appears stable and content"
66
+ )
67
+ ]
68
+
69
+ messages = [
70
+ ("I've been struggling with the meaning of my illness", "I understand this is a difficult time..."),
71
+ ("Sometimes I wonder if there's any point to all this", "Your feelings are completely valid..."),
72
+ ("I'm feeling a bit better today", "That's wonderful to hear...")
73
+ ]
74
+
75
+ for (user_msg, assistant_msg), assessment in zip(messages, assessments):
76
+ logger.log_exchange(user_msg, assistant_msg, assessment)
77
+
78
+ return logger
79
+
80
+ def test_enhanced_verification_session_creation(self):
81
+ """Test creating enhanced verification session with new formats."""
82
+ # Create test conversation
83
+ logger = self.create_test_conversation_logger()
84
+
85
+ # Create enhanced verification session
86
+ session = self.verification_manager.create_verification_session(
87
+ logger,
88
+ verifier_name="Test Verifier",
89
+ enable_enhanced_formats=True
90
+ )
91
+
92
+ # Verify session properties
93
+ assert isinstance(session, EnhancedVerificationSession)
94
+ assert session.enhanced_format_enabled is True
95
+ assert len(session.verification_records) == 3
96
+
97
+ # Verify enhanced records
98
+ for record in session.verification_records:
99
+ assert isinstance(record, EnhancedVerificationRecord)
100
+ assert record.enhanced_display_format is not None
101
+ assert record.visual_sections is not None
102
+ assert len(record.visual_sections) >= 2 # AI analysis + patient message
103
+
104
+ def test_enhanced_csv_export_with_new_data(self):
105
+ """Test CSV export includes enhanced format data."""
106
+ # Create and verify session
107
+ logger = self.create_test_conversation_logger()
108
+ session = self.verification_manager.create_verification_session(
109
+ logger,
110
+ enable_enhanced_formats=True
111
+ )
112
+
113
+ # Add some verification feedback
114
+ feedback = VerificationFeedback(
115
+ exchange_id=session.verification_records[0].exchange_id,
116
+ is_correct=True
117
+ )
118
+ self.verification_manager.submit_exchange_verification(
119
+ session.session_id,
120
+ session.verification_records[0].exchange_id,
121
+ feedback
122
+ )
123
+
124
+ # Export to CSV with enhanced data
125
+ csv_path = self.exporter.export_session_to_csv(session, include_enhanced_data=True)
126
+
127
+ # Verify CSV file exists and contains enhanced data
128
+ assert os.path.exists(csv_path)
129
+
130
+ with open(csv_path, 'r', encoding='utf-8') as f:
131
+ content = f.read()
132
+
133
+ # Check for enhanced format columns
134
+ assert 'has_enhanced_display' in content
135
+ assert 'visual_sections_count' in content
136
+ assert 'enhanced_indicators_count' in content
137
+ assert 'enhanced_display_preview' in content
138
+
139
+ # Check for enhanced session metadata
140
+ assert 'Enhanced Format: True' in content
141
+
142
+ def test_enhanced_summary_report_generation(self):
143
+ """Test enhanced summary report includes new format statistics."""
144
+ # Create session with enhanced formats
145
+ logger = self.create_test_conversation_logger()
146
+ session = self.verification_manager.create_verification_session(
147
+ logger,
148
+ enable_enhanced_formats=True
149
+ )
150
+
151
+ # Generate enhanced summary report
152
+ report_path = self.exporter.export_enhanced_summary_report(session)
153
+
154
+ # Verify report file exists
155
+ assert os.path.exists(report_path)
156
+
157
+ with open(report_path, 'r', encoding='utf-8') as f:
158
+ content = f.read()
159
+
160
+ # Check for enhanced format statistics
161
+ assert 'ENHANCED FORMAT STATISTICS' in content
162
+ assert 'Records with Enhanced Display:' in content
163
+ assert 'Enhanced Display Coverage:' in content
164
+ assert 'Enhanced Format Enabled: True' in content
165
+
166
+ def test_verification_compatibility_with_existing_system(self):
167
+ """Test that enhanced verification is compatible with existing verification system."""
168
+ # Create session without enhanced formats (legacy mode)
169
+ logger = self.create_test_conversation_logger()
170
+ legacy_session = self.verification_manager.create_verification_session(
171
+ logger,
172
+ enable_enhanced_formats=False
173
+ )
174
+
175
+ # Verify it still works with basic verification
176
+ feedback = VerificationFeedback(
177
+ exchange_id=legacy_session.verification_records[0].exchange_id,
178
+ is_correct=False,
179
+ correct_classification="YELLOW",
180
+ correction_reason="Should be yellow not red"
181
+ )
182
+
183
+ success = self.verification_manager.submit_exchange_verification(
184
+ legacy_session.session_id,
185
+ legacy_session.verification_records[0].exchange_id,
186
+ feedback
187
+ )
188
+
189
+ assert success is True
190
+
191
+ # Verify statistics still work
192
+ stats = self.verification_manager.get_session_statistics(legacy_session.session_id)
193
+ assert stats is not None
194
+ assert 'enhanced_format_enabled' in stats
195
+ assert stats['enhanced_format_enabled'] is False
196
+
197
+ def test_provider_summary_integration_with_verification(self):
198
+ """Test that provider summaries are properly integrated with verification."""
199
+ # Create session with provider summary data
200
+ logger = self.create_test_conversation_logger()
201
+ session = self.verification_manager.create_verification_session(
202
+ logger,
203
+ enable_enhanced_formats=True
204
+ )
205
+
206
+ # Add provider summary to a record
207
+ test_summary = ProviderSummary(
208
+ patient_name="Test Patient",
209
+ patient_phone="555-0123",
210
+ classification="RED",
211
+ confidence=0.85,
212
+ indicators=["loss of meaning", "spiritual distress"],
213
+ reasoning="Significant spiritual concerns detected",
214
+ urgency_level="IMMEDIATE",
215
+ severity_level="HIGH"
216
+ )
217
+
218
+ # Set provider summary on first record
219
+ session.verification_records[0].set_enhanced_formats(
220
+ provider_summary=test_summary
221
+ )
222
+
223
+ # Save updated session
224
+ self.verification_manager.save_session(session)
225
+
226
+ # Export with enhanced data
227
+ export_data = self.verification_manager.export_session_with_enhanced_data(session.session_id)
228
+
229
+ # Verify provider summary data is included
230
+ assert export_data is not None
231
+ record_data = export_data['records'][0]
232
+ assert record_data['provider_summary'] is not None
233
+ assert record_data['provider_summary']['urgency_level'] == 'IMMEDIATE'
234
+ assert record_data['provider_summary']['severity_level'] == 'HIGH'
235
+
236
+ def test_coherent_paragraph_format_in_verification(self):
237
+ """Test that coherent paragraph format is properly handled in verification."""
238
+ # Create session
239
+ logger = self.create_test_conversation_logger()
240
+ session = self.verification_manager.create_verification_session(
241
+ logger,
242
+ enable_enhanced_formats=True
243
+ )
244
+
245
+ # Add coherent paragraph to a record
246
+ coherent_paragraph = "Test Patient is a 45-year-old individual with clinical history of chronic illness. The patient expressed loss of meaning and spiritual distress, which may indicate significant spiritual concerns, resulting in generation of a RED FLAG. The patient has been identified for spiritual care team contact. The preferred contact number is 555-0123."
247
+
248
+ session.verification_records[0].coherent_summary_paragraph = coherent_paragraph
249
+
250
+ # Save and export
251
+ self.verification_manager.save_session(session)
252
+ csv_path = self.exporter.export_session_to_csv(session, include_enhanced_data=True)
253
+
254
+ # Verify coherent paragraph data is in CSV
255
+ with open(csv_path, 'r', encoding='utf-8') as f:
256
+ content = f.read()
257
+
258
+ # Should have coherent paragraph length > 0
259
+ lines = content.split('\n')
260
+ data_lines = [line for line in lines if line and not line.startswith('#') and 'coherent_paragraph_length' not in line]
261
+ if len(data_lines) > 2: # Header + data rows
262
+ # Find the data row for the first record
263
+ for line in data_lines[2:]: # Skip header and metadata
264
+ if line.strip():
265
+ fields = line.split(',')
266
+ if len(fields) > 15: # Should have enhanced fields
267
+ coherent_length_field = fields[-5] # coherent_paragraph_length field
268
+ if coherent_length_field.isdigit():
269
+ assert int(coherent_length_field) > 0
270
+ break
271
+
272
+
273
+ if __name__ == "__main__":
274
+ pytest.main([__file__])
tests/integration/test_error_handling_integration.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Integration tests for error handling in UI components.
4
+
5
+ Tests the integration of error handling across UI components.
6
+
7
+ Requirements: 9.1, 9.2, 9.3, 9.4
8
+ """
9
+
10
+ import pytest
11
+ from unittest.mock import Mock, patch
12
+
13
+ from src.interface.enhanced_results_display_manager import EnhancedResultsDisplayManager
14
+ from src.core.provider_summary_generator import ProviderSummary, ProviderSummaryGenerator
15
+ from src.core.improved_classification_prompt_manager import ImprovedClassificationPromptManager
16
+ from src.config.enhanced_display_config import EnhancedDisplayConfig
17
+
18
+
19
+ class TestErrorHandlingIntegration:
20
+ """Integration tests for error handling across UI components."""
21
+
22
+ def setup_method(self):
23
+ """Set up test fixtures."""
24
+ self.display_manager = EnhancedResultsDisplayManager()
25
+ self.summary_generator = ProviderSummaryGenerator()
26
+ self.classification_manager = ImprovedClassificationPromptManager()
27
+
28
+ def test_display_manager_handles_invalid_summary(self):
29
+ """Test that display manager handles invalid provider summary gracefully."""
30
+ # Create invalid summary with missing required fields
31
+ invalid_summary = ProviderSummary(
32
+ patient_name="[Patient Name]", # Placeholder
33
+ patient_phone="[Phone Number]", # Placeholder
34
+ classification="RED",
35
+ confidence=1.5, # Invalid confidence
36
+ reasoning="", # Empty reasoning
37
+ indicators=[], # No indicators
38
+ severity_level="INVALID", # Invalid level
39
+ urgency_level="INVALID" # Invalid level
40
+ )
41
+
42
+ # Format should not crash and should include validation warnings
43
+ result = self.display_manager.format_provider_summary_section(invalid_summary)
44
+
45
+ assert isinstance(result, str)
46
+ assert len(result) > 0
47
+ # Should contain some form of content even with invalid data
48
+ assert "Provider Summary" in result or "Display Error" in result
49
+
50
+ def test_display_manager_handles_formatting_error(self):
51
+ """Test that display manager handles formatting errors gracefully."""
52
+ # Mock the config to cause an error
53
+ with patch.object(self.display_manager, 'config', None):
54
+ result = self.display_manager.format_ai_analysis_section(
55
+ classification="RED",
56
+ indicators=["Test indicator"],
57
+ reasoning="Test reasoning"
58
+ )
59
+
60
+ assert isinstance(result, str)
61
+ assert len(result) > 0
62
+ # Should fall back to basic formatting or show error
63
+ assert "AI Analysis" in result or "Display Error" in result
64
+
65
+ def test_summary_generator_handles_missing_data(self):
66
+ """Test that summary generator handles missing data gracefully."""
67
+ # Generate summary with minimal data
68
+ summary = self.summary_generator.generate_summary(
69
+ indicators=[], # Empty indicators
70
+ reasoning="", # Empty reasoning
71
+ confidence=0.0,
72
+ patient_name=None, # Missing name
73
+ patient_phone=None # Missing phone
74
+ )
75
+
76
+ assert isinstance(summary, ProviderSummary)
77
+ # Should have fallback values
78
+ assert summary.patient_name != "[Patient Name]" or "Patient" in summary.patient_name
79
+ assert len(summary.reasoning) > 10 # Should have fallback reasoning
80
+ assert len(summary.recommended_actions) > 0 # Should have default actions
81
+
82
+ def test_summary_generator_handles_generation_error(self):
83
+ """Test that summary generator handles generation errors gracefully."""
84
+ # Mock an internal method to raise an error
85
+ with patch.object(self.summary_generator, '_generate_conversation_summary', side_effect=Exception("Test error")):
86
+ summary = self.summary_generator.generate_summary(
87
+ indicators=["Test indicator"],
88
+ reasoning="Test reasoning",
89
+ confidence=0.8,
90
+ patient_name="John Doe",
91
+ patient_phone="555-123-4567"
92
+ )
93
+
94
+ assert isinstance(summary, ProviderSummary)
95
+ # Should be a fallback summary
96
+ assert summary.patient_name in ["John Doe", "Patient (Name Not Available)"]
97
+ assert len(summary.reasoning) > 0
98
+
99
+ def test_classification_manager_handles_invalid_result(self):
100
+ """Test that classification manager handles invalid results gracefully."""
101
+ # Create invalid classification result
102
+ result = self.classification_manager.create_classification_result(
103
+ classification="INVALID", # Invalid classification
104
+ confidence=2.0, # Invalid confidence
105
+ indicators=[], # Empty indicators
106
+ reasoning="" # Empty reasoning
107
+ )
108
+
109
+ assert result is not None
110
+ assert result.classification in ["red", "yellow", "green"]
111
+ assert 0.0 <= result.confidence <= 1.0
112
+ assert len(result.indicators) > 0
113
+ assert len(result.reasoning) > 0
114
+
115
+ def test_end_to_end_error_recovery(self):
116
+ """Test end-to-end error recovery across components."""
117
+ # Start with problematic data
118
+ problematic_data = {
119
+ 'classification': 'INVALID',
120
+ 'confidence': -0.5,
121
+ 'indicators': [],
122
+ 'reasoning': '',
123
+ 'patient_name': '',
124
+ 'patient_phone': ''
125
+ }
126
+
127
+ # Generate summary (should apply fallbacks)
128
+ summary = self.summary_generator.generate_summary(
129
+ indicators=problematic_data['indicators'],
130
+ reasoning=problematic_data['reasoning'],
131
+ confidence=problematic_data['confidence'],
132
+ patient_name=problematic_data['patient_name'],
133
+ patient_phone=problematic_data['patient_phone']
134
+ )
135
+
136
+ # Display summary (should handle validation issues)
137
+ display_result = self.display_manager.format_provider_summary_section(summary)
138
+
139
+ # Verify the entire pipeline produces usable output
140
+ assert isinstance(display_result, str)
141
+ assert len(display_result) > 0
142
+ assert "Provider Summary" in display_result or "Display Error" in display_result
143
+
144
+ # Verify summary has been fixed
145
+ assert summary.confidence >= 0.0
146
+ assert len(summary.reasoning) > 10
147
+ assert len(summary.recommended_actions) > 0
148
+
149
+ def test_degraded_mode_functionality(self):
150
+ """Test that system continues to function in degraded mode."""
151
+ # Disable enhancements to test degraded mode
152
+ degraded_config = EnhancedDisplayConfig(enabled=False)
153
+ degraded_display_manager = EnhancedResultsDisplayManager(config=degraded_config)
154
+
155
+ # Create a valid summary
156
+ summary = ProviderSummary(
157
+ patient_name="John Doe",
158
+ patient_phone="555-123-4567",
159
+ classification="RED",
160
+ confidence=0.8,
161
+ reasoning="Test reasoning for degraded mode",
162
+ indicators=["Test indicator"],
163
+ severity_level="HIGH",
164
+ urgency_level="URGENT",
165
+ situation_description="Test situation",
166
+ recommended_actions=["Test action"]
167
+ )
168
+
169
+ # Format in degraded mode
170
+ result = degraded_display_manager.format_provider_summary_section(summary)
171
+
172
+ assert isinstance(result, str)
173
+ assert len(result) > 0
174
+ # Should contain basic information even in degraded mode
175
+ assert "John Doe" in result
176
+ assert "555-123-4567" in result
177
+ assert "URGENT" in result
178
+
179
+ def test_error_statistics_collection(self):
180
+ """Test that error statistics are properly collected."""
181
+ # Create summary with validation issues
182
+ problematic_summary = ProviderSummary(
183
+ patient_name="[Patient Name]",
184
+ patient_phone="[Phone Number]",
185
+ classification="RED",
186
+ confidence=1.5, # Invalid
187
+ reasoning="", # Empty
188
+ indicators=[] # Empty
189
+ )
190
+
191
+ # Validate and collect errors
192
+ validation_result = self.display_manager.error_handler.validate_provider_summary_structure(problematic_summary)
193
+
194
+ # Get error statistics
195
+ stats = self.display_manager.error_handler.get_error_statistics(validation_result.errors)
196
+
197
+ assert stats["total"] > 0
198
+ assert "validation" in stats["by_category"] or "data_missing" in stats["by_category"]
199
+ assert len(stats["by_severity"]) > 0
200
+ assert len(stats["by_component"]) > 0
201
+
202
+
203
+ if __name__ == "__main__":
204
+ pytest.main([__file__])
tests/integration/test_ui_classification_improvements_integration.py ADDED
@@ -0,0 +1,558 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Integration tests for UI Classification Improvements.
4
+
5
+ Tests the complete workflow from chat to verification with all enhanced components
6
+ working together correctly. This validates task 10 requirements.
7
+
8
+ Requirements: 10.1 - Complete workflow testing
9
+ """
10
+
11
+ import pytest
12
+ import tempfile
13
+ import os
14
+ import json
15
+ from datetime import datetime
16
+ from unittest.mock import Mock, patch
17
+
18
+ from src.interface.enhanced_results_display_manager import EnhancedResultsDisplayManager
19
+ from src.core.provider_summary_generator import ProviderSummary, ProviderSummaryGenerator
20
+ from src.core.improved_classification_prompt_manager import ImprovedClassificationPromptManager
21
+ from src.config.enhanced_display_config import EnhancedDisplayConfig, get_enhanced_display_config
22
+ from src.core.conversation_logger import ConversationLogger
23
+ from src.core.spiritual_state import SpiritualState, SpiritualAssessment
24
+ from src.core.conversation_verification import EnhancedConversationVerificationManager
25
+ from src.core.ui_error_handler import UIErrorHandler
26
+
27
+
28
+ class TestUIClassificationImprovementsIntegration:
29
+ """
30
+ Integration tests for UI Classification Improvements.
31
+
32
+ Tests the complete workflow: Chat → Classification → Display → Verification
33
+ """
34
+
35
+ def setup_method(self):
36
+ """Set up test environment."""
37
+ self.temp_dir = tempfile.mkdtemp()
38
+
39
+ # Initialize all components
40
+ self.display_manager = EnhancedResultsDisplayManager()
41
+ self.summary_generator = ProviderSummaryGenerator()
42
+ self.classification_manager = ImprovedClassificationPromptManager()
43
+ self.verification_manager = EnhancedConversationVerificationManager(self.temp_dir)
44
+ self.error_handler = UIErrorHandler()
45
+
46
+ # Test data
47
+ self.test_patient_name = "Integration Test Patient"
48
+ self.test_patient_phone = "555-0199"
49
+
50
+ def teardown_method(self):
51
+ """Clean up test environment."""
52
+ import shutil
53
+ shutil.rmtree(self.temp_dir, ignore_errors=True)
54
+
55
+ def create_test_conversation_logger(self) -> ConversationLogger:
56
+ """Create a conversation logger with test data for integration testing."""
57
+ logger = ConversationLogger(patient_name=self.test_patient_name)
58
+
59
+ # Simulate a conversation that should trigger RED classification
60
+ test_exchanges = [
61
+ {
62
+ "user_message": "I've been feeling really lost lately",
63
+ "assistant_response": "I understand this can be a difficult time. Can you tell me more about what you're experiencing?",
64
+ "classification": SpiritualState.YELLOW,
65
+ "confidence": 0.65,
66
+ "indicators": ["feeling lost", "emotional distress"],
67
+ "reasoning": "Patient expressing feelings of being lost, requires further assessment"
68
+ },
69
+ {
70
+ "user_message": "I just don't see the point in anything anymore. My life feels meaningless since my diagnosis.",
71
+ "assistant_response": "I hear that you're struggling with finding meaning right now. These feelings are understandable given what you're going through.",
72
+ "classification": SpiritualState.RED,
73
+ "confidence": 0.85,
74
+ "indicators": ["loss of meaning", "existential distress", "questioning purpose"],
75
+ "reasoning": "Patient expressing loss of meaning and purpose, which is an explicit red flag indicator"
76
+ },
77
+ {
78
+ "user_message": "Sometimes I wonder if there's any point to all this suffering",
79
+ "assistant_response": "Your feelings about suffering are very important. Many people in your situation have similar questions.",
80
+ "classification": SpiritualState.RED,
81
+ "confidence": 0.90,
82
+ "indicators": ["doubt about meaning of suffering", "existential questioning"],
83
+ "reasoning": "Patient questioning meaning of suffering - explicit red flag indicator requiring immediate attention"
84
+ }
85
+ ]
86
+
87
+ for exchange in test_exchanges:
88
+ assessment = SpiritualAssessment(
89
+ state=exchange["classification"],
90
+ confidence=exchange["confidence"],
91
+ indicators=exchange["indicators"],
92
+ reasoning=exchange["reasoning"]
93
+ )
94
+ logger.log_exchange(
95
+ exchange["user_message"],
96
+ exchange["assistant_response"],
97
+ assessment
98
+ )
99
+
100
+ return logger
101
+
102
+ def test_complete_workflow_integration(self):
103
+ """
104
+ Test the complete workflow from chat to verification.
105
+
106
+ This is the main integration test that validates all components
107
+ work together correctly.
108
+ """
109
+ print("🧪 Testing complete UI Classification Improvements workflow...")
110
+
111
+ # Step 1: Create conversation with enhanced classification
112
+ print(" 1. Creating conversation with enhanced classification...")
113
+ logger = self.create_test_conversation_logger()
114
+
115
+ # Verify conversation was logged correctly
116
+ assert len(logger.entries) == 3
117
+ assert logger.patient_name == self.test_patient_name
118
+
119
+ # Step 2: Generate provider summary with enhanced features
120
+ print(" 2. Generating enhanced provider summary...")
121
+
122
+ # Get the RED flag assessment from the conversation
123
+ red_assessment = None
124
+ for entry in logger.entries:
125
+ if entry.spiritual_classification == "RED":
126
+ # Create SpiritualAssessment from entry data
127
+ from src.core.spiritual_state import SpiritualState
128
+ red_assessment = SpiritualAssessment(
129
+ state=SpiritualState.RED,
130
+ confidence=entry.classification_confidence,
131
+ indicators=entry.classification_indicators,
132
+ reasoning=entry.classification_reasoning
133
+ )
134
+ break
135
+
136
+ assert red_assessment is not None, "Should have RED flag assessment"
137
+
138
+ # Generate enhanced provider summary
139
+ summary = self.summary_generator.generate_summary(
140
+ indicators=red_assessment.indicators,
141
+ reasoning=red_assessment.reasoning,
142
+ confidence=red_assessment.confidence,
143
+ patient_name=self.test_patient_name,
144
+ patient_phone=self.test_patient_phone,
145
+ conversation_context="Patient expressing loss of meaning and questioning suffering",
146
+ medical_context={
147
+ "age": 45,
148
+ "gender": "individual",
149
+ "conditions": ["chronic illness", "recent diagnosis"]
150
+ }
151
+ )
152
+
153
+ # Verify summary was generated correctly
154
+ assert isinstance(summary, ProviderSummary)
155
+ assert summary.patient_name == self.test_patient_name
156
+ assert summary.patient_phone == self.test_patient_phone
157
+ assert summary.classification == "RED"
158
+ assert len(summary.indicators) > 0
159
+
160
+ # Step 3: Format with enhanced display manager
161
+ print(" 3. Formatting with enhanced display manager...")
162
+
163
+ # Test AI analysis section formatting
164
+ ai_analysis_html = self.display_manager.format_ai_analysis_section(
165
+ classification="RED",
166
+ indicators=red_assessment.indicators,
167
+ reasoning=red_assessment.reasoning,
168
+ confidence=red_assessment.confidence
169
+ )
170
+
171
+ assert isinstance(ai_analysis_html, str)
172
+ assert len(ai_analysis_html) > 0
173
+ assert "AI Analysis" in ai_analysis_html
174
+ assert "RED FLAG" in ai_analysis_html
175
+
176
+ # Test patient message section formatting
177
+ patient_message_html = self.display_manager.format_patient_message_section(
178
+ "Sometimes I wonder if there's any point to all this suffering"
179
+ )
180
+
181
+ assert isinstance(patient_message_html, str)
182
+ assert "Patient Message" in patient_message_html
183
+ assert "suffering" in patient_message_html
184
+
185
+ # Test provider summary section formatting
186
+ provider_summary_html = self.display_manager.format_provider_summary_section(summary)
187
+
188
+ assert isinstance(provider_summary_html, str)
189
+ assert "Provider Summary" in provider_summary_html
190
+ assert self.test_patient_name in provider_summary_html
191
+ assert self.test_patient_phone in provider_summary_html
192
+
193
+ # Step 4: Test coherent paragraph formatting
194
+ print(" 4. Testing coherent paragraph formatting...")
195
+
196
+ coherent_paragraph = self.summary_generator.format_coherent_paragraph(summary)
197
+
198
+ assert isinstance(coherent_paragraph, str)
199
+ assert len(coherent_paragraph) > 50 # Should be substantial
200
+ assert self.test_patient_name in coherent_paragraph
201
+ assert "45-year-old" in coherent_paragraph or "individual" in coherent_paragraph
202
+ assert "RED FLAG" in coherent_paragraph
203
+
204
+ # Step 5: Test combined results formatting
205
+ print(" 5. Testing combined results formatting...")
206
+
207
+ combined_html = self.display_manager.format_combined_results(
208
+ ai_analysis={
209
+ 'classification': 'RED',
210
+ 'indicators': red_assessment.indicators,
211
+ 'reasoning': red_assessment.reasoning,
212
+ 'confidence': red_assessment.confidence
213
+ },
214
+ patient_message="Sometimes I wonder if there's any point to all this suffering",
215
+ provider_summary=summary
216
+ )
217
+
218
+ assert isinstance(combined_html, str)
219
+ assert "AI Analysis" in combined_html
220
+ assert "Patient Message" in combined_html
221
+ assert "Provider Summary" in combined_html
222
+
223
+ # Step 6: Test verification system integration
224
+ print(" 6. Testing verification system integration...")
225
+
226
+ verification_session = self.verification_manager.create_verification_session(
227
+ logger,
228
+ verifier_name="Integration Test",
229
+ enable_enhanced_formats=True
230
+ )
231
+
232
+ assert verification_session is not None
233
+ assert verification_session.enhanced_format_enabled is True
234
+ assert len(verification_session.verification_records) == 3
235
+
236
+ # Verify enhanced formats are applied
237
+ for record in verification_session.verification_records:
238
+ assert record.enhanced_display_format is not None
239
+ assert record.visual_sections is not None
240
+
241
+ # Step 7: Test CSV export with enhanced data
242
+ print(" 7. Testing CSV export with enhanced data...")
243
+
244
+ from src.core.verification_exporter import EnhancedVerificationExporter
245
+ exporter = EnhancedVerificationExporter(self.temp_dir)
246
+
247
+ csv_path = exporter.export_session_to_csv(
248
+ verification_session,
249
+ include_enhanced_data=True
250
+ )
251
+
252
+ assert os.path.exists(csv_path)
253
+
254
+ with open(csv_path, 'r', encoding='utf-8') as f:
255
+ csv_content = f.read()
256
+
257
+ # Verify enhanced data is in CSV
258
+ assert 'has_enhanced_display' in csv_content
259
+ assert 'enhanced_indicators_count' in csv_content
260
+ assert self.test_patient_name in csv_content
261
+
262
+ print(" ✅ Complete workflow integration test passed!")
263
+
264
+ def test_classification_consistency_validation(self):
265
+ """Test that classification consistency is maintained throughout workflow."""
266
+ print("🧪 Testing classification consistency validation...")
267
+
268
+ # Test explicit red indicators
269
+ explicit_red_indicators = self.classification_manager.get_explicit_red_indicators()
270
+
271
+ assert "Complex grief" in explicit_red_indicators
272
+ assert "Loss of a loved one" in explicit_red_indicators
273
+ assert "Doubt about meaning of life" in explicit_red_indicators
274
+ assert "Doubt about meaning of suffering" in explicit_red_indicators
275
+ assert "Doubt about personal dignity" in explicit_red_indicators
276
+
277
+ # Test classification validation
278
+ test_result = self.classification_manager.create_classification_result(
279
+ classification="red",
280
+ confidence=0.85,
281
+ indicators=["doubt about meaning of suffering"],
282
+ reasoning="Patient questioning meaning of suffering",
283
+ red_flag_indicators=["doubt about meaning of suffering"]
284
+ )
285
+
286
+ assert test_result.is_valid is True
287
+ assert test_result.classification == "red"
288
+
289
+ # Test invalid classification gets corrected
290
+ invalid_result = self.classification_manager.create_classification_result(
291
+ classification="invalid",
292
+ confidence=2.0,
293
+ indicators=[],
294
+ reasoning=""
295
+ )
296
+
297
+ assert invalid_result.classification in ["red", "yellow", "green"]
298
+ assert 0.0 <= invalid_result.confidence <= 1.0
299
+ assert len(invalid_result.indicators) > 0
300
+
301
+ print(" ✅ Classification consistency validation passed!")
302
+
303
+ def test_error_handling_throughout_workflow(self):
304
+ """Test error handling and recovery throughout the complete workflow."""
305
+ print("🧪 Testing error handling throughout workflow...")
306
+
307
+ # Test with problematic data
308
+ problematic_summary = ProviderSummary(
309
+ patient_name="[Patient Name]", # Placeholder
310
+ patient_phone="[Phone Number]", # Placeholder
311
+ classification="RED",
312
+ confidence=1.5, # Invalid confidence
313
+ reasoning="", # Empty reasoning
314
+ indicators=[], # No indicators
315
+ severity_level="INVALID",
316
+ urgency_level="INVALID"
317
+ )
318
+
319
+ # Display manager should handle this gracefully
320
+ display_result = self.display_manager.format_provider_summary_section(problematic_summary)
321
+
322
+ assert isinstance(display_result, str)
323
+ assert len(display_result) > 0
324
+ # Should contain validation warnings or fallback content
325
+ assert "Provider Summary" in display_result or "validation" in display_result.lower()
326
+
327
+ # Test error statistics collection
328
+ validation_result = self.error_handler.validate_provider_summary_structure(problematic_summary)
329
+ stats = self.error_handler.get_error_statistics(validation_result.errors)
330
+
331
+ assert stats["total"] > 0
332
+ assert len(stats["by_category"]) > 0
333
+
334
+ print(" ✅ Error handling throughout workflow passed!")
335
+
336
+ def test_data_integrity_across_operations(self):
337
+ """Test that data integrity is maintained across all operations."""
338
+ print("🧪 Testing data integrity across operations...")
339
+
340
+ # Create test data
341
+ original_indicators = ["loss of meaning", "spiritual distress", "questioning faith"]
342
+ original_reasoning = "Patient expressing significant spiritual concerns"
343
+ original_confidence = 0.85
344
+
345
+ # Generate summary
346
+ summary = self.summary_generator.generate_summary(
347
+ indicators=original_indicators,
348
+ reasoning=original_reasoning,
349
+ confidence=original_confidence,
350
+ patient_name=self.test_patient_name,
351
+ patient_phone=self.test_patient_phone
352
+ )
353
+
354
+ # Verify data integrity in summary
355
+ assert summary.patient_name == self.test_patient_name
356
+ assert summary.patient_phone == self.test_patient_phone
357
+ assert summary.confidence == original_confidence
358
+ assert all(indicator in summary.indicators for indicator in original_indicators)
359
+
360
+ # Format for display
361
+ display_html = self.display_manager.format_provider_summary_section(summary)
362
+
363
+ # Verify data integrity in display
364
+ assert self.test_patient_name in display_html
365
+ assert self.test_patient_phone in display_html
366
+
367
+ # Format coherent paragraph
368
+ coherent_paragraph = self.summary_generator.format_coherent_paragraph(summary)
369
+
370
+ # Verify data integrity in coherent paragraph
371
+ assert self.test_patient_name in coherent_paragraph
372
+ assert self.test_patient_phone in coherent_paragraph
373
+
374
+ # Export for verification
375
+ export_data = summary.to_dict()
376
+
377
+ # Verify data integrity in export
378
+ assert export_data["patient_name"] == self.test_patient_name
379
+ assert export_data["patient_phone"] == self.test_patient_phone
380
+ assert export_data["confidence"] == original_confidence
381
+
382
+ print(" ✅ Data integrity across operations passed!")
383
+
384
+ def test_performance_with_multiple_records(self):
385
+ """Test performance and stability with multiple conversation records."""
386
+ print("🧪 Testing performance with multiple records...")
387
+
388
+ # Create logger with multiple exchanges
389
+ logger = ConversationLogger(patient_name="Performance Test Patient")
390
+
391
+ # Add 10 exchanges to test performance
392
+ for i in range(10):
393
+ assessment = SpiritualAssessment(
394
+ state=SpiritualState.RED if i % 3 == 0 else SpiritualState.YELLOW,
395
+ confidence=0.7 + (i * 0.02),
396
+ indicators=[f"indicator_{i}", f"concern_{i}"],
397
+ reasoning=f"Test reasoning for exchange {i}"
398
+ )
399
+
400
+ logger.log_exchange(
401
+ f"User message {i}: I'm having concerns about my situation",
402
+ f"Assistant response {i}: I understand your concerns",
403
+ assessment
404
+ )
405
+
406
+ # Test verification session creation with multiple records
407
+ verification_session = self.verification_manager.create_verification_session(
408
+ logger,
409
+ enable_enhanced_formats=True
410
+ )
411
+
412
+ assert len(verification_session.verification_records) == 10
413
+
414
+ # Test that all records have enhanced formats
415
+ enhanced_count = sum(
416
+ 1 for record in verification_session.verification_records
417
+ if record.enhanced_display_format is not None
418
+ )
419
+
420
+ assert enhanced_count == 10
421
+
422
+ # Test CSV export performance
423
+ from src.core.verification_exporter import EnhancedVerificationExporter
424
+ exporter = EnhancedVerificationExporter(self.temp_dir)
425
+
426
+ csv_path = exporter.export_session_to_csv(
427
+ verification_session,
428
+ include_enhanced_data=True
429
+ )
430
+
431
+ assert os.path.exists(csv_path)
432
+
433
+ # Verify CSV contains all records
434
+ with open(csv_path, 'r', encoding='utf-8') as f:
435
+ csv_content = f.read()
436
+
437
+ # Should have header + metadata + 10 data rows
438
+ lines = [line for line in csv_content.split('\n') if line.strip()]
439
+ data_lines = [line for line in lines if not line.startswith('#')]
440
+
441
+ # At least header + 10 records
442
+ assert len(data_lines) >= 11
443
+
444
+ print(" ✅ Performance with multiple records passed!")
445
+
446
+ def test_configuration_management_integration(self):
447
+ """Test that configuration management works correctly across components."""
448
+ print("🧪 Testing configuration management integration...")
449
+
450
+ # Test default configuration
451
+ default_config = get_enhanced_display_config()
452
+ assert default_config.enabled is True
453
+ # Note: use_icons default may vary based on configuration file
454
+ assert hasattr(default_config, 'use_icons')
455
+
456
+ # Test custom configuration
457
+ custom_config = EnhancedDisplayConfig(
458
+ enabled=True,
459
+ use_icons=False,
460
+ use_visual_separators=False
461
+ )
462
+
463
+ custom_display_manager = EnhancedResultsDisplayManager(config=custom_config)
464
+
465
+ # Test that custom config is applied
466
+ assert custom_display_manager.config.use_icons is False
467
+ assert custom_display_manager.config.use_visual_separators is False
468
+
469
+ # Test formatting with custom config
470
+ result = custom_display_manager.format_ai_analysis_section(
471
+ classification="RED",
472
+ indicators=["test indicator"],
473
+ reasoning="test reasoning"
474
+ )
475
+
476
+ assert isinstance(result, str)
477
+ assert len(result) > 0
478
+
479
+ # Test disabled mode
480
+ disabled_config = EnhancedDisplayConfig(enabled=False)
481
+ disabled_display_manager = EnhancedResultsDisplayManager(config=disabled_config)
482
+
483
+ result = disabled_display_manager.format_ai_analysis_section(
484
+ classification="RED",
485
+ indicators=["test indicator"],
486
+ reasoning="test reasoning"
487
+ )
488
+
489
+ # Should fall back to basic formatting
490
+ assert isinstance(result, str)
491
+ assert "AI Analysis" in result
492
+
493
+ print(" ✅ Configuration management integration passed!")
494
+
495
+
496
+ def run_integration_checkpoint():
497
+ """
498
+ Run the integration checkpoint tests.
499
+
500
+ This function runs all integration tests and provides a summary
501
+ of the results for task 10 validation.
502
+ """
503
+ print("🚀 Running UI Classification Improvements Integration Checkpoint")
504
+ print("=" * 70)
505
+
506
+ # Create test instance
507
+ test_instance = TestUIClassificationImprovementsIntegration()
508
+
509
+ tests = [
510
+ ("Complete Workflow Integration", test_instance.test_complete_workflow_integration),
511
+ ("Classification Consistency", test_instance.test_classification_consistency_validation),
512
+ ("Error Handling Throughout Workflow", test_instance.test_error_handling_throughout_workflow),
513
+ ("Data Integrity Across Operations", test_instance.test_data_integrity_across_operations),
514
+ ("Performance with Multiple Records", test_instance.test_performance_with_multiple_records),
515
+ ("Configuration Management", test_instance.test_configuration_management_integration)
516
+ ]
517
+
518
+ passed = 0
519
+ failed = 0
520
+
521
+ for test_name, test_func in tests:
522
+ try:
523
+ print(f"\n🧪 Running: {test_name}")
524
+ test_instance.setup_method()
525
+ test_func()
526
+ test_instance.teardown_method()
527
+ print(f" ✅ {test_name} PASSED")
528
+ passed += 1
529
+ except Exception as e:
530
+ print(f" ❌ {test_name} FAILED: {e}")
531
+ failed += 1
532
+ import traceback
533
+ traceback.print_exc()
534
+
535
+ print("\n" + "=" * 70)
536
+ print("📊 INTEGRATION CHECKPOINT RESULTS")
537
+ print("=" * 70)
538
+ print(f"✅ Tests Passed: {passed}")
539
+ print(f"❌ Tests Failed: {failed}")
540
+ print(f"📈 Success Rate: {(passed / (passed + failed)) * 100:.1f}%")
541
+
542
+ if failed == 0:
543
+ print("\n🎉 ALL INTEGRATION TESTS PASSED!")
544
+ print("✅ All components work together correctly")
545
+ print("✅ Full workflow from chat to verification validated")
546
+ print("✅ Data integrity maintained across all operations")
547
+ print("✅ Error handling and recovery working properly")
548
+ print("✅ System ready for production use")
549
+ return True
550
+ else:
551
+ print(f"\n⚠️ {failed} integration tests failed")
552
+ print("❌ System requires fixes before production use")
553
+ return False
554
+
555
+
556
+ if __name__ == "__main__":
557
+ success = run_integration_checkpoint()
558
+ exit(0 if success else 1)
tests/unit/test_coherent_summary_formatter.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # test_coherent_summary_formatter.py
2
+ """
3
+ Unit tests for Coherent Summary Formatter functionality.
4
+
5
+ Tests the new coherent paragraph formatting functionality added to
6
+ ProviderSummaryGenerator for requirements 2.1-2.8.
7
+ """
8
+
9
+ import pytest
10
+ from unittest.mock import Mock
11
+ from datetime import datetime
12
+
13
+ from src.core.provider_summary_generator import ProviderSummaryGenerator, ProviderSummary
14
+
15
+
16
+ class TestCoherentSummaryFormatter:
17
+ """Test cases for coherent summary formatting functionality."""
18
+
19
+ def setup_method(self):
20
+ """Set up test fixtures."""
21
+ self.generator = ProviderSummaryGenerator()
22
+
23
+ def test_format_coherent_paragraph_basic(self):
24
+ """Test basic coherent paragraph formatting."""
25
+ # Create sample provider summary
26
+ summary = ProviderSummary(
27
+ patient_name="John Doe",
28
+ patient_phone="(555) 123-4567",
29
+ indicators=["Loss of faith", "Spiritual distress"],
30
+ situation_description="existential crisis",
31
+ medical_context={
32
+ "age": 45,
33
+ "gender": "male",
34
+ "conditions": ["Diabetes", "Hypertension"]
35
+ }
36
+ )
37
+
38
+ result = self.generator.format_coherent_paragraph(summary)
39
+
40
+ # Check that all required elements are present (Requirements 2.2, 2.3, 2.4, 2.5)
41
+ assert "John Doe is a 45-year-old male" in result
42
+ assert "clinical history of Diabetes and Hypertension" in result
43
+ assert "Loss of faith and Spiritual distress" in result
44
+ assert "existential crisis" in result
45
+ assert "RED FLAG" in result
46
+ assert "(555) 123-4567" in result
47
+
48
+ # Check that it's a single paragraph (Requirement 2.1)
49
+ lines = result.split('\n')
50
+ main_paragraph_lines = [line for line in lines if line.strip() and not line.startswith('Patient reported:')]
51
+ assert len(main_paragraph_lines) == 1
52
+
53
+ def test_format_coherent_paragraph_no_medical_history(self):
54
+ """Test coherent paragraph with no medical history."""
55
+ summary = ProviderSummary(
56
+ patient_name="Jane Smith",
57
+ patient_phone="(555) 987-6543",
58
+ indicators=["Anxiety"],
59
+ situation_description="mild distress",
60
+ medical_context={"age": 30, "gender": "female"}
61
+ )
62
+
63
+ result = self.generator.format_coherent_paragraph(summary)
64
+
65
+ assert "Jane Smith is a 30-year-old female" in result
66
+ assert "no significant medical history documented" in result
67
+ assert "Anxiety" in result
68
+ assert "mild distress" in result
69
+
70
+ def test_format_coherent_paragraph_single_condition(self):
71
+ """Test coherent paragraph with single medical condition."""
72
+ summary = ProviderSummary(
73
+ patient_name="Bob Wilson",
74
+ patient_phone="(555) 456-7890",
75
+ indicators=["Depression"],
76
+ situation_description="emotional distress",
77
+ medical_context={
78
+ "age": 55,
79
+ "gender": "male",
80
+ "conditions": ["Cancer"]
81
+ }
82
+ )
83
+
84
+ result = self.generator.format_coherent_paragraph(summary)
85
+
86
+ assert "clinical history of Cancer" in result
87
+ assert "Depression" in result
88
+
89
+ def test_format_coherent_paragraph_multiple_conditions(self):
90
+ """Test coherent paragraph with multiple medical conditions."""
91
+ summary = ProviderSummary(
92
+ patient_name="Alice Brown",
93
+ patient_phone="(555) 321-0987",
94
+ indicators=["Fear", "Uncertainty", "Loss of hope"],
95
+ situation_description="complex medical situation",
96
+ medical_context={
97
+ "age": 67,
98
+ "gender": "female",
99
+ "conditions": ["Heart Disease", "Diabetes", "Arthritis", "COPD"]
100
+ }
101
+ )
102
+
103
+ result = self.generator.format_coherent_paragraph(summary)
104
+
105
+ # Should format as "X, Y, Z, and W"
106
+ assert "clinical history of Heart Disease, Diabetes, Arthritis, and COPD" in result
107
+ assert "Fear, Uncertainty, and Loss of hope" in result
108
+
109
+ def test_format_coherent_paragraph_no_phone(self):
110
+ """Test coherent paragraph with no phone number."""
111
+ summary = ProviderSummary(
112
+ patient_name="Charlie Davis",
113
+ patient_phone="",
114
+ indicators=["Spiritual questioning"],
115
+ situation_description="seeking meaning",
116
+ medical_context={"age": 40, "gender": "male"}
117
+ )
118
+
119
+ result = self.generator.format_coherent_paragraph(summary)
120
+
121
+ assert "No contact number is currently available" in result
122
+
123
+ def test_format_coherent_paragraph_with_conversation_context(self):
124
+ """Test coherent paragraph with patient quote from conversation."""
125
+ summary = ProviderSummary(
126
+ patient_name="Diana Evans",
127
+ patient_phone="(555) 654-3210",
128
+ indicators=["Existential questioning"],
129
+ situation_description="spiritual crisis",
130
+ medical_context={"age": 35, "gender": "female"},
131
+ conversation_context="I don't understand why this is happening to me and my family"
132
+ )
133
+
134
+ result = self.generator.format_coherent_paragraph(summary)
135
+
136
+ # Should include patient quote as separate line (Requirement 2.8)
137
+ assert 'Patient reported: "I don\'t understand why this is happening to me and my family"' in result
138
+
139
+ # Should have main paragraph and quote separated
140
+ parts = result.split('\n\n')
141
+ assert len(parts) == 2
142
+
143
+ def test_format_coherent_paragraph_no_indicators(self):
144
+ """Test coherent paragraph with no specific indicators."""
145
+ summary = ProviderSummary(
146
+ patient_name="Frank Green",
147
+ patient_phone="(555) 789-0123",
148
+ indicators=[],
149
+ situation_description="general concern",
150
+ medical_context={"age": 50, "gender": "male"}
151
+ )
152
+
153
+ result = self.generator.format_coherent_paragraph(summary)
154
+
155
+ assert "general distress" in result
156
+ assert "general concern" in result
157
+
158
+ def test_format_coherent_paragraph_unknown_gender(self):
159
+ """Test coherent paragraph with unknown gender."""
160
+ summary = ProviderSummary(
161
+ patient_name="Taylor Smith",
162
+ patient_phone="(555) 111-2222",
163
+ indicators=["Anxiety"],
164
+ situation_description="stress",
165
+ medical_context={"age": 28} # No gender specified
166
+ )
167
+
168
+ result = self.generator.format_coherent_paragraph(summary)
169
+
170
+ assert "Taylor Smith is a 28-year-old individual" in result
171
+
172
+ def test_format_coherent_paragraph_no_age(self):
173
+ """Test coherent paragraph with no age specified."""
174
+ summary = ProviderSummary(
175
+ patient_name="Sam Johnson",
176
+ patient_phone="(555) 333-4444",
177
+ indicators=["Worry"],
178
+ situation_description="concern",
179
+ medical_context={"gender": "non-binary"}
180
+ )
181
+
182
+ result = self.generator.format_coherent_paragraph(summary)
183
+
184
+ assert "Sam Johnson is a unknown age non-binary" in result
185
+
186
+ def test_format_coherent_paragraph_minimal_data(self):
187
+ """Test coherent paragraph with minimal data."""
188
+ summary = ProviderSummary(
189
+ patient_name="Minimal Patient",
190
+ patient_phone="",
191
+ indicators=[],
192
+ situation_description="",
193
+ medical_context=None
194
+ )
195
+
196
+ result = self.generator.format_coherent_paragraph(summary)
197
+
198
+ # Should still produce a valid paragraph
199
+ assert "Minimal Patient is a unknown age individual" in result
200
+ assert "no significant medical history documented" in result
201
+ assert "general distress" in result
202
+ assert "spiritual or emotional distress" in result
203
+ assert "No contact number is currently available" in result
204
+
205
+ def test_format_coherent_paragraph_sentence_structure(self):
206
+ """Test that coherent paragraph has proper sentence structure."""
207
+ summary = ProviderSummary(
208
+ patient_name="Structure Test",
209
+ patient_phone="(555) 555-5555",
210
+ indicators=["Test indicator"],
211
+ situation_description="test situation",
212
+ medical_context={"age": 40, "gender": "male", "conditions": ["Test condition"]}
213
+ )
214
+
215
+ result = self.generator.format_coherent_paragraph(summary)
216
+
217
+ # Should end with period
218
+ main_paragraph = result.split('\n\n')[0]
219
+ assert main_paragraph.endswith('.')
220
+
221
+ # Should be properly connected sentences
222
+ sentences = main_paragraph.split('. ')
223
+ assert len(sentences) >= 4 # Should have multiple connected sentences
224
+
225
+ def test_format_coherent_paragraph_integration_with_existing(self):
226
+ """Test that coherent paragraph doesn't break existing functionality."""
227
+ summary = ProviderSummary(
228
+ patient_name="Integration Test",
229
+ patient_phone="(555) 999-8888",
230
+ indicators=["Integration test"],
231
+ situation_description="testing integration"
232
+ )
233
+
234
+ # Test that existing format_for_display still works
235
+ structured_result = self.generator.format_for_display(summary)
236
+ assert "PROVIDER SUMMARY - SPIRITUAL CARE REFERRAL" in structured_result
237
+
238
+ # Test that new coherent format works
239
+ coherent_result = self.generator.format_coherent_paragraph(summary)
240
+ assert "Integration Test is a unknown age individual" in coherent_result
241
+
242
+ # Results should be different formats
243
+ assert structured_result != coherent_result
244
+
245
+
246
+ if __name__ == "__main__":
247
+ pytest.main([__file__])
tests/unit/test_enhanced_display_config.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unit tests for Enhanced Display Configuration System.
4
+
5
+ Tests the configuration system for UI classification improvements
6
+ including feature toggles, styling options, and configuration management.
7
+
8
+ Requirements: 7.1, 7.2, 7.3
9
+ """
10
+
11
+ import unittest
12
+ import tempfile
13
+ import os
14
+ import json
15
+ from unittest.mock import patch, MagicMock
16
+
17
+ import sys
18
+ current_dir = os.path.dirname(os.path.abspath(__file__))
19
+ src_dir = os.path.join(os.path.dirname(os.path.dirname(current_dir)), 'src')
20
+ if src_dir not in sys.path:
21
+ sys.path.insert(0, src_dir)
22
+
23
+ from config.enhanced_display_config import (
24
+ EnhancedDisplayConfig,
25
+ EnhancedDisplayConfigManager,
26
+ SectionStylingConfig,
27
+ ClassificationColorConfig,
28
+ VisualSeparatorConfig,
29
+ create_high_contrast_config,
30
+ create_minimal_config,
31
+ create_mobile_optimized_config
32
+ )
33
+
34
+
35
+ class TestEnhancedDisplayConfig(unittest.TestCase):
36
+ """Test cases for EnhancedDisplayConfig class."""
37
+
38
+ def setUp(self):
39
+ """Set up test fixtures."""
40
+ self.config = EnhancedDisplayConfig()
41
+
42
+ def test_default_configuration(self):
43
+ """Test default configuration values."""
44
+ # Test feature toggles
45
+ self.assertTrue(self.config.enabled)
46
+ self.assertTrue(self.config.use_color_coding)
47
+ self.assertTrue(self.config.use_icons)
48
+ self.assertTrue(self.config.use_visual_separators)
49
+ self.assertTrue(self.config.use_enhanced_styling)
50
+
51
+ # Test default icons
52
+ self.assertEqual(self.config.ai_analysis.icon, "🤖")
53
+ self.assertEqual(self.config.patient_message.icon, "💬")
54
+ self.assertEqual(self.config.provider_summary.icon, "📋")
55
+
56
+ # Test default colors
57
+ self.assertEqual(self.config.classification_colors.red, "#ff4444")
58
+ self.assertEqual(self.config.classification_colors.yellow, "#ffaa00")
59
+ self.assertEqual(self.config.classification_colors.green, "#44aa44")
60
+
61
+ def test_get_classification_color(self):
62
+ """Test classification color retrieval."""
63
+ self.assertEqual(self.config.get_classification_color("RED"), "#ff4444")
64
+ self.assertEqual(self.config.get_classification_color("red"), "#ff4444")
65
+ self.assertEqual(self.config.get_classification_color("YELLOW"), "#ffaa00")
66
+ self.assertEqual(self.config.get_classification_color("GREEN"), "#44aa44")
67
+ self.assertEqual(self.config.get_classification_color("UNKNOWN"), "#666666")
68
+
69
+ def test_get_section_config(self):
70
+ """Test section configuration retrieval."""
71
+ ai_config = self.config.get_section_config("ai_analysis")
72
+ self.assertIsInstance(ai_config, SectionStylingConfig)
73
+ self.assertEqual(ai_config.icon, "🤖")
74
+
75
+ patient_config = self.config.get_section_config("patient_message")
76
+ self.assertEqual(patient_config.icon, "💬")
77
+
78
+ provider_config = self.config.get_section_config("provider_summary")
79
+ self.assertEqual(provider_config.icon, "📋")
80
+
81
+ # Test unknown section returns default
82
+ unknown_config = self.config.get_section_config("unknown")
83
+ self.assertEqual(unknown_config, self.config.ai_analysis)
84
+
85
+ def test_high_contrast_mode(self):
86
+ """Test high contrast mode application."""
87
+ config = EnhancedDisplayConfig(high_contrast_mode=True)
88
+
89
+ # Check that high contrast colors are applied
90
+ self.assertEqual(config.classification_colors.red, "#cc0000")
91
+ self.assertEqual(config.classification_colors.yellow, "#cc8800")
92
+ self.assertEqual(config.classification_colors.green, "#006600")
93
+
94
+ # Check section colors are updated
95
+ self.assertEqual(config.ai_analysis.border_color, "#000000")
96
+ self.assertEqual(config.ai_analysis.background_color, "#ffffff")
97
+
98
+ def test_css_variables_generation(self):
99
+ """Test CSS variables generation."""
100
+ css_vars = self.config.generate_css_variables()
101
+
102
+ # Check that CSS variables are generated
103
+ self.assertIn(":root {", css_vars)
104
+ self.assertIn("--enhanced-font-family:", css_vars)
105
+ self.assertIn("--classification-red:", css_vars)
106
+ self.assertIn("--ai-analysis-icon:", css_vars)
107
+ self.assertIn("}", css_vars)
108
+
109
+ def test_base_css_generation(self):
110
+ """Test base CSS generation."""
111
+ base_css = self.config.generate_base_css()
112
+
113
+ # Check that CSS classes are generated
114
+ self.assertIn(".enhanced-section", base_css)
115
+ self.assertIn(".enhanced-section-header", base_css)
116
+ self.assertIn(".ai-analysis-section", base_css)
117
+ self.assertIn(".patient-message-section", base_css)
118
+ self.assertIn(".provider-summary-section", base_css)
119
+
120
+ # Check responsive design
121
+ self.assertIn("@media", base_css)
122
+
123
+ def test_to_dict_conversion(self):
124
+ """Test conversion to dictionary."""
125
+ config_dict = self.config.to_dict()
126
+
127
+ self.assertIsInstance(config_dict, dict)
128
+ self.assertEqual(config_dict['enabled'], True)
129
+ self.assertEqual(config_dict['use_color_coding'], True)
130
+ self.assertIn('ai_analysis', config_dict)
131
+ self.assertIn('classification_colors', config_dict)
132
+
133
+ def test_to_json_conversion(self):
134
+ """Test conversion to JSON."""
135
+ config_json = self.config.to_json()
136
+
137
+ # Should be valid JSON
138
+ parsed = json.loads(config_json)
139
+ self.assertIsInstance(parsed, dict)
140
+ self.assertEqual(parsed['enabled'], True)
141
+
142
+
143
+ class TestEnhancedDisplayConfigManager(unittest.TestCase):
144
+ """Test cases for EnhancedDisplayConfigManager class."""
145
+
146
+ def setUp(self):
147
+ """Set up test fixtures."""
148
+ # Create temporary config file
149
+ self.temp_dir = tempfile.mkdtemp()
150
+ self.config_file = os.path.join(self.temp_dir, "test_config.json")
151
+ self.config_manager = EnhancedDisplayConfigManager(self.config_file)
152
+
153
+ def tearDown(self):
154
+ """Clean up test fixtures."""
155
+ # Clean up temporary files
156
+ if os.path.exists(self.config_file):
157
+ os.remove(self.config_file)
158
+ os.rmdir(self.temp_dir)
159
+
160
+ def test_load_default_config(self):
161
+ """Test loading default configuration when no file exists."""
162
+ config = self.config_manager.load_config()
163
+
164
+ self.assertIsInstance(config, EnhancedDisplayConfig)
165
+ self.assertTrue(config.enabled)
166
+ self.assertTrue(os.path.exists(self.config_file))
167
+
168
+ def test_save_and_load_config(self):
169
+ """Test saving and loading configuration."""
170
+ # Load and modify config
171
+ config = self.config_manager.load_config()
172
+ config.enabled = False
173
+ config.use_icons = False
174
+
175
+ # Save config
176
+ success = self.config_manager.save_config()
177
+ self.assertTrue(success)
178
+
179
+ # Create new manager and load
180
+ new_manager = EnhancedDisplayConfigManager(self.config_file)
181
+ loaded_config = new_manager.load_config()
182
+
183
+ self.assertFalse(loaded_config.enabled)
184
+ self.assertFalse(loaded_config.use_icons)
185
+
186
+ def test_update_config(self):
187
+ """Test configuration updates."""
188
+ success = self.config_manager.update_config(
189
+ enabled=False,
190
+ use_color_coding=False,
191
+ use_icons=False
192
+ )
193
+ self.assertTrue(success)
194
+
195
+ config = self.config_manager.load_config()
196
+ self.assertFalse(config.enabled)
197
+ self.assertFalse(config.use_color_coding)
198
+ self.assertFalse(config.use_icons)
199
+
200
+ def test_enable_disable_features(self):
201
+ """Test feature enable/disable functionality."""
202
+ # Disable color coding
203
+ success = self.config_manager.disable_feature('color_coding')
204
+ self.assertTrue(success)
205
+
206
+ config = self.config_manager.load_config()
207
+ self.assertFalse(config.use_color_coding)
208
+
209
+ # Enable color coding
210
+ success = self.config_manager.enable_feature('color_coding')
211
+ self.assertTrue(success)
212
+
213
+ config = self.config_manager.load_config()
214
+ self.assertTrue(config.use_color_coding)
215
+
216
+ def test_reset_to_defaults(self):
217
+ """Test resetting configuration to defaults."""
218
+ # Modify config
219
+ self.config_manager.update_config(enabled=False, use_icons=False)
220
+
221
+ # Reset to defaults
222
+ success = self.config_manager.reset_to_defaults()
223
+ self.assertTrue(success)
224
+
225
+ config = self.config_manager.load_config()
226
+ self.assertTrue(config.enabled)
227
+ self.assertTrue(config.use_icons)
228
+
229
+ def test_validate_config(self):
230
+ """Test configuration validation."""
231
+ # Valid configuration should have no issues
232
+ issues = self.config_manager.validate_config()
233
+ self.assertEqual(len(issues), 0)
234
+
235
+ # Test with invalid color (this would need to be implemented)
236
+ # For now, just test that validation runs without error
237
+ self.assertIsInstance(issues, list)
238
+
239
+ def test_config_file_error_handling(self):
240
+ """Test error handling for config file operations."""
241
+ # Test with invalid config file path
242
+ invalid_manager = EnhancedDisplayConfigManager("/invalid/path/config.json")
243
+
244
+ # Should still return default config
245
+ config = invalid_manager.load_config()
246
+ self.assertIsInstance(config, EnhancedDisplayConfig)
247
+ self.assertTrue(config.enabled)
248
+
249
+
250
+ class TestPresetConfigurations(unittest.TestCase):
251
+ """Test cases for preset configuration functions."""
252
+
253
+ def test_high_contrast_config(self):
254
+ """Test high contrast configuration preset."""
255
+ config = create_high_contrast_config()
256
+
257
+ self.assertTrue(config.high_contrast_mode)
258
+ self.assertEqual(config.classification_colors.red, "#cc0000")
259
+ self.assertEqual(config.ai_analysis.border_color, "#000000")
260
+
261
+ def test_minimal_config(self):
262
+ """Test minimal configuration preset."""
263
+ config = create_minimal_config()
264
+
265
+ self.assertFalse(config.use_icons)
266
+ self.assertFalse(config.use_visual_separators)
267
+ self.assertFalse(config.enable_animations)
268
+
269
+ def test_mobile_optimized_config(self):
270
+ """Test mobile optimized configuration preset."""
271
+ config = create_mobile_optimized_config()
272
+
273
+ self.assertEqual(config.responsive_breakpoint, "480px")
274
+ self.assertEqual(config.ai_analysis.padding, "12px")
275
+ self.assertFalse(config.enable_animations)
276
+
277
+
278
+ class TestSectionStylingConfig(unittest.TestCase):
279
+ """Test cases for SectionStylingConfig class."""
280
+
281
+ def test_default_values(self):
282
+ """Test default section styling values."""
283
+ config = SectionStylingConfig(
284
+ icon="🔧",
285
+ border_color="#000000",
286
+ background_color="#ffffff",
287
+ header_color="#000000"
288
+ )
289
+
290
+ self.assertEqual(config.icon, "🔧")
291
+ self.assertEqual(config.border_width, "2px")
292
+ self.assertEqual(config.border_radius, "8px")
293
+ self.assertEqual(config.padding, "15px")
294
+ self.assertEqual(config.margin, "10px 0")
295
+
296
+
297
+ class TestClassificationColorConfig(unittest.TestCase):
298
+ """Test cases for ClassificationColorConfig class."""
299
+
300
+ def test_default_colors(self):
301
+ """Test default classification colors."""
302
+ config = ClassificationColorConfig()
303
+
304
+ self.assertEqual(config.red, "#ff4444")
305
+ self.assertEqual(config.yellow, "#ffaa00")
306
+ self.assertEqual(config.green, "#44aa44")
307
+ self.assertEqual(config.unknown, "#666666")
308
+
309
+
310
+ class TestVisualSeparatorConfig(unittest.TestCase):
311
+ """Test cases for VisualSeparatorConfig class."""
312
+
313
+ def test_default_separators(self):
314
+ """Test default visual separator values."""
315
+ config = VisualSeparatorConfig()
316
+
317
+ self.assertEqual(config.section_separator, "---")
318
+ self.assertEqual(config.content_divider, "1px solid #ddd")
319
+ self.assertEqual(config.major_break_symbol, "● ● ●")
320
+ self.assertEqual(config.separator_color, "#e0e0e0")
321
+ self.assertEqual(config.separator_width, "80%")
322
+
323
+
324
+ if __name__ == '__main__':
325
+ unittest.main()
tests/unit/test_enhanced_results_display.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # test_enhanced_results_display.py
2
+ """
3
+ Unit tests for Enhanced Results Display Manager.
4
+
5
+ Tests the core functionality of the enhanced results display system
6
+ including section formatting, visual separation, and provider summary formatting.
7
+ """
8
+
9
+ import pytest
10
+ from unittest.mock import Mock
11
+ from datetime import datetime
12
+
13
+ from src.interface.enhanced_results_display_manager import (
14
+ EnhancedResultsDisplayManager,
15
+ EnhancedDisplayConfig,
16
+ ContentSection,
17
+ SectionType
18
+ )
19
+ from src.interface.visual_separation_manager import (
20
+ VisualSeparationManager,
21
+ ContentType,
22
+ VisualStyle
23
+ )
24
+ from src.interface.provider_summary_formatter import (
25
+ ProviderSummaryFormatter,
26
+ PatientData,
27
+ ClassificationData
28
+ )
29
+ from src.core.provider_summary_generator import ProviderSummary
30
+
31
+
32
+ class TestEnhancedResultsDisplayManager:
33
+ """Test cases for EnhancedResultsDisplayManager."""
34
+
35
+ def setup_method(self):
36
+ """Set up test fixtures."""
37
+ self.display_manager = EnhancedResultsDisplayManager()
38
+ self.config = EnhancedDisplayConfig()
39
+
40
+ def test_initialization_with_default_config(self):
41
+ """Test initialization with default configuration."""
42
+ manager = EnhancedResultsDisplayManager()
43
+ assert manager.config is not None
44
+ assert manager.config.ai_analysis_icon == "🤖"
45
+ assert manager.config.patient_message_icon == "💬"
46
+ assert manager.config.provider_summary_icon == "📋"
47
+
48
+ def test_initialization_with_custom_config(self):
49
+ """Test initialization with custom configuration."""
50
+ custom_config = EnhancedDisplayConfig(
51
+ ai_analysis_icon="🔍",
52
+ patient_message_icon="📝",
53
+ use_color_coding=False
54
+ )
55
+ manager = EnhancedResultsDisplayManager(custom_config)
56
+ assert manager.config.ai_analysis_icon == "🔍"
57
+ assert manager.config.patient_message_icon == "📝"
58
+ assert manager.config.use_color_coding is False
59
+
60
+ def test_format_ai_analysis_section_basic(self):
61
+ """Test basic AI analysis section formatting."""
62
+ result = self.display_manager.format_ai_analysis_section(
63
+ classification="RED",
64
+ indicators=["Loss of meaning", "Spiritual distress"],
65
+ reasoning="Patient expressed existential concerns"
66
+ )
67
+
68
+ assert "AI Analysis - RED FLAG" in result
69
+ assert "Loss of meaning" in result
70
+ assert "Spiritual distress" in result
71
+ assert "Patient expressed existential concerns" in result
72
+ assert self.config.ai_analysis_icon in result
73
+
74
+ def test_format_ai_analysis_section_with_confidence(self):
75
+ """Test AI analysis section formatting with confidence score."""
76
+ result = self.display_manager.format_ai_analysis_section(
77
+ classification="YELLOW",
78
+ indicators=["Mild distress"],
79
+ reasoning="Unclear indicators",
80
+ confidence=0.75
81
+ )
82
+
83
+ assert "AI Analysis - YELLOW FLAG" in result
84
+ assert "Confidence: 75%" in result
85
+ assert "Mild distress" in result
86
+
87
+ def test_format_ai_analysis_section_no_indicators(self):
88
+ """Test AI analysis section formatting with no indicators."""
89
+ result = self.display_manager.format_ai_analysis_section(
90
+ classification="GREEN",
91
+ indicators=[],
92
+ reasoning="Normal conversation"
93
+ )
94
+
95
+ assert "AI Analysis - GREEN FLAG" in result
96
+ assert "Normal conversation" in result
97
+ # Should not contain indicators section when empty
98
+ assert "Indicators:" not in result
99
+
100
+ def test_format_patient_message_section(self):
101
+ """Test patient message section formatting."""
102
+ message = "I'm feeling lost and don't know what to do."
103
+ result = self.display_manager.format_patient_message_section(message)
104
+
105
+ assert "Patient Message" in result
106
+ # Check for HTML-escaped version of the message
107
+ assert "I&#x27;m feeling lost and don&#x27;t know what to do." in result
108
+ assert self.config.patient_message_icon in result
109
+
110
+ def test_format_patient_message_section_html_escaping(self):
111
+ """Test patient message section with HTML characters."""
112
+ message = "I feel <confused> & don't know what's \"right\""
113
+ result = self.display_manager.format_patient_message_section(message)
114
+
115
+ assert "&lt;confused&gt;" in result
116
+ assert "&amp;" in result
117
+ assert "&quot;right&quot;" in result
118
+
119
+ def test_format_provider_summary_section(self):
120
+ """Test provider summary section formatting."""
121
+ # Create mock provider summary
122
+ summary = Mock(spec=ProviderSummary)
123
+ summary.patient_name = "John Doe"
124
+ summary.patient_phone = "(555) 123-4567"
125
+ summary.urgency_level = "URGENT"
126
+ summary.follow_up_timeline = "Within 24 hours"
127
+ summary.situation_description = "Patient experiencing spiritual crisis"
128
+ summary.indicators = ["Loss of faith", "Existential questioning"]
129
+
130
+ result = self.display_manager.format_provider_summary_section(summary)
131
+
132
+ assert "Provider Summary" in result
133
+ assert "John Doe" in result
134
+ assert "(555) 123-4567" in result
135
+ assert "URGENT" in result
136
+ assert "Loss of faith" in result
137
+ assert "Existential questioning" in result
138
+
139
+ def test_create_visual_separators(self):
140
+ """Test visual separator creation."""
141
+ separators = self.display_manager.create_visual_separators()
142
+
143
+ assert "section_break" in separators
144
+ assert "content_divider" in separators
145
+ assert "major_break" in separators
146
+
147
+ # Check that separators contain HTML
148
+ for separator in separators.values():
149
+ assert "<div" in separator
150
+
151
+ def test_apply_section_styling(self):
152
+ """Test section styling application."""
153
+ content = "Test content"
154
+
155
+ # Test AI analysis styling
156
+ result = self.display_manager.apply_section_styling(content, SectionType.AI_ANALYSIS)
157
+ assert content in result
158
+ assert "background-color: #f8f9fa" in result
159
+
160
+ # Test patient message styling
161
+ result = self.display_manager.apply_section_styling(content, SectionType.PATIENT_MESSAGE)
162
+ assert content in result
163
+ assert "background-color: #f0f7ff" in result
164
+
165
+ def test_format_combined_results_all_sections(self):
166
+ """Test combined results formatting with all sections."""
167
+ ai_analysis = {
168
+ "classification": "RED",
169
+ "indicators": ["Spiritual crisis"],
170
+ "reasoning": "Patient in distress",
171
+ "confidence": 0.9
172
+ }
173
+
174
+ patient_message = "I don't see the point anymore"
175
+
176
+ provider_summary = Mock(spec=ProviderSummary)
177
+ provider_summary.patient_name = "Jane Smith"
178
+ provider_summary.patient_phone = "(555) 987-6543"
179
+ provider_summary.urgency_level = "IMMEDIATE"
180
+ provider_summary.follow_up_timeline = "Immediately"
181
+ provider_summary.situation_description = "Crisis situation"
182
+ provider_summary.indicators = ["Hopelessness"]
183
+
184
+ result = self.display_manager.format_combined_results(
185
+ ai_analysis=ai_analysis,
186
+ patient_message=patient_message,
187
+ provider_summary=provider_summary
188
+ )
189
+
190
+ assert "AI Analysis - RED FLAG" in result
191
+ assert "Patient Message" in result
192
+ assert "Provider Summary" in result
193
+ assert "Jane Smith" in result
194
+ # Check for HTML-escaped version of the patient message
195
+ assert "I don&#x27;t see the point anymore" in result
196
+
197
+ def test_format_combined_results_empty(self):
198
+ """Test combined results formatting with no content."""
199
+ result = self.display_manager.format_combined_results()
200
+
201
+ assert "No content to display" in result
202
+
203
+
204
+ class TestVisualSeparationManager:
205
+ """Test cases for VisualSeparationManager."""
206
+
207
+ def setup_method(self):
208
+ """Set up test fixtures."""
209
+ self.visual_manager = VisualSeparationManager()
210
+
211
+ def test_initialization(self):
212
+ """Test visual separation manager initialization."""
213
+ assert self.visual_manager.styles is not None
214
+ assert ContentType.AI_ANALYSIS in self.visual_manager.styles
215
+ assert ContentType.PATIENT_MESSAGE in self.visual_manager.styles
216
+ assert ContentType.PROVIDER_SUMMARY in self.visual_manager.styles
217
+
218
+ def test_create_ai_analysis_styling(self):
219
+ """Test AI analysis styling creation."""
220
+ styling = self.visual_manager.create_ai_analysis_styling()
221
+
222
+ assert "container" in styling
223
+ assert "header" in styling
224
+ assert "icon" in styling
225
+ assert "content" in styling
226
+
227
+ # Check that styling contains CSS properties
228
+ assert "border:" in styling["container"]
229
+ assert "background-color:" in styling["container"]
230
+
231
+ def test_create_patient_message_styling(self):
232
+ """Test patient message styling creation."""
233
+ styling = self.visual_manager.create_patient_message_styling()
234
+
235
+ assert "container" in styling
236
+ assert "header" in styling
237
+ assert "message_box" in styling
238
+
239
+ # Check for patient message specific colors
240
+ assert "#4a90e2" in styling["container"]
241
+
242
+ def test_create_provider_summary_styling(self):
243
+ """Test provider summary styling creation."""
244
+ styling = self.visual_manager.create_provider_summary_styling()
245
+
246
+ assert "container" in styling
247
+ assert "header" in styling
248
+ assert "info_box" in styling
249
+ assert "urgency_box" in styling
250
+
251
+ def test_generate_section_separators(self):
252
+ """Test section separator generation."""
253
+ separators = self.visual_manager.generate_section_separators()
254
+
255
+ assert "light" in separators
256
+ assert "medium" in separators
257
+ assert "heavy" in separators
258
+ assert "section_break" in separators
259
+
260
+ # Check that all separators are HTML
261
+ for separator in separators.values():
262
+ assert "<div" in separator
263
+
264
+ def test_get_classification_styling(self):
265
+ """Test classification-specific styling."""
266
+ red_styling = self.visual_manager.get_classification_styling("RED")
267
+ yellow_styling = self.visual_manager.get_classification_styling("YELLOW")
268
+ green_styling = self.visual_manager.get_classification_styling("GREEN")
269
+
270
+ assert "#dc3545" in red_styling["badge"]
271
+ assert "#ffc107" in yellow_styling["badge"]
272
+ assert "#28a745" in green_styling["badge"]
273
+
274
+ def test_get_urgency_styling(self):
275
+ """Test urgency-specific styling."""
276
+ immediate_styling = self.visual_manager.get_urgency_styling("IMMEDIATE")
277
+ urgent_styling = self.visual_manager.get_urgency_styling("URGENT")
278
+ standard_styling = self.visual_manager.get_urgency_styling("STANDARD")
279
+
280
+ assert "#dc3545" in immediate_styling["badge"]
281
+ assert "#fd7e14" in urgent_styling["badge"]
282
+ assert "#28a745" in standard_styling["badge"]
283
+
284
+ def test_apply_consistent_formatting_multiple_sections(self):
285
+ """Test consistent formatting with multiple sections."""
286
+ sections = [
287
+ {"type": "ai_analysis", "content": "AI analysis content"},
288
+ {"type": "patient_message", "content": "Patient message content"},
289
+ {"type": "provider_summary", "content": "Provider summary content"}
290
+ ]
291
+
292
+ result = self.visual_manager.apply_consistent_formatting(sections)
293
+
294
+ assert "AI analysis content" in result
295
+ assert "Patient message content" in result
296
+ assert "Provider summary content" in result
297
+ # Should contain section separators between sections
298
+ assert "---" in result
299
+
300
+ def test_apply_consistent_formatting_empty_sections(self):
301
+ """Test consistent formatting with empty sections list."""
302
+ result = self.visual_manager.apply_consistent_formatting([])
303
+
304
+ assert "No content to display" in result
305
+
306
+ def test_apply_consistent_formatting_single_section(self):
307
+ """Test consistent formatting with single section."""
308
+ sections = [
309
+ {"type": "ai_analysis", "content": "Single section content"}
310
+ ]
311
+
312
+ result = self.visual_manager.apply_consistent_formatting(sections)
313
+
314
+ assert "Single section content" in result
315
+ # Should not contain separators for single section
316
+ assert result.count("---") == 0
317
+
318
+ def test_create_icon_styling(self):
319
+ """Test icon styling creation for different content types."""
320
+ ai_styling = self.visual_manager.create_icon_styling(ContentType.AI_ANALYSIS)
321
+ patient_styling = self.visual_manager.create_icon_styling(ContentType.PATIENT_MESSAGE)
322
+ provider_styling = self.visual_manager.create_icon_styling(ContentType.PROVIDER_SUMMARY)
323
+
324
+ # All should contain base styling
325
+ assert "font-size: 1.2em" in ai_styling
326
+ assert "margin-right: 8px" in patient_styling
327
+ assert "color:" in provider_styling
328
+
329
+
330
+ class TestProviderSummaryFormatter:
331
+ """Test cases for ProviderSummaryFormatter."""
332
+
333
+ def setup_method(self):
334
+ """Set up test fixtures."""
335
+ self.formatter = ProviderSummaryFormatter()
336
+
337
+ self.sample_patient_data = PatientData(
338
+ name="John Doe",
339
+ age=45,
340
+ gender="male",
341
+ phone="(555) 123-4567",
342
+ medical_history=["Diabetes", "Hypertension"],
343
+ expressed_concerns=["Loss of faith", "Feeling hopeless"],
344
+ patient_input="I don't know what to believe anymore"
345
+ )
346
+
347
+ self.sample_classification_data = ClassificationData(
348
+ classification="RED",
349
+ spiritual_concern_type="existential crisis",
350
+ consent_given=True
351
+ )
352
+
353
+ def test_build_demographic_section(self):
354
+ """Test demographic section building."""
355
+ result = self.formatter.build_demographic_section(self.sample_patient_data)
356
+
357
+ assert "John Doe is a 45-year-old male" == result
358
+
359
+ def test_build_demographic_section_no_gender(self):
360
+ """Test demographic section with no gender specified."""
361
+ patient_data = PatientData(
362
+ name="Jane Smith",
363
+ age=30,
364
+ gender="",
365
+ phone="",
366
+ medical_history=[],
367
+ expressed_concerns=[],
368
+ patient_input=""
369
+ )
370
+
371
+ result = self.formatter.build_demographic_section(patient_data)
372
+ assert "Jane Smith is a 30-year-old individual" == result
373
+
374
+ def test_build_medical_history_section_multiple(self):
375
+ """Test medical history section with multiple conditions."""
376
+ result = self.formatter.build_medical_history_section(
377
+ ["Diabetes", "Hypertension", "Arthritis"]
378
+ )
379
+
380
+ assert "with clinical history of Diabetes, Hypertension, and Arthritis" == result
381
+
382
+ def test_build_medical_history_section_single(self):
383
+ """Test medical history section with single condition."""
384
+ result = self.formatter.build_medical_history_section(["Diabetes"])
385
+
386
+ assert "with clinical history of Diabetes" == result
387
+
388
+ def test_build_medical_history_section_empty(self):
389
+ """Test medical history section with no conditions."""
390
+ result = self.formatter.build_medical_history_section([])
391
+
392
+ assert "with no significant medical history documented" == result
393
+
394
+ def test_build_spiritual_concerns_section(self):
395
+ """Test spiritual concerns section building."""
396
+ result = self.formatter.build_spiritual_concerns_section(
397
+ ["Loss of faith", "Feeling hopeless"],
398
+ "existential crisis",
399
+ "RED"
400
+ )
401
+
402
+ expected = ("The patient expressed Loss of faith and Feeling hopeless, "
403
+ "which may indicate existential crisis, resulting in generation of a RED FLAG")
404
+ assert expected == result
405
+
406
+ def test_build_contact_section_with_consent(self):
407
+ """Test contact section with consent given."""
408
+ result = self.formatter.build_contact_section("(555) 123-4567", True)
409
+
410
+ expected = ("The patient has given consent to be contacted by the spiritual care team. "
411
+ "The preferred contact number is (555) 123-4567")
412
+ assert expected == result
413
+
414
+ def test_build_contact_section_no_consent(self):
415
+ """Test contact section without consent."""
416
+ result = self.formatter.build_contact_section("(555) 123-4567", False)
417
+
418
+ expected = ("The patient has not yet provided consent for spiritual care contact. "
419
+ "The preferred contact number is (555) 123-4567")
420
+ assert expected == result
421
+
422
+ def test_add_patient_quote_section(self):
423
+ """Test patient quote section formatting."""
424
+ quote = "I don't know what to believe anymore"
425
+ result = self.formatter.add_patient_quote_section(quote)
426
+
427
+ expected = 'Patient reported: "I don\'t know what to believe anymore"'
428
+ assert expected == result
429
+
430
+ def test_add_patient_quote_section_empty(self):
431
+ """Test patient quote section with empty quote."""
432
+ result = self.formatter.add_patient_quote_section("")
433
+ assert "" == result
434
+
435
+ def test_format_coherent_paragraph(self):
436
+ """Test complete coherent paragraph formatting."""
437
+ result = self.formatter.format_coherent_paragraph(
438
+ self.sample_patient_data,
439
+ self.sample_classification_data
440
+ )
441
+
442
+ # Check that all required elements are present
443
+ assert "John Doe is a 45-year-old male" in result
444
+ assert "clinical history of Diabetes and Hypertension" in result
445
+ assert "Loss of faith and Feeling hopeless" in result
446
+ assert "existential crisis" in result
447
+ assert "RED FLAG" in result
448
+ assert "consent to be contacted" in result
449
+ assert "(555) 123-4567" in result
450
+
451
+ # Check that it's a single paragraph (no line breaks except at end)
452
+ lines = result.split('\n')
453
+ assert len([line for line in lines if line.strip()]) == 1
454
+
455
+ def test_format_enhanced_summary_with_quote(self):
456
+ """Test enhanced summary formatting with patient quote."""
457
+ result = self.formatter.format_enhanced_summary(
458
+ self.sample_patient_data,
459
+ self.sample_classification_data
460
+ )
461
+
462
+ # Should contain both paragraph and quote
463
+ assert "John Doe is a 45-year-old male" in result
464
+ assert 'Patient reported: "I don\'t know what to believe anymore"' in result
465
+
466
+ # Should have paragraph and quote separated by double newline
467
+ parts = result.split('\n\n')
468
+ assert len(parts) == 2
469
+
470
+
471
+ if __name__ == "__main__":
472
+ pytest.main([__file__])
tests/unit/test_improved_classification_prompt_manager.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unit tests for ImprovedClassificationPromptManager.
4
+
5
+ Tests the implementation of updated classification prompts with new red flag definitions
6
+ and explicit indicators based on medical professional feedback.
7
+
8
+ Requirements: 4.1, 4.2, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6
9
+ """
10
+
11
+ import pytest
12
+ from src.core.improved_classification_prompt_manager import (
13
+ ImprovedClassificationPromptManager,
14
+ ClassificationResult,
15
+ create_improved_classification_prompt_manager
16
+ )
17
+
18
+
19
+ class TestImprovedClassificationPromptManager:
20
+ """Test suite for ImprovedClassificationPromptManager."""
21
+
22
+ def setup_method(self):
23
+ """Set up test fixtures."""
24
+ self.manager = create_improved_classification_prompt_manager()
25
+
26
+ def test_updated_red_flag_definition(self):
27
+ """
28
+ Test updated red flag definition - Requirements 4.1, 4.2.
29
+
30
+ Verifies that the new definition is broader and includes active distress,
31
+ not just crisis situations.
32
+ """
33
+ definition = self.manager.get_updated_red_flag_definition()
34
+
35
+ # Check that definition contains key phrases from requirements
36
+ assert "caused by or actively causing" in definition
37
+ assert "emotional or spiritual distress" in definition
38
+
39
+ # Ensure it's not empty
40
+ assert len(definition.strip()) > 0
41
+
42
+ def test_explicit_red_indicators_completeness(self):
43
+ """
44
+ Test explicit red indicators - Requirements 5.2, 5.3, 5.4, 5.5, 5.6.
45
+
46
+ Verifies that all five required explicit indicators are present.
47
+ """
48
+ indicators = self.manager.get_explicit_red_indicators()
49
+
50
+ # Check that all 5 required indicators are present
51
+ expected_indicators = [
52
+ "Complex grief",
53
+ "Loss of a loved one",
54
+ "Doubt about meaning of life",
55
+ "Doubt about meaning of suffering",
56
+ "Doubt about personal dignity"
57
+ ]
58
+
59
+ assert len(indicators) == 5
60
+ for expected in expected_indicators:
61
+ assert expected in indicators
62
+
63
+ def test_yellow_flag_criteria(self):
64
+ """
65
+ Test yellow flag criteria - Requirements 6.1, 6.2, 6.3, 6.4, 6.5.
66
+
67
+ Verifies that yellow flag criteria include appropriate situations
68
+ requiring further investigation.
69
+ """
70
+ criteria = self.manager.get_yellow_flag_criteria()
71
+
72
+ # Check that criteria are not empty
73
+ assert len(criteria) > 0
74
+
75
+ # Check for key criteria from requirements
76
+ criteria_text = " ".join(criteria).lower()
77
+ assert "sadness without additional context" in criteria_text
78
+ assert "further clarification" in criteria_text or "further investigation" in criteria_text
79
+
80
+ def test_enhanced_prompt_structure(self):
81
+ """
82
+ Test enhanced prompt generation - Requirements 5.1, 5.7.
83
+
84
+ Verifies that the prompt contains all required sections and indicators.
85
+ """
86
+ prompt = self.manager.build_enhanced_classification_prompt()
87
+
88
+ # Check that prompt contains all required sections
89
+ required_sections = [
90
+ "<system_role>",
91
+ "<updated_red_flag_definition>",
92
+ "<explicit_red_indicators>",
93
+ "<yellow_flag_criteria>",
94
+ "<classification_logic>",
95
+ "<output_format>"
96
+ ]
97
+
98
+ for section in required_sections:
99
+ assert section in prompt
100
+
101
+ # Check that all explicit indicators are in the prompt
102
+ red_indicators = self.manager.get_explicit_red_indicators()
103
+ for indicator in red_indicators:
104
+ assert indicator in prompt
105
+
106
+ def test_classification_validation_valid_red(self):
107
+ """
108
+ Test classification validation for valid red classification.
109
+
110
+ Requirements: 5.7, 4.1, 4.2
111
+ """
112
+ # Test valid red classification with explicit indicator
113
+ valid_red = ClassificationResult(
114
+ classification="red",
115
+ confidence=0.9,
116
+ indicators=["complex grief", "loss"],
117
+ reasoning="Complex grief detected",
118
+ red_flag_indicators=["Complex grief"],
119
+ yellow_flag_indicators=[],
120
+ is_valid=False
121
+ )
122
+
123
+ assert self.manager.validate_classification_consistency(valid_red) == True
124
+
125
+ def test_classification_validation_invalid_simple_sadness(self):
126
+ """
127
+ Test classification validation rejects simple sadness as red.
128
+
129
+ Requirements: 3.1, 6.1
130
+ """
131
+ # Test invalid classification (simple sadness as red without context)
132
+ invalid_red = ClassificationResult(
133
+ classification="red",
134
+ confidence=0.8,
135
+ indicators=["feeling sad"],
136
+ reasoning="Sadness",
137
+ red_flag_indicators=[],
138
+ yellow_flag_indicators=[],
139
+ is_valid=False
140
+ )
141
+
142
+ assert self.manager.validate_classification_consistency(invalid_red) == False
143
+
144
+ def test_classification_validation_meaning_loss_should_be_red(self):
145
+ """
146
+ Test that loss of meaning/purpose should be classified as red.
147
+
148
+ Requirements: 3.2
149
+ """
150
+ # Test loss of meaning should be red
151
+ meaning_loss = ClassificationResult(
152
+ classification="yellow",
153
+ confidence=0.7,
154
+ indicators=["loss of meaning in life"],
155
+ reasoning="Loss of meaning",
156
+ red_flag_indicators=[],
157
+ yellow_flag_indicators=["loss of meaning"],
158
+ is_valid=False
159
+ )
160
+
161
+ assert self.manager.validate_classification_consistency(meaning_loss) == False
162
+
163
+ def test_classification_validation_invalid_classification_type(self):
164
+ """Test validation rejects invalid classification types."""
165
+ invalid_classification = ClassificationResult(
166
+ classification="blue", # Invalid
167
+ confidence=0.8,
168
+ indicators=["test"],
169
+ reasoning="Test",
170
+ red_flag_indicators=[],
171
+ yellow_flag_indicators=[],
172
+ is_valid=False
173
+ )
174
+
175
+ assert self.manager.validate_classification_consistency(invalid_classification) == False
176
+
177
+ def test_classification_validation_invalid_confidence(self):
178
+ """Test validation rejects invalid confidence values."""
179
+ invalid_confidence = ClassificationResult(
180
+ classification="red",
181
+ confidence=1.5, # Invalid - should be 0.0-1.0
182
+ indicators=["test"],
183
+ reasoning="Test",
184
+ red_flag_indicators=[],
185
+ yellow_flag_indicators=[],
186
+ is_valid=False
187
+ )
188
+
189
+ assert self.manager.validate_classification_consistency(invalid_confidence) == False
190
+
191
+ def test_classification_guidelines(self):
192
+ """Test classification guidelines content."""
193
+ guidelines = self.manager.get_classification_guidelines()
194
+
195
+ # Check that all classification types are present
196
+ assert "red" in guidelines
197
+ assert "yellow" in guidelines
198
+ assert "green" in guidelines
199
+
200
+ # Check content quality
201
+ assert len(guidelines["red"]) > 0
202
+ assert len(guidelines["yellow"]) > 0
203
+ assert len(guidelines["green"]) > 0
204
+
205
+ # Check for key terms
206
+ assert "spiritual" in guidelines["red"]
207
+ assert "further investigation" in guidelines["yellow"]
208
+ assert "No signs" in guidelines["green"]
209
+
210
+ def test_create_classification_result(self):
211
+ """Test classification result creation with validation."""
212
+ result = self.manager.create_classification_result(
213
+ classification="red",
214
+ confidence=0.9,
215
+ indicators=["complex grief"],
216
+ reasoning="Complex grief detected",
217
+ red_flag_indicators=["Complex grief"]
218
+ )
219
+
220
+ assert result.classification == "red"
221
+ assert result.confidence == 0.9
222
+ assert result.indicators == ["complex grief"]
223
+ assert result.reasoning == "Complex grief detected"
224
+ assert result.red_flag_indicators == ["Complex grief"]
225
+ assert result.yellow_flag_indicators == []
226
+ assert result.is_valid == True
227
+
228
+ def test_create_classification_result_with_validation_failure(self):
229
+ """Test classification result creation that fails validation."""
230
+ result = self.manager.create_classification_result(
231
+ classification="red",
232
+ confidence=0.8,
233
+ indicators=["почуття смутку"], # Simple sadness without context
234
+ reasoning="Смуток",
235
+ red_flag_indicators=[],
236
+ yellow_flag_indicators=[]
237
+ )
238
+
239
+ assert result.classification == "red"
240
+ assert result.is_valid == False # Should fail validation
241
+
242
+ def test_factory_function(self):
243
+ """Test factory function creates proper instance."""
244
+ manager = create_improved_classification_prompt_manager()
245
+
246
+ assert isinstance(manager, ImprovedClassificationPromptManager)
247
+
248
+ # Test that it works properly
249
+ definition = manager.get_updated_red_flag_definition()
250
+ assert len(definition) > 0
251
+
252
+ indicators = manager.get_explicit_red_indicators()
253
+ assert len(indicators) == 5
254
+
255
+
256
+ class TestClassificationResult:
257
+ """Test suite for ClassificationResult data class."""
258
+
259
+ def test_classification_result_creation(self):
260
+ """Test ClassificationResult creation."""
261
+ result = ClassificationResult(
262
+ classification="red",
263
+ confidence=0.9,
264
+ indicators=["test indicator"],
265
+ reasoning="test reasoning",
266
+ red_flag_indicators=["red indicator"],
267
+ yellow_flag_indicators=["yellow indicator"],
268
+ is_valid=True
269
+ )
270
+
271
+ assert result.classification == "red"
272
+ assert result.confidence == 0.9
273
+ assert result.indicators == ["test indicator"]
274
+ assert result.reasoning == "test reasoning"
275
+ assert result.red_flag_indicators == ["red indicator"]
276
+ assert result.yellow_flag_indicators == ["yellow indicator"]
277
+ assert result.is_valid == True
278
+
279
+ def test_classification_result_defaults(self):
280
+ """Test ClassificationResult with default values."""
281
+ result = ClassificationResult(
282
+ classification="green",
283
+ confidence=0.7,
284
+ indicators=[],
285
+ reasoning="No indicators found",
286
+ red_flag_indicators=[],
287
+ yellow_flag_indicators=[],
288
+ is_valid=True
289
+ )
290
+
291
+ assert result.red_flag_indicators == []
292
+ assert result.yellow_flag_indicators == []
293
+
294
+
295
+ if __name__ == "__main__":
296
+ pytest.main([__file__])
tests/unit/test_spiritual_monitor_improved_logic.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unit tests for SpiritualMonitor improved classification logic.
4
+
5
+ Tests the enhanced classification logic for specific cases:
6
+ - Proper handling of "feeling sad" (Requirements 3.1, 6.1)
7
+ - Recognition of "loss of meaning and purpose" as red indicator (Requirement 3.2)
8
+ - Validation for yellow flags in unclear cases (Requirements 6.2, 6.3)
9
+
10
+ Requirements: 3.1, 3.2, 6.1, 6.2, 6.3
11
+ """
12
+
13
+ import pytest
14
+ from unittest.mock import Mock, MagicMock
15
+ from src.core.spiritual_monitor import SpiritualMonitor, create_spiritual_monitor
16
+ from src.core.spiritual_state import SpiritualState, SpiritualAssessment
17
+ from src.core.ai_client import AIClientManager
18
+
19
+
20
+ class TestSpiritualMonitorImprovedLogic:
21
+ """Test suite for SpiritualMonitor improved classification logic."""
22
+
23
+ def setup_method(self):
24
+ """Set up test fixtures."""
25
+ # Mock AI client
26
+ self.mock_api_client = Mock(spec=AIClientManager)
27
+ self.mock_api_client.call_spiritual_api = Mock()
28
+
29
+ # Create spiritual monitor with mocked client
30
+ self.monitor = SpiritualMonitor(self.mock_api_client)
31
+
32
+ def test_simple_sadness_without_context_is_yellow(self):
33
+ """
34
+ Test that simple sadness without spiritual context is classified as YELLOW.
35
+
36
+ Requirements: 3.1, 6.1
37
+ """
38
+ # Test various simple sadness expressions
39
+ simple_sadness_messages = [
40
+ "I'm feeling sad today",
41
+ "I feel sad about my diagnosis",
42
+ "Feeling down lately",
43
+ "I am sad"
44
+ ]
45
+
46
+ for message in simple_sadness_messages:
47
+ # Mock LLM response for simple sadness (should be YELLOW)
48
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "yellow", "indicators": ["sadness"], "confidence": 0.7, "reasoning": "Simple sadness requires investigation"}'
49
+
50
+ # Classify the message
51
+ assessment = self.monitor.classify(message)
52
+
53
+ # Should be YELLOW for simple sadness
54
+ assert assessment.state == SpiritualState.YELLOW, f"Simple sadness should be YELLOW for: {message}"
55
+ assert "sadness" in " ".join(assessment.indicators).lower() or "investigation" in assessment.reasoning.lower(), f"Should indicate sadness or need for investigation: {message}"
56
+
57
+ def test_sadness_with_spiritual_context_allows_red(self):
58
+ """
59
+ Test that sadness with spiritual context can be classified as RED.
60
+
61
+ Requirements: 3.1, 6.1
62
+ """
63
+ # Test sadness with spiritual context
64
+ spiritual_sadness_messages = [
65
+ "I'm feeling sad and God has abandoned me",
66
+ "I feel sad because I've lost my faith",
67
+ "Feeling down about my spiritual life",
68
+ "I am sad and questioning God's plan"
69
+ ]
70
+
71
+ for message in spiritual_sadness_messages:
72
+ # Mock LLM response for spiritual sadness (should be RED)
73
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "red", "indicators": ["spiritual_distress", "sadness"], "confidence": 0.9, "reasoning": "Sadness with spiritual context indicates distress"}'
74
+
75
+ # Classify the message
76
+ assessment = self.monitor.classify(message)
77
+
78
+ # Should be RED for sadness with spiritual context
79
+ assert assessment.state == SpiritualState.RED, f"Spiritual sadness should be RED for: {message}"
80
+ assert "spiritual" in " ".join(assessment.indicators).lower() or "distress" in assessment.reasoning.lower(), f"Should indicate spiritual distress: {message}"
81
+
82
+ def test_loss_of_meaning_is_red(self):
83
+ """
84
+ Test that loss of meaning/purpose is always classified as RED.
85
+
86
+ Requirements: 3.2
87
+ """
88
+ # Test various loss of meaning expressions
89
+ meaning_loss_messages = [
90
+ "I've lost all meaning in life",
91
+ "Life has no purpose anymore",
92
+ "What's the point of living?",
93
+ "I feel like life is meaningless",
94
+ "I've lost my sense of purpose"
95
+ ]
96
+
97
+ for message in meaning_loss_messages:
98
+ # Mock LLM response for loss of meaning (should be RED)
99
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "red", "indicators": ["loss_of_meaning"], "confidence": 0.95, "reasoning": "Loss of meaning requires immediate support"}'
100
+
101
+ # Classify the message
102
+ assessment = self.monitor.classify(message)
103
+
104
+ # Should be RED for loss of meaning
105
+ assert assessment.state == SpiritualState.RED, f"Loss of meaning should be RED for: {message}"
106
+ assert "meaning" in " ".join(assessment.indicators).lower() or "meaning" in assessment.reasoning.lower(), f"Should indicate loss of meaning: {message}"
107
+ assert assessment.confidence >= 0.8, f"Should have high confidence for meaning loss: {message}"
108
+
109
+ def test_meaning_loss_classification_flow(self):
110
+ """
111
+ Test the full classification flow for loss of meaning messages.
112
+
113
+ Requirements: 3.2
114
+ """
115
+ message = "I feel like life has no meaning anymore"
116
+
117
+ # Mock LLM response for loss of meaning
118
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "red", "indicators": ["loss_of_meaning"], "confidence": 0.95, "reasoning": "Loss of meaning requires immediate support"}'
119
+
120
+ # Classify the message
121
+ assessment = self.monitor.classify(message)
122
+
123
+ # Should be RED due to loss of meaning
124
+ assert assessment.state == SpiritualState.RED
125
+ assert assessment.confidence >= 0.9 # High confidence for meaning loss
126
+ assert "meaning" in assessment.reasoning.lower()
127
+ assert len(assessment.indicators) > 0
128
+
129
+ def test_simple_sadness_classification_flow(self):
130
+ """
131
+ Test the full classification flow for simple sadness messages.
132
+
133
+ Requirements: 3.1, 6.1
134
+ """
135
+ message = "I'm feeling sad today"
136
+
137
+ # Mock LLM response for simple sadness
138
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "yellow", "indicators": ["sadness"], "confidence": 0.7, "reasoning": "Simple sadness requires investigation"}'
139
+
140
+ # Classify the message
141
+ assessment = self.monitor.classify(message)
142
+
143
+ # Should be YELLOW due to simple sadness without spiritual context
144
+ assert assessment.state == SpiritualState.YELLOW
145
+ assert "investigation" in assessment.reasoning.lower() or "sadness" in " ".join(assessment.indicators).lower()
146
+ assert len(assessment.indicators) > 0
147
+
148
+ def test_yellow_flag_validation_adds_clarification(self):
149
+ """
150
+ Test that YELLOW classifications get proper clarification for investigation.
151
+
152
+ Requirements: 6.2, 6.3
153
+ """
154
+ message = "I'm worried about something"
155
+
156
+ # Mock LLM response with proper YELLOW reasoning
157
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "yellow", "indicators": ["worried", "needs_clarification"], "confidence": 0.7, "reasoning": "Patient expressed worry - requires further investigation"}'
158
+
159
+ # Classify the message
160
+ assessment = self.monitor.classify(message)
161
+
162
+ assert assessment.state == SpiritualState.YELLOW
163
+ assert "investigation" in assessment.reasoning.lower() or "clarification" in assessment.reasoning.lower()
164
+ assert "needs_clarification" in assessment.indicators or any("clarification" in ind for ind in assessment.indicators) or "worried" in assessment.indicators
165
+
166
+ def test_enhanced_llm_classification_uses_improved_prompt(self):
167
+ """
168
+ Test that enhanced LLM classification uses the improved prompt.
169
+ """
170
+ message = "I'm having some concerns"
171
+
172
+ # Mock LLM response
173
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "yellow", "indicators": ["concerns"], "confidence": 0.7, "reasoning": "Needs investigation"}'
174
+
175
+ # Call enhanced LLM classification
176
+ assessment = self.monitor._classify_with_enhanced_llm(message)
177
+
178
+ # Verify LLM was called
179
+ self.mock_api_client.call_spiritual_api.assert_called_once()
180
+
181
+ # Check that enhanced prompt was used (contains improved classification rules)
182
+ call_args = self.mock_api_client.call_spiritual_api.call_args
183
+ system_prompt = call_args[1]['system_prompt']
184
+
185
+ # Should contain enhanced classification rules (check for English terms)
186
+ assert ("red flag" in system_prompt.lower() and
187
+ "yellow flag" in system_prompt.lower() and
188
+ "explicit_red_indicators" in system_prompt.lower()) or \
189
+ ("enhanced_classification_rules" in system_prompt.lower() or "improved" in system_prompt.lower())
190
+
191
+ # Verify assessment
192
+ assert assessment.state == SpiritualState.YELLOW
193
+ assert assessment.indicators == ["concerns"]
194
+ assert assessment.confidence == 0.7
195
+
196
+ def test_factory_function_creates_enhanced_monitor(self):
197
+ """
198
+ Test that factory function creates monitor with enhanced logic.
199
+ """
200
+ monitor = create_spiritual_monitor(self.mock_api_client)
201
+
202
+ assert isinstance(monitor, SpiritualMonitor)
203
+ assert hasattr(monitor, 'improved_prompt_manager')
204
+ assert monitor.api == self.mock_api_client
205
+
206
+ def test_performance_monitoring_includes_enhanced_metadata(self):
207
+ """
208
+ Test that performance monitoring includes enhanced logic metadata.
209
+ """
210
+ # Mock performance monitor
211
+ mock_performance_monitor = Mock()
212
+ monitor = SpiritualMonitor(self.mock_api_client, mock_performance_monitor)
213
+
214
+ message = "I feel sad"
215
+
216
+ # Mock LLM response
217
+ self.mock_api_client.call_spiritual_api.return_value = '{"state": "yellow", "indicators": ["sadness"], "confidence": 0.7, "reasoning": "Simple sadness requires investigation"}'
218
+
219
+ # Classify message (will trigger performance monitoring)
220
+ assessment = monitor.classify(message)
221
+
222
+ # Verify performance monitoring was called
223
+ mock_performance_monitor.track_execution.assert_called_once()
224
+
225
+ # Check metadata includes LLM-only logic flag
226
+ call_args = mock_performance_monitor.track_execution.call_args
227
+ metadata = call_args[1]['metadata']
228
+
229
+ assert metadata['llm_only_logic_used'] is True
230
+ assert 'classification_result' in metadata
231
+ assert 'indicators_count' in metadata
232
+
233
+
234
+ if __name__ == "__main__":
235
+ pytest.main([__file__])
tests/unit/test_ui_error_handler.py ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unit tests for UI Error Handler.
4
+
5
+ Tests error handling and recovery mechanisms for UI components.
6
+
7
+ Requirements: 9.1, 9.2, 9.3, 9.4
8
+ """
9
+
10
+ import pytest
11
+ from unittest.mock import Mock, patch
12
+ from datetime import datetime
13
+
14
+ from src.core.ui_error_handler import (
15
+ UIErrorHandler, UIError, ValidationResult, ErrorCategory, ErrorSeverity
16
+ )
17
+ from src.core.provider_summary_generator import ProviderSummary
18
+ from src.core.improved_classification_prompt_manager import ClassificationResult
19
+
20
+
21
+ class TestUIErrorHandler:
22
+ """Test cases for UIErrorHandler."""
23
+
24
+ def setup_method(self):
25
+ """Set up test fixtures."""
26
+ self.error_handler = UIErrorHandler()
27
+
28
+ def test_initialization(self):
29
+ """Test error handler initialization."""
30
+ assert self.error_handler is not None
31
+ assert hasattr(self.error_handler, 'fallback_templates')
32
+ assert hasattr(self.error_handler, 'validation_rules')
33
+
34
+ def test_validate_provider_summary_structure_valid(self):
35
+ """Test validation of valid provider summary."""
36
+ # Create valid summary
37
+ summary = ProviderSummary(
38
+ patient_name="John Doe",
39
+ patient_phone="555-123-4567",
40
+ classification="RED",
41
+ confidence=0.8,
42
+ reasoning="Patient expressing significant spiritual distress",
43
+ indicators=["Loss of meaning", "Spiritual questioning"],
44
+ severity_level="HIGH",
45
+ urgency_level="URGENT",
46
+ situation_description="Patient experiencing spiritual crisis requiring immediate attention",
47
+ recommended_actions=["Contact within 24 hours", "Assess support needs"]
48
+ )
49
+
50
+ result = self.error_handler.validate_provider_summary_structure(summary)
51
+
52
+ assert isinstance(result, ValidationResult)
53
+ assert result.is_valid
54
+ assert len(result.errors) == 0
55
+
56
+ def test_validate_provider_summary_structure_missing_contact(self):
57
+ """Test validation with missing contact information."""
58
+ # Create summary with missing contact info
59
+ summary = ProviderSummary(
60
+ patient_name="[Patient Name]",
61
+ patient_phone="[Phone Number]",
62
+ classification="RED",
63
+ confidence=0.8,
64
+ reasoning="Patient expressing significant spiritual distress",
65
+ indicators=["Loss of meaning"],
66
+ severity_level="HIGH",
67
+ urgency_level="URGENT"
68
+ )
69
+
70
+ result = self.error_handler.validate_provider_summary_structure(summary)
71
+
72
+ assert not result.is_valid
73
+ assert len(result.errors) >= 2 # Name and phone errors
74
+
75
+ # Check for specific errors
76
+ error_messages = [error.message for error in result.errors]
77
+ assert any("Patient name is missing" in msg for msg in error_messages)
78
+ assert any("Patient phone is missing" in msg for msg in error_messages)
79
+
80
+ def test_validate_provider_summary_structure_invalid_confidence(self):
81
+ """Test validation with invalid confidence value."""
82
+ summary = ProviderSummary(
83
+ patient_name="John Doe",
84
+ patient_phone="555-123-4567",
85
+ classification="RED",
86
+ confidence=1.5, # Invalid - out of range
87
+ reasoning="Test reasoning",
88
+ indicators=["Test indicator"]
89
+ )
90
+
91
+ result = self.error_handler.validate_provider_summary_structure(summary)
92
+
93
+ assert not result.is_valid
94
+ confidence_errors = [e for e in result.errors if e.field == "confidence"]
95
+ assert len(confidence_errors) > 0
96
+ assert "out of valid range" in confidence_errors[0].message
97
+
98
+ def test_validate_provider_summary_structure_missing_reasoning(self):
99
+ """Test validation with missing reasoning."""
100
+ summary = ProviderSummary(
101
+ patient_name="John Doe",
102
+ patient_phone="555-123-4567",
103
+ classification="RED",
104
+ confidence=0.8,
105
+ reasoning="", # Empty reasoning
106
+ indicators=["Test indicator"]
107
+ )
108
+
109
+ result = self.error_handler.validate_provider_summary_structure(summary)
110
+
111
+ assert not result.is_valid
112
+ reasoning_errors = [e for e in result.errors if e.field == "reasoning"]
113
+ assert len(reasoning_errors) > 0
114
+ assert "missing or insufficient" in reasoning_errors[0].message
115
+
116
+ def test_apply_fallback_template_missing_contact(self):
117
+ """Test applying fallback template for missing contact information."""
118
+ summary = ProviderSummary(
119
+ patient_name="[Patient Name]",
120
+ patient_phone="[Phone Number]",
121
+ classification="RED",
122
+ confidence=0.8,
123
+ reasoning="Test reasoning",
124
+ indicators=["Test indicator"]
125
+ )
126
+
127
+ fixed_summary = self.error_handler.apply_fallback_template(summary, "missing_contact")
128
+
129
+ assert fixed_summary.patient_name != "[Patient Name]"
130
+ assert fixed_summary.patient_phone != "[Phone Number]"
131
+ assert "Patient (Name Not Provided)" in fixed_summary.patient_name
132
+ assert "not available" in fixed_summary.patient_phone.lower()
133
+
134
+ def test_apply_fallback_template_missing_reasoning(self):
135
+ """Test applying fallback template for missing reasoning."""
136
+ summary = ProviderSummary(
137
+ patient_name="John Doe",
138
+ patient_phone="555-123-4567",
139
+ classification="RED",
140
+ confidence=0.8,
141
+ reasoning="", # Empty reasoning
142
+ indicators=["Loss of meaning", "Spiritual distress"]
143
+ )
144
+
145
+ fixed_summary = self.error_handler.apply_fallback_template(summary, "missing_reasoning")
146
+
147
+ assert len(fixed_summary.reasoning) > 10
148
+ assert "RED flag classification" in fixed_summary.reasoning
149
+ assert "Loss of meaning" in fixed_summary.reasoning
150
+
151
+ def test_apply_fallback_template_invalid_levels(self):
152
+ """Test applying fallback template for invalid severity/urgency levels."""
153
+ summary = ProviderSummary(
154
+ patient_name="John Doe",
155
+ patient_phone="555-123-4567",
156
+ classification="RED",
157
+ confidence=0.8,
158
+ reasoning="Test reasoning",
159
+ indicators=["Test indicator"],
160
+ severity_level="INVALID",
161
+ urgency_level="INVALID"
162
+ )
163
+
164
+ fixed_summary = self.error_handler.apply_fallback_template(summary, "invalid_levels")
165
+
166
+ assert fixed_summary.severity_level in ["CRITICAL", "HIGH", "MODERATE"]
167
+ assert fixed_summary.urgency_level in ["IMMEDIATE", "URGENT", "STANDARD"]
168
+
169
+ def test_apply_fallback_template_missing_actions(self):
170
+ """Test applying fallback template for missing recommended actions."""
171
+ summary = ProviderSummary(
172
+ patient_name="John Doe",
173
+ patient_phone="555-123-4567",
174
+ classification="RED",
175
+ confidence=0.8,
176
+ reasoning="Test reasoning",
177
+ indicators=["Test indicator"],
178
+ urgency_level="URGENT",
179
+ recommended_actions=[] # Empty actions
180
+ )
181
+
182
+ fixed_summary = self.error_handler.apply_fallback_template(summary, "missing_actions")
183
+
184
+ assert len(fixed_summary.recommended_actions) > 0
185
+ assert any("Contact patient" in action for action in fixed_summary.recommended_actions)
186
+
187
+ def test_create_degraded_display(self):
188
+ """Test creating degraded display for errors."""
189
+ error_context = "Test error occurred"
190
+ original_content = "<div>Original content</div>"
191
+
192
+ degraded_display = self.error_handler.create_degraded_display(error_context, original_content)
193
+
194
+ assert isinstance(degraded_display, str)
195
+ assert "Display Error Detected" in degraded_display
196
+ assert error_context in degraded_display
197
+ assert original_content in degraded_display
198
+ assert "degraded mode" in degraded_display
199
+
200
+ def test_create_degraded_display_no_content(self):
201
+ """Test creating degraded display without original content."""
202
+ error_context = "Test error occurred"
203
+
204
+ degraded_display = self.error_handler.create_degraded_display(error_context)
205
+
206
+ assert isinstance(degraded_display, str)
207
+ assert "Display Error Detected" in degraded_display
208
+ assert error_context in degraded_display
209
+ assert "Recovery Actions" in degraded_display
210
+
211
+ def test_handle_classification_error(self):
212
+ """Test handling classification errors."""
213
+ error = Exception("Test classification error")
214
+ input_data = {
215
+ 'message': 'I feel hopeless and lost',
216
+ 'classification': 'red',
217
+ 'confidence': 0.8
218
+ }
219
+
220
+ result = self.error_handler.handle_classification_error(error, input_data)
221
+
222
+ assert isinstance(result, ClassificationResult)
223
+ assert result.classification in ["red", "yellow", "green"]
224
+ assert 0.0 <= result.confidence <= 1.0
225
+ assert len(result.indicators) > 0
226
+ assert len(result.reasoning) > 0
227
+ assert not result.is_valid # Should be marked as invalid due to error
228
+
229
+ def test_handle_classification_error_critical_keywords(self):
230
+ """Test handling classification error with critical keywords."""
231
+ error = Exception("Test error")
232
+ input_data = {
233
+ 'message': 'I want to kill myself and end it all'
234
+ }
235
+
236
+ result = self.error_handler.handle_classification_error(error, input_data)
237
+
238
+ assert result.classification == "red"
239
+ assert result.confidence >= 0.8
240
+ assert any("Critical" in indicator for indicator in result.indicators)
241
+
242
+ def test_handle_classification_error_spiritual_keywords(self):
243
+ """Test handling classification error with spiritual keywords."""
244
+ error = Exception("Test error")
245
+ input_data = {
246
+ 'message': 'I have lost all meaning and purpose in life'
247
+ }
248
+
249
+ result = self.error_handler.handle_classification_error(error, input_data)
250
+
251
+ assert result.classification == "red"
252
+ assert result.confidence >= 0.6
253
+ assert any("Spiritual" in indicator for indicator in result.indicators)
254
+
255
+ def test_get_error_statistics(self):
256
+ """Test getting error statistics."""
257
+ errors = [
258
+ UIError(
259
+ category=ErrorCategory.VALIDATION,
260
+ severity=ErrorSeverity.HIGH,
261
+ message="Test error 1",
262
+ component="test_component"
263
+ ),
264
+ UIError(
265
+ category=ErrorCategory.VALIDATION,
266
+ severity=ErrorSeverity.MEDIUM,
267
+ message="Test error 2",
268
+ component="test_component"
269
+ ),
270
+ UIError(
271
+ category=ErrorCategory.FORMATTING,
272
+ severity=ErrorSeverity.HIGH,
273
+ message="Test error 3",
274
+ component="other_component"
275
+ )
276
+ ]
277
+
278
+ stats = self.error_handler.get_error_statistics(errors)
279
+
280
+ assert stats["total"] == 3
281
+ assert stats["by_category"]["validation"] == 2
282
+ assert stats["by_category"]["formatting"] == 1
283
+ assert stats["by_severity"]["high"] == 2
284
+ assert stats["by_severity"]["medium"] == 1
285
+ assert stats["by_component"]["test_component"] == 2
286
+ assert stats["by_component"]["other_component"] == 1
287
+
288
+ def test_get_error_statistics_empty(self):
289
+ """Test getting error statistics with empty list."""
290
+ stats = self.error_handler.get_error_statistics([])
291
+
292
+ assert stats["total"] == 0
293
+ assert stats["by_category"] == {}
294
+ assert stats["by_severity"] == {}
295
+
296
+ def test_validation_result_add_error(self):
297
+ """Test adding error to validation result."""
298
+ result = ValidationResult(is_valid=True)
299
+ error = UIError(
300
+ category=ErrorCategory.VALIDATION,
301
+ severity=ErrorSeverity.HIGH,
302
+ message="Test error",
303
+ component="test"
304
+ )
305
+
306
+ result.add_error(error)
307
+
308
+ assert not result.is_valid
309
+ assert len(result.errors) == 1
310
+ assert result.errors[0] == error
311
+
312
+ def test_validation_result_add_warning(self):
313
+ """Test adding warning to validation result."""
314
+ result = ValidationResult(is_valid=True)
315
+ warning = UIError(
316
+ category=ErrorCategory.VALIDATION,
317
+ severity=ErrorSeverity.LOW,
318
+ message="Test warning",
319
+ component="test"
320
+ )
321
+
322
+ result.add_warning(warning)
323
+
324
+ assert result.is_valid # Warnings don't invalidate
325
+ assert len(result.warnings) == 1
326
+ assert result.warnings[0] == warning
327
+
328
+ def test_validation_result_has_critical_errors(self):
329
+ """Test checking for critical errors."""
330
+ result = ValidationResult(is_valid=True)
331
+
332
+ # Add non-critical error
333
+ result.add_error(UIError(
334
+ category=ErrorCategory.VALIDATION,
335
+ severity=ErrorSeverity.HIGH,
336
+ message="High error",
337
+ component="test"
338
+ ))
339
+
340
+ assert not result.has_critical_errors()
341
+
342
+ # Add critical error
343
+ result.add_error(UIError(
344
+ category=ErrorCategory.VALIDATION,
345
+ severity=ErrorSeverity.CRITICAL,
346
+ message="Critical error",
347
+ component="test"
348
+ ))
349
+
350
+ assert result.has_critical_errors()
351
+
352
+ def test_validation_result_get_error_summary(self):
353
+ """Test getting error summary."""
354
+ result = ValidationResult(is_valid=True)
355
+
356
+ # No errors or warnings
357
+ summary = result.get_error_summary()
358
+ assert "No validation issues" in summary
359
+
360
+ # Add errors and warnings
361
+ result.add_error(UIError(
362
+ category=ErrorCategory.VALIDATION,
363
+ severity=ErrorSeverity.HIGH,
364
+ message="Test error",
365
+ component="test"
366
+ ))
367
+ result.add_warning(UIError(
368
+ category=ErrorCategory.VALIDATION,
369
+ severity=ErrorSeverity.LOW,
370
+ message="Test warning",
371
+ component="test"
372
+ ))
373
+
374
+ summary = result.get_error_summary()
375
+ assert "1 error(s)" in summary
376
+ assert "1 warning(s)" in summary
377
+
378
+ def test_ui_error_to_dict(self):
379
+ """Test converting UIError to dictionary."""
380
+ error = UIError(
381
+ category=ErrorCategory.VALIDATION,
382
+ severity=ErrorSeverity.HIGH,
383
+ message="Test error",
384
+ component="test_component",
385
+ field="test_field",
386
+ value="test_value",
387
+ suggestion="Test suggestion",
388
+ recovery_actions=["Action 1", "Action 2"]
389
+ )
390
+
391
+ error_dict = error.to_dict()
392
+
393
+ assert error_dict["category"] == "validation"
394
+ assert error_dict["severity"] == "high"
395
+ assert error_dict["message"] == "Test error"
396
+ assert error_dict["component"] == "test_component"
397
+ assert error_dict["field"] == "test_field"
398
+ assert error_dict["value"] == "test_value"
399
+ assert error_dict["suggestion"] == "Test suggestion"
400
+ assert error_dict["recovery_actions"] == ["Action 1", "Action 2"]
401
+
402
+
403
+ if __name__ == "__main__":
404
+ pytest.main([__file__])