1pub mod financial_package;
7pub mod inventory_cogs;
8pub mod je_risk_scoring;
9pub mod ratio_analysis;
10pub mod sampling_validation;
11pub mod treasury_tax;
12pub mod trend_analysis;
13
14mod audit;
15mod balance;
16mod bank_reconciliation;
17mod country_packs;
18mod cross_process;
19mod document_chain;
20mod esg;
21mod financial_reporting;
22mod fraud_packs;
23mod hr_payroll;
24mod intercompany;
25mod manufacturing;
26mod multi_period;
27mod multi_table;
28mod network;
29mod project_accounting;
30mod referential;
31mod sales_quotes;
32mod sourcing;
33mod standards;
34mod subledger;
35mod tax;
36mod treasury;
37
38pub use audit::{
39 AuditEvaluation, AuditEvaluator, AuditFindingData, AuditRiskData, AuditThresholds,
40 MaterialityData, WorkpaperData,
41};
42pub use balance::{AccountType, BalanceSheetEvaluation, BalanceSheetEvaluator, BalanceSnapshot};
43pub use bank_reconciliation::{
44 BankReconciliationEvaluation, BankReconciliationEvaluator, BankReconciliationThresholds,
45 ReconciliationData,
46};
47pub use country_packs::{
48 ApprovalLevelData, CountryPackData, CountryPackEvaluation, CountryPackEvaluator,
49 CountryPackThresholds, HolidayData, TaxRateData,
50};
51pub use cross_process::{
52 CrossProcessEvaluation, CrossProcessEvaluator, CrossProcessLinkData, CrossProcessThresholds,
53};
54pub use document_chain::{
55 DocumentChainEvaluation, DocumentChainEvaluator, DocumentReferenceData, O2CChainData,
56 P2PChainData,
57};
58pub use esg::{
59 EsgEvaluation, EsgEvaluator, EsgThresholds, GovernanceData, SafetyMetricData, SupplierEsgData,
60 WaterUsageData,
61};
62pub use financial_reporting::{
63 BudgetVarianceData, FinancialReportingEvaluation, FinancialReportingEvaluator,
64 FinancialReportingThresholds, FinancialStatementData, KpiData,
65};
66pub use fraud_packs::{FraudPackAnalysis, FraudPackAnalyzer, FraudPackData, FraudPackThresholds};
67pub use hr_payroll::{
68 ExpenseReportData, HrPayrollEvaluation, HrPayrollEvaluator, HrPayrollThresholds,
69 PayrollGLProofData, PayrollGLProofEvaluation, PayrollGLProofEvaluator, PayrollHoursData,
70 PayrollLineItemData, PayrollRunData, TimeEntryData,
71};
72pub use intercompany::{
73 ICEliminationLineData, ICMatchingData, ICMatchingEvaluation, ICMatchingEvaluator,
74 ICNetZeroData, ICNetZeroEvaluation, ICNetZeroEvaluator, UnmatchedICItem,
75};
76pub use inventory_cogs::{
77 ICEliminationData, ICEliminationEvaluation, ICEliminationEvaluator, InventoryCOGSData,
78 InventoryCOGSEvaluation, InventoryCOGSEvaluator,
79};
80pub use je_risk_scoring::{JeRiskScoringResult, RiskAttributeStats, RiskDistribution};
81pub use manufacturing::{
82 CycleCountData, ManufacturingEvaluation, ManufacturingEvaluator, ManufacturingGLProofData,
83 ManufacturingGLProofEvaluation, ManufacturingGLProofEvaluator, ManufacturingThresholds,
84 ProductionOrderData, QualityInspectionData, RoutingOperationData,
85};
86pub use multi_period::{
87 MultiPeriodAnalysis, MultiPeriodAnalyzer, MultiPeriodThresholds, PeriodData,
88};
89pub use multi_table::{
90 get_o2c_flow_relationships, get_p2p_flow_relationships, AnomalyRecord, CascadeAnomalyAnalysis,
91 CascadePath, ConsistencyViolation, MultiTableConsistencyEvaluator, MultiTableData,
92 MultiTableEvaluation, TableConsistencyResult, TableRecord, TableRelationship,
93 TableRelationshipDef, ViolationType,
94};
95pub use network::{
96 ConcentrationMetrics, NetworkEdge, NetworkEvaluation, NetworkEvaluator, NetworkNode,
97 NetworkThresholds, StrengthStats,
98};
99pub use project_accounting::{
100 EarnedValueData, ProjectAccountingEvaluation, ProjectAccountingEvaluator,
101 ProjectAccountingThresholds, ProjectRevenueData, RetainageData,
102};
103pub use ratio_analysis::{FinancialRatios, RatioAnalysisResult, RatioCheck};
104pub use referential::{
105 EntityReferenceData, ReferentialData, ReferentialIntegrityEvaluation,
106 ReferentialIntegrityEvaluator,
107};
108pub use sales_quotes::{
109 QuoteLineData, SalesQuoteData, SalesQuoteEvaluation, SalesQuoteEvaluator, SalesQuoteThresholds,
110};
111pub use sampling_validation::{
112 validate_sampling, SamplingValidationResult, Stratum, StratumResult,
113};
114pub use sourcing::{
115 BidEvaluationData, ScorecardCoverageData, SourcingEvaluation, SourcingEvaluator,
116 SourcingProjectData, SourcingThresholds, SpendAnalysisData,
117};
118pub use standards::{
119 AuditTrailEvaluation, AuditTrailGap, FairValueEvaluation, FrameworkViolation,
120 ImpairmentEvaluation, IsaComplianceEvaluation, LeaseAccountingEvaluation,
121 LeaseAccountingEvaluator, LeaseEvaluation, PcaobComplianceEvaluation, PerformanceObligation,
122 RevenueContract, RevenueRecognitionEvaluation, RevenueRecognitionEvaluator,
123 SoxComplianceEvaluation, StandardsComplianceEvaluation, StandardsThresholds,
124 VariableConsideration, ViolationSeverity,
125};
126pub use subledger::{SubledgerEvaluator, SubledgerReconciliationEvaluation};
127pub use tax::{
128 TaxEvaluation, TaxEvaluator, TaxLineData, TaxReturnData, TaxThresholds, WithholdingData,
129};
130pub use treasury::{
131 CashPositionData, CovenantData, HedgeEffectivenessData, NettingData, TreasuryCashProofData,
132 TreasuryCashProofEvaluation, TreasuryCashProofEvaluator, TreasuryEvaluation, TreasuryEvaluator,
133 TreasuryThresholds,
134};
135pub use trend_analysis::{analyze_trends, TrendConsistencyCheck, TrendPlausibilityResult};
136
137use serde::{Deserialize, Serialize};
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct CoherenceEvaluation {
142 pub balance: Option<BalanceSheetEvaluation>,
144 pub subledger: Option<SubledgerReconciliationEvaluation>,
146 pub document_chain: Option<DocumentChainEvaluation>,
148 pub intercompany: Option<ICMatchingEvaluation>,
150 pub referential: Option<ReferentialIntegrityEvaluation>,
152 pub multi_table: Option<MultiTableEvaluation>,
154 pub standards: Option<StandardsComplianceEvaluation>,
156 pub network: Option<NetworkEvaluation>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub financial_reporting: Option<FinancialReportingEvaluation>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub hr_payroll: Option<HrPayrollEvaluation>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub manufacturing: Option<ManufacturingEvaluation>,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub bank_reconciliation: Option<BankReconciliationEvaluation>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub sourcing: Option<SourcingEvaluation>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub cross_process: Option<CrossProcessEvaluation>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub audit: Option<AuditEvaluation>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub tax: Option<TaxEvaluation>,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub treasury: Option<TreasuryEvaluation>,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub project_accounting: Option<ProjectAccountingEvaluation>,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub esg: Option<EsgEvaluation>,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub sales_quotes: Option<SalesQuoteEvaluation>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub country_packs: Option<CountryPackEvaluation>,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub multi_period: Option<MultiPeriodAnalysis>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub inventory_cogs: Option<inventory_cogs::InventoryCOGSEvaluation>,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub ic_elimination: Option<inventory_cogs::ICEliminationEvaluation>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub ic_net_zero: Option<ICNetZeroEvaluation>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub interest_expense_proof: Option<treasury_tax::InterestExpenseProofEvaluation>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub etr_reconciliation: Option<treasury_tax::ETRReconciliationEvaluation>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub hedge_effectiveness: Option<treasury_tax::HedgeEffectivenessEvaluation>,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub payroll_hr: Option<treasury_tax::PayrollHRReconciliationEvaluation>,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
223 pub cash_flow_reconciliation: Option<financial_package::CashFlowReconciliationEvaluation>,
224 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub equity_rollforward: Option<financial_package::EquityRollforwardEvaluation>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub segment_reconciliation: Option<financial_package::SegmentReconciliationEvaluation>,
230 #[serde(default, skip_serializing_if = "Option::is_none")]
232 pub tb_master_proof: Option<financial_package::TrialBalanceMasterProofEvaluation>,
233 pub passes: bool,
235 pub failures: Vec<String>,
237}
238
239impl CoherenceEvaluation {
240 pub fn new() -> Self {
242 Self {
243 balance: None,
244 subledger: None,
245 document_chain: None,
246 intercompany: None,
247 referential: None,
248 multi_table: None,
249 standards: None,
250 network: None,
251 financial_reporting: None,
252 hr_payroll: None,
253 manufacturing: None,
254 bank_reconciliation: None,
255 sourcing: None,
256 cross_process: None,
257 audit: None,
258 tax: None,
259 treasury: None,
260 project_accounting: None,
261 esg: None,
262 sales_quotes: None,
263 country_packs: None,
264 multi_period: None,
265 inventory_cogs: None,
266 ic_elimination: None,
267 ic_net_zero: None,
268 interest_expense_proof: None,
269 etr_reconciliation: None,
270 hedge_effectiveness: None,
271 payroll_hr: None,
272 cash_flow_reconciliation: None,
273 equity_rollforward: None,
274 segment_reconciliation: None,
275 tb_master_proof: None,
276 passes: true,
277 failures: Vec::new(),
278 }
279 }
280
281 pub fn check_thresholds(&mut self, thresholds: &crate::config::EvaluationThresholds) {
283 self.failures.clear();
284
285 if let Some(ref balance) = self.balance {
286 if !balance.equation_balanced {
287 self.failures.push(format!(
288 "Balance sheet equation not balanced (max imbalance: {})",
289 balance.max_imbalance
290 ));
291 }
292 }
293
294 if let Some(ref subledger) = self.subledger {
295 if subledger.completeness_score < thresholds.subledger_reconciliation_rate_min {
296 self.failures.push(format!(
297 "Subledger reconciliation {} < {} (threshold)",
298 subledger.completeness_score, thresholds.subledger_reconciliation_rate_min
299 ));
300 }
301 }
302
303 if let Some(ref doc_chain) = self.document_chain {
304 let min_rate = thresholds.document_chain_completion_min;
305 if doc_chain.p2p_completion_rate < min_rate {
306 self.failures.push(format!(
307 "P2P chain completion {} < {} (threshold)",
308 doc_chain.p2p_completion_rate, min_rate
309 ));
310 }
311 if doc_chain.o2c_completion_rate < min_rate {
312 self.failures.push(format!(
313 "O2C chain completion {} < {} (threshold)",
314 doc_chain.o2c_completion_rate, min_rate
315 ));
316 }
317 }
318
319 if let Some(ref ic) = self.intercompany {
320 if ic.match_rate < thresholds.ic_match_rate_min {
321 self.failures.push(format!(
322 "IC match rate {} < {} (threshold)",
323 ic.match_rate, thresholds.ic_match_rate_min
324 ));
325 }
326 }
327
328 if let Some(ref referential) = self.referential {
329 if referential.overall_integrity_score < thresholds.referential_integrity_min {
330 self.failures.push(format!(
331 "Referential integrity {} < {} (threshold)",
332 referential.overall_integrity_score, thresholds.referential_integrity_min
333 ));
334 }
335 }
336
337 if let Some(ref multi_table) = self.multi_table {
338 if multi_table.overall_consistency_score < thresholds.referential_integrity_min {
339 self.failures.push(format!(
340 "Multi-table consistency {} < {} (threshold)",
341 multi_table.overall_consistency_score, thresholds.referential_integrity_min
342 ));
343 }
344 self.failures.extend(multi_table.issues.clone());
345 }
346
347 if let Some(ref mut standards_eval) = self.standards.clone() {
348 let standards_thresholds = StandardsThresholds::default();
349 standards_eval.check_thresholds(&standards_thresholds);
350 self.failures.extend(standards_eval.failures.clone());
351 }
352
353 if let Some(ref network_eval) = self.network {
354 if !network_eval.passes {
355 self.failures.extend(network_eval.issues.clone());
356 }
357 }
358
359 if let Some(ref eval) = self.financial_reporting {
361 if !eval.passes {
362 self.failures.extend(eval.issues.clone());
363 }
364 }
365 if let Some(ref eval) = self.hr_payroll {
366 if !eval.passes {
367 self.failures.extend(eval.issues.clone());
368 }
369 }
370 if let Some(ref eval) = self.manufacturing {
371 if !eval.passes {
372 self.failures.extend(eval.issues.clone());
373 }
374 }
375 if let Some(ref eval) = self.bank_reconciliation {
376 if !eval.passes {
377 self.failures.extend(eval.issues.clone());
378 }
379 }
380 if let Some(ref eval) = self.sourcing {
381 if !eval.passes {
382 self.failures.extend(eval.issues.clone());
383 }
384 }
385 if let Some(ref eval) = self.cross_process {
386 if !eval.passes {
387 self.failures.extend(eval.issues.clone());
388 }
389 }
390 if let Some(ref eval) = self.audit {
391 if !eval.passes {
392 self.failures.extend(eval.issues.clone());
393 }
394 }
395 if let Some(ref eval) = self.tax {
396 if !eval.passes {
397 self.failures.extend(eval.issues.clone());
398 }
399 }
400 if let Some(ref eval) = self.treasury {
401 if !eval.passes {
402 self.failures.extend(eval.issues.clone());
403 }
404 }
405 if let Some(ref eval) = self.project_accounting {
406 if !eval.passes {
407 self.failures.extend(eval.issues.clone());
408 }
409 }
410 if let Some(ref eval) = self.esg {
411 if !eval.passes {
412 self.failures.extend(eval.issues.clone());
413 }
414 }
415 if let Some(ref eval) = self.sales_quotes {
416 if !eval.passes {
417 self.failures.extend(eval.issues.clone());
418 }
419 }
420 if let Some(ref eval) = self.country_packs {
421 if !eval.passes {
422 self.failures.extend(eval.issues.clone());
423 }
424 }
425 if let Some(ref eval) = self.multi_period {
426 if !eval.passes {
427 self.failures.extend(eval.issues.clone());
428 }
429 }
430 if let Some(ref eval) = self.inventory_cogs {
431 if !eval.passes {
432 self.failures.extend(eval.failures.clone());
433 }
434 }
435 if let Some(ref eval) = self.ic_elimination {
436 if !eval.passes {
437 self.failures.extend(eval.failures.clone());
438 }
439 }
440 if let Some(ref eval) = self.interest_expense_proof {
441 if !eval.passes {
442 self.failures.extend(eval.failures.clone());
443 }
444 }
445 if let Some(ref eval) = self.etr_reconciliation {
446 if !eval.passes {
447 self.failures.extend(eval.failures.clone());
448 }
449 }
450 if let Some(ref eval) = self.hedge_effectiveness {
451 if !eval.passes {
452 self.failures.extend(eval.failures.clone());
453 }
454 }
455 if let Some(ref eval) = self.payroll_hr {
456 if !eval.passes {
457 self.failures.extend(eval.failures.clone());
458 }
459 }
460 if let Some(ref eval) = self.cash_flow_reconciliation {
461 if !eval.passes {
462 self.failures.extend(eval.failures.clone());
463 }
464 }
465 if let Some(ref eval) = self.equity_rollforward {
466 if !eval.passes {
467 self.failures.extend(eval.failures.clone());
468 }
469 }
470 if let Some(ref eval) = self.segment_reconciliation {
471 if !eval.passes {
472 self.failures.extend(eval.failures.clone());
473 }
474 }
475 if let Some(ref eval) = self.tb_master_proof {
476 if !eval.passes {
477 self.failures.extend(eval.failures.clone());
478 }
479 }
480
481 self.passes = self.failures.is_empty();
482 }
483}
484
485impl Default for CoherenceEvaluation {
486 fn default() -> Self {
487 Self::new()
488 }
489}