# Hotfix Design: v1.1.1-rc.2 - Metrics Dashboard Template Data Mismatch ## Problem Summary Production deployment of v1.1.1-rc.1 exposed two critical issues in the metrics dashboard: 1. **Route Conflict** (Fixed in initial attempt): Two routes mapped to similar paths causing ambiguity 2. **Template/Data Mismatch** (Root cause): Template expects different data structure than monitoring module provides ### The Template/Data Mismatch **Template Expects** (`metrics_dashboard.html` line 163): ```jinja2 {{ metrics.database.count|default(0) }} {{ metrics.database.avg|default(0) }} {{ metrics.database.min|default(0) }} {{ metrics.database.max|default(0) }} ``` **Monitoring Module Returns**: ```python { "by_type": { "database": { "count": 50, "avg_duration_ms": 12.5, "min_duration_ms": 2.0, "max_duration_ms": 45.0 } } } ``` Note the two mismatches: 1. **Nesting**: Template wants `metrics.database` but gets `metrics.by_type.database` 2. **Field Names**: Template wants `avg` but gets `avg_duration_ms` ## Solution: Route Adapter Pattern Transform data at the presentation layer (route handler) to match template expectations. ### Implementation Added a transformer function in `admin.py` that: 1. Flattens the nested structure (`by_type.database` → `database`) 2. Maps field names (`avg_duration_ms` → `avg`) 3. Provides safe defaults for missing data ```python def transform_metrics_for_template(metrics_stats): """Transform metrics stats to match template structure""" transformed = {} # Map by_type to direct access with field name mapping for op_type in ['database', 'http', 'render']: if 'by_type' in metrics_stats and op_type in metrics_stats['by_type']: type_data = metrics_stats['by_type'][op_type] transformed[op_type] = { 'count': type_data.get('count', 0), 'avg': type_data.get('avg_duration_ms', 0), # Note field name change 'min': type_data.get('min_duration_ms', 0), 'max': type_data.get('max_duration_ms', 0) } else: # Safe defaults transformed[op_type] = {'count': 0, 'avg': 0, 'min': 0, 'max': 0} # Keep other top-level stats transformed['total_count'] = metrics_stats.get('total_count', 0) transformed['max_size'] = metrics_stats.get('max_size', 1000) transformed['process_id'] = metrics_stats.get('process_id', 0) return transformed ``` ### Why This Approach? 1. **Minimal Risk**: Only changes route handler, not core monitoring module 2. **Preserves API**: Monitoring module remains unchanged for other consumers 3. **No Template Changes**: Avoids modifying template and JavaScript 4. **Clear Separation**: Route acts as adapter between business logic and view ## Additional Fixes Applied 1. **Route Path Change**: `/admin/dashboard` → `/admin/metrics-dashboard` (prevents conflict) 2. **Defensive Imports**: Graceful handling of missing monitoring module 3. **Error Handling**: Safe defaults when metrics collection fails ## Testing and Validation Created comprehensive test script validating: - Data structure transformation works correctly - All template fields accessible after transformation - Safe defaults provided for missing data - Field name mapping correct All 32 admin route tests pass with 100% success rate. ## Files Modified 1. `/starpunk/routes/admin.py`: - Lines 218-260: Added transformer function - Line 263: Changed route path - Lines 285-314: Applied transformer and added error handling 2. `/starpunk/__init__.py`: Version bump to 1.1.1-rc.2 3. `/CHANGELOG.md`: Documented hotfix ## Production Impact **Before**: 500 error with `'dict object' has no attribute 'database'` **After**: Metrics dashboard loads correctly with properly structured data This is a tactical bug fix, not an architectural change, and should be documented as such.