datasynth_eval/coherence/
mod.rs

1//! Semantic coherence evaluation module.
2//!
3//! Validates that generated data maintains accounting coherence including
4//! balance sheet equations, subledger reconciliation, and document chain integrity.
5
6mod 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/// Combined coherence evaluation results.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct CoherenceEvaluation {
30    /// Balance sheet validation results.
31    pub balance: Option<BalanceSheetEvaluation>,
32    /// Subledger reconciliation results.
33    pub subledger: Option<SubledgerReconciliationEvaluation>,
34    /// Document chain completeness results.
35    pub document_chain: Option<DocumentChainEvaluation>,
36    /// Intercompany matching results.
37    pub intercompany: Option<ICMatchingEvaluation>,
38    /// Referential integrity results.
39    pub referential: Option<ReferentialIntegrityEvaluation>,
40    /// Multi-table consistency results.
41    pub multi_table: Option<MultiTableEvaluation>,
42    /// Overall pass/fail status.
43    pub passes: bool,
44    /// Summary of failed checks.
45    pub failures: Vec<String>,
46}
47
48impl CoherenceEvaluation {
49    /// Create a new empty evaluation.
50    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    /// Check all results against thresholds and update pass status.
64    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            // Check multi-table consistency (use referential_integrity_min as default threshold)
121            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            // Add any issues from the multi-table evaluation
128            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}