Files
wifi-densepose/rust-port/wifi-densepose-rs/crates/wifi-densepose-mat/src/domain/alert.rs
Claude cd877f87c2 docs: Add comprehensive wifi-Mat user guide and fix compilation
- Add detailed wifi-Mat user guide covering:
  - Installation and setup
  - Detection capabilities (breathing, heartbeat, movement)
  - Localization system (triangulation, depth estimation)
  - START protocol triage classification
  - Alert system with priority escalation
  - Field deployment guide
  - Hardware setup requirements
  - API reference and troubleshooting

- Update main README.md with wifi-Mat section and links

- Fix compilation issues:
  - Add missing deadline field in AlertPayload
  - Fix type ambiguity in powi calls
  - Resolve borrow checker issues in scan_cycle
  - Export CsiDataBuffer from detection module
  - Add missing imports in test modules

- All 83 tests now passing
2026-01-13 17:55:50 +00:00

460 lines
12 KiB
Rust

//! Alert types for emergency notifications.
use chrono::{DateTime, Utc};
use uuid::Uuid;
use super::{SurvivorId, TriageStatus, Coordinates3D};
/// Unique identifier for an alert
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AlertId(Uuid);
impl AlertId {
/// Create a new random alert ID
pub fn new() -> Self {
Self(Uuid::new_v4())
}
/// Get the inner UUID
pub fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for AlertId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for AlertId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// Alert priority levels
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Priority {
/// Critical - immediate action required
Critical = 1,
/// High - urgent attention needed
High = 2,
/// Medium - important but not urgent
Medium = 3,
/// Low - informational
Low = 4,
}
impl Priority {
/// Create from triage status
pub fn from_triage(status: &TriageStatus) -> Self {
match status {
TriageStatus::Immediate => Priority::Critical,
TriageStatus::Delayed => Priority::High,
TriageStatus::Minor => Priority::Medium,
TriageStatus::Deceased => Priority::Low,
TriageStatus::Unknown => Priority::Medium,
}
}
/// Get numeric value (lower = higher priority)
pub fn value(&self) -> u8 {
*self as u8
}
/// Get display color
pub fn color(&self) -> &'static str {
match self {
Priority::Critical => "red",
Priority::High => "orange",
Priority::Medium => "yellow",
Priority::Low => "blue",
}
}
/// Get sound pattern for audio alerts
pub fn audio_pattern(&self) -> &'static str {
match self {
Priority::Critical => "rapid_beep",
Priority::High => "double_beep",
Priority::Medium => "single_beep",
Priority::Low => "soft_tone",
}
}
}
impl std::fmt::Display for Priority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Priority::Critical => write!(f, "CRITICAL"),
Priority::High => write!(f, "HIGH"),
Priority::Medium => write!(f, "MEDIUM"),
Priority::Low => write!(f, "LOW"),
}
}
}
/// Payload containing alert details
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AlertPayload {
/// Human-readable title
pub title: String,
/// Detailed message
pub message: String,
/// Triage status of survivor
pub triage_status: TriageStatus,
/// Location if known
pub location: Option<Coordinates3D>,
/// Recommended action
pub recommended_action: String,
/// Time-critical deadline (if any)
pub deadline: Option<DateTime<Utc>>,
/// Additional metadata
pub metadata: std::collections::HashMap<String, String>,
}
impl AlertPayload {
/// Create a new alert payload
pub fn new(
title: impl Into<String>,
message: impl Into<String>,
triage_status: TriageStatus,
) -> Self {
Self {
title: title.into(),
message: message.into(),
triage_status,
location: None,
recommended_action: String::new(),
deadline: None,
metadata: std::collections::HashMap::new(),
}
}
/// Set location
pub fn with_location(mut self, location: Coordinates3D) -> Self {
self.location = Some(location);
self
}
/// Set recommended action
pub fn with_action(mut self, action: impl Into<String>) -> Self {
self.recommended_action = action.into();
self
}
/// Set deadline
pub fn with_deadline(mut self, deadline: DateTime<Utc>) -> Self {
self.deadline = Some(deadline);
self
}
/// Add metadata
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
/// Status of an alert
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AlertStatus {
/// Alert is pending acknowledgement
Pending,
/// Alert has been acknowledged
Acknowledged,
/// Alert is being worked on
InProgress,
/// Alert has been resolved
Resolved,
/// Alert was cancelled/superseded
Cancelled,
/// Alert expired without action
Expired,
}
/// Resolution details for a closed alert
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AlertResolution {
/// Resolution type
pub resolution_type: ResolutionType,
/// Resolution notes
pub notes: String,
/// Team that resolved
pub resolved_by: Option<String>,
/// Resolution time
pub resolved_at: DateTime<Utc>,
}
/// Types of alert resolution
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ResolutionType {
/// Survivor was rescued
Rescued,
/// Alert was a false positive
FalsePositive,
/// Survivor deceased before rescue
Deceased,
/// Alert superseded by new information
Superseded,
/// Alert timed out
TimedOut,
/// Other resolution
Other,
}
/// An alert for rescue teams
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Alert {
id: AlertId,
survivor_id: SurvivorId,
priority: Priority,
payload: AlertPayload,
status: AlertStatus,
created_at: DateTime<Utc>,
acknowledged_at: Option<DateTime<Utc>>,
acknowledged_by: Option<String>,
resolution: Option<AlertResolution>,
escalation_count: u32,
}
impl Alert {
/// Create a new alert
pub fn new(survivor_id: SurvivorId, priority: Priority, payload: AlertPayload) -> Self {
Self {
id: AlertId::new(),
survivor_id,
priority,
payload,
status: AlertStatus::Pending,
created_at: Utc::now(),
acknowledged_at: None,
acknowledged_by: None,
resolution: None,
escalation_count: 0,
}
}
/// Get the alert ID
pub fn id(&self) -> &AlertId {
&self.id
}
/// Get the survivor ID
pub fn survivor_id(&self) -> &SurvivorId {
&self.survivor_id
}
/// Get the priority
pub fn priority(&self) -> Priority {
self.priority
}
/// Get the payload
pub fn payload(&self) -> &AlertPayload {
&self.payload
}
/// Get the status
pub fn status(&self) -> &AlertStatus {
&self.status
}
/// Get creation time
pub fn created_at(&self) -> &DateTime<Utc> {
&self.created_at
}
/// Get acknowledgement time
pub fn acknowledged_at(&self) -> Option<&DateTime<Utc>> {
self.acknowledged_at.as_ref()
}
/// Get who acknowledged
pub fn acknowledged_by(&self) -> Option<&str> {
self.acknowledged_by.as_deref()
}
/// Get resolution
pub fn resolution(&self) -> Option<&AlertResolution> {
self.resolution.as_ref()
}
/// Get escalation count
pub fn escalation_count(&self) -> u32 {
self.escalation_count
}
/// Acknowledge the alert
pub fn acknowledge(&mut self, by: impl Into<String>) {
self.status = AlertStatus::Acknowledged;
self.acknowledged_at = Some(Utc::now());
self.acknowledged_by = Some(by.into());
}
/// Mark as in progress
pub fn start_work(&mut self) {
if self.status == AlertStatus::Acknowledged {
self.status = AlertStatus::InProgress;
}
}
/// Resolve the alert
pub fn resolve(&mut self, resolution: AlertResolution) {
self.status = AlertStatus::Resolved;
self.resolution = Some(resolution);
}
/// Cancel the alert
pub fn cancel(&mut self, reason: &str) {
self.status = AlertStatus::Cancelled;
self.resolution = Some(AlertResolution {
resolution_type: ResolutionType::Other,
notes: reason.to_string(),
resolved_by: None,
resolved_at: Utc::now(),
});
}
/// Escalate the alert (increase priority)
pub fn escalate(&mut self) {
self.escalation_count += 1;
if self.priority != Priority::Critical {
self.priority = match self.priority {
Priority::Low => Priority::Medium,
Priority::Medium => Priority::High,
Priority::High => Priority::Critical,
Priority::Critical => Priority::Critical,
};
}
}
/// Check if alert is pending
pub fn is_pending(&self) -> bool {
self.status == AlertStatus::Pending
}
/// Check if alert is active (not resolved/cancelled)
pub fn is_active(&self) -> bool {
matches!(
self.status,
AlertStatus::Pending | AlertStatus::Acknowledged | AlertStatus::InProgress
)
}
/// Time since alert was created
pub fn age(&self) -> chrono::Duration {
Utc::now() - self.created_at
}
/// Time since acknowledgement
pub fn time_since_ack(&self) -> Option<chrono::Duration> {
self.acknowledged_at.map(|t| Utc::now() - t)
}
/// Check if alert needs escalation based on time
pub fn needs_escalation(&self, max_pending_seconds: i64) -> bool {
if !self.is_pending() {
return false;
}
self.age().num_seconds() > max_pending_seconds
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_payload() -> AlertPayload {
AlertPayload::new(
"Survivor Detected",
"Vital signs detected in Zone A",
TriageStatus::Immediate,
)
}
#[test]
fn test_alert_creation() {
let survivor_id = SurvivorId::new();
let alert = Alert::new(
survivor_id.clone(),
Priority::Critical,
create_test_payload(),
);
assert_eq!(alert.survivor_id(), &survivor_id);
assert_eq!(alert.priority(), Priority::Critical);
assert!(alert.is_pending());
assert!(alert.is_active());
}
#[test]
fn test_alert_lifecycle() {
let mut alert = Alert::new(
SurvivorId::new(),
Priority::High,
create_test_payload(),
);
// Initial state
assert!(alert.is_pending());
// Acknowledge
alert.acknowledge("Team Alpha");
assert_eq!(alert.status(), &AlertStatus::Acknowledged);
assert_eq!(alert.acknowledged_by(), Some("Team Alpha"));
// Start work
alert.start_work();
assert_eq!(alert.status(), &AlertStatus::InProgress);
// Resolve
alert.resolve(AlertResolution {
resolution_type: ResolutionType::Rescued,
notes: "Survivor extracted successfully".to_string(),
resolved_by: Some("Team Alpha".to_string()),
resolved_at: Utc::now(),
});
assert_eq!(alert.status(), &AlertStatus::Resolved);
assert!(!alert.is_active());
}
#[test]
fn test_alert_escalation() {
let mut alert = Alert::new(
SurvivorId::new(),
Priority::Low,
create_test_payload(),
);
alert.escalate();
assert_eq!(alert.priority(), Priority::Medium);
assert_eq!(alert.escalation_count(), 1);
alert.escalate();
assert_eq!(alert.priority(), Priority::High);
alert.escalate();
assert_eq!(alert.priority(), Priority::Critical);
// Can't escalate beyond critical
alert.escalate();
assert_eq!(alert.priority(), Priority::Critical);
}
#[test]
fn test_priority_from_triage() {
assert_eq!(Priority::from_triage(&TriageStatus::Immediate), Priority::Critical);
assert_eq!(Priority::from_triage(&TriageStatus::Delayed), Priority::High);
assert_eq!(Priority::from_triage(&TriageStatus::Minor), Priority::Medium);
}
}