1#![cfg_attr(docsrs, feature(doc_cfg))]
77#![warn(missing_docs)]
78#![warn(rustdoc::missing_crate_level_docs)]
79
80pub mod alerting;
81pub mod api;
82pub mod detection;
83pub mod domain;
84pub mod integration;
85pub mod localization;
86pub mod ml;
87pub mod tracking;
88
89pub use domain::{
91 survivor::{Survivor, SurvivorId, SurvivorMetadata, SurvivorStatus},
92 disaster_event::{DisasterEvent, DisasterEventId, DisasterType, EventStatus},
93 scan_zone::{ScanZone, ScanZoneId, ZoneBounds, ZoneStatus, ScanParameters},
94 alert::{Alert, AlertId, AlertPayload, Priority},
95 vital_signs::{
96 VitalSignsReading, BreathingPattern, BreathingType,
97 HeartbeatSignature, MovementProfile, MovementType,
98 },
99 triage::{TriageStatus, TriageCalculator},
100 coordinates::{Coordinates3D, LocationUncertainty, DepthEstimate},
101 events::{DetectionEvent, AlertEvent, DomainEvent, EventStore, InMemoryEventStore, TrackingEvent},
102};
103
104pub use detection::{
105 BreathingDetector, BreathingDetectorConfig,
106 HeartbeatDetector, HeartbeatDetectorConfig,
107 MovementClassifier, MovementClassifierConfig,
108 VitalSignsDetector, DetectionPipeline, DetectionConfig,
109 EnsembleClassifier, EnsembleConfig, EnsembleResult,
110};
111
112pub use localization::{
113 Triangulator, TriangulationConfig,
114 DepthEstimator, DepthEstimatorConfig,
115 PositionFuser, LocalizationService,
116};
117
118pub use alerting::{
119 AlertGenerator, AlertDispatcher, AlertConfig,
120 TriageService, PriorityCalculator,
121};
122
123pub use integration::{
124 SignalAdapter, NeuralAdapter, HardwareAdapter,
125 AdapterError, IntegrationConfig,
126};
127
128pub use api::{
129 create_router, AppState,
130};
131
132pub use ml::{
133 MlError, MlResult, MlDetectionConfig, MlDetectionPipeline, MlDetectionResult,
135 DebrisPenetrationModel, DebrisFeatures, DepthEstimate as MlDepthEstimate,
137 DebrisModel, DebrisModelConfig, DebrisFeatureExtractor,
138 MaterialType, DebrisClassification, AttenuationPrediction,
139 VitalSignsClassifier, VitalSignsClassifierConfig,
141 BreathingClassification, HeartbeatClassification,
142 UncertaintyEstimate, ClassifierOutput,
143};
144
145pub use tracking::{
146 SurvivorTracker, TrackerConfig, TrackId, TrackedSurvivor,
147 DetectionObservation, AssociationResult,
148 KalmanState, CsiFingerprint,
149 TrackState, TrackLifecycle,
150};
151
152pub const VERSION: &str = env!("CARGO_PKG_VERSION");
154
155pub type Result<T> = std::result::Result<T, MatError>;
157
158#[derive(Debug, thiserror::Error)]
160pub enum MatError {
161 #[error("Detection error: {0}")]
163 Detection(String),
164
165 #[error("Localization error: {0}")]
167 Localization(String),
168
169 #[error("Alerting error: {0}")]
171 Alerting(String),
172
173 #[error("Integration error: {0}")]
175 Integration(#[from] AdapterError),
176
177 #[error("Configuration error: {0}")]
179 Config(String),
180
181 #[error("Domain error: {0}")]
183 Domain(String),
184
185 #[error("Repository error: {0}")]
187 Repository(String),
188
189 #[error("Signal processing error: {0}")]
191 Signal(#[from] wifi_densepose_signal::SignalError),
192
193 #[error("I/O error: {0}")]
195 Io(#[from] std::io::Error),
196
197 #[error("ML error: {0}")]
199 Ml(#[from] ml::MlError),
200}
201
202#[derive(Debug, Clone)]
204pub struct DisasterConfig {
205 pub disaster_type: DisasterType,
207 pub sensitivity: f64,
209 pub confidence_threshold: f64,
211 pub max_depth: f64,
213 pub scan_interval_ms: u64,
215 pub continuous_monitoring: bool,
217 pub alert_config: AlertConfig,
219}
220
221impl Default for DisasterConfig {
222 fn default() -> Self {
223 Self {
224 disaster_type: DisasterType::Unknown,
225 sensitivity: 0.8,
226 confidence_threshold: 0.5,
227 max_depth: 5.0,
228 scan_interval_ms: 500,
229 continuous_monitoring: true,
230 alert_config: AlertConfig::default(),
231 }
232 }
233}
234
235impl DisasterConfig {
236 pub fn builder() -> DisasterConfigBuilder {
238 DisasterConfigBuilder::default()
239 }
240}
241
242#[derive(Debug, Default)]
244pub struct DisasterConfigBuilder {
245 config: DisasterConfig,
246}
247
248impl DisasterConfigBuilder {
249 pub fn disaster_type(mut self, disaster_type: DisasterType) -> Self {
251 self.config.disaster_type = disaster_type;
252 self
253 }
254
255 pub fn sensitivity(mut self, sensitivity: f64) -> Self {
257 self.config.sensitivity = sensitivity.clamp(0.0, 1.0);
258 self
259 }
260
261 pub fn confidence_threshold(mut self, threshold: f64) -> Self {
263 self.config.confidence_threshold = threshold.clamp(0.0, 1.0);
264 self
265 }
266
267 pub fn max_depth(mut self, depth: f64) -> Self {
269 self.config.max_depth = depth.max(0.0);
270 self
271 }
272
273 pub fn scan_interval_ms(mut self, interval: u64) -> Self {
275 self.config.scan_interval_ms = interval.max(100);
276 self
277 }
278
279 pub fn continuous_monitoring(mut self, enabled: bool) -> Self {
281 self.config.continuous_monitoring = enabled;
282 self
283 }
284
285 pub fn build(self) -> DisasterConfig {
287 self.config
288 }
289}
290
291pub struct DisasterResponse {
293 config: DisasterConfig,
294 event: Option<DisasterEvent>,
295 detection_pipeline: DetectionPipeline,
296 localization_service: LocalizationService,
297 alert_dispatcher: AlertDispatcher,
298 event_store: std::sync::Arc<dyn domain::events::EventStore>,
299 ensemble_classifier: EnsembleClassifier,
300 tracker: tracking::SurvivorTracker,
301 running: std::sync::atomic::AtomicBool,
302}
303
304impl DisasterResponse {
305 pub fn new(config: DisasterConfig) -> Self {
307 let detection_config = DetectionConfig::from_disaster_config(&config);
308 let detection_pipeline = DetectionPipeline::new(detection_config);
309
310 let localization_service = LocalizationService::new();
311 let alert_dispatcher = AlertDispatcher::new(config.alert_config.clone());
312 let event_store: std::sync::Arc<dyn domain::events::EventStore> =
313 std::sync::Arc::new(InMemoryEventStore::new());
314 let ensemble_classifier = EnsembleClassifier::new(EnsembleConfig::default());
315
316 Self {
317 config,
318 event: None,
319 detection_pipeline,
320 localization_service,
321 alert_dispatcher,
322 event_store,
323 ensemble_classifier,
324 tracker: tracking::SurvivorTracker::with_defaults(),
325 running: std::sync::atomic::AtomicBool::new(false),
326 }
327 }
328
329 pub fn with_event_store(
331 config: DisasterConfig,
332 event_store: std::sync::Arc<dyn domain::events::EventStore>,
333 ) -> Self {
334 let detection_config = DetectionConfig::from_disaster_config(&config);
335 let detection_pipeline = DetectionPipeline::new(detection_config);
336 let localization_service = LocalizationService::new();
337 let alert_dispatcher = AlertDispatcher::new(config.alert_config.clone());
338 let ensemble_classifier = EnsembleClassifier::new(EnsembleConfig::default());
339
340 Self {
341 config,
342 event: None,
343 detection_pipeline,
344 localization_service,
345 alert_dispatcher,
346 event_store,
347 ensemble_classifier,
348 tracker: tracking::SurvivorTracker::with_defaults(),
349 running: std::sync::atomic::AtomicBool::new(false),
350 }
351 }
352
353 pub fn push_csi_data(&self, amplitudes: &[f64], phases: &[f64]) -> Result<()> {
359 if amplitudes.len() != phases.len() {
360 return Err(MatError::Detection(
361 "Amplitude and phase arrays must have equal length".into(),
362 ));
363 }
364 if amplitudes.is_empty() {
365 return Err(MatError::Detection("CSI data cannot be empty".into()));
366 }
367 self.detection_pipeline.add_data(amplitudes, phases);
368 Ok(())
369 }
370
371 pub fn event_store(&self) -> &std::sync::Arc<dyn domain::events::EventStore> {
373 &self.event_store
374 }
375
376 pub fn ensemble_classifier(&self) -> &EnsembleClassifier {
378 &self.ensemble_classifier
379 }
380
381 pub fn detection_pipeline(&self) -> &DetectionPipeline {
383 &self.detection_pipeline
384 }
385
386 pub fn tracker(&self) -> &tracking::SurvivorTracker {
388 &self.tracker
389 }
390
391 pub fn tracker_mut(&mut self) -> &mut tracking::SurvivorTracker {
393 &mut self.tracker
394 }
395
396 pub fn initialize_event(
398 &mut self,
399 location: geo::Point<f64>,
400 description: &str,
401 ) -> Result<&DisasterEvent> {
402 let event = DisasterEvent::new(
403 self.config.disaster_type.clone(),
404 location,
405 description,
406 );
407 self.event = Some(event);
408 self.event.as_ref().ok_or_else(|| MatError::Domain("Failed to create event".into()))
409 }
410
411 pub fn add_zone(&mut self, zone: ScanZone) -> Result<()> {
413 let event = self.event.as_mut()
414 .ok_or_else(|| MatError::Domain("No active disaster event".into()))?;
415 event.add_zone(zone);
416 Ok(())
417 }
418
419 pub async fn start_scanning(&mut self) -> Result<()> {
421 use std::sync::atomic::Ordering;
422
423 self.running.store(true, Ordering::SeqCst);
424
425 while self.running.load(Ordering::SeqCst) {
426 self.scan_cycle().await?;
427
428 if !self.config.continuous_monitoring {
429 break;
430 }
431
432 tokio::time::sleep(
433 std::time::Duration::from_millis(self.config.scan_interval_ms)
434 ).await;
435 }
436
437 Ok(())
438 }
439
440 pub fn stop_scanning(&self) {
442 use std::sync::atomic::Ordering;
443 self.running.store(false, Ordering::SeqCst);
444 }
445
446 async fn scan_cycle(&mut self) -> Result<()> {
452 let scan_start = std::time::Instant::now();
453
454 let mut detections = Vec::new();
456
457 {
458 let event = self.event.as_ref()
459 .ok_or_else(|| MatError::Domain("No active disaster event".into()))?;
460
461 for zone in event.zones() {
462 if zone.status() != &ZoneStatus::Active {
463 continue;
464 }
465
466 let detection_result = self.detection_pipeline.process_zone(zone).await?;
468
469 if let Some(vital_signs) = detection_result {
470 let ensemble_result = self.ensemble_classifier.classify(&vital_signs);
472
473 if ensemble_result.confidence >= self.config.confidence_threshold {
475 let location = self.localization_service
477 .estimate_position(&vital_signs, zone);
478
479 detections.push((zone.id().clone(), zone.name().to_string(), vital_signs, location, ensemble_result));
480 }
481 }
482
483 let scan_duration = scan_start.elapsed();
485 let _ = self.event_store.append(DomainEvent::Zone(
486 domain::events::ZoneEvent::ZoneScanCompleted {
487 zone_id: zone.id().clone(),
488 detections_found: detections.len() as u32,
489 scan_duration_ms: scan_duration.as_millis() as u64,
490 timestamp: chrono::Utc::now(),
491 },
492 ));
493 }
494 }
495
496 let event = self.event.as_mut()
498 .ok_or_else(|| MatError::Domain("No active disaster event".into()))?;
499
500 for (zone_id, _zone_name, vital_signs, location, _ensemble) in detections {
501 let survivor = event.record_detection(zone_id.clone(), vital_signs.clone(), location.clone())?;
502
503 let _ = self.event_store.append(DomainEvent::Detection(
505 DetectionEvent::SurvivorDetected {
506 survivor_id: survivor.id().clone(),
507 zone_id,
508 vital_signs,
509 location,
510 timestamp: chrono::Utc::now(),
511 },
512 ));
513
514 if survivor.should_alert() {
516 let alert = self.alert_dispatcher.generate_alert(survivor)?;
517 let alert_id = alert.id().clone();
518 let priority = alert.priority();
519 let survivor_id = alert.survivor_id().clone();
520
521 let _ = self.event_store.append(DomainEvent::Alert(
523 AlertEvent::AlertGenerated {
524 alert_id,
525 survivor_id,
526 priority,
527 timestamp: chrono::Utc::now(),
528 },
529 ));
530
531 self.alert_dispatcher.dispatch(alert).await?;
532 }
533 }
534
535 Ok(())
536 }
537
538 pub fn event(&self) -> Option<&DisasterEvent> {
540 self.event.as_ref()
541 }
542
543 pub fn survivors(&self) -> Vec<&Survivor> {
545 self.event.as_ref()
546 .map(|e| e.survivors())
547 .unwrap_or_default()
548 }
549
550 pub fn survivors_by_triage(&self, status: TriageStatus) -> Vec<&Survivor> {
552 self.survivors()
553 .into_iter()
554 .filter(|s| s.triage_status() == &status)
555 .collect()
556 }
557}
558
559pub mod prelude {
561 pub use crate::{
562 DisasterConfig, DisasterConfigBuilder, DisasterResponse,
563 MatError, Result,
564 Survivor, SurvivorId, DisasterEvent, DisasterType,
566 ScanZone, ZoneBounds, TriageStatus,
567 VitalSignsReading, BreathingPattern, HeartbeatSignature,
568 Coordinates3D, Alert, Priority,
569 DomainEvent, EventStore, InMemoryEventStore,
571 DetectionEvent, AlertEvent, TrackingEvent,
572 DetectionPipeline, VitalSignsDetector,
574 EnsembleClassifier, EnsembleConfig, EnsembleResult,
575 LocalizationService,
577 AlertDispatcher,
579 MlDetectionConfig, MlDetectionPipeline, MlDetectionResult,
581 DebrisModel, MaterialType, DebrisClassification,
582 VitalSignsClassifier, UncertaintyEstimate,
583 SurvivorTracker, TrackerConfig, TrackId, DetectionObservation, AssociationResult,
585 };
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591
592 #[test]
593 fn test_config_builder() {
594 let config = DisasterConfig::builder()
595 .disaster_type(DisasterType::Earthquake)
596 .sensitivity(0.9)
597 .confidence_threshold(0.6)
598 .max_depth(10.0)
599 .build();
600
601 assert!(matches!(config.disaster_type, DisasterType::Earthquake));
602 assert!((config.sensitivity - 0.9).abs() < f64::EPSILON);
603 assert!((config.confidence_threshold - 0.6).abs() < f64::EPSILON);
604 assert!((config.max_depth - 10.0).abs() < f64::EPSILON);
605 }
606
607 #[test]
608 fn test_sensitivity_clamping() {
609 let config = DisasterConfig::builder()
610 .sensitivity(1.5)
611 .build();
612
613 assert!((config.sensitivity - 1.0).abs() < f64::EPSILON);
614
615 let config = DisasterConfig::builder()
616 .sensitivity(-0.5)
617 .build();
618
619 assert!(config.sensitivity.abs() < f64::EPSILON);
620 }
621
622 #[test]
623 fn test_version() {
624 assert!(!VERSION.is_empty());
625 }
626}