Skip to main content

swarm_engine_eval/scenario/
conditions.rs

1//! 評価条件の定義
2//!
3//! シナリオの成功/失敗を判定するための条件定義。
4
5use serde::{Deserialize, Serialize};
6
7/// 評価の成功/失敗条件
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct EvalConditions {
10    /// 成功条件 (全て満たすと success)
11    #[serde(default)]
12    pub success: Vec<Condition>,
13
14    /// 失敗条件 (いずれか満たすと即 fail)
15    #[serde(default)]
16    pub failure: Vec<Condition>,
17
18    /// タイムアウト (max_ticks に達した場合の扱い)
19    #[serde(default)]
20    pub on_timeout: TimeoutBehavior,
21}
22
23/// 条件定義
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Condition {
26    /// 条件名 (デバッグ・レポート用)
27    pub name: String,
28
29    /// メトリクス参照パス (e.g., "task.completion_rate", "tick")
30    pub metric: String,
31
32    /// 比較演算子
33    pub op: CompareOp,
34
35    /// 比較値
36    pub value: ConditionValue,
37}
38
39impl Condition {
40    /// 条件を作成
41    pub fn new(
42        name: impl Into<String>,
43        metric: impl Into<String>,
44        op: CompareOp,
45        value: impl Into<ConditionValue>,
46    ) -> Self {
47        Self {
48            name: name.into(),
49            metric: metric.into(),
50            op,
51            value: value.into(),
52        }
53    }
54
55    /// 条件を評価
56    pub fn evaluate(&self, actual: &ConditionValue) -> bool {
57        self.op.compare(actual, &self.value)
58    }
59}
60
61/// 比較演算子
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum CompareOp {
65    /// 等しい (==)
66    Eq,
67    /// 等しくない (!=)
68    Ne,
69    /// より大きい (>)
70    Gt,
71    /// 以上 (>=)
72    Gte,
73    /// より小さい (<)
74    Lt,
75    /// 以下 (<=)
76    Lte,
77}
78
79impl CompareOp {
80    /// 比較を実行
81    pub fn compare(&self, actual: &ConditionValue, expected: &ConditionValue) -> bool {
82        match (actual, expected) {
83            (ConditionValue::Integer(a), ConditionValue::Integer(e)) => self.compare_ord(a, e),
84            (ConditionValue::Float(a), ConditionValue::Float(e)) => self.compare_float(*a, *e),
85            (ConditionValue::Integer(a), ConditionValue::Float(e)) => {
86                self.compare_float(*a as f64, *e)
87            }
88            (ConditionValue::Float(a), ConditionValue::Integer(e)) => {
89                self.compare_float(*a, *e as f64)
90            }
91            (ConditionValue::Bool(a), ConditionValue::Bool(e)) => match self {
92                CompareOp::Eq => a == e,
93                CompareOp::Ne => a != e,
94                _ => false,
95            },
96            (ConditionValue::String(a), ConditionValue::String(e)) => match self {
97                CompareOp::Eq => a == e,
98                CompareOp::Ne => a != e,
99                _ => false,
100            },
101            _ => false,
102        }
103    }
104
105    fn compare_ord<T: Ord>(&self, a: &T, e: &T) -> bool {
106        match self {
107            CompareOp::Eq => a == e,
108            CompareOp::Ne => a != e,
109            CompareOp::Gt => a > e,
110            CompareOp::Gte => a >= e,
111            CompareOp::Lt => a < e,
112            CompareOp::Lte => a <= e,
113        }
114    }
115
116    fn compare_float(&self, a: f64, e: f64) -> bool {
117        const EPSILON: f64 = 1e-9;
118        match self {
119            CompareOp::Eq => (a - e).abs() < EPSILON,
120            CompareOp::Ne => (a - e).abs() >= EPSILON,
121            CompareOp::Gt => a > e,
122            CompareOp::Gte => a >= e - EPSILON,
123            CompareOp::Lt => a < e,
124            CompareOp::Lte => a <= e + EPSILON,
125        }
126    }
127}
128
129/// 条件値 (型付き)
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(untagged)]
132pub enum ConditionValue {
133    Integer(i64),
134    Float(f64),
135    Bool(bool),
136    String(String),
137}
138
139impl From<i32> for ConditionValue {
140    fn from(v: i32) -> Self {
141        Self::Integer(v as i64)
142    }
143}
144
145impl From<i64> for ConditionValue {
146    fn from(v: i64) -> Self {
147        Self::Integer(v)
148    }
149}
150
151impl From<u64> for ConditionValue {
152    fn from(v: u64) -> Self {
153        Self::Integer(v as i64)
154    }
155}
156
157impl From<f64> for ConditionValue {
158    fn from(v: f64) -> Self {
159        Self::Float(v)
160    }
161}
162
163impl From<bool> for ConditionValue {
164    fn from(v: bool) -> Self {
165        Self::Bool(v)
166    }
167}
168
169impl From<&str> for ConditionValue {
170    fn from(v: &str) -> Self {
171        Self::String(v.to_string())
172    }
173}
174
175impl From<String> for ConditionValue {
176    fn from(v: String) -> Self {
177        Self::String(v)
178    }
179}
180
181/// タイムアウト時の振る舞い
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
183#[serde(rename_all = "snake_case")]
184pub enum TimeoutBehavior {
185    /// タイムアウト = 失敗
186    #[default]
187    Fail,
188    /// タイムアウト = 成功条件の達成度で判定
189    PartialSuccess,
190    /// タイムアウト = マイルストーン達成度で判定
191    MilestoneScore,
192}
193
194/// 条件評価結果
195#[derive(Debug, Clone)]
196pub struct ConditionResult {
197    /// 条件名
198    pub name: String,
199    /// 評価結果
200    pub passed: bool,
201    /// 実際の値
202    pub actual: Option<ConditionValue>,
203    /// 期待値
204    pub expected: ConditionValue,
205}
206
207impl ConditionResult {
208    pub fn new(condition: &Condition, actual: Option<ConditionValue>, passed: bool) -> Self {
209        Self {
210            name: condition.name.clone(),
211            passed,
212            actual,
213            expected: condition.value.clone(),
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_compare_op_integer() {
224        let a = ConditionValue::Integer(5);
225        let e = ConditionValue::Integer(3);
226
227        assert!(CompareOp::Gt.compare(&a, &e));
228        assert!(CompareOp::Gte.compare(&a, &e));
229        assert!(!CompareOp::Lt.compare(&a, &e));
230        assert!(!CompareOp::Lte.compare(&a, &e));
231        assert!(!CompareOp::Eq.compare(&a, &e));
232        assert!(CompareOp::Ne.compare(&a, &e));
233    }
234
235    #[test]
236    fn test_compare_op_float() {
237        let a = ConditionValue::Float(0.8);
238        let e = ConditionValue::Float(0.7);
239
240        assert!(CompareOp::Gt.compare(&a, &e));
241        assert!(CompareOp::Gte.compare(&a, &e));
242    }
243
244    #[test]
245    fn test_compare_op_mixed() {
246        let a = ConditionValue::Integer(5);
247        let e = ConditionValue::Float(3.0);
248
249        assert!(CompareOp::Gt.compare(&a, &e));
250    }
251
252    #[test]
253    fn test_condition_evaluate() {
254        let condition = Condition::new("completion_check", "task.completed", CompareOp::Gte, 5);
255
256        assert!(condition.evaluate(&ConditionValue::Integer(5)));
257        assert!(condition.evaluate(&ConditionValue::Integer(10)));
258        assert!(!condition.evaluate(&ConditionValue::Integer(3)));
259    }
260
261    #[test]
262    fn test_condition_deserialize() {
263        let json = r#"{
264            "name": "success_rate",
265            "metric": "task.success_rate",
266            "op": "gte",
267            "value": 0.8
268        }"#;
269
270        let condition: Condition = serde_json::from_str(json).unwrap();
271        assert_eq!(condition.name, "success_rate");
272        assert_eq!(condition.metric, "task.success_rate");
273        assert_eq!(condition.op, CompareOp::Gte);
274    }
275
276    #[test]
277    fn test_timeout_behavior_default() {
278        let behavior = TimeoutBehavior::default();
279        assert_eq!(behavior, TimeoutBehavior::Fail);
280    }
281}