use crate::hierarchy::deltas::{CompanyDelta, PlatoonDelta, SquadDelta};
use crate::Result;
use async_trait::async_trait;
use peat_schema::hierarchy::v1::{CompanySummary, PlatoonSummary, SquadSummary};
#[async_trait]
pub trait SummaryStorage: Send + Sync {
async fn create_squad_summary(
&self,
squad_id: &str,
initial_state: &SquadSummary,
) -> Result<String>;
async fn update_squad_summary(&self, squad_id: &str, delta: SquadDelta) -> Result<()>;
async fn get_squad_summary(&self, squad_id: &str) -> Result<Option<SquadSummary>>;
async fn delete_squad_summary(&self, squad_id: &str) -> Result<()>;
async fn create_platoon_summary(
&self,
platoon_id: &str,
initial_state: &PlatoonSummary,
) -> Result<String>;
async fn update_platoon_summary(&self, platoon_id: &str, delta: PlatoonDelta) -> Result<()>;
async fn get_platoon_summary(&self, platoon_id: &str) -> Result<Option<PlatoonSummary>>;
async fn delete_platoon_summary(&self, platoon_id: &str) -> Result<()>;
async fn create_company_summary(
&self,
company_id: &str,
initial_state: &CompanySummary,
) -> Result<String>;
async fn update_company_summary(&self, company_id: &str, delta: CompanyDelta) -> Result<()>;
async fn get_company_summary(&self, company_id: &str) -> Result<Option<CompanySummary>>;
async fn delete_company_summary(&self, company_id: &str) -> Result<()>;
async fn get_document_metrics(&self, doc_id: &str) -> Result<DocumentMetrics>;
}
#[derive(Debug, Clone)]
pub struct DocumentMetrics {
pub document_id: String,
pub created_at_us: u64,
pub create_count: u64,
pub update_count: u64,
pub last_update_us: u64,
pub total_delta_bytes: usize,
pub full_doc_size: usize,
pub compression_ratio: f32,
pub sequence: u64,
}
impl DocumentMetrics {
pub fn validate(&self) -> Result<()> {
if self.create_count != 1 {
return Err(crate::Error::storage_error(
format!(
"Document {} violated create-once invariant: create_count={}",
self.document_id, self.create_count
),
"validate_metrics",
Some(self.document_id.clone()),
));
}
if self.update_count > 0 && self.compression_ratio < 10.0 {
tracing::warn!(
document_id = %self.document_id,
compression_ratio = self.compression_ratio,
"Delta efficiency below target (should be >10×)"
);
}
Ok(())
}
pub fn avg_delta_size(&self) -> usize {
if self.update_count == 0 {
0
} else {
self.total_delta_bytes / self.update_count as usize
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_validation_success() {
let metrics = DocumentMetrics {
document_id: "squad-1A-summary".to_string(),
created_at_us: 1234567890,
create_count: 1, update_count: 20,
last_update_us: 1234567900,
total_delta_bytes: 1000,
full_doc_size: 2000,
compression_ratio: 40.0, sequence: 20,
};
assert!(metrics.validate().is_ok());
}
#[test]
fn test_metrics_validation_create_count_violation() {
let metrics = DocumentMetrics {
document_id: "squad-1A-summary".to_string(),
created_at_us: 1234567890,
create_count: 21, update_count: 0,
last_update_us: 1234567900,
total_delta_bytes: 0,
full_doc_size: 2000,
compression_ratio: 0.0,
sequence: 0,
};
assert!(metrics.validate().is_err());
}
#[test]
fn test_avg_delta_size() {
let metrics = DocumentMetrics {
document_id: "squad-1A-summary".to_string(),
created_at_us: 1234567890,
create_count: 1,
update_count: 20,
last_update_us: 1234567900,
total_delta_bytes: 1000,
full_doc_size: 2000,
compression_ratio: 40.0,
sequence: 20,
};
assert_eq!(metrics.avg_delta_size(), 50); }
}