Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Enhanced Display Configuration System for UI Classification Improvements. | |
| Provides comprehensive configuration management for enhanced display features | |
| including colors, icons, styles, and feature toggles. | |
| Requirements: 7.1, 7.2, 7.3 | |
| """ | |
| import json | |
| import os | |
| from dataclasses import dataclass, field, asdict | |
| from typing import Dict, List, Optional, Any | |
| from pathlib import Path | |
| import logging | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class SectionStylingConfig: | |
| """Configuration for individual section styling.""" | |
| icon: str | |
| border_color: str | |
| background_color: str | |
| header_color: str | |
| border_width: str = "2px" | |
| border_radius: str = "8px" | |
| padding: str = "15px" | |
| margin: str = "10px 0" | |
| class ClassificationColorConfig: | |
| """Configuration for classification-based colors.""" | |
| red: str = "#ff4444" | |
| yellow: str = "#ffaa00" | |
| green: str = "#44aa44" | |
| unknown: str = "#666666" | |
| class VisualSeparatorConfig: | |
| """Configuration for visual separators.""" | |
| section_separator: str = "---" | |
| content_divider: str = "1px solid #ddd" | |
| major_break_symbol: str = "● ● ●" | |
| separator_color: str = "#e0e0e0" | |
| separator_width: str = "80%" | |
| class EnhancedDisplayConfig: | |
| """ | |
| Comprehensive configuration for enhanced display formatting. | |
| Manages all aspects of the enhanced UI including colors, icons, styles, | |
| and feature toggles for the classification improvements system. | |
| Requirements: 7.1, 7.2, 7.3 | |
| """ | |
| # Feature toggles | |
| enabled: bool = True | |
| use_color_coding: bool = True | |
| use_icons: bool = True | |
| use_visual_separators: bool = True | |
| use_enhanced_styling: bool = True | |
| # Section styling configurations | |
| ai_analysis: SectionStylingConfig = field(default_factory=lambda: SectionStylingConfig( | |
| icon="🤖", | |
| border_color="#6c757d", | |
| background_color="#f8f9fa", | |
| header_color="#6c757d" | |
| )) | |
| patient_message: SectionStylingConfig = field(default_factory=lambda: SectionStylingConfig( | |
| icon="💬", | |
| border_color="#4a90e2", | |
| background_color="#f0f7ff", | |
| header_color="#4a90e2" | |
| )) | |
| provider_summary: SectionStylingConfig = field(default_factory=lambda: SectionStylingConfig( | |
| icon="📋", | |
| border_color="#dc3545", | |
| background_color="#fff8f0", | |
| header_color="#dc3545" | |
| )) | |
| # Classification colors | |
| classification_colors: ClassificationColorConfig = field(default_factory=ClassificationColorConfig) | |
| # Visual separators | |
| separators: VisualSeparatorConfig = field(default_factory=VisualSeparatorConfig) | |
| # Typography settings | |
| font_family: str = "system-ui, -apple-system, sans-serif" | |
| base_font_size: str = "14px" | |
| header_font_size: str = "1.1em" | |
| small_font_size: str = "0.9em" | |
| # Layout settings | |
| max_content_width: str = "100%" | |
| responsive_breakpoint: str = "768px" | |
| # Animation settings | |
| enable_animations: bool = True | |
| transition_duration: str = "0.3s" | |
| # Accessibility settings | |
| high_contrast_mode: bool = False | |
| reduced_motion: bool = False | |
| def __post_init__(self): | |
| """Post-initialization to apply high contrast mode if enabled.""" | |
| if self.high_contrast_mode: | |
| self._apply_high_contrast_colors() | |
| def _apply_high_contrast_colors(self): | |
| """Apply high contrast color scheme for accessibility.""" | |
| # Update classification colors for high contrast | |
| self.classification_colors.red = "#cc0000" | |
| self.classification_colors.yellow = "#cc8800" | |
| self.classification_colors.green = "#006600" | |
| # Update section colors for high contrast | |
| self.ai_analysis.border_color = "#000000" | |
| self.ai_analysis.background_color = "#ffffff" | |
| self.ai_analysis.header_color = "#000000" | |
| self.patient_message.border_color = "#0066cc" | |
| self.patient_message.background_color = "#f0f8ff" | |
| self.patient_message.header_color = "#0066cc" | |
| self.provider_summary.border_color = "#cc0000" | |
| self.provider_summary.background_color = "#fff0f0" | |
| self.provider_summary.header_color = "#cc0000" | |
| def get_classification_color(self, classification: str) -> str: | |
| """ | |
| Get color for a specific classification. | |
| Args: | |
| classification: Classification level (RED/YELLOW/GREEN) | |
| Returns: | |
| Hex color code for the classification | |
| """ | |
| classification_lower = classification.lower() | |
| if classification_lower == "red": | |
| return self.classification_colors.red | |
| elif classification_lower == "yellow": | |
| return self.classification_colors.yellow | |
| elif classification_lower == "green": | |
| return self.classification_colors.green | |
| else: | |
| return self.classification_colors.unknown | |
| def get_section_config(self, section_type: str) -> SectionStylingConfig: | |
| """ | |
| Get styling configuration for a specific section type. | |
| Args: | |
| section_type: Type of section (ai_analysis/patient_message/provider_summary) | |
| Returns: | |
| SectionStylingConfig for the specified section | |
| """ | |
| section_configs = { | |
| "ai_analysis": self.ai_analysis, | |
| "patient_message": self.patient_message, | |
| "provider_summary": self.provider_summary | |
| } | |
| return section_configs.get(section_type, self.ai_analysis) | |
| def generate_css_variables(self) -> str: | |
| """ | |
| Generate CSS custom properties (variables) for the configuration. | |
| Returns: | |
| CSS string with custom properties | |
| """ | |
| css_vars = [ | |
| ":root {", | |
| f" --enhanced-font-family: {self.font_family};", | |
| f" --enhanced-base-font-size: {self.base_font_size};", | |
| f" --enhanced-header-font-size: {self.header_font_size};", | |
| f" --enhanced-small-font-size: {self.small_font_size};", | |
| f" --enhanced-max-width: {self.max_content_width};", | |
| f" --enhanced-transition: {self.transition_duration};", | |
| "", | |
| " /* Classification Colors */", | |
| f" --classification-red: {self.classification_colors.red};", | |
| f" --classification-yellow: {self.classification_colors.yellow};", | |
| f" --classification-green: {self.classification_colors.green};", | |
| f" --classification-unknown: {self.classification_colors.unknown};", | |
| "", | |
| " /* AI Analysis Section */", | |
| f" --ai-analysis-icon: '{self.ai_analysis.icon}';", | |
| f" --ai-analysis-border: {self.ai_analysis.border_color};", | |
| f" --ai-analysis-bg: {self.ai_analysis.background_color};", | |
| f" --ai-analysis-header: {self.ai_analysis.header_color};", | |
| "", | |
| " /* Patient Message Section */", | |
| f" --patient-message-icon: '{self.patient_message.icon}';", | |
| f" --patient-message-border: {self.patient_message.border_color};", | |
| f" --patient-message-bg: {self.patient_message.background_color};", | |
| f" --patient-message-header: {self.patient_message.header_color};", | |
| "", | |
| " /* Provider Summary Section */", | |
| f" --provider-summary-icon: '{self.provider_summary.icon}';", | |
| f" --provider-summary-border: {self.provider_summary.border_color};", | |
| f" --provider-summary-bg: {self.provider_summary.background_color};", | |
| f" --provider-summary-header: {self.provider_summary.header_color};", | |
| "", | |
| " /* Separators */", | |
| f" --separator-color: {self.separators.separator_color};", | |
| f" --separator-width: {self.separators.separator_width};", | |
| "}" | |
| ] | |
| return "\n".join(css_vars) | |
| def generate_base_css(self) -> str: | |
| """ | |
| Generate base CSS classes for enhanced display. | |
| Returns: | |
| CSS string with base classes | |
| """ | |
| css_classes = [ | |
| self.generate_css_variables(), | |
| "", | |
| "/* Enhanced Display Base Styles */", | |
| ".enhanced-section {", | |
| f" font-family: var(--enhanced-font-family);", | |
| f" font-size: var(--enhanced-base-font-size);", | |
| f" border-radius: {self.ai_analysis.border_radius};", | |
| f" padding: {self.ai_analysis.padding};", | |
| f" margin: {self.ai_analysis.margin};", | |
| f" border-width: {self.ai_analysis.border_width};", | |
| " border-style: solid;", | |
| "}", | |
| "", | |
| ".enhanced-section-header {", | |
| " display: flex;", | |
| " align-items: center;", | |
| " margin-bottom: 10px;", | |
| f" font-size: var(--enhanced-header-font-size);", | |
| " font-weight: bold;", | |
| "}", | |
| "", | |
| ".enhanced-section-icon {", | |
| " font-size: 1.2em;", | |
| " margin-right: 8px;", | |
| "}", | |
| "", | |
| ".enhanced-separator {", | |
| " margin: 20px 0;", | |
| " text-align: center;", | |
| "}", | |
| "", | |
| ".enhanced-separator hr {", | |
| " border: none;", | |
| f" border-top: 2px solid var(--separator-color);", | |
| f" width: var(--separator-width);", | |
| " margin: 0 auto;", | |
| "}", | |
| "", | |
| "/* AI Analysis Styles */", | |
| ".ai-analysis-section {", | |
| " border-color: var(--ai-analysis-border);", | |
| " background-color: var(--ai-analysis-bg);", | |
| "}", | |
| "", | |
| ".ai-analysis-section .enhanced-section-header {", | |
| " color: var(--ai-analysis-header);", | |
| "}", | |
| "", | |
| "/* Patient Message Styles */", | |
| ".patient-message-section {", | |
| " border-color: var(--patient-message-border);", | |
| " background-color: var(--patient-message-bg);", | |
| "}", | |
| "", | |
| ".patient-message-section .enhanced-section-header {", | |
| " color: var(--patient-message-header);", | |
| "}", | |
| "", | |
| "/* Provider Summary Styles */", | |
| ".provider-summary-section {", | |
| " border-color: var(--provider-summary-border);", | |
| " background-color: var(--provider-summary-bg);", | |
| "}", | |
| "", | |
| ".provider-summary-section .enhanced-section-header {", | |
| " color: var(--provider-summary-header);", | |
| "}", | |
| "", | |
| "/* Classification-specific colors */", | |
| ".classification-red {", | |
| " border-color: var(--classification-red) !important;", | |
| "}", | |
| "", | |
| ".classification-yellow {", | |
| " border-color: var(--classification-yellow) !important;", | |
| "}", | |
| "", | |
| ".classification-green {", | |
| " border-color: var(--classification-green) !important;", | |
| "}", | |
| "", | |
| "/* Responsive design */", | |
| f"@media (max-width: {self.responsive_breakpoint}) {{", | |
| " .enhanced-section {", | |
| " margin: 8px 0;", | |
| " padding: 12px;", | |
| " }", | |
| " ", | |
| " .enhanced-section-header {", | |
| " font-size: 1em;", | |
| " }", | |
| "}", | |
| ] | |
| # Add animation styles if enabled | |
| if self.enable_animations and not self.reduced_motion: | |
| css_classes.extend([ | |
| "", | |
| "/* Animations */", | |
| ".enhanced-section {", | |
| f" transition: all var(--enhanced-transition);", | |
| "}", | |
| "", | |
| ".enhanced-section:hover {", | |
| " transform: translateY(-1px);", | |
| " box-shadow: 0 2px 8px rgba(0,0,0,0.1);", | |
| "}", | |
| ]) | |
| return "\n".join(css_classes) | |
| def to_dict(self) -> Dict[str, Any]: | |
| """Convert configuration to dictionary.""" | |
| return asdict(self) | |
| def to_json(self) -> str: | |
| """Convert configuration to JSON string.""" | |
| return json.dumps(self.to_dict(), indent=2) | |
| class EnhancedDisplayConfigManager: | |
| """ | |
| Manager for enhanced display configuration with persistence and validation. | |
| Handles loading, saving, and managing configuration for the enhanced display system. | |
| """ | |
| def __init__(self, config_file: Optional[str] = None): | |
| """ | |
| Initialize configuration manager. | |
| Args: | |
| config_file: Optional path to configuration file | |
| """ | |
| self.config_file = config_file or self._get_default_config_path() | |
| self._config: Optional[EnhancedDisplayConfig] = None | |
| logger.info(f"🔧 EnhancedDisplayConfigManager initialized with config: {self.config_file}") | |
| def _get_default_config_path(self) -> str: | |
| """Get default configuration file path.""" | |
| # Create config directory if it doesn't exist | |
| config_dir = Path("src/config/display") | |
| config_dir.mkdir(parents=True, exist_ok=True) | |
| return str(config_dir / "enhanced_display_config.json") | |
| def load_config(self) -> EnhancedDisplayConfig: | |
| """ | |
| Load configuration from file or create default. | |
| Returns: | |
| EnhancedDisplayConfig instance | |
| """ | |
| if self._config is not None: | |
| return self._config | |
| try: | |
| if os.path.exists(self.config_file): | |
| with open(self.config_file, 'r', encoding='utf-8') as f: | |
| config_data = json.load(f) | |
| # Convert nested dictionaries back to dataclasses | |
| config_data = self._convert_dict_to_config(config_data) | |
| self._config = EnhancedDisplayConfig(**config_data) | |
| logger.info(f"✅ Configuration loaded from {self.config_file}") | |
| else: | |
| logger.info("📝 Creating default configuration") | |
| self._config = EnhancedDisplayConfig() | |
| self.save_config() | |
| except Exception as e: | |
| logger.error(f"❌ Error loading configuration: {e}") | |
| logger.info("📝 Using default configuration") | |
| self._config = EnhancedDisplayConfig() | |
| return self._config | |
| def _convert_dict_to_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]: | |
| """Convert dictionary data to proper dataclass format.""" | |
| # Convert nested section configs | |
| for section_key in ['ai_analysis', 'patient_message', 'provider_summary']: | |
| if section_key in config_data and isinstance(config_data[section_key], dict): | |
| config_data[section_key] = SectionStylingConfig(**config_data[section_key]) | |
| # Convert classification colors | |
| if 'classification_colors' in config_data and isinstance(config_data['classification_colors'], dict): | |
| config_data['classification_colors'] = ClassificationColorConfig(**config_data['classification_colors']) | |
| # Convert separators | |
| if 'separators' in config_data and isinstance(config_data['separators'], dict): | |
| config_data['separators'] = VisualSeparatorConfig(**config_data['separators']) | |
| return config_data | |
| def save_config(self) -> bool: | |
| """ | |
| Save current configuration to file. | |
| Returns: | |
| True if saved successfully | |
| """ | |
| if self._config is None: | |
| logger.error("❌ No configuration to save") | |
| return False | |
| try: | |
| # Ensure directory exists | |
| os.makedirs(os.path.dirname(self.config_file), exist_ok=True) | |
| with open(self.config_file, 'w', encoding='utf-8') as f: | |
| f.write(self._config.to_json()) | |
| logger.info(f"✅ Configuration saved to {self.config_file}") | |
| return True | |
| except Exception as e: | |
| logger.error(f"❌ Error saving configuration: {e}") | |
| return False | |
| def update_config(self, **kwargs) -> bool: | |
| """ | |
| Update configuration with new values. | |
| Args: | |
| **kwargs: Configuration values to update | |
| Returns: | |
| True if updated successfully | |
| """ | |
| try: | |
| config = self.load_config() | |
| # Update configuration attributes | |
| for key, value in kwargs.items(): | |
| if hasattr(config, key): | |
| # Handle nested dataclass updates | |
| if key == 'classification_colors' and isinstance(value, dict): | |
| # Convert dict to ClassificationColorConfig | |
| from config.enhanced_display_config import ClassificationColorConfig | |
| setattr(config, key, ClassificationColorConfig(**value)) | |
| elif key in ['ai_analysis', 'patient_message', 'provider_summary'] and isinstance(value, dict): | |
| # Convert dict to SectionStylingConfig | |
| from config.enhanced_display_config import SectionStylingConfig | |
| setattr(config, key, SectionStylingConfig(**value)) | |
| elif key == 'separators' and isinstance(value, dict): | |
| # Convert dict to VisualSeparatorConfig | |
| from config.enhanced_display_config import VisualSeparatorConfig | |
| setattr(config, key, VisualSeparatorConfig(**value)) | |
| else: | |
| setattr(config, key, value) | |
| logger.info(f"🔄 Updated config.{key} = {value}") | |
| else: | |
| logger.warning(f"⚠️ Unknown configuration key: {key}") | |
| return self.save_config() | |
| except Exception as e: | |
| logger.error(f"❌ Error updating configuration: {e}") | |
| return False | |
| def reset_to_defaults(self) -> bool: | |
| """ | |
| Reset configuration to defaults. | |
| Returns: | |
| True if reset successfully | |
| """ | |
| try: | |
| self._config = EnhancedDisplayConfig() | |
| return self.save_config() | |
| except Exception as e: | |
| logger.error(f"❌ Error resetting configuration: {e}") | |
| return False | |
| def enable_feature(self, feature: str) -> bool: | |
| """ | |
| Enable a specific feature. | |
| Args: | |
| feature: Feature name to enable | |
| Returns: | |
| True if enabled successfully | |
| """ | |
| feature_map = { | |
| 'color_coding': 'use_color_coding', | |
| 'icons': 'use_icons', | |
| 'separators': 'use_visual_separators', | |
| 'styling': 'use_enhanced_styling', | |
| 'animations': 'enable_animations' | |
| } | |
| config_key = feature_map.get(feature, feature) | |
| return self.update_config(**{config_key: True}) | |
| def disable_feature(self, feature: str) -> bool: | |
| """ | |
| Disable a specific feature. | |
| Args: | |
| feature: Feature name to disable | |
| Returns: | |
| True if disabled successfully | |
| """ | |
| feature_map = { | |
| 'color_coding': 'use_color_coding', | |
| 'icons': 'use_icons', | |
| 'separators': 'use_visual_separators', | |
| 'styling': 'use_enhanced_styling', | |
| 'animations': 'enable_animations' | |
| } | |
| config_key = feature_map.get(feature, feature) | |
| return self.update_config(**{config_key: False}) | |
| def get_config(self) -> EnhancedDisplayConfig: | |
| """Get current configuration.""" | |
| return self.load_config() | |
| def validate_config(self) -> List[str]: | |
| """ | |
| Validate current configuration. | |
| Returns: | |
| List of validation issues (empty if valid) | |
| """ | |
| issues = [] | |
| config = self.load_config() | |
| # Validate color formats | |
| try: | |
| color_fields = [ | |
| config.classification_colors.red, | |
| config.classification_colors.yellow, | |
| config.classification_colors.green, | |
| config.ai_analysis.border_color, | |
| config.patient_message.border_color, | |
| config.provider_summary.border_color | |
| ] | |
| for color in color_fields: | |
| if not self._is_valid_color(color): | |
| issues.append(f"Invalid color format: {color}") | |
| except AttributeError as e: | |
| issues.append(f"Configuration structure error: {e}") | |
| # Validate icons (should be single emoji or short string) | |
| try: | |
| icon_fields = [ | |
| config.ai_analysis.icon, | |
| config.patient_message.icon, | |
| config.provider_summary.icon | |
| ] | |
| for icon in icon_fields: | |
| if len(icon) > 5: | |
| issues.append(f"Icon too long: {icon}") | |
| except AttributeError as e: | |
| issues.append(f"Configuration structure error: {e}") | |
| return issues | |
| def _is_valid_color(self, color: str) -> bool: | |
| """Check if color is in valid hex format.""" | |
| import re | |
| hex_pattern = r'^#[0-9A-Fa-f]{6}$' | |
| return bool(re.match(hex_pattern, color)) | |
| # Global configuration manager instance | |
| _config_manager: Optional[EnhancedDisplayConfigManager] = None | |
| def get_config_manager(config_file: Optional[str] = None) -> EnhancedDisplayConfigManager: | |
| """ | |
| Get global configuration manager instance. | |
| Args: | |
| config_file: Optional path to configuration file | |
| Returns: | |
| EnhancedDisplayConfigManager instance | |
| """ | |
| global _config_manager | |
| if _config_manager is None: | |
| _config_manager = EnhancedDisplayConfigManager(config_file) | |
| return _config_manager | |
| def get_enhanced_display_config() -> EnhancedDisplayConfig: | |
| """ | |
| Get current enhanced display configuration. | |
| Returns: | |
| EnhancedDisplayConfig instance | |
| """ | |
| return get_config_manager().get_config() | |
| def update_display_config(**kwargs) -> bool: | |
| """ | |
| Update display configuration. | |
| Args: | |
| **kwargs: Configuration values to update | |
| Returns: | |
| True if updated successfully | |
| """ | |
| return get_config_manager().update_config(**kwargs) | |
| # Factory functions for common configurations | |
| def create_high_contrast_config() -> EnhancedDisplayConfig: | |
| """Create configuration optimized for high contrast accessibility.""" | |
| config = EnhancedDisplayConfig(high_contrast_mode=True) | |
| # Ensure high contrast colors are applied | |
| config._apply_high_contrast_colors() | |
| return config | |
| def create_minimal_config() -> EnhancedDisplayConfig: | |
| """Create minimal configuration with reduced visual elements.""" | |
| config = EnhancedDisplayConfig() | |
| config.use_icons = False | |
| config.use_visual_separators = False | |
| config.enable_animations = False | |
| return config | |
| def create_mobile_optimized_config() -> EnhancedDisplayConfig: | |
| """Create configuration optimized for mobile devices.""" | |
| config = EnhancedDisplayConfig() | |
| config.responsive_breakpoint = "480px" | |
| config.ai_analysis.padding = "12px" | |
| config.patient_message.padding = "12px" | |
| config.provider_summary.padding = "12px" | |
| config.enable_animations = False | |
| return config |