Spiritual_Health_Project / src /config /enhanced_display_config.py
DocUA's picture
feat: Fix classification logic and remove redundant spiritual care message functionality
be1b5d2
#!/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__)
@dataclass
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"
@dataclass
class ClassificationColorConfig:
"""Configuration for classification-based colors."""
red: str = "#ff4444"
yellow: str = "#ffaa00"
green: str = "#44aa44"
unknown: str = "#666666"
@dataclass
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%"
@dataclass
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