rust_rule_engine/engine/
coverage.rs1use std::collections::{HashMap, HashSet};
5
6#[derive(Debug, Default)]
7pub struct RuleCoverage {
8 pub rule_hits: HashMap<String, usize>,
10 pub rule_facts: HashMap<String, HashSet<String>>,
12 pub tested_facts: HashSet<String>,
14}
15
16impl RuleCoverage {
17 pub fn new() -> Self {
18 Self::default()
19 }
20
21 pub fn record_hit(&mut self, rule_name: &str, facts_id: &str) {
23 *self.rule_hits.entry(rule_name.to_string()).or_insert(0) += 1;
24 self.rule_facts.entry(rule_name.to_string())
25 .or_insert_with(HashSet::new)
26 .insert(facts_id.to_string());
27 self.tested_facts.insert(facts_id.to_string());
28 }
29
30 pub fn report(&self, all_rules: &[String]) -> String {
32 let mut report = String::new();
33 report.push_str("Rule Coverage Report\n====================\n");
34 let mut untested = Vec::new();
35 for rule in all_rules {
36 let hits = self.rule_hits.get(rule).copied().unwrap_or(0);
37 let facts = self.rule_facts.get(rule).map(|f| f.len()).unwrap_or(0);
38 report.push_str(&format!(
39 "Rule: {} | Hits: {} | Facts tested: {}\n",
40 rule, hits, facts
41 ));
42 if hits == 0 {
43 untested.push(rule.clone());
44 }
45 }
46 report.push_str(&format!("Total facts tested: {}\n", self.tested_facts.len()));
47 if !untested.is_empty() {
48 report.push_str("\n⚠️ Cảnh báo: Các rule chưa được test:\n");
49 for rule in untested {
50 report.push_str(&format!(" - {}\n", rule));
51 }
52 }
53 report
54 }
55}
56
57fn flatten_conditions(group: &crate::engine::rule::ConditionGroup) -> Vec<crate::engine::rule::Condition> {
62 use crate::engine::rule::ConditionGroup;
63 let mut out = Vec::new();
64 match group {
65 ConditionGroup::Single(cond) => out.push(cond.clone()),
66 ConditionGroup::Compound { left, right, .. } => {
67 out.extend(flatten_conditions(left));
68 out.extend(flatten_conditions(right));
69 }
70 ConditionGroup::Not(inner) | ConditionGroup::Exists(inner) | ConditionGroup::Forall(inner) => {
71 out.extend(flatten_conditions(inner));
72 }
73 ConditionGroup::Accumulate { .. } => {
74 }
77 }
78 out
79}
80
81pub fn generate_test_facts_for_rule(rule: &crate::engine::rule::Rule) -> Vec<crate::Facts> {
83 use crate::types::Value;
84 let conds = flatten_conditions(&rule.conditions);
85 let mut test_facts = Vec::new();
86
87 for cond in &conds {
89 let mut facts = crate::Facts::new();
90 let field = cond.field.clone();
91 match &cond.value {
92 Value::Integer(i) => {
93 facts.set(&field, Value::Integer(*i));
94 test_facts.push(facts.clone());
95 facts.set(&field, Value::Integer(i + 1));
96 test_facts.push(facts.clone());
97 }
98 Value::Boolean(b) => {
99 facts.set(&field, Value::Boolean(*b));
100 test_facts.push(facts.clone());
101 facts.set(&field, Value::Boolean(!b));
102 test_facts.push(facts.clone());
103 }
104 Value::String(s) => {
105 facts.set(&field, Value::String(s.clone()));
106 test_facts.push(facts.clone());
107 facts.set(&field, Value::String("other_value".to_string()));
108 test_facts.push(facts.clone());
109 }
110 _ => {}
111 }
112 }
113
114 if !conds.is_empty() {
116 let mut facts = crate::Facts::new();
117 for cond in &conds {
118 let field = cond.field.clone();
119 match &cond.value {
120 Value::Integer(i) => facts.set(&field, Value::Integer(*i)),
121 Value::Boolean(b) => facts.set(&field, Value::Boolean(*b)),
122 Value::String(s) => facts.set(&field, Value::String(s.clone())),
123 _ => {}
124 }
125 }
126 test_facts.push(facts);
127 }
128
129 test_facts
130}