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