#![allow(missing_docs)]
use chrono::{DateTime, Utc};
use super::{
AlertId, AlertResolution, Coordinates3D, Priority, ScanZoneId, SurvivorId, TriageStatus,
VitalSignsReading,
};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DomainEvent {
Detection(DetectionEvent),
Alert(AlertEvent),
Zone(ZoneEvent),
System(SystemEvent),
Tracking(TrackingEvent),
}
impl DomainEvent {
pub fn timestamp(&self) -> DateTime<Utc> {
match self {
DomainEvent::Detection(e) => e.timestamp(),
DomainEvent::Alert(e) => e.timestamp(),
DomainEvent::Zone(e) => e.timestamp(),
DomainEvent::System(e) => e.timestamp(),
DomainEvent::Tracking(e) => e.timestamp(),
}
}
pub fn event_type(&self) -> &'static str {
match self {
DomainEvent::Detection(e) => e.event_type(),
DomainEvent::Alert(e) => e.event_type(),
DomainEvent::Zone(e) => e.event_type(),
DomainEvent::System(e) => e.event_type(),
DomainEvent::Tracking(e) => e.event_type(),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DetectionEvent {
SurvivorDetected {
survivor_id: SurvivorId,
zone_id: ScanZoneId,
vital_signs: VitalSignsReading,
location: Option<Coordinates3D>,
timestamp: DateTime<Utc>,
},
VitalsUpdated {
survivor_id: SurvivorId,
previous_triage: TriageStatus,
current_triage: TriageStatus,
confidence: f64,
timestamp: DateTime<Utc>,
},
TriageStatusChanged {
survivor_id: SurvivorId,
previous: TriageStatus,
current: TriageStatus,
reason: String,
timestamp: DateTime<Utc>,
},
LocationRefined {
survivor_id: SurvivorId,
previous: Option<Coordinates3D>,
current: Coordinates3D,
uncertainty_reduced: bool,
timestamp: DateTime<Utc>,
},
SurvivorLost {
survivor_id: SurvivorId,
last_detection: DateTime<Utc>,
reason: LostReason,
timestamp: DateTime<Utc>,
},
SurvivorRescued {
survivor_id: SurvivorId,
rescue_team: Option<String>,
timestamp: DateTime<Utc>,
},
SurvivorDeceased {
survivor_id: SurvivorId,
timestamp: DateTime<Utc>,
},
}
impl DetectionEvent {
pub fn timestamp(&self) -> DateTime<Utc> {
match self {
Self::SurvivorDetected { timestamp, .. } => *timestamp,
Self::VitalsUpdated { timestamp, .. } => *timestamp,
Self::TriageStatusChanged { timestamp, .. } => *timestamp,
Self::LocationRefined { timestamp, .. } => *timestamp,
Self::SurvivorLost { timestamp, .. } => *timestamp,
Self::SurvivorRescued { timestamp, .. } => *timestamp,
Self::SurvivorDeceased { timestamp, .. } => *timestamp,
}
}
pub fn event_type(&self) -> &'static str {
match self {
Self::SurvivorDetected { .. } => "SurvivorDetected",
Self::VitalsUpdated { .. } => "VitalsUpdated",
Self::TriageStatusChanged { .. } => "TriageStatusChanged",
Self::LocationRefined { .. } => "LocationRefined",
Self::SurvivorLost { .. } => "SurvivorLost",
Self::SurvivorRescued { .. } => "SurvivorRescued",
Self::SurvivorDeceased { .. } => "SurvivorDeceased",
}
}
pub fn survivor_id(&self) -> &SurvivorId {
match self {
Self::SurvivorDetected { survivor_id, .. } => survivor_id,
Self::VitalsUpdated { survivor_id, .. } => survivor_id,
Self::TriageStatusChanged { survivor_id, .. } => survivor_id,
Self::LocationRefined { survivor_id, .. } => survivor_id,
Self::SurvivorLost { survivor_id, .. } => survivor_id,
Self::SurvivorRescued { survivor_id, .. } => survivor_id,
Self::SurvivorDeceased { survivor_id, .. } => survivor_id,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LostReason {
Rescued,
FalsePositive,
SignalLost,
ZoneDeactivated,
SensorFailure,
Unknown,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AlertEvent {
AlertGenerated {
alert_id: AlertId,
survivor_id: SurvivorId,
priority: Priority,
timestamp: DateTime<Utc>,
},
AlertAcknowledged {
alert_id: AlertId,
acknowledged_by: String,
timestamp: DateTime<Utc>,
},
AlertEscalated {
alert_id: AlertId,
previous_priority: Priority,
new_priority: Priority,
reason: String,
timestamp: DateTime<Utc>,
},
AlertResolved {
alert_id: AlertId,
resolution: AlertResolution,
timestamp: DateTime<Utc>,
},
AlertCancelled {
alert_id: AlertId,
reason: String,
timestamp: DateTime<Utc>,
},
}
impl AlertEvent {
pub fn timestamp(&self) -> DateTime<Utc> {
match self {
Self::AlertGenerated { timestamp, .. } => *timestamp,
Self::AlertAcknowledged { timestamp, .. } => *timestamp,
Self::AlertEscalated { timestamp, .. } => *timestamp,
Self::AlertResolved { timestamp, .. } => *timestamp,
Self::AlertCancelled { timestamp, .. } => *timestamp,
}
}
pub fn event_type(&self) -> &'static str {
match self {
Self::AlertGenerated { .. } => "AlertGenerated",
Self::AlertAcknowledged { .. } => "AlertAcknowledged",
Self::AlertEscalated { .. } => "AlertEscalated",
Self::AlertResolved { .. } => "AlertResolved",
Self::AlertCancelled { .. } => "AlertCancelled",
}
}
pub fn alert_id(&self) -> &AlertId {
match self {
Self::AlertGenerated { alert_id, .. } => alert_id,
Self::AlertAcknowledged { alert_id, .. } => alert_id,
Self::AlertEscalated { alert_id, .. } => alert_id,
Self::AlertResolved { alert_id, .. } => alert_id,
Self::AlertCancelled { alert_id, .. } => alert_id,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ZoneEvent {
ZoneActivated {
zone_id: ScanZoneId,
zone_name: String,
timestamp: DateTime<Utc>,
},
ZoneScanCompleted {
zone_id: ScanZoneId,
detections_found: u32,
scan_duration_ms: u64,
timestamp: DateTime<Utc>,
},
ZonePaused {
zone_id: ScanZoneId,
reason: String,
timestamp: DateTime<Utc>,
},
ZoneResumed {
zone_id: ScanZoneId,
timestamp: DateTime<Utc>,
},
ZoneCompleted {
zone_id: ScanZoneId,
total_survivors_found: u32,
timestamp: DateTime<Utc>,
},
ZoneDeactivated {
zone_id: ScanZoneId,
reason: String,
timestamp: DateTime<Utc>,
},
}
impl ZoneEvent {
pub fn timestamp(&self) -> DateTime<Utc> {
match self {
Self::ZoneActivated { timestamp, .. } => *timestamp,
Self::ZoneScanCompleted { timestamp, .. } => *timestamp,
Self::ZonePaused { timestamp, .. } => *timestamp,
Self::ZoneResumed { timestamp, .. } => *timestamp,
Self::ZoneCompleted { timestamp, .. } => *timestamp,
Self::ZoneDeactivated { timestamp, .. } => *timestamp,
}
}
pub fn event_type(&self) -> &'static str {
match self {
Self::ZoneActivated { .. } => "ZoneActivated",
Self::ZoneScanCompleted { .. } => "ZoneScanCompleted",
Self::ZonePaused { .. } => "ZonePaused",
Self::ZoneResumed { .. } => "ZoneResumed",
Self::ZoneCompleted { .. } => "ZoneCompleted",
Self::ZoneDeactivated { .. } => "ZoneDeactivated",
}
}
pub fn zone_id(&self) -> &ScanZoneId {
match self {
Self::ZoneActivated { zone_id, .. } => zone_id,
Self::ZoneScanCompleted { zone_id, .. } => zone_id,
Self::ZonePaused { zone_id, .. } => zone_id,
Self::ZoneResumed { zone_id, .. } => zone_id,
Self::ZoneCompleted { zone_id, .. } => zone_id,
Self::ZoneDeactivated { zone_id, .. } => zone_id,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SystemEvent {
SystemStarted {
version: String,
timestamp: DateTime<Utc>,
},
SystemStopped {
reason: String,
timestamp: DateTime<Utc>,
},
SensorConnected {
sensor_id: String,
zone_id: ScanZoneId,
timestamp: DateTime<Utc>,
},
SensorDisconnected {
sensor_id: String,
reason: String,
timestamp: DateTime<Utc>,
},
ConfigChanged {
setting: String,
previous_value: String,
new_value: String,
timestamp: DateTime<Utc>,
},
ErrorOccurred {
error_type: String,
message: String,
severity: ErrorSeverity,
timestamp: DateTime<Utc>,
},
}
impl SystemEvent {
pub fn timestamp(&self) -> DateTime<Utc> {
match self {
Self::SystemStarted { timestamp, .. } => *timestamp,
Self::SystemStopped { timestamp, .. } => *timestamp,
Self::SensorConnected { timestamp, .. } => *timestamp,
Self::SensorDisconnected { timestamp, .. } => *timestamp,
Self::ConfigChanged { timestamp, .. } => *timestamp,
Self::ErrorOccurred { timestamp, .. } => *timestamp,
}
}
pub fn event_type(&self) -> &'static str {
match self {
Self::SystemStarted { .. } => "SystemStarted",
Self::SystemStopped { .. } => "SystemStopped",
Self::SensorConnected { .. } => "SensorConnected",
Self::SensorDisconnected { .. } => "SensorDisconnected",
Self::ConfigChanged { .. } => "ConfigChanged",
Self::ErrorOccurred { .. } => "ErrorOccurred",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ErrorSeverity {
Warning,
Error,
Critical,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TrackingEvent {
TrackBorn {
track_id: String, survivor_id: SurvivorId,
zone_id: ScanZoneId,
timestamp: DateTime<Utc>,
},
TrackLost {
track_id: String,
survivor_id: SurvivorId,
last_position: Option<Coordinates3D>,
timestamp: DateTime<Utc>,
},
TrackReidentified {
track_id: String,
survivor_id: SurvivorId,
gap_secs: f64,
fingerprint_distance: f32,
timestamp: DateTime<Utc>,
},
TrackTerminated {
track_id: String,
survivor_id: SurvivorId,
lost_duration_secs: f64,
timestamp: DateTime<Utc>,
},
TrackRescued {
track_id: String,
survivor_id: SurvivorId,
timestamp: DateTime<Utc>,
},
}
impl TrackingEvent {
pub fn timestamp(&self) -> DateTime<Utc> {
match self {
TrackingEvent::TrackBorn { timestamp, .. } => *timestamp,
TrackingEvent::TrackLost { timestamp, .. } => *timestamp,
TrackingEvent::TrackReidentified { timestamp, .. } => *timestamp,
TrackingEvent::TrackTerminated { timestamp, .. } => *timestamp,
TrackingEvent::TrackRescued { timestamp, .. } => *timestamp,
}
}
pub fn event_type(&self) -> &'static str {
match self {
TrackingEvent::TrackBorn { .. } => "TrackBorn",
TrackingEvent::TrackLost { .. } => "TrackLost",
TrackingEvent::TrackReidentified { .. } => "TrackReidentified",
TrackingEvent::TrackTerminated { .. } => "TrackTerminated",
TrackingEvent::TrackRescued { .. } => "TrackRescued",
}
}
}
pub trait EventStore: Send + Sync {
fn append(&self, event: DomainEvent) -> Result<(), crate::MatError>;
fn all(&self) -> Result<Vec<DomainEvent>, crate::MatError>;
fn since(&self, timestamp: DateTime<Utc>) -> Result<Vec<DomainEvent>, crate::MatError>;
fn for_survivor(&self, survivor_id: &SurvivorId) -> Result<Vec<DomainEvent>, crate::MatError>;
}
#[derive(Debug, Default)]
pub struct InMemoryEventStore {
events: parking_lot::RwLock<Vec<DomainEvent>>,
}
impl InMemoryEventStore {
pub fn new() -> Self {
Self::default()
}
}
impl EventStore for InMemoryEventStore {
fn append(&self, event: DomainEvent) -> Result<(), crate::MatError> {
self.events.write().push(event);
Ok(())
}
fn all(&self) -> Result<Vec<DomainEvent>, crate::MatError> {
Ok(self.events.read().clone())
}
fn since(&self, timestamp: DateTime<Utc>) -> Result<Vec<DomainEvent>, crate::MatError> {
Ok(self
.events
.read()
.iter()
.filter(|e| e.timestamp() >= timestamp)
.cloned()
.collect())
}
fn for_survivor(&self, survivor_id: &SurvivorId) -> Result<Vec<DomainEvent>, crate::MatError> {
Ok(self
.events
.read()
.iter()
.filter(|e| {
if let DomainEvent::Detection(de) = e {
de.survivor_id() == survivor_id
} else {
false
}
})
.cloned()
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_in_memory_event_store() {
let store = InMemoryEventStore::new();
let event = DomainEvent::System(SystemEvent::SystemStarted {
version: "1.0.0".to_string(),
timestamp: Utc::now(),
});
store.append(event).unwrap();
let events = store.all().unwrap();
assert_eq!(events.len(), 1);
}
}