1mod 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#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct CoherenceEvaluation {
120 pub balance: Option<BalanceSheetEvaluation>,
122 pub subledger: Option<SubledgerReconciliationEvaluation>,
124 pub document_chain: Option<DocumentChainEvaluation>,
126 pub intercompany: Option<ICMatchingEvaluation>,
128 pub referential: Option<ReferentialIntegrityEvaluation>,
130 pub multi_table: Option<MultiTableEvaluation>,
132 pub standards: Option<StandardsComplianceEvaluation>,
134 pub network: Option<NetworkEvaluation>,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub financial_reporting: Option<FinancialReportingEvaluation>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub hr_payroll: Option<HrPayrollEvaluation>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub manufacturing: Option<ManufacturingEvaluation>,
145 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub bank_reconciliation: Option<BankReconciliationEvaluation>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub sourcing: Option<SourcingEvaluation>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub cross_process: Option<CrossProcessEvaluation>,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub audit: Option<AuditEvaluation>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub tax: Option<TaxEvaluation>,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub treasury: Option<TreasuryEvaluation>,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub project_accounting: Option<ProjectAccountingEvaluation>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub esg: Option<EsgEvaluation>,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub sales_quotes: Option<SalesQuoteEvaluation>,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub country_packs: Option<CountryPackEvaluation>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub multi_period: Option<MultiPeriodAnalysis>,
178 pub passes: bool,
180 pub failures: Vec<String>,
182}
183
184impl CoherenceEvaluation {
185 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 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 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}