use crate::model::Severity;
use crate::recommendation::{MetricSnapshot, RecStatus};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::BTreeMap;
pub const SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoopPersisted {
#[serde(default = "default_schema_version")]
pub schema_version: u32,
#[serde(default)]
pub config: BTreeMap<String, AnalyzerConfig>,
#[serde(default)]
pub state: LoopState,
#[serde(default)]
pub status_index: BTreeMap<String, RecStatus>,
#[serde(default)]
pub audit_heads: BTreeMap<String, String>,
#[serde(default)]
pub creators: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub co_creators: BTreeMap<String, String>,
#[serde(default)]
pub cooldowns: BTreeMap<String, i64>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub cooldown_strikes: BTreeMap<String, u32>,
#[serde(default)]
pub applied: BTreeMap<String, AppliedRecord>,
#[serde(default)]
pub measured: BTreeMap<String, Vec<crate::recommendation::Checkpoint>>,
#[serde(default)]
pub outcomes: BTreeMap<String, Vec<crate::recommendation::OutcomeResult>>,
}
fn default_schema_version() -> u32 {
SCHEMA_VERSION
}
impl Default for LoopPersisted {
fn default() -> Self {
LoopPersisted {
schema_version: SCHEMA_VERSION,
config: BTreeMap::new(),
state: LoopState::default(),
status_index: BTreeMap::new(),
audit_heads: BTreeMap::new(),
creators: BTreeMap::new(),
co_creators: BTreeMap::new(),
cooldowns: BTreeMap::new(),
cooldown_strikes: BTreeMap::new(),
applied: BTreeMap::new(),
measured: BTreeMap::new(),
outcomes: BTreeMap::new(),
}
}
}
impl LoopPersisted {
pub fn from_value(v: Value) -> crate::error::Result<Self> {
if v.is_null() {
return Ok(Self::default());
}
serde_json::from_value(v)
.map_err(|e| crate::error::Error::Internal(format!("decode loop state: {e}")))
}
pub fn to_value(&self) -> crate::error::Result<Value> {
serde_json::to_value(self)
.map_err(|e| crate::error::Error::Internal(format!("encode loop state: {e}")))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AnalyzerConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(default)]
pub params: Map<String, Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub severity_floor: Option<Severity>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub namespaces: Vec<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct AnalyzerConfigUpdate {
#[serde(default)]
pub enabled: Option<bool>,
#[serde(default)]
pub severity_floor: Option<Severity>,
#[serde(default)]
pub clear_floor: bool,
#[serde(default)]
pub params: Option<Map<String, Value>>,
#[serde(default)]
pub namespaces: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnalyzerSetting {
pub id: String,
pub title: String,
pub description: String,
pub tier: String,
pub trust_class: String,
pub default_on: bool,
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub severity_floor: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LoopState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_run_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub watermark_ms: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppliedRecord {
pub applied_at_ms: i64,
pub target_ref: String,
pub rollbackable: bool,
#[serde(default)]
pub created_hashes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub inverse_cal: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metric: Option<MetricSnapshot>,
}