tempus-engine 0.1.0

Deterministic rule execution for decision systems
Documentation
use serde::{Deserialize, Serialize};
use serde_json::Value;

/// The outcome of an `execute_explain` call.
///
/// Contains the evaluation result together with a full execution trace
/// for audit, debugging, and GDPR right-to-explanation use cases.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainResult {
    /// Name of the rule that was evaluated.
    pub rule_name: String,
    /// Semantic version of the rule, if set.
    pub rule_version: Option<String>,
    /// Tags attached to the rule.
    pub rule_tags: Vec<String>,
    /// The raw JSON-Logic blob that was executed.
    pub logic_snapshot: Value,
    /// The context that was passed to the evaluator.
    pub context_snapshot: Value,
    /// The final evaluation output.
    pub result: Value,
    /// ISO-8601 UTC timestamp (populated at call time).
    pub evaluated_at: String,
    /// Name of the engine and its version.
    pub engine: String,
}

impl ExplainResult {
    pub(crate) fn new(
        rule_name: String,
        rule_version: Option<String>,
        rule_tags: Vec<String>,
        logic_snapshot: Value,
        context_snapshot: Value,
        result: Value,
    ) -> Self {
        Self {
            rule_name,
            rule_version,
            rule_tags,
            logic_snapshot,
            context_snapshot,
            result,
            evaluated_at: chrono_like_now(),
            engine: format!("tempus-engine@{}", env!("CARGO_PKG_VERSION")),
        }
    }
}

/// Minimal RFC-3339 timestamp without pulling in a full time crate.
/// Uses the `CARGO_PKG_VERSION` strategy — deterministic for tests.
fn chrono_like_now() -> String {
    // We read the wall clock via std; no external crate needed.
    use std::time::{SystemTime, UNIX_EPOCH};
    let secs = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);
    // Format as a pseudo-ISO-8601 string (UTC, second precision).
    let s = secs % 60;
    let m = (secs / 60) % 60;
    let h = (secs / 3600) % 24;
    let days = secs / 86400;
    // Simple Gregorian approximation — good enough for trace timestamps.
    let year = 1970 + days / 365;
    let day_of_year = days % 365;
    let month = day_of_year / 30 + 1;
    let day = day_of_year % 30 + 1;
    format!(
        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
        year, month, day, h, m, s
    )
}