Spaces:
Sleeping
Sleeping
| # enhanced_results_display_manager.py | |
| """ | |
| Enhanced Results Display Manager for UI Classification Improvements. | |
| This module provides enhanced display formatting for chat results with clear | |
| visual separation between AI analysis, patient messages, and provider summaries. | |
| Requirements: 1.1, 1.2, 7.1, 7.2 | |
| """ | |
| import html | |
| from typing import Dict, List, Optional, Any | |
| from dataclasses import dataclass | |
| from enum import Enum | |
| from src.core.provider_summary_generator import ProviderSummary | |
| from src.core.spiritual_state import SpiritualAssessment, SpiritualState | |
| from src.config.enhanced_display_config import ( | |
| EnhancedDisplayConfig, | |
| get_enhanced_display_config, | |
| EnhancedDisplayConfigManager | |
| ) | |
| class SectionType(Enum): | |
| """Types of content sections for display.""" | |
| AI_ANALYSIS = "ai_analysis" | |
| PATIENT_MESSAGE = "patient_message" | |
| PROVIDER_SUMMARY = "provider_summary" | |
| class ContentSection: | |
| """Represents a content section with styling information.""" | |
| section_type: SectionType | |
| content: str | |
| styling: Dict[str, str] | |
| icon: Optional[str] = None | |
| priority: int = 0 | |
| class ContentSection: | |
| """Represents a content section with styling information.""" | |
| section_type: SectionType | |
| content: str | |
| styling: Dict[str, str] | |
| icon: Optional[str] = None | |
| priority: int = 0 | |
| class EnhancedResultsDisplayManager: | |
| """ | |
| Enhanced Results Display Manager for improved UI formatting. | |
| Provides methods to format different types of content with clear visual | |
| separation and consistent styling according to requirements 1.1, 1.2, 7.1, 7.2. | |
| """ | |
| def __init__(self, config: Optional[EnhancedDisplayConfig] = None, config_manager: Optional[EnhancedDisplayConfigManager] = None): | |
| """ | |
| Initialize the display manager. | |
| Args: | |
| config: Optional configuration for display formatting | |
| config_manager: Optional configuration manager for dynamic updates | |
| """ | |
| self.config_manager = config_manager | |
| if config is not None: | |
| self.config = config | |
| elif self.config_manager is not None: | |
| self.config = self.config_manager.get_config() | |
| else: | |
| self.config = get_enhanced_display_config() | |
| # Initialize error handler for validation and recovery (lazy loaded) | |
| self._error_handler = None | |
| def error_handler(self): | |
| """Lazy load error handler to avoid circular imports.""" | |
| if self._error_handler is None: | |
| from src.core.ui_error_handler import UIErrorHandler | |
| self._error_handler = UIErrorHandler() | |
| return self._error_handler | |
| def format_ai_analysis_section( | |
| self, | |
| classification: str, | |
| indicators: List[str], | |
| reasoning: str, | |
| confidence: Optional[float] = None | |
| ) -> str: | |
| """ | |
| Format AI analysis section with clear labeling and visual styling. | |
| Args: | |
| classification: The classification result (RED/YELLOW/GREEN) | |
| indicators: List of distress indicators | |
| reasoning: AI reasoning for the classification | |
| confidence: Optional confidence score | |
| Returns: | |
| Formatted HTML string for AI analysis section | |
| Requirements: 1.1, 7.1 | |
| """ | |
| try: | |
| # Validate inputs | |
| if not classification: | |
| classification = "UNKNOWN" | |
| if not indicators: | |
| indicators = ["No indicators available"] | |
| if not reasoning: | |
| reasoning = "No reasoning provided" | |
| # Check if enhancements are enabled | |
| if not self.config.enabled: | |
| return self._format_basic_ai_analysis(classification, indicators, reasoning, confidence) | |
| # Get classification color | |
| color = self.config.get_classification_color(classification) | |
| section_config = self.config.get_section_config("ai_analysis") | |
| # Build confidence display | |
| confidence_text = "" | |
| if confidence is not None: | |
| confidence_percent = int(confidence * 100) | |
| confidence_text = f"<div style='margin: 5px 0; font-size: {self.config.small_font_size}; color: #666;'>Confidence: {confidence_percent}%</div>" | |
| # Build indicators list | |
| indicators_html = "" | |
| if indicators: | |
| indicators_list = "".join([f"<li>{html.escape(indicator)}</li>" for indicator in indicators]) | |
| indicators_html = f""" | |
| <div style='margin: 10px 0;'> | |
| <strong>Indicators:</strong> | |
| <ul style='margin: 5px 0 0 20px; padding: 0;'> | |
| {indicators_list} | |
| </ul> | |
| </div> | |
| """ | |
| # Build reasoning section | |
| reasoning_html = f""" | |
| <div style='margin: 10px 0;'> | |
| <strong>Reasoning:</strong> | |
| <div style='margin: 5px 0; padding: 8px; background-color: #f8f9fa; border-radius: 4px; font-style: italic;'> | |
| {html.escape(reasoning)} | |
| </div> | |
| </div> | |
| """ | |
| # Build icon display | |
| icon_html = "" | |
| if self.config.use_icons: | |
| icon_html = f"<span style='font-size: 1.2em; margin-right: 8px;'>{section_config.icon}</span>" | |
| # Combine all elements with enhanced styling | |
| content = f""" | |
| <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};'> | |
| <div style='display: flex; align-items: center; margin-bottom: 10px;'> | |
| {icon_html} | |
| <strong style='color: {color}; font-size: {self.config.header_font_size};'>AI Analysis - {classification} FLAG</strong> | |
| </div> | |
| {confidence_text} | |
| {indicators_html} | |
| {reasoning_html} | |
| </div> | |
| """ | |
| return content | |
| except Exception as e: | |
| # Handle formatting errors with degraded display | |
| error_context = f"AI analysis formatting failed: {str(e)}" | |
| fallback_content = self._format_basic_ai_analysis(classification, indicators, reasoning, confidence) | |
| return self.error_handler.create_degraded_display(error_context, fallback_content) | |
| def format_patient_message_section(self, message: str) -> str: | |
| """ | |
| Format patient message section with appropriate styling. | |
| Args: | |
| message: The patient's message content | |
| Returns: | |
| Formatted HTML string for patient message section | |
| Requirements: 1.2, 7.2 | |
| """ | |
| try: | |
| # Validate input | |
| if not message: | |
| message = "No message content available" | |
| # Check if enhancements are enabled | |
| if not self.config.enabled: | |
| return self._format_basic_patient_message(message) | |
| section_config = self.config.get_section_config("patient_message") | |
| # Build icon display | |
| icon_html = "" | |
| if self.config.use_icons: | |
| icon_html = f"<span style='font-size: 1.2em; margin-right: 8px;'>{section_config.icon}</span>" | |
| content = f""" | |
| <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};'> | |
| <div style='display: flex; align-items: center; margin-bottom: 10px;'> | |
| {icon_html} | |
| <strong style='color: {section_config.header_color}; font-size: {self.config.header_font_size};'>Patient Message</strong> | |
| </div> | |
| <div style='padding: 8px; background-color: white; border-radius: 4px; border-left: 4px solid {section_config.border_color};'> | |
| {html.escape(message)} | |
| </div> | |
| </div> | |
| """ | |
| return content | |
| except Exception as e: | |
| # Handle formatting errors with degraded display | |
| error_context = f"Patient message formatting failed: {str(e)}" | |
| fallback_content = self._format_basic_patient_message(message) | |
| return self.error_handler.create_degraded_display(error_context, fallback_content) | |
| def format_provider_summary_section(self, summary_data: ProviderSummary) -> str: | |
| """ | |
| Format provider summary section with enhanced styling. | |
| Args: | |
| summary_data: Provider summary data to format | |
| Returns: | |
| Formatted HTML string for provider summary section | |
| Requirements: 1.1, 1.2, 7.1, 7.2 | |
| """ | |
| try: | |
| # Validate and fix summary data if needed | |
| validation_result = self.error_handler.validate_provider_summary_structure(summary_data) | |
| if validation_result.has_critical_errors(): | |
| # Apply fallback template for critical errors | |
| summary_data = self.error_handler.apply_fallback_template(summary_data, "general") | |
| # Check if enhancements are enabled | |
| if not self.config.enabled: | |
| return self._format_basic_provider_summary(summary_data) | |
| section_config = self.config.get_section_config("provider_summary") | |
| # Get urgency color | |
| urgency_colors = { | |
| 'IMMEDIATE': self.config.classification_colors.red, | |
| 'URGENT': self.config.classification_colors.yellow, | |
| 'STANDARD': self.config.classification_colors.green | |
| } | |
| urgency_color = urgency_colors.get(summary_data.urgency_level, self.config.classification_colors.red) | |
| # Build patient info section | |
| patient_info = f""" | |
| <div style='margin: 10px 0;'> | |
| <strong>Patient Information:</strong> | |
| <div style='margin: 5px 0; padding: 8px; background-color: #f8f9fa; border-radius: 4px;'> | |
| <div><strong>Name:</strong> {html.escape(summary_data.patient_name)}</div> | |
| <div><strong>Phone:</strong> {html.escape(summary_data.patient_phone)}</div> | |
| </div> | |
| </div> | |
| """ | |
| # Build indicators section | |
| indicators_html = "" | |
| if summary_data.indicators: | |
| indicators_list = "".join([f"<li>{html.escape(indicator)}</li>" for indicator in summary_data.indicators]) | |
| indicators_html = f""" | |
| <div style='margin: 10px 0;'> | |
| <strong>Distress Indicators:</strong> | |
| <ul style='margin: 5px 0 0 20px; padding: 0;'> | |
| {indicators_list} | |
| </ul> | |
| </div> | |
| """ | |
| # Build situation description | |
| situation_html = f""" | |
| <div style='margin: 10px 0;'> | |
| <strong>Situation Overview:</strong> | |
| <div style='margin: 5px 0; padding: 8px; background-color: #fff3cd; border-radius: 4px; border-left: 4px solid {urgency_color};'> | |
| {html.escape(summary_data.situation_description)} | |
| </div> | |
| </div> | |
| """ | |
| # Build urgency information | |
| urgency_html = f""" | |
| <div style='margin: 10px 0;'> | |
| <strong>Urgency Level:</strong> | |
| <span style='color: {urgency_color}; font-weight: bold; margin-left: 8px;'>{summary_data.urgency_level}</span> | |
| <div style='margin: 5px 0; font-size: {self.config.small_font_size}; color: #666;'> | |
| Follow-up Timeline: {summary_data.follow_up_timeline} | |
| </div> | |
| </div> | |
| """ | |
| # Build icon display | |
| icon_html = "" | |
| if self.config.use_icons: | |
| icon_html = f"<span style='font-size: 1.2em; margin-right: 8px;'>{section_config.icon}</span>" | |
| # Add validation warnings if any | |
| warnings_html = "" | |
| if validation_result.warnings: | |
| warnings_list = "".join([f"<li>{warning.message}</li>" for warning in validation_result.warnings]) | |
| warnings_html = f""" | |
| <div style='margin: 10px 0; padding: 8px; background-color: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;'> | |
| <strong style='color: #856404;'>⚠️ Validation Warnings:</strong> | |
| <ul style='margin: 5px 0 0 20px; padding: 0; color: #856404;'> | |
| {warnings_list} | |
| </ul> | |
| </div> | |
| """ | |
| # Combine all elements with enhanced styling | |
| content = f""" | |
| <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};'> | |
| <div style='display: flex; align-items: center; margin-bottom: 10px;'> | |
| {icon_html} | |
| <strong style='color: {urgency_color}; font-size: {self.config.header_font_size};'>Provider Summary</strong> | |
| <span style='margin-left: 8px; font-size: {self.config.small_font_size}; color: #666;'>For Spiritual Care Team</span> | |
| </div> | |
| {patient_info} | |
| {urgency_html} | |
| {situation_html} | |
| {indicators_html} | |
| {warnings_html} | |
| </div> | |
| """ | |
| return content | |
| except Exception as e: | |
| # Handle formatting errors with degraded display | |
| error_context = f"Provider summary formatting failed: {str(e)}" | |
| fallback_content = self._format_basic_provider_summary(summary_data) | |
| return self.error_handler.create_degraded_display(error_context, fallback_content) | |
| def create_visual_separators(self) -> Dict[str, str]: | |
| """ | |
| Create visual separators for different content types. | |
| Returns: | |
| Dictionary mapping section types to separator HTML | |
| Requirements: 7.1, 7.2 | |
| """ | |
| # Check if separators are enabled | |
| if not self.config.use_visual_separators: | |
| return { | |
| "section_break": "<div style='margin: 10px 0;'></div>", | |
| "content_divider": "<div style='margin: 5px 0;'></div>", | |
| "major_break": "<div style='margin: 15px 0;'></div>" | |
| } | |
| separators = { | |
| "section_break": f""" | |
| <div style='margin: 20px 0; text-align: center;'> | |
| <hr style='border: none; border-top: 2px solid {self.config.separators.separator_color}; width: {self.config.separators.separator_width}; margin: 0 auto;'> | |
| <div style='margin: 10px 0; color: #666; font-size: {self.config.small_font_size};'>{self.config.separators.section_separator}</div> | |
| </div> | |
| """, | |
| "content_divider": f""" | |
| <div style='margin: 15px 0; border-top: {self.config.separators.content_divider};'></div> | |
| """, | |
| "major_break": f""" | |
| <div style='margin: 30px 0; text-align: center;'> | |
| <div style='display: inline-block; padding: 8px 16px; background-color: #f0f0f0; border-radius: 20px; color: #666; font-size: {self.config.small_font_size};'> | |
| {self.config.separators.major_break_symbol} | |
| </div> | |
| </div> | |
| """ | |
| } | |
| return separators | |
| def apply_section_styling(self, content: str, section_type: SectionType) -> str: | |
| """ | |
| Apply consistent styling to a content section. | |
| Args: | |
| content: The content to style | |
| section_type: The type of section for appropriate styling | |
| Returns: | |
| Styled HTML content | |
| Requirements: 7.1, 7.2 | |
| """ | |
| # Base styling for all sections | |
| base_style = "margin: 15px 0; padding: 10px; border-radius: 6px;" | |
| # Section-specific styling | |
| section_styles = { | |
| SectionType.AI_ANALYSIS: base_style + "background-color: #f8f9fa; border-left: 4px solid #6c757d;", | |
| SectionType.PATIENT_MESSAGE: base_style + "background-color: #f0f7ff; border-left: 4px solid #4a90e2;", | |
| SectionType.PROVIDER_SUMMARY: base_style + "background-color: #fff8f0; border-left: 4px solid #dc3545;" | |
| } | |
| style = section_styles.get(section_type, base_style) | |
| return f""" | |
| <div style='{style}'> | |
| {content} | |
| </div> | |
| """ | |
| def format_combined_results( | |
| self, | |
| ai_analysis: Optional[Dict[str, Any]] = None, | |
| patient_message: Optional[str] = None, | |
| provider_summary: Optional[ProviderSummary] = None | |
| ) -> str: | |
| """ | |
| Format combined results with all sections and proper separation. | |
| Args: | |
| ai_analysis: Optional AI analysis data | |
| patient_message: Optional patient message | |
| provider_summary: Optional provider summary data | |
| Returns: | |
| Complete formatted HTML with all sections | |
| Requirements: 1.1, 1.2, 7.1, 7.2 | |
| """ | |
| sections = [] | |
| separators = self.create_visual_separators() | |
| # Add AI analysis section if provided | |
| if ai_analysis: | |
| ai_section = self.format_ai_analysis_section( | |
| classification=ai_analysis.get('classification', 'UNKNOWN'), | |
| indicators=ai_analysis.get('indicators', []), | |
| reasoning=ai_analysis.get('reasoning', ''), | |
| confidence=ai_analysis.get('confidence') | |
| ) | |
| sections.append(ai_section) | |
| # Add patient message section if provided | |
| if patient_message: | |
| patient_section = self.format_patient_message_section(patient_message) | |
| sections.append(patient_section) | |
| # Add provider summary section if provided | |
| if provider_summary: | |
| summary_section = self.format_provider_summary_section(provider_summary) | |
| sections.append(summary_section) | |
| # Join sections with separators | |
| if len(sections) > 1: | |
| content = separators["section_break"].join(sections) | |
| elif sections: | |
| content = sections[0] | |
| else: | |
| content = "<div style='padding: 20px; text-align: center; color: #666;'>No content to display</div>" | |
| return content | |
| def _format_basic_ai_analysis(self, classification: str, indicators: List[str], reasoning: str, confidence: Optional[float] = None) -> str: | |
| """Fallback basic formatting for AI analysis when enhancements are disabled.""" | |
| confidence_text = f" (Confidence: {int(confidence * 100)}%)" if confidence else "" | |
| indicators_text = f"Indicators: {', '.join(indicators)}" if indicators else "No indicators" | |
| return f""" | |
| <div style='border: 1px solid #ccc; padding: 10px; margin: 5px 0;'> | |
| <strong>AI Analysis - {classification} FLAG{confidence_text}</strong><br> | |
| {indicators_text}<br> | |
| Reasoning: {reasoning} | |
| </div> | |
| """ | |
| def _format_basic_patient_message(self, message: str) -> str: | |
| """Fallback basic formatting for patient message when enhancements are disabled.""" | |
| return f""" | |
| <div style='border: 1px solid #ccc; padding: 10px; margin: 5px 0;'> | |
| <strong>Patient Message</strong><br> | |
| {html.escape(message)} | |
| </div> | |
| """ | |
| def _format_basic_provider_summary(self, summary_data: ProviderSummary) -> str: | |
| """Fallback basic formatting for provider summary when enhancements are disabled.""" | |
| return f""" | |
| <div style='border: 1px solid #ccc; padding: 10px; margin: 5px 0;'> | |
| <strong>Provider Summary</strong><br> | |
| Patient: {summary_data.patient_name}<br> | |
| Phone: {summary_data.patient_phone}<br> | |
| Urgency: {summary_data.urgency_level}<br> | |
| Situation: {summary_data.situation_description} | |
| </div> | |
| """ | |
| def update_config(self, new_config: EnhancedDisplayConfig) -> None: | |
| """ | |
| Update the configuration for this display manager. | |
| Args: | |
| new_config: New configuration to use | |
| """ | |
| self.config = new_config | |
| def reload_config(self) -> None: | |
| """Reload configuration from the config manager if available.""" | |
| if self.config_manager is not None: | |
| self.config = self.config_manager.get_config() | |
| else: | |
| self.config = get_enhanced_display_config() | |
| def is_enhanced_mode_enabled(self) -> bool: | |
| """Check if enhanced display mode is enabled.""" | |
| return self.config.enabled | |
| def get_css_styles(self) -> str: | |
| """ | |
| Get CSS styles for enhanced display. | |
| Returns: | |
| CSS string for enhanced display styling | |
| """ | |
| return self.config.generate_base_css() |