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