1mod 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#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct CoherenceEvaluation {
112 pub balance: Option<BalanceSheetEvaluation>,
114 pub subledger: Option<SubledgerReconciliationEvaluation>,
116 pub document_chain: Option<DocumentChainEvaluation>,
118 pub intercompany: Option<ICMatchingEvaluation>,
120 pub referential: Option<ReferentialIntegrityEvaluation>,
122 pub multi_table: Option<MultiTableEvaluation>,
124 pub standards: Option<StandardsComplianceEvaluation>,
126 pub network: Option<NetworkEvaluation>,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub financial_reporting: Option<FinancialReportingEvaluation>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub hr_payroll: Option<HrPayrollEvaluation>,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub manufacturing: Option<ManufacturingEvaluation>,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub bank_reconciliation: Option<BankReconciliationEvaluation>,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub sourcing: Option<SourcingEvaluation>,
143 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub cross_process: Option<CrossProcessEvaluation>,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub audit: Option<AuditEvaluation>,
149 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub tax: Option<TaxEvaluation>,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub treasury: Option<TreasuryEvaluation>,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub project_accounting: Option<ProjectAccountingEvaluation>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub esg: Option<EsgEvaluation>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub sales_quotes: Option<SalesQuoteEvaluation>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub country_packs: Option<CountryPackEvaluation>,
167 pub passes: bool,
169 pub failures: Vec<String>,
171}
172
173impl CoherenceEvaluation {
174 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 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 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}