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