datasynth_eval/coherence/
mod.rs1mod balance;
7mod document_chain;
8mod intercompany;
9mod multi_table;
10mod referential;
11mod subledger;
12
13pub use balance::{BalanceSheetEvaluation, BalanceSheetEvaluator};
14pub use document_chain::{DocumentChainEvaluation, DocumentChainEvaluator};
15pub use intercompany::{ICMatchingEvaluation, ICMatchingEvaluator};
16pub use multi_table::{
17 get_o2c_flow_relationships, get_p2p_flow_relationships, AnomalyRecord, CascadeAnomalyAnalysis,
18 CascadePath, ConsistencyViolation, MultiTableConsistencyEvaluator, MultiTableData,
19 MultiTableEvaluation, TableConsistencyResult, TableRecord, TableRelationship,
20 TableRelationshipDef, ViolationType,
21};
22pub use referential::{ReferentialIntegrityEvaluation, ReferentialIntegrityEvaluator};
23pub use subledger::{SubledgerEvaluator, SubledgerReconciliationEvaluation};
24
25use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct CoherenceEvaluation {
30 pub balance: Option<BalanceSheetEvaluation>,
32 pub subledger: Option<SubledgerReconciliationEvaluation>,
34 pub document_chain: Option<DocumentChainEvaluation>,
36 pub intercompany: Option<ICMatchingEvaluation>,
38 pub referential: Option<ReferentialIntegrityEvaluation>,
40 pub multi_table: Option<MultiTableEvaluation>,
42 pub passes: bool,
44 pub failures: Vec<String>,
46}
47
48impl CoherenceEvaluation {
49 pub fn new() -> Self {
51 Self {
52 balance: None,
53 subledger: None,
54 document_chain: None,
55 intercompany: None,
56 referential: None,
57 multi_table: None,
58 passes: true,
59 failures: Vec::new(),
60 }
61 }
62
63 pub fn check_thresholds(&mut self, thresholds: &crate::config::EvaluationThresholds) {
65 self.failures.clear();
66
67 if let Some(ref balance) = self.balance {
68 if !balance.equation_balanced {
69 self.failures.push(format!(
70 "Balance sheet equation not balanced (max imbalance: {})",
71 balance.max_imbalance
72 ));
73 }
74 }
75
76 if let Some(ref subledger) = self.subledger {
77 if subledger.completeness_score < thresholds.subledger_reconciliation_rate_min {
78 self.failures.push(format!(
79 "Subledger reconciliation {} < {} (threshold)",
80 subledger.completeness_score, thresholds.subledger_reconciliation_rate_min
81 ));
82 }
83 }
84
85 if let Some(ref doc_chain) = self.document_chain {
86 let min_rate = thresholds.document_chain_completion_min;
87 if doc_chain.p2p_completion_rate < min_rate {
88 self.failures.push(format!(
89 "P2P chain completion {} < {} (threshold)",
90 doc_chain.p2p_completion_rate, min_rate
91 ));
92 }
93 if doc_chain.o2c_completion_rate < min_rate {
94 self.failures.push(format!(
95 "O2C chain completion {} < {} (threshold)",
96 doc_chain.o2c_completion_rate, min_rate
97 ));
98 }
99 }
100
101 if let Some(ref ic) = self.intercompany {
102 if ic.match_rate < thresholds.ic_match_rate_min {
103 self.failures.push(format!(
104 "IC match rate {} < {} (threshold)",
105 ic.match_rate, thresholds.ic_match_rate_min
106 ));
107 }
108 }
109
110 if let Some(ref referential) = self.referential {
111 if referential.overall_integrity_score < thresholds.referential_integrity_min {
112 self.failures.push(format!(
113 "Referential integrity {} < {} (threshold)",
114 referential.overall_integrity_score, thresholds.referential_integrity_min
115 ));
116 }
117 }
118
119 if let Some(ref multi_table) = self.multi_table {
120 if multi_table.overall_consistency_score < thresholds.referential_integrity_min {
122 self.failures.push(format!(
123 "Multi-table consistency {} < {} (threshold)",
124 multi_table.overall_consistency_score, thresholds.referential_integrity_min
125 ));
126 }
127 self.failures.extend(multi_table.issues.clone());
129 }
130
131 self.passes = self.failures.is_empty();
132 }
133}
134
135impl Default for CoherenceEvaluation {
136 fn default() -> Self {
137 Self::new()
138 }
139}