Skip to main content

crue_engine/
context.rs

1//! Evaluation Context - Runtime context for rule evaluation
2
3use crate::EvaluationRequest;
4use std::collections::HashMap;
5
6/// Evaluation context - provides field values for rule evaluation
7pub struct EvaluationContext {
8    /// Field values keyed by field path (e.g., "agent.requests_last_hour")
9    fields: HashMap<String, FieldValue>,
10}
11
12impl EvaluationContext {
13    /// Create context from evaluation request
14    pub fn from_request(request: &EvaluationRequest) -> Self {
15        let mut ctx = EvaluationContext {
16            fields: HashMap::new(),
17        };
18        
19        // Agent fields
20        ctx.set_field("agent.id", FieldValue::String(request.agent_id.clone()));
21        ctx.set_field("agent.org", FieldValue::String(request.agent_org.clone()));
22        ctx.set_field("agent.level", FieldValue::String(request.agent_level.clone()));
23        ctx.set_field("agent.requests_last_hour", FieldValue::Number(request.requests_last_hour as i64));
24        ctx.set_field("agent.requests_last_24h", FieldValue::Number(request.requests_last_24h as i64));
25        
26        // Mission fields
27        if let Some(mission_id) = &request.mission_id {
28            ctx.set_field("mission.id", FieldValue::String(mission_id.clone()));
29        }
30        if let Some(mission_type) = &request.mission_type {
31            ctx.set_field("mission.type", FieldValue::String(mission_type.clone()));
32        }
33        
34        // Request fields
35        if let Some(query_type) = &request.query_type {
36            ctx.set_field("request.query_type", FieldValue::String(query_type.clone()));
37        }
38        if let Some(justification) = &request.justification {
39            ctx.set_field("request.justification", FieldValue::String(justification.clone()));
40            ctx.set_field("request.justification_length", FieldValue::Number(justification.len() as i64));
41        }
42        if let Some(export_format) = &request.export_format {
43            ctx.set_field("request.export_format", FieldValue::String(export_format.clone()));
44        }
45        if let Some(limit) = request.result_limit {
46            ctx.set_field("request.result_limit", FieldValue::Number(limit as i64));
47        }
48        
49        // Results
50        ctx.set_field("request.results_last_query", FieldValue::Number(request.results_last_query as i64));
51        
52        // Geographic
53        if let Some(dept) = &request.account_department {
54            ctx.set_field("account.department", FieldValue::String(dept.clone()));
55        }
56        
57        // Allowed departments
58        for (i, dept) in request.allowed_departments.iter().enumerate() {
59            ctx.set_field(
60                &format!("agent.allowed_departments[{}]", i),
61                FieldValue::Number(*dept as i64),
62            );
63        }
64        
65        // Time
66        ctx.set_field("context.request_hour", FieldValue::Number(request.request_hour as i64));
67        ctx.set_field("context.is_within_mission_hours", FieldValue::Boolean(request.is_within_mission_hours));
68        
69        ctx
70    }
71    
72    /// Get field value
73    pub fn get_field(&self, path: &str) -> Option<&FieldValue> {
74        self.fields.get(path)
75    }
76    
77    /// Set field value
78    pub fn set_field(&mut self, path: &str, value: FieldValue) {
79        self.fields.insert(path.to_string(), value);
80    }
81    
82    /// Get all fields
83    pub fn fields(&self) -> &HashMap<String, FieldValue> {
84        &self.fields
85    }
86}
87
88/// Field value types
89#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
90pub enum FieldValue {
91    Number(i64),
92    Float(f64),
93    String(String),
94    Boolean(bool),
95}
96
97impl FieldValue {
98    /// Compare using operator
99    pub fn compare(&self, op: &str, other: &FieldValue) -> bool {
100        match (self, other) {
101            (FieldValue::Number(a), FieldValue::Number(b)) => {
102                match op {
103                    ">" => a > b,
104                    "<" => a < b,
105                    ">=" => a >= b,
106                    "<=" => a <= b,
107                    "==" => a == b,
108                    "!=" => a != b,
109                    _ => false,
110                }
111            }
112            (FieldValue::String(a), FieldValue::String(b)) => {
113                match op {
114                    "==" => a == b,
115                    "!=" => a != b,
116                    _ => false,
117                }
118            }
119            (FieldValue::Boolean(a), FieldValue::Boolean(b)) => {
120                match op {
121                    "==" => a == b,
122                    "!=" => a != b,
123                    _ => false,
124                }
125            }
126            _ => false,
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    
135    #[test]
136    fn test_context_creation() {
137        let request = EvaluationRequest {
138            request_id: "test".to_string(),
139            agent_id: "AGENT_001".to_string(),
140            agent_org: "DGFiP".to_string(),
141            agent_level: "standard".to_string(),
142            mission_id: Some("MIS_001".to_string()),
143            mission_type: None,
144            query_type: None,
145            justification: None,
146            export_format: None,
147            result_limit: None,
148            requests_last_hour: 10,
149            requests_last_24h: 100,
150            results_last_query: 5,
151            account_department: None,
152            allowed_departments: vec![],
153            request_hour: 14,
154            is_within_mission_hours: true,
155        };
156        
157        let ctx = EvaluationContext::from_request(&request);
158        
159        assert_eq!(
160            ctx.get_field("agent.id"),
161            Some(&FieldValue::String("AGENT_001".to_string()))
162        );
163        assert_eq!(
164            ctx.get_field("agent.requests_last_hour"),
165            Some(&FieldValue::Number(10))
166        );
167    }
168    
169    #[test]
170    fn test_field_comparison() {
171        assert!(FieldValue::Number(10).compare(">", &FieldValue::Number(5)));
172        assert!(FieldValue::Number(10).compare(">=", &FieldValue::Number(10)));
173        assert!(FieldValue::String("test".to_string()).compare("==", &FieldValue::String("test".to_string())));
174    }
175}