use crate::behavioral_economics::actions::ActionExecutor;
use crate::behavioral_economics::conditions::ConditionEvaluator;
use crate::behavioral_economics::config::BehavioralEconomicsConfig;
use crate::behavioral_economics::rules::BehaviorRule;
use crate::Result;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info, warn};
pub struct BehavioralEconomicsEngine {
config: BehavioralEconomicsConfig,
condition_evaluator: Arc<RwLock<ConditionEvaluator>>,
action_executor: ActionExecutor,
rules: Vec<BehaviorRule>,
}
impl BehavioralEconomicsEngine {
pub fn new(config: BehavioralEconomicsConfig) -> Result<Self> {
for rule in &config.rules {
rule.validate()?;
}
let mut rules = config.rules.clone();
rules.sort_by(|a, b| b.priority.cmp(&a.priority));
Ok(Self {
config,
condition_evaluator: Arc::new(RwLock::new(ConditionEvaluator::new())),
action_executor: ActionExecutor::new(),
rules,
})
}
#[allow(clippy::should_implement_trait)]
pub fn default() -> Self {
Self::new(BehavioralEconomicsConfig::default()).expect("Failed to create default engine")
}
pub fn condition_evaluator(&self) -> Arc<RwLock<ConditionEvaluator>> {
Arc::clone(&self.condition_evaluator)
}
pub async fn evaluate(&self) -> Result<Vec<String>> {
if !self.config.enabled {
return Ok(Vec::new());
}
let evaluator = self.condition_evaluator.read().await;
let mut executed_actions = Vec::new();
for rule in &self.rules {
match evaluator.evaluate(&rule.condition) {
Ok(true) => {
debug!("Rule '{}' condition met, executing action", rule.name);
match self.action_executor.execute(&rule.action) {
Ok(action_desc) => {
info!("Executed action for rule '{}': {}", rule.name, action_desc);
executed_actions.push(format!("{}: {}", rule.name, action_desc));
}
Err(e) => {
warn!("Failed to execute action for rule '{}': {}", rule.name, e);
}
}
}
Ok(false) => {
debug!("Rule '{}' condition not met", rule.name);
}
Err(e) => {
warn!("Failed to evaluate condition for rule '{}': {}", rule.name, e);
}
}
}
Ok(executed_actions)
}
pub fn add_rule(&mut self, rule: BehaviorRule) -> Result<()> {
rule.validate()?;
self.rules.push(rule);
self.rules.sort_by(|a, b| b.priority.cmp(&a.priority));
Ok(())
}
pub fn remove_rule(&mut self, name: &str) -> bool {
let initial_len = self.rules.len();
self.rules.retain(|r| r.name != name);
self.rules.len() < initial_len
}
pub fn rules(&self) -> &[BehaviorRule] {
&self.rules
}
pub fn update_config(&mut self, config: BehavioralEconomicsConfig) -> Result<()> {
for rule in &config.rules {
rule.validate()?;
}
let mut rules = config.rules.clone();
rules.sort_by(|a, b| b.priority.cmp(&a.priority));
self.config = config;
self.rules = rules;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::behavioral_economics::actions::BehaviorAction;
use crate::behavioral_economics::conditions::BehaviorCondition;
use crate::behavioral_economics::rules::BehaviorRule;
#[tokio::test]
async fn test_engine_creation() {
let config = BehavioralEconomicsConfig::new();
let engine = BehavioralEconomicsEngine::new(config).unwrap();
assert!(engine.rules().is_empty());
}
#[tokio::test]
async fn test_engine_evaluation() {
let rule = BehaviorRule::declarative(
"test-rule".to_string(),
BehaviorCondition::Always,
BehaviorAction::NoOp,
100,
);
let config = BehavioralEconomicsConfig::new().enable().with_rule(rule);
let engine = BehavioralEconomicsEngine::new(config).unwrap();
let results = engine.evaluate().await.unwrap();
assert!(!results.is_empty());
}
#[tokio::test]
async fn test_engine_disabled() {
let rule = BehaviorRule::declarative(
"test-rule".to_string(),
BehaviorCondition::Always,
BehaviorAction::NoOp,
100,
);
let config = BehavioralEconomicsConfig::new().disable().with_rule(rule);
let engine = BehavioralEconomicsEngine::new(config).unwrap();
let results = engine.evaluate().await.unwrap();
assert!(results.is_empty());
}
}