Skip to main content

agentic_contract/
condition.rs

1//! Conditional execution rules.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::ContractId;
7
8/// Type of condition.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum ConditionType {
12    /// Value must be above/below a threshold.
13    Threshold,
14    /// Time-based condition (before/after/during).
15    TimeBased,
16    /// Depends on another entity's state.
17    Dependency,
18    /// Custom expression.
19    Custom,
20}
21
22/// Evaluation status of a condition.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum ConditionStatus {
26    /// Not yet evaluated.
27    Unevaluated,
28    /// Condition is met.
29    Met,
30    /// Condition is not met.
31    NotMet,
32    /// Could not evaluate (missing data).
33    Unknown,
34}
35
36/// A conditional execution rule.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Condition {
39    /// Unique identifier.
40    pub id: ContractId,
41    /// Human-readable label.
42    pub label: String,
43    /// Type of condition.
44    pub condition_type: ConditionType,
45    /// Expression to evaluate.
46    pub expression: String,
47    /// Current evaluation status.
48    pub status: ConditionStatus,
49    /// Last evaluation result message.
50    #[serde(default)]
51    pub last_result: Option<String>,
52    /// When this condition was created.
53    pub created_at: DateTime<Utc>,
54    /// When this condition was last evaluated.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub evaluated_at: Option<DateTime<Utc>>,
57}
58
59impl Condition {
60    /// Create a new condition.
61    pub fn new(
62        label: impl Into<String>,
63        condition_type: ConditionType,
64        expression: impl Into<String>,
65    ) -> Self {
66        Self {
67            id: ContractId::new(),
68            label: label.into(),
69            condition_type,
70            expression: expression.into(),
71            status: ConditionStatus::Unevaluated,
72            last_result: None,
73            created_at: Utc::now(),
74            evaluated_at: None,
75        }
76    }
77
78    /// Mark the condition as met.
79    pub fn mark_met(&mut self, message: impl Into<String>) {
80        self.status = ConditionStatus::Met;
81        self.last_result = Some(message.into());
82        self.evaluated_at = Some(Utc::now());
83    }
84
85    /// Mark the condition as not met.
86    pub fn mark_not_met(&mut self, message: impl Into<String>) {
87        self.status = ConditionStatus::NotMet;
88        self.last_result = Some(message.into());
89        self.evaluated_at = Some(Utc::now());
90    }
91
92    /// Check if this condition is currently met.
93    pub fn is_met(&self) -> bool {
94        self.status == ConditionStatus::Met
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_condition_lifecycle() {
104        let mut condition = Condition::new(
105            "CPU below 80%",
106            ConditionType::Threshold,
107            "system.cpu_usage < 0.8",
108        );
109
110        assert_eq!(condition.status, ConditionStatus::Unevaluated);
111        assert!(!condition.is_met());
112
113        condition.mark_met("CPU at 65%");
114        assert!(condition.is_met());
115        assert!(condition.evaluated_at.is_some());
116    }
117}