swarm-engine-eval 0.1.6

Evaluation framework for SwarmEngine
Documentation
//! 評価条件の定義
//!
//! シナリオの成功/失敗を判定するための条件定義。

use serde::{Deserialize, Serialize};

/// 評価の成功/失敗条件
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EvalConditions {
    /// 成功条件 (全て満たすと success)
    #[serde(default)]
    pub success: Vec<Condition>,

    /// 失敗条件 (いずれか満たすと即 fail)
    #[serde(default)]
    pub failure: Vec<Condition>,

    /// タイムアウト (max_ticks に達した場合の扱い)
    #[serde(default)]
    pub on_timeout: TimeoutBehavior,
}

/// 条件定義
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Condition {
    /// 条件名 (デバッグ・レポート用)
    pub name: String,

    /// メトリクス参照パス (e.g., "task.completion_rate", "tick")
    pub metric: String,

    /// 比較演算子
    pub op: CompareOp,

    /// 比較値
    pub value: ConditionValue,
}

impl Condition {
    /// 条件を作成
    pub fn new(
        name: impl Into<String>,
        metric: impl Into<String>,
        op: CompareOp,
        value: impl Into<ConditionValue>,
    ) -> Self {
        Self {
            name: name.into(),
            metric: metric.into(),
            op,
            value: value.into(),
        }
    }

    /// 条件を評価
    pub fn evaluate(&self, actual: &ConditionValue) -> bool {
        self.op.compare(actual, &self.value)
    }
}

/// 比較演算子
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompareOp {
    /// 等しい (==)
    Eq,
    /// 等しくない (!=)
    Ne,
    /// より大きい (>)
    Gt,
    /// 以上 (>=)
    Gte,
    /// より小さい (<)
    Lt,
    /// 以下 (<=)
    Lte,
}

impl CompareOp {
    /// 比較を実行
    pub fn compare(&self, actual: &ConditionValue, expected: &ConditionValue) -> bool {
        match (actual, expected) {
            (ConditionValue::Integer(a), ConditionValue::Integer(e)) => self.compare_ord(a, e),
            (ConditionValue::Float(a), ConditionValue::Float(e)) => self.compare_float(*a, *e),
            (ConditionValue::Integer(a), ConditionValue::Float(e)) => {
                self.compare_float(*a as f64, *e)
            }
            (ConditionValue::Float(a), ConditionValue::Integer(e)) => {
                self.compare_float(*a, *e as f64)
            }
            (ConditionValue::Bool(a), ConditionValue::Bool(e)) => match self {
                CompareOp::Eq => a == e,
                CompareOp::Ne => a != e,
                _ => false,
            },
            (ConditionValue::String(a), ConditionValue::String(e)) => match self {
                CompareOp::Eq => a == e,
                CompareOp::Ne => a != e,
                _ => false,
            },
            _ => false,
        }
    }

    fn compare_ord<T: Ord>(&self, a: &T, e: &T) -> bool {
        match self {
            CompareOp::Eq => a == e,
            CompareOp::Ne => a != e,
            CompareOp::Gt => a > e,
            CompareOp::Gte => a >= e,
            CompareOp::Lt => a < e,
            CompareOp::Lte => a <= e,
        }
    }

    fn compare_float(&self, a: f64, e: f64) -> bool {
        const EPSILON: f64 = 1e-9;
        match self {
            CompareOp::Eq => (a - e).abs() < EPSILON,
            CompareOp::Ne => (a - e).abs() >= EPSILON,
            CompareOp::Gt => a > e,
            CompareOp::Gte => a >= e - EPSILON,
            CompareOp::Lt => a < e,
            CompareOp::Lte => a <= e + EPSILON,
        }
    }
}

/// 条件値 (型付き)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ConditionValue {
    Integer(i64),
    Float(f64),
    Bool(bool),
    String(String),
}

impl From<i32> for ConditionValue {
    fn from(v: i32) -> Self {
        Self::Integer(v as i64)
    }
}

impl From<i64> for ConditionValue {
    fn from(v: i64) -> Self {
        Self::Integer(v)
    }
}

impl From<u64> for ConditionValue {
    fn from(v: u64) -> Self {
        Self::Integer(v as i64)
    }
}

impl From<f64> for ConditionValue {
    fn from(v: f64) -> Self {
        Self::Float(v)
    }
}

impl From<bool> for ConditionValue {
    fn from(v: bool) -> Self {
        Self::Bool(v)
    }
}

impl From<&str> for ConditionValue {
    fn from(v: &str) -> Self {
        Self::String(v.to_string())
    }
}

impl From<String> for ConditionValue {
    fn from(v: String) -> Self {
        Self::String(v)
    }
}

/// タイムアウト時の振る舞い
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TimeoutBehavior {
    /// タイムアウト = 失敗
    #[default]
    Fail,
    /// タイムアウト = 成功条件の達成度で判定
    PartialSuccess,
    /// タイムアウト = マイルストーン達成度で判定
    MilestoneScore,
}

/// 条件評価結果
#[derive(Debug, Clone)]
pub struct ConditionResult {
    /// 条件名
    pub name: String,
    /// 評価結果
    pub passed: bool,
    /// 実際の値
    pub actual: Option<ConditionValue>,
    /// 期待値
    pub expected: ConditionValue,
}

impl ConditionResult {
    pub fn new(condition: &Condition, actual: Option<ConditionValue>, passed: bool) -> Self {
        Self {
            name: condition.name.clone(),
            passed,
            actual,
            expected: condition.value.clone(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_compare_op_integer() {
        let a = ConditionValue::Integer(5);
        let e = ConditionValue::Integer(3);

        assert!(CompareOp::Gt.compare(&a, &e));
        assert!(CompareOp::Gte.compare(&a, &e));
        assert!(!CompareOp::Lt.compare(&a, &e));
        assert!(!CompareOp::Lte.compare(&a, &e));
        assert!(!CompareOp::Eq.compare(&a, &e));
        assert!(CompareOp::Ne.compare(&a, &e));
    }

    #[test]
    fn test_compare_op_float() {
        let a = ConditionValue::Float(0.8);
        let e = ConditionValue::Float(0.7);

        assert!(CompareOp::Gt.compare(&a, &e));
        assert!(CompareOp::Gte.compare(&a, &e));
    }

    #[test]
    fn test_compare_op_mixed() {
        let a = ConditionValue::Integer(5);
        let e = ConditionValue::Float(3.0);

        assert!(CompareOp::Gt.compare(&a, &e));
    }

    #[test]
    fn test_condition_evaluate() {
        let condition = Condition::new("completion_check", "task.completed", CompareOp::Gte, 5);

        assert!(condition.evaluate(&ConditionValue::Integer(5)));
        assert!(condition.evaluate(&ConditionValue::Integer(10)));
        assert!(!condition.evaluate(&ConditionValue::Integer(3)));
    }

    #[test]
    fn test_condition_deserialize() {
        let json = r#"{
            "name": "success_rate",
            "metric": "task.success_rate",
            "op": "gte",
            "value": 0.8
        }"#;

        let condition: Condition = serde_json::from_str(json).unwrap();
        assert_eq!(condition.name, "success_rate");
        assert_eq!(condition.metric, "task.success_rate");
        assert_eq!(condition.op, CompareOp::Gte);
    }

    #[test]
    fn test_timeout_behavior_default() {
        let behavior = TimeoutBehavior::default();
        assert_eq!(behavior, TimeoutBehavior::Fail);
    }
}