datasynth_eval/coherence/
mod.rs1mod audit;
7mod balance;
8mod bank_reconciliation;
9mod cross_process;
10mod document_chain;
11mod financial_reporting;
12mod hr_payroll;
13mod intercompany;
14mod manufacturing;
15mod multi_table;
16mod network;
17mod referential;
18mod sourcing;
19mod standards;
20mod subledger;
21
22pub use audit::{
23 AuditEvaluation, AuditEvaluator, AuditFindingData, AuditRiskData, AuditThresholds,
24 MaterialityData, WorkpaperData,
25};
26pub use balance::{AccountType, BalanceSheetEvaluation, BalanceSheetEvaluator, BalanceSnapshot};
27pub use bank_reconciliation::{
28 BankReconciliationEvaluation, BankReconciliationEvaluator, BankReconciliationThresholds,
29 ReconciliationData,
30};
31pub use cross_process::{
32 CrossProcessEvaluation, CrossProcessEvaluator, CrossProcessLinkData, CrossProcessThresholds,
33};
34pub use document_chain::{
35 DocumentChainEvaluation, DocumentChainEvaluator, DocumentReferenceData, O2CChainData,
36 P2PChainData,
37};
38pub use financial_reporting::{
39 BudgetVarianceData, FinancialReportingEvaluation, FinancialReportingEvaluator,
40 FinancialReportingThresholds, FinancialStatementData, KpiData,
41};
42pub use hr_payroll::{
43 ExpenseReportData, HrPayrollEvaluation, HrPayrollEvaluator, HrPayrollThresholds,
44 PayrollHoursData, PayrollLineItemData, PayrollRunData, TimeEntryData,
45};
46pub use intercompany::{ICMatchingData, ICMatchingEvaluation, ICMatchingEvaluator};
47pub use manufacturing::{
48 CycleCountData, ManufacturingEvaluation, ManufacturingEvaluator, ManufacturingThresholds,
49 ProductionOrderData, QualityInspectionData, RoutingOperationData,
50};
51pub use multi_table::{
52 get_o2c_flow_relationships, get_p2p_flow_relationships, AnomalyRecord, CascadeAnomalyAnalysis,
53 CascadePath, ConsistencyViolation, MultiTableConsistencyEvaluator, MultiTableData,
54 MultiTableEvaluation, TableConsistencyResult, TableRecord, TableRelationship,
55 TableRelationshipDef, ViolationType,
56};
57pub use network::{
58 ConcentrationMetrics, NetworkEdge, NetworkEvaluation, NetworkEvaluator, NetworkNode,
59 NetworkThresholds, StrengthStats,
60};
61pub use referential::{
62 EntityReferenceData, ReferentialData, ReferentialIntegrityEvaluation,
63 ReferentialIntegrityEvaluator,
64};
65pub use sourcing::{
66 BidEvaluationData, ScorecardCoverageData, SourcingEvaluation, SourcingEvaluator,
67 SourcingProjectData, SourcingThresholds, SpendAnalysisData,
68};
69pub use standards::{
70 AuditTrailEvaluation, AuditTrailGap, FairValueEvaluation, FrameworkViolation,
71 ImpairmentEvaluation, IsaComplianceEvaluation, LeaseAccountingEvaluation,
72 LeaseAccountingEvaluator, LeaseEvaluation, PcaobComplianceEvaluation, PerformanceObligation,
73 RevenueContract, RevenueRecognitionEvaluation, RevenueRecognitionEvaluator,
74 SoxComplianceEvaluation, StandardsComplianceEvaluation, StandardsThresholds,
75 VariableConsideration, ViolationSeverity,
76};
77pub use subledger::{SubledgerEvaluator, SubledgerReconciliationEvaluation};
78
79use serde::{Deserialize, Serialize};
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct CoherenceEvaluation {
84 pub balance: Option<BalanceSheetEvaluation>,
86 pub subledger: Option<SubledgerReconciliationEvaluation>,
88 pub document_chain: Option<DocumentChainEvaluation>,
90 pub intercompany: Option<ICMatchingEvaluation>,
92 pub referential: Option<ReferentialIntegrityEvaluation>,
94 pub multi_table: Option<MultiTableEvaluation>,
96 pub standards: Option<StandardsComplianceEvaluation>,
98 pub network: Option<NetworkEvaluation>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub financial_reporting: Option<FinancialReportingEvaluation>,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub hr_payroll: Option<HrPayrollEvaluation>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub manufacturing: Option<ManufacturingEvaluation>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub bank_reconciliation: Option<BankReconciliationEvaluation>,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub sourcing: Option<SourcingEvaluation>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub cross_process: Option<CrossProcessEvaluation>,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub audit: Option<AuditEvaluation>,
121 pub passes: bool,
123 pub failures: Vec<String>,
125}
126
127impl CoherenceEvaluation {
128 pub fn new() -> Self {
130 Self {
131 balance: None,
132 subledger: None,
133 document_chain: None,
134 intercompany: None,
135 referential: None,
136 multi_table: None,
137 standards: None,
138 network: None,
139 financial_reporting: None,
140 hr_payroll: None,
141 manufacturing: None,
142 bank_reconciliation: None,
143 sourcing: None,
144 cross_process: None,
145 audit: None,
146 passes: true,
147 failures: Vec::new(),
148 }
149 }
150
151 pub fn check_thresholds(&mut self, thresholds: &crate::config::EvaluationThresholds) {
153 self.failures.clear();
154
155 if let Some(ref balance) = self.balance {
156 if !balance.equation_balanced {
157 self.failures.push(format!(
158 "Balance sheet equation not balanced (max imbalance: {})",
159 balance.max_imbalance
160 ));
161 }
162 }
163
164 if let Some(ref subledger) = self.subledger {
165 if subledger.completeness_score < thresholds.subledger_reconciliation_rate_min {
166 self.failures.push(format!(
167 "Subledger reconciliation {} < {} (threshold)",
168 subledger.completeness_score, thresholds.subledger_reconciliation_rate_min
169 ));
170 }
171 }
172
173 if let Some(ref doc_chain) = self.document_chain {
174 let min_rate = thresholds.document_chain_completion_min;
175 if doc_chain.p2p_completion_rate < min_rate {
176 self.failures.push(format!(
177 "P2P chain completion {} < {} (threshold)",
178 doc_chain.p2p_completion_rate, min_rate
179 ));
180 }
181 if doc_chain.o2c_completion_rate < min_rate {
182 self.failures.push(format!(
183 "O2C chain completion {} < {} (threshold)",
184 doc_chain.o2c_completion_rate, min_rate
185 ));
186 }
187 }
188
189 if let Some(ref ic) = self.intercompany {
190 if ic.match_rate < thresholds.ic_match_rate_min {
191 self.failures.push(format!(
192 "IC match rate {} < {} (threshold)",
193 ic.match_rate, thresholds.ic_match_rate_min
194 ));
195 }
196 }
197
198 if let Some(ref referential) = self.referential {
199 if referential.overall_integrity_score < thresholds.referential_integrity_min {
200 self.failures.push(format!(
201 "Referential integrity {} < {} (threshold)",
202 referential.overall_integrity_score, thresholds.referential_integrity_min
203 ));
204 }
205 }
206
207 if let Some(ref multi_table) = self.multi_table {
208 if multi_table.overall_consistency_score < thresholds.referential_integrity_min {
209 self.failures.push(format!(
210 "Multi-table consistency {} < {} (threshold)",
211 multi_table.overall_consistency_score, thresholds.referential_integrity_min
212 ));
213 }
214 self.failures.extend(multi_table.issues.clone());
215 }
216
217 if let Some(ref mut standards_eval) = self.standards.clone() {
218 let standards_thresholds = StandardsThresholds::default();
219 standards_eval.check_thresholds(&standards_thresholds);
220 self.failures.extend(standards_eval.failures.clone());
221 }
222
223 if let Some(ref network_eval) = self.network {
224 if !network_eval.passes {
225 self.failures.extend(network_eval.issues.clone());
226 }
227 }
228
229 if let Some(ref eval) = self.financial_reporting {
231 if !eval.passes {
232 self.failures.extend(eval.issues.clone());
233 }
234 }
235 if let Some(ref eval) = self.hr_payroll {
236 if !eval.passes {
237 self.failures.extend(eval.issues.clone());
238 }
239 }
240 if let Some(ref eval) = self.manufacturing {
241 if !eval.passes {
242 self.failures.extend(eval.issues.clone());
243 }
244 }
245 if let Some(ref eval) = self.bank_reconciliation {
246 if !eval.passes {
247 self.failures.extend(eval.issues.clone());
248 }
249 }
250 if let Some(ref eval) = self.sourcing {
251 if !eval.passes {
252 self.failures.extend(eval.issues.clone());
253 }
254 }
255 if let Some(ref eval) = self.cross_process {
256 if !eval.passes {
257 self.failures.extend(eval.issues.clone());
258 }
259 }
260 if let Some(ref eval) = self.audit {
261 if !eval.passes {
262 self.failures.extend(eval.issues.clone());
263 }
264 }
265
266 self.passes = self.failures.is_empty();
267 }
268}
269
270impl Default for CoherenceEvaluation {
271 fn default() -> Self {
272 Self::new()
273 }
274}