use crate::error::DoDResult;
use crate::observation::Observation;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObservationPhase {
observations: Vec<Observation>,
started_at: DateTime<Utc>,
}
impl ObservationPhase {
pub fn new() -> Self {
Self {
observations: Vec::new(),
started_at: Utc::now(),
}
}
pub fn observe(mut self, obs: Observation) -> Self {
self.observations.push(obs);
self
}
pub fn observations(&self) -> &[Observation] {
&self.observations
}
pub fn analyze(self) -> DoDResult<AnalysisPhase> {
if self.observations.is_empty() {
return Err(crate::error::DoDError::MAPEKPhase(
"no observations collected".to_string(),
));
}
Ok(AnalysisPhase::from_observations(self.observations))
}
}
impl Default for ObservationPhase {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Finding {
finding_type: String,
severity: String,
description: String,
affected_subsystems: Vec<String>,
}
impl Finding {
pub fn new(
finding_type: impl Into<String>, severity: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
finding_type: finding_type.into(),
severity: severity.into(),
description: description.into(),
affected_subsystems: Vec::new(),
}
}
pub fn with_subsystem(mut self, subsystem: impl Into<String>) -> Self {
self.affected_subsystems.push(subsystem.into());
self
}
pub fn finding_type(&self) -> &str {
&self.finding_type
}
pub fn severity(&self) -> &str {
&self.severity
}
pub fn is_critical(&self) -> bool {
self.severity == "error" || self.severity == "critical"
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisPhase {
observations: Vec<Observation>,
findings: Vec<Finding>,
started_at: DateTime<Utc>,
}
impl AnalysisPhase {
fn from_observations(observations: Vec<Observation>) -> Self {
Self {
observations,
findings: Vec::new(),
started_at: Utc::now(),
}
}
pub fn with_finding(mut self, finding: Finding) -> Self {
self.findings.push(finding);
self
}
pub fn findings(&self) -> &[Finding] {
&self.findings
}
pub fn plan(self) -> DoDResult<PlanningPhase> {
Ok(PlanningPhase::from_findings(self.findings))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaProposal {
id: String,
description: String,
changes: Vec<String>,
risk_level: String,
expected_benefits: Vec<String>,
}
impl SchemaProposal {
pub fn new(description: impl Into<String>) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
description: description.into(),
changes: Vec::new(),
risk_level: "low".to_string(),
expected_benefits: Vec::new(),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn with_change(mut self, change: impl Into<String>) -> Self {
self.changes.push(change.into());
self
}
pub fn with_benefit(mut self, benefit: impl Into<String>) -> Self {
self.expected_benefits.push(benefit.into());
self
}
pub fn with_risk_level(mut self, risk: impl Into<String>) -> Self {
self.risk_level = risk.into();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanningPhase {
findings: Vec<Finding>,
proposals: Vec<SchemaProposal>,
started_at: DateTime<Utc>,
}
impl PlanningPhase {
fn from_findings(findings: Vec<Finding>) -> Self {
Self {
findings,
proposals: Vec::new(),
started_at: Utc::now(),
}
}
pub fn with_proposal(mut self, proposal: SchemaProposal) -> Self {
self.proposals.push(proposal);
self
}
pub fn proposals(&self) -> &[SchemaProposal] {
&self.proposals
}
pub fn execute(self) -> DoDResult<ExecutionPhase> {
Ok(ExecutionPhase::from_proposals(self.proposals))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExecutionResult {
Success,
RolledBack,
Rejected,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionPhase {
proposals: Vec<SchemaProposal>,
results: Vec<(String, ExecutionResult)>,
started_at: DateTime<Utc>,
}
impl ExecutionPhase {
fn from_proposals(proposals: Vec<SchemaProposal>) -> Self {
Self {
proposals,
results: Vec::new(),
started_at: Utc::now(),
}
}
pub fn record_result(
mut self, proposal_id: impl Into<String>, result: ExecutionResult,
) -> Self {
self.results.push((proposal_id.into(), result));
self
}
pub fn results(&self) -> &[(String, ExecutionResult)] {
&self.results
}
pub fn learn(self) -> KnowledgePhase {
KnowledgePhase::from_execution(self.results)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgePhase {
results: Vec<(String, ExecutionResult)>,
updated_at: DateTime<Utc>,
}
impl KnowledgePhase {
fn from_execution(results: Vec<(String, ExecutionResult)>) -> Self {
Self {
results,
updated_at: Utc::now(),
}
}
pub fn results(&self) -> &[(String, ExecutionResult)] {
&self.results
}
}
#[derive(Debug)]
pub struct MAPEKLoop {
cycles: VecDeque<MAPEKCycle>,
current_cycle: Option<MAPEKCycleState>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MAPEKCycle {
cycle_num: u64,
observation_count: usize,
finding_count: usize,
proposal_count: usize,
successful_executions: usize,
failed_executions: usize,
completed_at: DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub enum MAPEKCycleState {
Monitoring(ObservationPhase),
Analyzing(AnalysisPhase),
Planning(PlanningPhase),
Executing(ExecutionPhase),
Learning(KnowledgePhase),
}
impl MAPEKLoop {
pub fn new() -> Self {
Self {
cycles: VecDeque::new(),
current_cycle: None,
}
}
pub fn start_monitor(&mut self) {
self.current_cycle = Some(MAPEKCycleState::Monitoring(ObservationPhase::new()));
}
pub fn completed_cycles(&self) -> usize {
self.cycles.len()
}
pub fn recent_cycles(&self, n: usize) -> Vec<&MAPEKCycle> {
self.cycles.iter().rev().take(n).collect()
}
pub fn current_phase(&self) -> Option<&'static str> {
match &self.current_cycle {
Some(MAPEKCycleState::Monitoring(_)) => Some("Monitoring"),
Some(MAPEKCycleState::Analyzing(_)) => Some("Analyzing"),
Some(MAPEKCycleState::Planning(_)) => Some("Planning"),
Some(MAPEKCycleState::Executing(_)) => Some("Executing"),
Some(MAPEKCycleState::Learning(_)) => Some("Learning"),
None => None,
}
}
}
impl Default for MAPEKLoop {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_observation_phase() {
let phase = ObservationPhase::new();
assert_eq!(phase.observations().len(), 0);
}
#[test]
fn test_finding() {
let finding = Finding::new("drift", "warning", "detected drift in metric");
assert_eq!(finding.finding_type(), "drift");
assert!(!finding.is_critical());
}
#[test]
fn test_schema_proposal() {
let proposal = SchemaProposal::new("add new field")
.with_change("fields += 1")
.with_benefit("better observability");
assert_eq!(proposal.changes.len(), 1);
assert_eq!(proposal.expected_benefits.len(), 1);
}
#[test]
fn test_mape_k_loop() {
let loop_ = MAPEKLoop::new();
assert_eq!(loop_.completed_cycles(), 0);
}
}