#![allow(missing_docs)]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::domain::{
AlertStatus, DisasterType, EventStatus, Priority, SurvivorStatus, TriageStatus, ZoneStatus,
};
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CreateEventRequest {
pub event_type: DisasterTypeDto,
pub latitude: f64,
pub longitude: f64,
pub description: String,
#[serde(default)]
pub estimated_occupancy: Option<u32>,
#[serde(default)]
pub lead_agency: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct EventResponse {
pub id: Uuid,
pub event_type: DisasterTypeDto,
pub status: EventStatusDto,
pub start_time: DateTime<Utc>,
pub latitude: f64,
pub longitude: f64,
pub description: String,
pub zone_count: usize,
pub survivor_count: usize,
pub triage_summary: TriageSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<EventMetadataDto>,
}
#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename_all = "snake_case")]
pub struct TriageSummary {
pub immediate: u32,
pub delayed: u32,
pub minor: u32,
pub deceased: u32,
pub unknown: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct EventMetadataDto {
#[serde(skip_serializing_if = "Option::is_none")]
pub estimated_occupancy: Option<u32>,
#[serde(default)]
pub confirmed_rescued: u32,
#[serde(default)]
pub confirmed_deceased: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub weather: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lead_agency: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct EventListResponse {
pub events: Vec<EventResponse>,
pub total: usize,
pub page: usize,
pub page_size: usize,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CreateZoneRequest {
pub name: String,
pub bounds: ZoneBoundsDto,
#[serde(default)]
pub parameters: Option<ScanParametersDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ZoneBoundsDto {
Rectangle {
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
},
Circle {
center_x: f64,
center_y: f64,
radius: f64,
},
Polygon { vertices: Vec<(f64, f64)> },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct ScanParametersDto {
#[serde(default = "default_sensitivity")]
pub sensitivity: f64,
#[serde(default = "default_max_depth")]
pub max_depth: f64,
#[serde(default)]
pub resolution: ScanResolutionDto,
#[serde(default = "default_true")]
pub enhanced_breathing: bool,
#[serde(default)]
pub heartbeat_detection: bool,
}
fn default_sensitivity() -> f64 {
0.8
}
fn default_max_depth() -> f64 {
5.0
}
fn default_true() -> bool {
true
}
impl Default for ScanParametersDto {
fn default() -> Self {
Self {
sensitivity: default_sensitivity(),
max_depth: default_max_depth(),
resolution: ScanResolutionDto::default(),
enhanced_breathing: default_true(),
heartbeat_detection: false,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ScanResolutionDto {
Quick,
#[default]
Standard,
High,
Maximum,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ZoneResponse {
pub id: Uuid,
pub name: String,
pub status: ZoneStatusDto,
pub bounds: ZoneBoundsDto,
pub area: f64,
pub parameters: ScanParametersDto,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_scan: Option<DateTime<Utc>>,
pub scan_count: u32,
pub detections_count: u32,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ZoneListResponse {
pub zones: Vec<ZoneResponse>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct SurvivorResponse {
pub id: Uuid,
pub zone_id: Uuid,
pub status: SurvivorStatusDto,
pub triage_status: TriageStatusDto,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<LocationDto>,
pub vital_signs: VitalSignsSummaryDto,
pub confidence: f64,
pub first_detected: DateTime<Utc>,
pub last_updated: DateTime<Utc>,
pub is_deteriorating: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<SurvivorMetadataDto>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct LocationDto {
pub x: f64,
pub y: f64,
pub z: f64,
pub depth: f64,
pub uncertainty_radius: f64,
pub confidence: f64,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct VitalSignsSummaryDto {
#[serde(skip_serializing_if = "Option::is_none")]
pub breathing_rate: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub breathing_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub heart_rate: Option<f32>,
pub has_heartbeat: bool,
pub has_movement: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub movement_type: Option<String>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct SurvivorMetadataDto {
#[serde(skip_serializing_if = "Option::is_none")]
pub estimated_age_category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigned_team: Option<String>,
pub notes: Vec<String>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct SurvivorListResponse {
pub survivors: Vec<SurvivorResponse>,
pub total: usize,
pub triage_summary: TriageSummary,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct AlertResponse {
pub id: Uuid,
pub survivor_id: Uuid,
pub priority: PriorityDto,
pub status: AlertStatusDto,
pub title: String,
pub message: String,
pub triage_status: TriageStatusDto,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<LocationDto>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recommended_action: Option<String>,
pub created_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub acknowledged_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub acknowledged_by: Option<String>,
pub escalation_count: u32,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct AcknowledgeAlertRequest {
pub acknowledged_by: String,
#[serde(default)]
pub notes: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct AcknowledgeAlertResponse {
pub success: bool,
pub alert: AlertResponse,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct AlertListResponse {
pub alerts: Vec<AlertResponse>,
pub total: usize,
pub priority_counts: PriorityCounts,
}
#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename_all = "snake_case")]
pub struct PriorityCounts {
pub critical: usize,
pub high: usize,
pub medium: usize,
pub low: usize,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WebSocketMessage {
SurvivorDetected {
event_id: Uuid,
survivor: SurvivorResponse,
},
SurvivorUpdated {
event_id: Uuid,
survivor: SurvivorResponse,
},
SurvivorLost { event_id: Uuid, survivor_id: Uuid },
AlertCreated {
event_id: Uuid,
alert: AlertResponse,
},
AlertUpdated {
event_id: Uuid,
alert: AlertResponse,
},
ZoneScanComplete {
event_id: Uuid,
zone_id: Uuid,
detections: u32,
},
EventStatusChanged {
event_id: Uuid,
old_status: EventStatusDto,
new_status: EventStatusDto,
},
Heartbeat { timestamp: DateTime<Utc> },
Error { code: String, message: String },
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum WebSocketRequest {
Subscribe { event_id: Uuid },
Unsubscribe { event_id: Uuid },
SubscribeAll,
GetState { event_id: Uuid },
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum DisasterTypeDto {
BuildingCollapse,
Earthquake,
Landslide,
Avalanche,
Flood,
MineCollapse,
Industrial,
TunnelCollapse,
Unknown,
}
impl From<DisasterType> for DisasterTypeDto {
fn from(dt: DisasterType) -> Self {
match dt {
DisasterType::BuildingCollapse => DisasterTypeDto::BuildingCollapse,
DisasterType::Earthquake => DisasterTypeDto::Earthquake,
DisasterType::Landslide => DisasterTypeDto::Landslide,
DisasterType::Avalanche => DisasterTypeDto::Avalanche,
DisasterType::Flood => DisasterTypeDto::Flood,
DisasterType::MineCollapse => DisasterTypeDto::MineCollapse,
DisasterType::Industrial => DisasterTypeDto::Industrial,
DisasterType::TunnelCollapse => DisasterTypeDto::TunnelCollapse,
DisasterType::Unknown => DisasterTypeDto::Unknown,
}
}
}
impl From<DisasterTypeDto> for DisasterType {
fn from(dt: DisasterTypeDto) -> Self {
match dt {
DisasterTypeDto::BuildingCollapse => DisasterType::BuildingCollapse,
DisasterTypeDto::Earthquake => DisasterType::Earthquake,
DisasterTypeDto::Landslide => DisasterType::Landslide,
DisasterTypeDto::Avalanche => DisasterType::Avalanche,
DisasterTypeDto::Flood => DisasterType::Flood,
DisasterTypeDto::MineCollapse => DisasterType::MineCollapse,
DisasterTypeDto::Industrial => DisasterType::Industrial,
DisasterTypeDto::TunnelCollapse => DisasterType::TunnelCollapse,
DisasterTypeDto::Unknown => DisasterType::Unknown,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum EventStatusDto {
Initializing,
Active,
Suspended,
SecondarySearch,
Closed,
}
impl From<EventStatus> for EventStatusDto {
fn from(es: EventStatus) -> Self {
match es {
EventStatus::Initializing => EventStatusDto::Initializing,
EventStatus::Active => EventStatusDto::Active,
EventStatus::Suspended => EventStatusDto::Suspended,
EventStatus::SecondarySearch => EventStatusDto::SecondarySearch,
EventStatus::Closed => EventStatusDto::Closed,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum ZoneStatusDto {
Active,
Paused,
Complete,
Inaccessible,
Deactivated,
}
impl From<ZoneStatus> for ZoneStatusDto {
fn from(zs: ZoneStatus) -> Self {
match zs {
ZoneStatus::Active => ZoneStatusDto::Active,
ZoneStatus::Paused => ZoneStatusDto::Paused,
ZoneStatus::Complete => ZoneStatusDto::Complete,
ZoneStatus::Inaccessible => ZoneStatusDto::Inaccessible,
ZoneStatus::Deactivated => ZoneStatusDto::Deactivated,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum TriageStatusDto {
Immediate,
Delayed,
Minor,
Deceased,
Unknown,
}
impl From<TriageStatus> for TriageStatusDto {
fn from(ts: TriageStatus) -> Self {
match ts {
TriageStatus::Immediate => TriageStatusDto::Immediate,
TriageStatus::Delayed => TriageStatusDto::Delayed,
TriageStatus::Minor => TriageStatusDto::Minor,
TriageStatus::Deceased => TriageStatusDto::Deceased,
TriageStatus::Unknown => TriageStatusDto::Unknown,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum PriorityDto {
Critical,
High,
Medium,
Low,
}
impl From<Priority> for PriorityDto {
fn from(p: Priority) -> Self {
match p {
Priority::Critical => PriorityDto::Critical,
Priority::High => PriorityDto::High,
Priority::Medium => PriorityDto::Medium,
Priority::Low => PriorityDto::Low,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum AlertStatusDto {
Pending,
Acknowledged,
InProgress,
Resolved,
Cancelled,
Expired,
}
impl From<AlertStatus> for AlertStatusDto {
fn from(as_: AlertStatus) -> Self {
match as_ {
AlertStatus::Pending => AlertStatusDto::Pending,
AlertStatus::Acknowledged => AlertStatusDto::Acknowledged,
AlertStatus::InProgress => AlertStatusDto::InProgress,
AlertStatus::Resolved => AlertStatusDto::Resolved,
AlertStatus::Cancelled => AlertStatusDto::Cancelled,
AlertStatus::Expired => AlertStatusDto::Expired,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum SurvivorStatusDto {
Active,
Rescued,
Lost,
Deceased,
FalsePositive,
}
impl From<SurvivorStatus> for SurvivorStatusDto {
fn from(ss: SurvivorStatus) -> Self {
match ss {
SurvivorStatus::Active => SurvivorStatusDto::Active,
SurvivorStatus::Rescued => SurvivorStatusDto::Rescued,
SurvivorStatus::Lost => SurvivorStatusDto::Lost,
SurvivorStatus::Deceased => SurvivorStatusDto::Deceased,
SurvivorStatus::FalsePositive => SurvivorStatusDto::FalsePositive,
}
}
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub struct ListEventsQuery {
pub status: Option<EventStatusDto>,
pub event_type: Option<DisasterTypeDto>,
#[serde(default)]
pub page: usize,
#[serde(default = "default_page_size")]
pub page_size: usize,
}
fn default_page_size() -> usize {
20
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub struct ListSurvivorsQuery {
pub triage_status: Option<TriageStatusDto>,
pub zone_id: Option<Uuid>,
pub min_confidence: Option<f64>,
#[serde(default)]
pub deteriorating_only: bool,
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub struct ListAlertsQuery {
pub priority: Option<PriorityDto>,
pub status: Option<AlertStatusDto>,
#[serde(default)]
pub pending_only: bool,
#[serde(default)]
pub active_only: bool,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct PushCsiDataRequest {
pub amplitudes: Vec<f64>,
pub phases: Vec<f64>,
#[serde(default)]
pub sample_rate: Option<f64>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PushCsiDataResponse {
pub accepted: bool,
pub samples_ingested: usize,
pub buffer_duration_secs: f64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct ScanControlRequest {
pub action: ScanAction,
}
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ScanAction {
Start,
Stop,
Pause,
Resume,
ClearBuffer,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ScanControlResponse {
pub success: bool,
pub state: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct PipelineStatusResponse {
pub scanning: bool,
pub buffer_duration_secs: f64,
pub ml_enabled: bool,
pub ml_ready: bool,
pub sample_rate: f64,
pub heartbeat_enabled: bool,
pub min_confidence: f64,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DomainEventsResponse {
pub events: Vec<DomainEventDto>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DomainEventDto {
pub event_type: String,
pub timestamp: DateTime<Utc>,
pub details: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_event_request_deserialize() {
let json = r#"{
"event_type": "Earthquake",
"latitude": 37.7749,
"longitude": -122.4194,
"description": "Test earthquake"
}"#;
let req: CreateEventRequest = serde_json::from_str(json).unwrap();
assert_eq!(req.event_type, DisasterTypeDto::Earthquake);
assert!((req.latitude - 37.7749).abs() < 0.0001);
}
#[test]
fn test_zone_bounds_dto_deserialize() {
let rect_json = r#"{
"type": "rectangle",
"min_x": 0.0,
"min_y": 0.0,
"max_x": 10.0,
"max_y": 10.0
}"#;
let bounds: ZoneBoundsDto = serde_json::from_str(rect_json).unwrap();
assert!(matches!(bounds, ZoneBoundsDto::Rectangle { .. }));
}
#[test]
fn test_websocket_message_serialize() {
let msg = WebSocketMessage::Heartbeat {
timestamp: Utc::now(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"heartbeat\""));
}
}