STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
calibration_robustness.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3Calibration Robustness Module
4Provides mission-critical robustness features for the pressure transducer calibration system.
5
6Features:
7- Automatic backup and recovery
8- Flight mode vs test mode control
9- Comprehensive validation and sanity checks
10- Health monitoring and diagnostics
11- Anomaly detection and automatic recovery
12- Redundancy and fallback mechanisms
13"""
14
15import os
16import json
17import time
18import shutil
19import logging
20import threading
21from datetime import datetime
22from pathlib import Path
23from typing import Dict, List, Tuple, Optional, Any
24from dataclasses import dataclass, asdict
25from enum import Enum
26from collections import deque
27import numpy as np
28
29logger = logging.getLogger(__name__)
30
31# ===========================================================================================
32# CONFIGURATION MANAGEMENT
33# ===========================================================================================
34
35
36class OperationMode(Enum):
37 """System operation modes"""
38
39 TEST = "test" # Ground testing - consensus enabled, all features active
40 CALIBRATION = "calibration" # Calibration mode - similar to test
41 FLIGHT = "flight" # Flight/mission mode - consensus disabled, independent readings
42 SAFE = "safe" # Safe mode - basic functionality only
43
44
45@dataclass
47 """Mission-critical system configuration"""
48
49 mode: OperationMode = OperationMode.TEST
50 consensus_enabled: bool = True
51 auto_backup_enabled: bool = True
52 backup_interval_seconds: float = 60.0
53 max_backup_files: int = 10
54 validation_enabled: bool = True
55 health_monitoring_enabled: bool = True
56 anomaly_detection_enabled: bool = True
57 num_sensors: int = 16
58 model_order: int = 8 # 9 parameters (0-8)
59 max_voltage: float = 10.0
60 min_voltage: float = 0.0
61 max_pressure: float = 1500.0 # PSI
62 min_pressure: float = -50.0 # PSI (allow slight negative for validation)
63 max_uncertainty: float = 500.0 # PSI
64 consensus_agreement_threshold: float = 0.6
65 min_sensors_for_consensus: int = 2
66 backup_directory: str = "./calibration_backups"
67 log_directory: str = "./calibration_logs"
68
69 def to_dict(self) -> Dict:
70 """Convert to dictionary for JSON serialization"""
71 d = asdict(self)
72 d["mode"] = self.mode.value
73 return d
74
75 @classmethod
76 def from_dict(cls, d: Dict) -> "SystemConfig":
77 """Load from dictionary"""
78 if "mode" in d and isinstance(d["mode"], str):
79 d["mode"] = OperationMode(d["mode"])
80 return cls(**d)
81
82 def save(self, filepath: str = "system_config.json"):
83 """Save configuration to file"""
84 with open(filepath, "w") as f:
85 json.dump(self.to_dict(), f, indent=2)
86 logger.info(f"System configuration saved to {filepath}")
87
88 @classmethod
89 def load(cls, filepath: str = "system_config.json") -> "SystemConfig":
90 """Load configuration from file"""
91 if not os.path.exists(filepath):
92 logger.warning(f"Config file {filepath} not found, using defaults")
93 return cls()
94 try:
95 with open(filepath, "r") as f:
96 data = json.load(f)
97 config = cls.from_dict(data)
98 logger.info(
99 f"System configuration loaded from {filepath}: mode={config.mode.value}"
100 )
101 return config
102 except Exception as e:
103 logger.error(f"Failed to load config from {filepath}: {e}, using defaults")
104 return cls()
105
106
107# ===========================================================================================
108# BACKUP AND RECOVERY SYSTEM
109# ===========================================================================================
110
111
113 """Automatic backup and recovery for calibration data"""
114
115 def __init__(self, config: SystemConfig):
116 self.config = config
117 self.backup_dir = Path(config.backup_directory)
118 self.backup_dir.mkdir(parents=True, exist_ok=True)
120 self._backup_lock = threading.Lock()
121 logger.info(f"Backup manager initialized: {self.backup_dir}")
122
123 def should_backup(self) -> bool:
124 """Check if it's time for automatic backup"""
125 if not self.config.auto_backup_enabled:
126 return False
127 return (
129 ) >= self.config.backup_interval_seconds
130
132 self,
133 population_prior: Dict,
134 pt_states: Dict[int, Dict],
135 metadata: Optional[Dict] = None,
136 ) -> bool:
137 """
138 Backup complete calibration state
139
140 Args:
141 population_prior: Population prior data (mean, covariance, strength)
142 pt_states: Per-PT calibration states
143 metadata: Optional metadata (timestamp, mode, etc.)
144
145 Returns:
146 True if backup successful
147 """
148 with self._backup_lock:
149 try:
150 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
151 backup_file = self.backup_dir / f"calibration_backup_{timestamp}.json"
152
153 backup_data = {
154 "timestamp": time.time(),
155 "datetime": timestamp,
156 "mode": self.config.mode.value,
157 "population_prior": population_prior,
158 "pt_states": pt_states,
159 "metadata": metadata or {},
160 }
161
162 # Write to temporary file first, then rename (atomic)
163 temp_file = backup_file.with_suffix(".tmp")
164 with open(temp_file, "w") as f:
165 json.dump(backup_data, f, indent=2, default=self._json_serializer)
166 temp_file.rename(backup_file)
167
169 logger.info(f"✅ Calibration backup saved: {backup_file}")
170
171 # Cleanup old backups
173
174 return True
175 except Exception as e:
176 logger.error(f"❌ Backup failed: {e}")
177 return False
178
179 def _json_serializer(self, obj):
180 """Custom JSON serializer for numpy arrays"""
181 if isinstance(obj, np.ndarray):
182 return obj.tolist()
183 if isinstance(obj, np.integer):
184 return int(obj)
185 if isinstance(obj, np.floating):
186 return float(obj)
187 raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
188
190 """Keep only the most recent N backup files"""
191 try:
192 backup_files = sorted(self.backup_dir.glob("calibration_backup_*.json"))
193 if len(backup_files) > self.config.max_backup_files:
194 files_to_delete = backup_files[: -self.config.max_backup_files]
195 for f in files_to_delete:
196 f.unlink()
197 logger.info(f"Deleted old backup: {f}")
198 except Exception as e:
199 logger.error(f"Backup cleanup failed: {e}")
200
201 def list_backups(self) -> List[Tuple[Path, float]]:
202 """List available backups with timestamps"""
203 backups = []
204 for backup_file in sorted(self.backup_dir.glob("calibration_backup_*.json")):
205 try:
206 with open(backup_file, "r") as f:
207 data = json.load(f)
208 timestamp = data.get("timestamp", 0.0)
209 backups.append((backup_file, timestamp))
210 except:
211 continue
212 return sorted(backups, key=lambda x: x[1], reverse=True)
213
214 def restore_latest_backup(self) -> Optional[Dict]:
215 """Restore from the most recent backup"""
216 backups = self.list_backups()
217 if not backups:
218 logger.warning("No backups available for restore")
219 return None
220
221 latest_backup, _ = backups[0]
222 return self.restore_from_backup(latest_backup)
223
224 def restore_from_backup(self, backup_file: Path) -> Optional[Dict]:
225 """
226 Restore calibration state from backup
227
228 Returns:
229 Backup data dictionary or None if failed
230 """
231 try:
232 with open(backup_file, "r") as f:
233 data = json.load(f)
234
235 # Convert lists back to numpy arrays
236 if "population_prior" in data:
237 pp = data["population_prior"]
238 if "population_mean" in pp and isinstance(pp["population_mean"], list):
239 pp["population_mean"] = np.array(pp["population_mean"])
240 if "population_covariance" in pp and isinstance(
241 pp["population_covariance"], list
242 ):
243 pp["population_covariance"] = np.array(pp["population_covariance"])
244
245 logger.info(f"✅ Restored calibration from backup: {backup_file}")
246 return data
247 except Exception as e:
248 logger.error(f"❌ Restore failed: {e}")
249 return None
250
251
252# ===========================================================================================
253# VALIDATION AND SANITY CHECKS
254# ===========================================================================================
255
256
258 """Result of a validation check"""
259
260 def __init__(self, valid: bool, message: str = "", severity: str = "info"):
261 self.valid = valid
262 self.message = message
263 self.severity = severity # info, warning, error, critical
264
265 def __bool__(self):
266 return self.valid
267
268
270 """Comprehensive validation and sanity checks"""
271
272 def __init__(self, config: SystemConfig):
273 self.config = config
274
275 def validate_voltage(self, voltage: float, sensor_id: int) -> ValidationResult:
276 """Validate voltage reading"""
277 if not self.config.validation_enabled:
278 return ValidationResult(True)
279
280 if not np.isfinite(voltage):
281 return ValidationResult(
282 False, f"PT{sensor_id}: Non-finite voltage {voltage}", "error"
283 )
284
285 if voltage < self.config.min_voltage or voltage > self.config.max_voltage:
286 return ValidationResult(
287 False,
288 f"PT{sensor_id}: Voltage {voltage:.3f}V out of range [{self.config.min_voltage}, {self.config.max_voltage}]",
289 "warning",
290 )
291
292 return ValidationResult(True)
293
294 def validate_pressure(self, pressure: float, sensor_id: int) -> ValidationResult:
295 """Validate pressure reading"""
296 if not self.config.validation_enabled:
297 return ValidationResult(True)
298
299 if not np.isfinite(pressure):
300 return ValidationResult(
301 False, f"PT{sensor_id}: Non-finite pressure {pressure}", "error"
302 )
303
304 if pressure < self.config.min_pressure or pressure > self.config.max_pressure:
305 return ValidationResult(
306 False,
307 f"PT{sensor_id}: Pressure {pressure:.1f} PSI out of range [{self.config.min_pressure}, {self.config.max_pressure}]",
308 "warning",
309 )
310
311 return ValidationResult(True)
312
314 self, uncertainty: float, sensor_id: int
315 ) -> ValidationResult:
316 """Validate uncertainty value"""
317 if not self.config.validation_enabled:
318 return ValidationResult(True)
319
320 if not np.isfinite(uncertainty) or uncertainty < 0:
321 return ValidationResult(
322 False, f"PT{sensor_id}: Invalid uncertainty {uncertainty}", "error"
323 )
324
325 if uncertainty > self.config.max_uncertainty:
326 return ValidationResult(
327 False,
328 f"PT{sensor_id}: Uncertainty {uncertainty:.1f} PSI exceeds maximum {self.config.max_uncertainty}",
329 "warning",
330 )
331
332 return ValidationResult(True)
333
335 self, coefficients: np.ndarray, sensor_id: int
336 ) -> ValidationResult:
337 """Validate calibration coefficients"""
338 if not self.config.validation_enabled:
339 return ValidationResult(True)
340
341 if coefficients is None:
342 return ValidationResult(
343 False, f"PT{sensor_id}: Coefficients are None", "error"
344 )
345
346 expected_size = self.config.model_order + 1
347 if len(coefficients) != expected_size:
348 return ValidationResult(
349 False,
350 f"PT{sensor_id}: Expected {expected_size} coefficients, got {len(coefficients)}",
351 "critical",
352 )
353
354 if not np.all(np.isfinite(coefficients)):
355 return ValidationResult(
356 False, f"PT{sensor_id}: Non-finite coefficients", "error"
357 )
358
359 # Sanity check: linear coefficient (slope) should be reasonable
360 # For 0-5V, 0-1000 PSI transducer: slope ~ 200 PSI/V
361 linear_coeff = coefficients[1] if len(coefficients) > 1 else 0
362 if abs(linear_coeff) > 1000.0: # Sanity check
363 return ValidationResult(
364 False,
365 f"PT{sensor_id}: Linear coefficient {linear_coeff:.1f} seems unreasonable",
366 "warning",
367 )
368
369 return ValidationResult(True)
370
372 self, covariance: np.ndarray, sensor_id: int
373 ) -> ValidationResult:
374 """Validate covariance matrix"""
375 if not self.config.validation_enabled:
376 return ValidationResult(True)
377
378 if covariance is None:
379 return ValidationResult(
380 False, f"PT{sensor_id}: Covariance is None", "error"
381 )
382
383 expected_shape = (self.config.model_order + 1, self.config.model_order + 1)
384 if covariance.shape != expected_shape:
385 return ValidationResult(
386 False,
387 f"PT{sensor_id}: Expected covariance shape {expected_shape}, got {covariance.shape}",
388 "critical",
389 )
390
391 if not np.all(np.isfinite(covariance)):
392 return ValidationResult(
393 False, f"PT{sensor_id}: Non-finite covariance", "error"
394 )
395
396 # Check positive definiteness (all eigenvalues > 0)
397 try:
398 eigenvalues = np.linalg.eigvalsh(covariance)
399 if np.any(eigenvalues <= 0):
400 return ValidationResult(
401 False,
402 f"PT{sensor_id}: Covariance not positive definite (min eigenvalue: {np.min(eigenvalues)})",
403 "error",
404 )
405 except np.linalg.LinAlgError:
406 return ValidationResult(
407 False, f"PT{sensor_id}: Covariance matrix computation failed", "error"
408 )
409
410 return ValidationResult(True)
411
413 self, consensus_pressure: float, agreement_score: float, num_sensors: int
414 ) -> ValidationResult:
415 """Validate consensus state"""
416 if not self.config.validation_enabled:
417 return ValidationResult(True)
418
419 if not np.isfinite(consensus_pressure):
420 return ValidationResult(False, "Consensus pressure is non-finite", "error")
421
422 if not (0 <= agreement_score <= 1):
423 return ValidationResult(
424 False, f"Agreement score {agreement_score} out of [0,1]", "warning"
425 )
426
427 if num_sensors < self.config.min_sensors_for_consensus:
428 return ValidationResult(
429 False,
430 f"Only {num_sensors} sensors participating, need {self.config.min_sensors_for_consensus}",
431 "warning",
432 )
433
434 if agreement_score < self.config.consensus_agreement_threshold:
435 return ValidationResult(
436 False,
437 f"Agreement score {agreement_score:.3f} below threshold {self.config.consensus_agreement_threshold}",
438 "warning",
439 )
440
441 return ValidationResult(True)
442
443
444# ===========================================================================================
445# HEALTH MONITORING AND DIAGNOSTICS
446# ===========================================================================================
447
448
449@dataclass
451 """System health metrics"""
452
453 timestamp: float
454 mode: OperationMode
455 active_sensors: int
456 calibrated_sensors: int
457 consensus_confidence: float
458 average_uncertainty: float
459 max_uncertainty: float
460 drift_detected_count: int
461 validation_errors: int
462 backup_status: str
463 uptime_seconds: float
464
465
467 """Real-time health monitoring and diagnostics"""
468
469 def __init__(self, config: SystemConfig):
470 self.config = config
472 self.metrics_history: List[HealthMetrics] = []
475 self.error_count = 0
476 self._metrics_lock = threading.Lock()
477
478 # Create log directory
479 log_dir = Path(config.log_directory)
480 log_dir.mkdir(parents=True, exist_ok=True)
481
482 # Setup file logging
483 log_file = log_dir / f"health_log_{datetime.now().strftime('%Y%m%d')}.log"
484 file_handler = logging.FileHandler(log_file)
485 file_handler.setLevel(logging.INFO)
486 formatter = logging.Formatter(
487 "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
488 )
489 file_handler.setFormatter(formatter)
490 logger.addHandler(file_handler)
491
492 logger.info(f"Health monitor initialized: {log_file}")
493
494 def record_metrics(self, metrics: HealthMetrics):
495 """Record health metrics"""
496 with self._metrics_lock:
497 self.metrics_history.append(metrics)
498 # Keep only last 1000 metrics
499 if len(self.metrics_history) > 1000:
500 self.metrics_history = self.metrics_history[-1000:]
501
502 def get_current_status(self) -> Dict[str, Any]:
503 """Get current system status"""
504 with self._metrics_lock:
505 if not self.metrics_history:
506 return {"status": "no_data"}
507
508 latest = self.metrics_history[-1]
509 return {
510 "status": "healthy" if self.is_healthy() else "degraded",
511 "mode": latest.mode.value,
512 "active_sensors": latest.active_sensors,
513 "calibrated_sensors": latest.calibrated_sensors,
514 "consensus_confidence": latest.consensus_confidence,
515 "average_uncertainty": latest.average_uncertainty,
516 "max_uncertainty": latest.max_uncertainty,
517 "drift_detected": latest.drift_detected_count,
518 "validation_errors": self.validation_errors,
519 "warnings": self.warning_count,
520 "errors": self.error_count,
521 "uptime_seconds": time.time() - self.start_time,
522 }
523
524 def is_healthy(self) -> bool:
525 """Check if system is healthy"""
526 if not self.metrics_history:
527 return False
528
529 latest = self.metrics_history[-1]
530
531 # Critical checks
532 if latest.calibrated_sensors == 0:
533 return False
534
535 if latest.mode == OperationMode.FLIGHT and latest.consensus_confidence < 0.5:
536 logger.warning("Low consensus confidence in flight mode")
537 return False
538
539 if latest.max_uncertainty > self.config.max_uncertainty:
540 logger.warning(
541 f"Maximum uncertainty {latest.max_uncertainty} exceeds limit"
542 )
543 return False
544
545 if self.error_count > 10: # More than 10 errors
546 logger.error("Too many errors detected")
547 return False
548
549 return True
550
551 def log_validation_error(self, result: ValidationResult):
552 """Log validation error"""
553 self.validation_errors += 1
554 if result.severity == "warning":
555 self.warning_count += 1
556 logger.warning(result.message)
557 elif result.severity in ("error", "critical"):
558 self.error_count += 1
559 logger.error(result.message)
560
561 def get_diagnostics_report(self) -> str:
562 """Generate comprehensive diagnostics report"""
563 status = self.get_current_status()
564
565 report = [
566 "=" * 80,
567 "CALIBRATION SYSTEM DIAGNOSTICS REPORT",
568 "=" * 80,
569 f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
570 f"Mode: {status['mode']}",
571 f"Status: {status['status'].upper()}",
572 f"Uptime: {status['uptime_seconds']:.1f} seconds",
573 "",
574 "SENSORS:",
575 f" Active: {status['active_sensors']}/{self.config.num_sensors}",
576 f" Calibrated: {status['calibrated_sensors']}/{self.config.num_sensors}",
577 "",
578 "CALIBRATION QUALITY:",
579 f" Average Uncertainty: {status['average_uncertainty']:.2f} PSI",
580 f" Maximum Uncertainty: {status['max_uncertainty']:.2f} PSI",
581 f" Consensus Confidence: {status['consensus_confidence']:.3f}",
582 "",
583 "ERRORS AND WARNINGS:",
584 f" Validation Errors: {status['validation_errors']}",
585 f" Warnings: {status['warnings']}",
586 f" Errors: {status['errors']}",
587 f" Drift Detected: {status['drift_detected']} sensors",
588 "",
589 "=" * 80,
590 ]
591
592 return "\n".join(report)
593
594
595# ===========================================================================================
596# ANOMALY DETECTION
597# ===========================================================================================
598
599
600class AnomalyDetector:
601 """Detect anomalies in calibration and sensor readings"""
602
603 def __init__(self, config: SystemConfig):
604 self.config = config
605 self.voltage_history: Dict[int, deque] = {
606 i: deque(maxlen=100) for i in range(config.num_sensors)
607 }
608 self.pressure_history: Dict[int, deque] = {
609 i: deque(maxlen=100) for i in range(config.num_sensors)
610 }
611 self.anomaly_counts: Dict[int, int] = {i: 0 for i in range(config.num_sensors)}
612
613 def detect_voltage_anomaly(self, sensor_id: int, voltage: float) -> bool:
614 """Detect voltage anomaly using statistical methods"""
615 if sensor_id not in self.voltage_history:
616 return False
617
618 history = self.voltage_history[sensor_id]
619 history.append(voltage)
620
621 if len(history) < 10: # Need some history
622 return False
623
624 # Simple outlier detection: > 3 sigma from mean
625 mean = np.mean(history)
626 std = np.std(history)
627
628 if std < 1e-6: # No variation
629 return False
630
631 z_score = abs((voltage - mean) / std)
632 if z_score > 3.0:
633 self.anomaly_counts[sensor_id] += 1
634 logger.warning(
635 f"PT{sensor_id} voltage anomaly: {voltage:.3f}V (z-score: {z_score:.2f})"
636 )
637 return True
638
639 return False
640
641 def detect_pressure_anomaly(self, sensor_id: int, pressure: float) -> bool:
642 """Detect pressure anomaly"""
643 if sensor_id not in self.pressure_history:
644 return False
645
646 history = self.pressure_history[sensor_id]
647 history.append(pressure)
648
649 if len(history) < 10:
650 return False
651
652 # Check for rapid changes (rate limit)
653 if len(history) >= 2:
654 rate_of_change = abs(pressure - history[-2])
655 if rate_of_change > 100.0: # > 100 PSI change in one sample
656 logger.warning(
657 f"PT{sensor_id} rapid pressure change: {rate_of_change:.1f} PSI/sample"
658 )
659 return True
660
661 return False
662
663 def get_anomaly_report(self) -> Dict[int, int]:
664 """Get anomaly counts per sensor"""
665 return self.anomaly_counts.copy()
666
667
668# ===========================================================================================
669# MAIN ROBUSTNESS MANAGER
670# ===========================================================================================
671
672
673class RobustnessManager:
674 """
675 Main robustness manager integrating all features.
676
677 Usage:
678 config = SystemConfig.load()
679 manager = RobustnessManager(config)
680
681 # Check if backup needed
682 if manager.backup_manager.should_backup():
683 manager.backup_calibration_state(...)
684
685 # Validate readings
686 result = manager.validator.validate_voltage(voltage, sensor_id)
687 if not result:
688 manager.health_monitor.log_validation_error(result)
689
690 # Get diagnostics
691 print(manager.health_monitor.get_diagnostics_report())
692 """
693
694 def __init__(self, config: Optional[SystemConfig] = None):
695 self.config = config or SystemConfig.load()
696 self.config.save() # Save for future reference
697
698 self.backup_manager = BackupManager(self.config)
699 self.validator = CalibrationValidator(self.config)
700 self.health_monitor = HealthMonitor(self.config)
701 self.anomaly_detector = AnomalyDetector(self.config)
702
703 logger.info(
704 f"🛡️ Robustness Manager initialized: mode={self.config.mode.value}, "
705 f"consensus={self.config.consensus_enabled}, validation={self.config.validation_enabled}"
706 )
707
708 def set_mode(self, mode: OperationMode):
709 """Change operation mode"""
710 old_mode = self.config.mode
711 self.config.mode = mode
712
713 # Update consensus based on mode
714 if mode == OperationMode.FLIGHT:
715 self.config.consensus_enabled = False
716 logger.warning(
717 "⚠️ FLIGHT MODE: Consensus disabled, measurements are independent"
718 )
719 elif mode in (OperationMode.TEST, OperationMode.CALIBRATION):
720 self.config.consensus_enabled = True
721 logger.info(f"✅ {mode.value.upper()} MODE: Consensus enabled")
722
723 self.config.save()
724 logger.info(f"Mode changed: {old_mode.value} → {mode.value}")
725
726 def emergency_backup(self, data: Dict) -> bool:
727 """Emergency backup (ignore interval)"""
728 return self.backup_manager.backup_calibration_state(
729 population_prior=data.get("population_prior", {}),
730 pt_states=data.get("pt_states", {}),
731 metadata={"emergency": True},
732 )
733
734 def auto_recover(self) -> Optional[Dict]:
735 """Automatic recovery from latest backup"""
736 logger.warning("🚨 Attempting automatic recovery...")
737 data = self.backup_manager.restore_latest_backup()
738 if data:
739 logger.info("✅ Recovery successful")
740 else:
741 logger.error("❌ Recovery failed")
742 return data
743
744
745if __name__ == "__main__":
746 # Demo usage
747 logging.basicConfig(
748 level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
749 )
750
751 # Create configuration
752 config = SystemConfig()
753 config.mode = OperationMode.TEST
754 config.save()
755
756 # Create manager
757 manager = RobustnessManager(config)
758
759 # Simulate some validations
760 v_result = manager.validator.validate_voltage(2.5, 0)
761 print(f"Voltage validation: {v_result.valid} - {v_result.message}")
762
763 p_result = manager.validator.validate_pressure(500.0, 0)
764 print(f"Pressure validation: {p_result.valid} - {p_result.message}")
765
766 # Generate diagnostics
767 print("\n" + manager.health_monitor.get_diagnostics_report())
768
769 # Change to flight mode
770 manager.set_mode(OperationMode.FLIGHT)
771 print(f"\nConsensus enabled: {manager.config.consensus_enabled}")
Optional[Dict] restore_from_backup(self, Path backup_file)
bool backup_calibration_state(self, Dict population_prior, Dict[int, Dict] pt_states, Optional[Dict] metadata=None)
List[Tuple[Path, float]] list_backups(self)
ValidationResult validate_voltage(self, float voltage, int sensor_id)
ValidationResult validate_pressure(self, float pressure, int sensor_id)
ValidationResult validate_uncertainty(self, float uncertainty, int sensor_id)
ValidationResult validate_consensus_state(self, float consensus_pressure, float agreement_score, int num_sensors)
ValidationResult validate_covariance_matrix(self, np.ndarray covariance, int sensor_id)
ValidationResult validate_calibration_coefficients(self, np.ndarray coefficients, int sensor_id)
record_metrics(self, HealthMetrics metrics)
log_validation_error(self, ValidationResult result)
save(self, str filepath="system_config.json")
"SystemConfig" load(cls, str filepath="system_config.json")
__init__(self, bool valid, str message="", str severity="info")
TOML_NODISCARD_CTOR time() noexcept=default