use crate::rule::matcher::{CandidateInfo, RuleMatcher};
use crate::storage::{EdgeStorage, RuleStorage};
use anyhow::Result;
use mr_common::{BoostType, RetrievalRule};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct RuleResult {
pub rule_id: Uuid,
pub rule_name: String,
pub hit_count: usize,
pub action: String,
}
pub struct RuleEngine {
rule_storage: Arc<dyn RuleStorage>,
edge_storage: Option<Arc<dyn EdgeStorage>>,
matcher: RuleMatcher,
}
impl RuleEngine {
pub fn new(rule_storage: Arc<dyn RuleStorage>) -> Self {
Self {
rule_storage,
edge_storage: None,
matcher: RuleMatcher::new(),
}
}
pub fn with_edge_storage(mut self, edge_storage: Arc<dyn EdgeStorage>) -> Self {
self.edge_storage = Some(edge_storage);
self
}
pub async fn apply(
&mut self,
query: &str,
candidates: &mut [CandidateInfo],
) -> Result<Vec<RuleResult>> {
let rules = self.rule_storage.list_by_priority().await?;
let mut results = Vec::new();
for rule in rules {
if !self.matcher.matches_query(query, &rule) {
continue;
}
let hit_count = self.apply_rule(&rule, candidates).await?;
if hit_count > 0 {
self.rule_storage.record_hit(&rule.id).await?;
results.push(RuleResult {
rule_id: rule.id,
rule_name: rule.name.clone(),
hit_count,
action: format!("{:?}", rule.action.boost_type),
});
}
}
if !results.is_empty() {
candidates.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
}
Ok(results)
}
async fn apply_rule(
&self,
rule: &RetrievalRule,
candidates: &mut [CandidateInfo],
) -> Result<usize> {
let mut hit_count = 0;
for candidate in candidates.iter_mut() {
if self.matcher.matches_candidate(candidate, &rule.condition) {
hit_count += 1;
self.apply_action(&rule.action, candidate);
}
}
Ok(hit_count)
}
fn apply_action(&self, action: &mr_common::RuleAction, candidate: &mut CandidateInfo) {
match action.boost_type {
BoostType::Boost => {
candidate.score *= action.factor;
}
BoostType::Suppress => {
candidate.score *= action.factor;
}
BoostType::Filter => {
candidate.score = 0.0;
}
BoostType::Expand => {
}
}
}
pub async fn list_rules(&self) -> Result<Vec<RetrievalRule>> {
self.rule_storage.list(true).await
}
pub async fn stats(&self) -> Result<RuleEngineStats> {
let rules = self.rule_storage.list(false).await?;
let enabled = rules.iter().filter(|r| r.enabled).count();
let total_hits: u32 = rules.iter().map(|r| r.hit_count).sum();
Ok(RuleEngineStats {
total_rules: rules.len(),
enabled_rules: enabled,
total_hits,
})
}
}
#[derive(Debug, Clone)]
pub struct RuleEngineStats {
pub total_rules: usize,
pub enabled_rules: usize,
pub total_hits: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_result_debug() {
let result = RuleResult {
rule_id: Uuid::nil(),
rule_name: "test".to_string(),
hit_count: 5,
action: "Boost".to_string(),
};
let debug_str = format!("{:?}", result);
assert!(debug_str.contains("rule_name"));
}
#[test]
fn test_rule_engine_stats() {
let stats = RuleEngineStats {
total_rules: 10,
enabled_rules: 8,
total_hits: 42,
};
assert_eq!(stats.total_rules, 10);
assert_eq!(stats.enabled_rules, 8);
assert_eq!(stats.total_hits, 42);
}
}