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 PayrollHoursData, PayrollLineItemData, PayrollRunData, TimeEntryData,
70};
71pub use intercompany::{
72 ICMatchingData, ICMatchingEvaluation, ICMatchingEvaluator, UnmatchedICItem,
73};
74pub use inventory_cogs::{
75 ICEliminationData, ICEliminationEvaluation, ICEliminationEvaluator, InventoryCOGSData,
76 InventoryCOGSEvaluation, InventoryCOGSEvaluator,
77};
78pub use je_risk_scoring::{JeRiskScoringResult, RiskAttributeStats, RiskDistribution};
79pub use manufacturing::{
80 CycleCountData, ManufacturingEvaluation, ManufacturingEvaluator, ManufacturingThresholds,
81 ProductionOrderData, QualityInspectionData, RoutingOperationData,
82};
83pub use multi_period::{
84 MultiPeriodAnalysis, MultiPeriodAnalyzer, MultiPeriodThresholds, PeriodData,
85};
86pub use multi_table::{
87 get_o2c_flow_relationships, get_p2p_flow_relationships, AnomalyRecord, CascadeAnomalyAnalysis,
88 CascadePath, ConsistencyViolation, MultiTableConsistencyEvaluator, MultiTableData,
89 MultiTableEvaluation, TableConsistencyResult, TableRecord, TableRelationship,
90 TableRelationshipDef, ViolationType,
91};
92pub use network::{
93 ConcentrationMetrics, NetworkEdge, NetworkEvaluation, NetworkEvaluator, NetworkNode,
94 NetworkThresholds, StrengthStats,
95};
96pub use project_accounting::{
97 EarnedValueData, ProjectAccountingEvaluation, ProjectAccountingEvaluator,
98 ProjectAccountingThresholds, ProjectRevenueData, RetainageData,
99};
100pub use ratio_analysis::{FinancialRatios, RatioAnalysisResult, RatioCheck};
101pub use referential::{
102 EntityReferenceData, ReferentialData, ReferentialIntegrityEvaluation,
103 ReferentialIntegrityEvaluator,
104};
105pub use sales_quotes::{
106 QuoteLineData, SalesQuoteData, SalesQuoteEvaluation, SalesQuoteEvaluator, SalesQuoteThresholds,
107};
108pub use sampling_validation::{
109 validate_sampling, SamplingValidationResult, Stratum, StratumResult,
110};
111pub use sourcing::{
112 BidEvaluationData, ScorecardCoverageData, SourcingEvaluation, SourcingEvaluator,
113 SourcingProjectData, SourcingThresholds, SpendAnalysisData,
114};
115pub use standards::{
116 AuditTrailEvaluation, AuditTrailGap, FairValueEvaluation, FrameworkViolation,
117 ImpairmentEvaluation, IsaComplianceEvaluation, LeaseAccountingEvaluation,
118 LeaseAccountingEvaluator, LeaseEvaluation, PcaobComplianceEvaluation, PerformanceObligation,
119 RevenueContract, RevenueRecognitionEvaluation, RevenueRecognitionEvaluator,
120 SoxComplianceEvaluation, StandardsComplianceEvaluation, StandardsThresholds,
121 VariableConsideration, ViolationSeverity,
122};
123pub use subledger::{SubledgerEvaluator, SubledgerReconciliationEvaluation};
124pub use tax::{
125 TaxEvaluation, TaxEvaluator, TaxLineData, TaxReturnData, TaxThresholds, WithholdingData,
126};
127pub use treasury::{
128 CashPositionData, CovenantData, HedgeEffectivenessData, NettingData, TreasuryEvaluation,
129 TreasuryEvaluator, TreasuryThresholds,
130};
131pub use trend_analysis::{analyze_trends, TrendConsistencyCheck, TrendPlausibilityResult};
132
133use serde::{Deserialize, Serialize};
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct CoherenceEvaluation {
138 pub balance: Option<BalanceSheetEvaluation>,
140 pub subledger: Option<SubledgerReconciliationEvaluation>,
142 pub document_chain: Option<DocumentChainEvaluation>,
144 pub intercompany: Option<ICMatchingEvaluation>,
146 pub referential: Option<ReferentialIntegrityEvaluation>,
148 pub multi_table: Option<MultiTableEvaluation>,
150 pub standards: Option<StandardsComplianceEvaluation>,
152 pub network: Option<NetworkEvaluation>,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub financial_reporting: Option<FinancialReportingEvaluation>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub hr_payroll: Option<HrPayrollEvaluation>,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub manufacturing: Option<ManufacturingEvaluation>,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub bank_reconciliation: Option<BankReconciliationEvaluation>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub sourcing: Option<SourcingEvaluation>,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub cross_process: Option<CrossProcessEvaluation>,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub audit: Option<AuditEvaluation>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub tax: Option<TaxEvaluation>,
178 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub treasury: Option<TreasuryEvaluation>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub project_accounting: Option<ProjectAccountingEvaluation>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub esg: Option<EsgEvaluation>,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub sales_quotes: Option<SalesQuoteEvaluation>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub country_packs: Option<CountryPackEvaluation>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub multi_period: Option<MultiPeriodAnalysis>,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub inventory_cogs: Option<inventory_cogs::InventoryCOGSEvaluation>,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub ic_elimination: Option<inventory_cogs::ICEliminationEvaluation>,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub interest_expense_proof: Option<treasury_tax::InterestExpenseProofEvaluation>,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub etr_reconciliation: Option<treasury_tax::ETRReconciliationEvaluation>,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub hedge_effectiveness: Option<treasury_tax::HedgeEffectivenessEvaluation>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
213 pub payroll_hr: Option<treasury_tax::PayrollHRReconciliationEvaluation>,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub cash_flow_reconciliation: Option<financial_package::CashFlowReconciliationEvaluation>,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub equity_rollforward: Option<financial_package::EquityRollforwardEvaluation>,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub segment_reconciliation: Option<financial_package::SegmentReconciliationEvaluation>,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub tb_master_proof: Option<financial_package::TrialBalanceMasterProofEvaluation>,
226 pub passes: bool,
228 pub failures: Vec<String>,
230}
231
232impl CoherenceEvaluation {
233 pub fn new() -> Self {
235 Self {
236 balance: None,
237 subledger: None,
238 document_chain: None,
239 intercompany: None,
240 referential: None,
241 multi_table: None,
242 standards: None,
243 network: None,
244 financial_reporting: None,
245 hr_payroll: None,
246 manufacturing: None,
247 bank_reconciliation: None,
248 sourcing: None,
249 cross_process: None,
250 audit: None,
251 tax: None,
252 treasury: None,
253 project_accounting: None,
254 esg: None,
255 sales_quotes: None,
256 country_packs: None,
257 multi_period: None,
258 inventory_cogs: None,
259 ic_elimination: None,
260 interest_expense_proof: None,
261 etr_reconciliation: None,
262 hedge_effectiveness: None,
263 payroll_hr: None,
264 cash_flow_reconciliation: None,
265 equity_rollforward: None,
266 segment_reconciliation: None,
267 tb_master_proof: None,
268 passes: true,
269 failures: Vec::new(),
270 }
271 }
272
273 pub fn check_thresholds(&mut self, thresholds: &crate::config::EvaluationThresholds) {
275 self.failures.clear();
276
277 if let Some(ref balance) = self.balance {
278 if !balance.equation_balanced {
279 self.failures.push(format!(
280 "Balance sheet equation not balanced (max imbalance: {})",
281 balance.max_imbalance
282 ));
283 }
284 }
285
286 if let Some(ref subledger) = self.subledger {
287 if subledger.completeness_score < thresholds.subledger_reconciliation_rate_min {
288 self.failures.push(format!(
289 "Subledger reconciliation {} < {} (threshold)",
290 subledger.completeness_score, thresholds.subledger_reconciliation_rate_min
291 ));
292 }
293 }
294
295 if let Some(ref doc_chain) = self.document_chain {
296 let min_rate = thresholds.document_chain_completion_min;
297 if doc_chain.p2p_completion_rate < min_rate {
298 self.failures.push(format!(
299 "P2P chain completion {} < {} (threshold)",
300 doc_chain.p2p_completion_rate, min_rate
301 ));
302 }
303 if doc_chain.o2c_completion_rate < min_rate {
304 self.failures.push(format!(
305 "O2C chain completion {} < {} (threshold)",
306 doc_chain.o2c_completion_rate, min_rate
307 ));
308 }
309 }
310
311 if let Some(ref ic) = self.intercompany {
312 if ic.match_rate < thresholds.ic_match_rate_min {
313 self.failures.push(format!(
314 "IC match rate {} < {} (threshold)",
315 ic.match_rate, thresholds.ic_match_rate_min
316 ));
317 }
318 }
319
320 if let Some(ref referential) = self.referential {
321 if referential.overall_integrity_score < thresholds.referential_integrity_min {
322 self.failures.push(format!(
323 "Referential integrity {} < {} (threshold)",
324 referential.overall_integrity_score, thresholds.referential_integrity_min
325 ));
326 }
327 }
328
329 if let Some(ref multi_table) = self.multi_table {
330 if multi_table.overall_consistency_score < thresholds.referential_integrity_min {
331 self.failures.push(format!(
332 "Multi-table consistency {} < {} (threshold)",
333 multi_table.overall_consistency_score, thresholds.referential_integrity_min
334 ));
335 }
336 self.failures.extend(multi_table.issues.clone());
337 }
338
339 if let Some(ref mut standards_eval) = self.standards.clone() {
340 let standards_thresholds = StandardsThresholds::default();
341 standards_eval.check_thresholds(&standards_thresholds);
342 self.failures.extend(standards_eval.failures.clone());
343 }
344
345 if let Some(ref network_eval) = self.network {
346 if !network_eval.passes {
347 self.failures.extend(network_eval.issues.clone());
348 }
349 }
350
351 if let Some(ref eval) = self.financial_reporting {
353 if !eval.passes {
354 self.failures.extend(eval.issues.clone());
355 }
356 }
357 if let Some(ref eval) = self.hr_payroll {
358 if !eval.passes {
359 self.failures.extend(eval.issues.clone());
360 }
361 }
362 if let Some(ref eval) = self.manufacturing {
363 if !eval.passes {
364 self.failures.extend(eval.issues.clone());
365 }
366 }
367 if let Some(ref eval) = self.bank_reconciliation {
368 if !eval.passes {
369 self.failures.extend(eval.issues.clone());
370 }
371 }
372 if let Some(ref eval) = self.sourcing {
373 if !eval.passes {
374 self.failures.extend(eval.issues.clone());
375 }
376 }
377 if let Some(ref eval) = self.cross_process {
378 if !eval.passes {
379 self.failures.extend(eval.issues.clone());
380 }
381 }
382 if let Some(ref eval) = self.audit {
383 if !eval.passes {
384 self.failures.extend(eval.issues.clone());
385 }
386 }
387 if let Some(ref eval) = self.tax {
388 if !eval.passes {
389 self.failures.extend(eval.issues.clone());
390 }
391 }
392 if let Some(ref eval) = self.treasury {
393 if !eval.passes {
394 self.failures.extend(eval.issues.clone());
395 }
396 }
397 if let Some(ref eval) = self.project_accounting {
398 if !eval.passes {
399 self.failures.extend(eval.issues.clone());
400 }
401 }
402 if let Some(ref eval) = self.esg {
403 if !eval.passes {
404 self.failures.extend(eval.issues.clone());
405 }
406 }
407 if let Some(ref eval) = self.sales_quotes {
408 if !eval.passes {
409 self.failures.extend(eval.issues.clone());
410 }
411 }
412 if let Some(ref eval) = self.country_packs {
413 if !eval.passes {
414 self.failures.extend(eval.issues.clone());
415 }
416 }
417 if let Some(ref eval) = self.multi_period {
418 if !eval.passes {
419 self.failures.extend(eval.issues.clone());
420 }
421 }
422 if let Some(ref eval) = self.inventory_cogs {
423 if !eval.passes {
424 self.failures.extend(eval.failures.clone());
425 }
426 }
427 if let Some(ref eval) = self.ic_elimination {
428 if !eval.passes {
429 self.failures.extend(eval.failures.clone());
430 }
431 }
432 if let Some(ref eval) = self.interest_expense_proof {
433 if !eval.passes {
434 self.failures.extend(eval.failures.clone());
435 }
436 }
437 if let Some(ref eval) = self.etr_reconciliation {
438 if !eval.passes {
439 self.failures.extend(eval.failures.clone());
440 }
441 }
442 if let Some(ref eval) = self.hedge_effectiveness {
443 if !eval.passes {
444 self.failures.extend(eval.failures.clone());
445 }
446 }
447 if let Some(ref eval) = self.payroll_hr {
448 if !eval.passes {
449 self.failures.extend(eval.failures.clone());
450 }
451 }
452 if let Some(ref eval) = self.cash_flow_reconciliation {
453 if !eval.passes {
454 self.failures.extend(eval.failures.clone());
455 }
456 }
457 if let Some(ref eval) = self.equity_rollforward {
458 if !eval.passes {
459 self.failures.extend(eval.failures.clone());
460 }
461 }
462 if let Some(ref eval) = self.segment_reconciliation {
463 if !eval.passes {
464 self.failures.extend(eval.failures.clone());
465 }
466 }
467 if let Some(ref eval) = self.tb_master_proof {
468 if !eval.passes {
469 self.failures.extend(eval.failures.clone());
470 }
471 }
472
473 self.passes = self.failures.is_empty();
474 }
475}
476
477impl Default for CoherenceEvaluation {
478 fn default() -> Self {
479 Self::new()
480 }
481}