Skip to main content

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 audit;
7mod balance;
8mod bank_reconciliation;
9mod country_packs;
10mod cross_process;
11mod document_chain;
12mod esg;
13mod financial_reporting;
14mod fraud_packs;
15mod hr_payroll;
16mod intercompany;
17mod manufacturing;
18mod multi_period;
19mod multi_table;
20mod network;
21mod project_accounting;
22mod referential;
23mod sales_quotes;
24mod sourcing;
25mod standards;
26mod subledger;
27mod tax;
28mod treasury;
29
30pub use audit::{
31    AuditEvaluation, AuditEvaluator, AuditFindingData, AuditRiskData, AuditThresholds,
32    MaterialityData, WorkpaperData,
33};
34pub use balance::{AccountType, BalanceSheetEvaluation, BalanceSheetEvaluator, BalanceSnapshot};
35pub use bank_reconciliation::{
36    BankReconciliationEvaluation, BankReconciliationEvaluator, BankReconciliationThresholds,
37    ReconciliationData,
38};
39pub use country_packs::{
40    ApprovalLevelData, CountryPackData, CountryPackEvaluation, CountryPackEvaluator,
41    CountryPackThresholds, HolidayData, TaxRateData,
42};
43pub use cross_process::{
44    CrossProcessEvaluation, CrossProcessEvaluator, CrossProcessLinkData, CrossProcessThresholds,
45};
46pub use document_chain::{
47    DocumentChainEvaluation, DocumentChainEvaluator, DocumentReferenceData, O2CChainData,
48    P2PChainData,
49};
50pub use esg::{
51    EsgEvaluation, EsgEvaluator, EsgThresholds, GovernanceData, SafetyMetricData, SupplierEsgData,
52    WaterUsageData,
53};
54pub use financial_reporting::{
55    BudgetVarianceData, FinancialReportingEvaluation, FinancialReportingEvaluator,
56    FinancialReportingThresholds, FinancialStatementData, KpiData,
57};
58pub use fraud_packs::{FraudPackAnalysis, FraudPackAnalyzer, FraudPackData, FraudPackThresholds};
59pub use hr_payroll::{
60    ExpenseReportData, HrPayrollEvaluation, HrPayrollEvaluator, HrPayrollThresholds,
61    PayrollHoursData, PayrollLineItemData, PayrollRunData, TimeEntryData,
62};
63pub use intercompany::{ICMatchingData, ICMatchingEvaluation, ICMatchingEvaluator};
64pub use manufacturing::{
65    CycleCountData, ManufacturingEvaluation, ManufacturingEvaluator, ManufacturingThresholds,
66    ProductionOrderData, QualityInspectionData, RoutingOperationData,
67};
68pub use multi_period::{
69    MultiPeriodAnalysis, MultiPeriodAnalyzer, MultiPeriodThresholds, PeriodData,
70};
71pub use multi_table::{
72    get_o2c_flow_relationships, get_p2p_flow_relationships, AnomalyRecord, CascadeAnomalyAnalysis,
73    CascadePath, ConsistencyViolation, MultiTableConsistencyEvaluator, MultiTableData,
74    MultiTableEvaluation, TableConsistencyResult, TableRecord, TableRelationship,
75    TableRelationshipDef, ViolationType,
76};
77pub use network::{
78    ConcentrationMetrics, NetworkEdge, NetworkEvaluation, NetworkEvaluator, NetworkNode,
79    NetworkThresholds, StrengthStats,
80};
81pub use project_accounting::{
82    EarnedValueData, ProjectAccountingEvaluation, ProjectAccountingEvaluator,
83    ProjectAccountingThresholds, ProjectRevenueData, RetainageData,
84};
85pub use referential::{
86    EntityReferenceData, ReferentialData, ReferentialIntegrityEvaluation,
87    ReferentialIntegrityEvaluator,
88};
89pub use sales_quotes::{
90    QuoteLineData, SalesQuoteData, SalesQuoteEvaluation, SalesQuoteEvaluator, SalesQuoteThresholds,
91};
92pub use sourcing::{
93    BidEvaluationData, ScorecardCoverageData, SourcingEvaluation, SourcingEvaluator,
94    SourcingProjectData, SourcingThresholds, SpendAnalysisData,
95};
96pub use standards::{
97    AuditTrailEvaluation, AuditTrailGap, FairValueEvaluation, FrameworkViolation,
98    ImpairmentEvaluation, IsaComplianceEvaluation, LeaseAccountingEvaluation,
99    LeaseAccountingEvaluator, LeaseEvaluation, PcaobComplianceEvaluation, PerformanceObligation,
100    RevenueContract, RevenueRecognitionEvaluation, RevenueRecognitionEvaluator,
101    SoxComplianceEvaluation, StandardsComplianceEvaluation, StandardsThresholds,
102    VariableConsideration, ViolationSeverity,
103};
104pub use subledger::{SubledgerEvaluator, SubledgerReconciliationEvaluation};
105pub use tax::{
106    TaxEvaluation, TaxEvaluator, TaxLineData, TaxReturnData, TaxThresholds, WithholdingData,
107};
108pub use treasury::{
109    CashPositionData, CovenantData, HedgeEffectivenessData, NettingData, TreasuryEvaluation,
110    TreasuryEvaluator, TreasuryThresholds,
111};
112
113use serde::{Deserialize, Serialize};
114
115/// Combined coherence evaluation results.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct CoherenceEvaluation {
118    /// Balance sheet validation results.
119    pub balance: Option<BalanceSheetEvaluation>,
120    /// Subledger reconciliation results.
121    pub subledger: Option<SubledgerReconciliationEvaluation>,
122    /// Document chain completeness results.
123    pub document_chain: Option<DocumentChainEvaluation>,
124    /// Intercompany matching results.
125    pub intercompany: Option<ICMatchingEvaluation>,
126    /// Referential integrity results.
127    pub referential: Option<ReferentialIntegrityEvaluation>,
128    /// Multi-table consistency results.
129    pub multi_table: Option<MultiTableEvaluation>,
130    /// Accounting and audit standards compliance results.
131    pub standards: Option<StandardsComplianceEvaluation>,
132    /// Network/interconnectivity evaluation results.
133    pub network: Option<NetworkEvaluation>,
134    /// Financial reporting evaluation results.
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub financial_reporting: Option<FinancialReportingEvaluation>,
137    /// HR/payroll evaluation results.
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub hr_payroll: Option<HrPayrollEvaluation>,
140    /// Manufacturing evaluation results.
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub manufacturing: Option<ManufacturingEvaluation>,
143    /// Bank reconciliation evaluation results.
144    #[serde(default, skip_serializing_if = "Option::is_none")]
145    pub bank_reconciliation: Option<BankReconciliationEvaluation>,
146    /// Source-to-contract evaluation results.
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub sourcing: Option<SourcingEvaluation>,
149    /// Cross-process link evaluation results.
150    #[serde(default, skip_serializing_if = "Option::is_none")]
151    pub cross_process: Option<CrossProcessEvaluation>,
152    /// Audit evaluation results.
153    #[serde(default, skip_serializing_if = "Option::is_none")]
154    pub audit: Option<AuditEvaluation>,
155    /// Tax evaluation results.
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub tax: Option<TaxEvaluation>,
158    /// Treasury evaluation results.
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub treasury: Option<TreasuryEvaluation>,
161    /// Project accounting evaluation results.
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub project_accounting: Option<ProjectAccountingEvaluation>,
164    /// ESG evaluation results.
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub esg: Option<EsgEvaluation>,
167    /// Sales quote evaluation results.
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub sales_quotes: Option<SalesQuoteEvaluation>,
170    /// Country pack evaluation results.
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub country_packs: Option<CountryPackEvaluation>,
173    /// Multi-period coherence evaluation results.
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub multi_period: Option<MultiPeriodAnalysis>,
176    /// Overall pass/fail status.
177    pub passes: bool,
178    /// Summary of failed checks.
179    pub failures: Vec<String>,
180}
181
182impl CoherenceEvaluation {
183    /// Create a new empty evaluation.
184    pub fn new() -> Self {
185        Self {
186            balance: None,
187            subledger: None,
188            document_chain: None,
189            intercompany: None,
190            referential: None,
191            multi_table: None,
192            standards: None,
193            network: None,
194            financial_reporting: None,
195            hr_payroll: None,
196            manufacturing: None,
197            bank_reconciliation: None,
198            sourcing: None,
199            cross_process: None,
200            audit: None,
201            tax: None,
202            treasury: None,
203            project_accounting: None,
204            esg: None,
205            sales_quotes: None,
206            country_packs: None,
207            multi_period: None,
208            passes: true,
209            failures: Vec::new(),
210        }
211    }
212
213    /// Check all results against thresholds and update pass status.
214    pub fn check_thresholds(&mut self, thresholds: &crate::config::EvaluationThresholds) {
215        self.failures.clear();
216
217        if let Some(ref balance) = self.balance {
218            if !balance.equation_balanced {
219                self.failures.push(format!(
220                    "Balance sheet equation not balanced (max imbalance: {})",
221                    balance.max_imbalance
222                ));
223            }
224        }
225
226        if let Some(ref subledger) = self.subledger {
227            if subledger.completeness_score < thresholds.subledger_reconciliation_rate_min {
228                self.failures.push(format!(
229                    "Subledger reconciliation {} < {} (threshold)",
230                    subledger.completeness_score, thresholds.subledger_reconciliation_rate_min
231                ));
232            }
233        }
234
235        if let Some(ref doc_chain) = self.document_chain {
236            let min_rate = thresholds.document_chain_completion_min;
237            if doc_chain.p2p_completion_rate < min_rate {
238                self.failures.push(format!(
239                    "P2P chain completion {} < {} (threshold)",
240                    doc_chain.p2p_completion_rate, min_rate
241                ));
242            }
243            if doc_chain.o2c_completion_rate < min_rate {
244                self.failures.push(format!(
245                    "O2C chain completion {} < {} (threshold)",
246                    doc_chain.o2c_completion_rate, min_rate
247                ));
248            }
249        }
250
251        if let Some(ref ic) = self.intercompany {
252            if ic.match_rate < thresholds.ic_match_rate_min {
253                self.failures.push(format!(
254                    "IC match rate {} < {} (threshold)",
255                    ic.match_rate, thresholds.ic_match_rate_min
256                ));
257            }
258        }
259
260        if let Some(ref referential) = self.referential {
261            if referential.overall_integrity_score < thresholds.referential_integrity_min {
262                self.failures.push(format!(
263                    "Referential integrity {} < {} (threshold)",
264                    referential.overall_integrity_score, thresholds.referential_integrity_min
265                ));
266            }
267        }
268
269        if let Some(ref multi_table) = self.multi_table {
270            if multi_table.overall_consistency_score < thresholds.referential_integrity_min {
271                self.failures.push(format!(
272                    "Multi-table consistency {} < {} (threshold)",
273                    multi_table.overall_consistency_score, thresholds.referential_integrity_min
274                ));
275            }
276            self.failures.extend(multi_table.issues.clone());
277        }
278
279        if let Some(ref mut standards_eval) = self.standards.clone() {
280            let standards_thresholds = StandardsThresholds::default();
281            standards_eval.check_thresholds(&standards_thresholds);
282            self.failures.extend(standards_eval.failures.clone());
283        }
284
285        if let Some(ref network_eval) = self.network {
286            if !network_eval.passes {
287                self.failures.extend(network_eval.issues.clone());
288            }
289        }
290
291        // New evaluators: propagate issues
292        if let Some(ref eval) = self.financial_reporting {
293            if !eval.passes {
294                self.failures.extend(eval.issues.clone());
295            }
296        }
297        if let Some(ref eval) = self.hr_payroll {
298            if !eval.passes {
299                self.failures.extend(eval.issues.clone());
300            }
301        }
302        if let Some(ref eval) = self.manufacturing {
303            if !eval.passes {
304                self.failures.extend(eval.issues.clone());
305            }
306        }
307        if let Some(ref eval) = self.bank_reconciliation {
308            if !eval.passes {
309                self.failures.extend(eval.issues.clone());
310            }
311        }
312        if let Some(ref eval) = self.sourcing {
313            if !eval.passes {
314                self.failures.extend(eval.issues.clone());
315            }
316        }
317        if let Some(ref eval) = self.cross_process {
318            if !eval.passes {
319                self.failures.extend(eval.issues.clone());
320            }
321        }
322        if let Some(ref eval) = self.audit {
323            if !eval.passes {
324                self.failures.extend(eval.issues.clone());
325            }
326        }
327        if let Some(ref eval) = self.tax {
328            if !eval.passes {
329                self.failures.extend(eval.issues.clone());
330            }
331        }
332        if let Some(ref eval) = self.treasury {
333            if !eval.passes {
334                self.failures.extend(eval.issues.clone());
335            }
336        }
337        if let Some(ref eval) = self.project_accounting {
338            if !eval.passes {
339                self.failures.extend(eval.issues.clone());
340            }
341        }
342        if let Some(ref eval) = self.esg {
343            if !eval.passes {
344                self.failures.extend(eval.issues.clone());
345            }
346        }
347        if let Some(ref eval) = self.sales_quotes {
348            if !eval.passes {
349                self.failures.extend(eval.issues.clone());
350            }
351        }
352        if let Some(ref eval) = self.country_packs {
353            if !eval.passes {
354                self.failures.extend(eval.issues.clone());
355            }
356        }
357        if let Some(ref eval) = self.multi_period {
358            if !eval.passes {
359                self.failures.extend(eval.issues.clone());
360            }
361        }
362
363        self.passes = self.failures.is_empty();
364    }
365}
366
367impl Default for CoherenceEvaluation {
368    fn default() -> Self {
369        Self::new()
370    }
371}