datasynth_eval/coherence/
subledger.rs1use crate::error::EvalResult;
7use rust_decimal::Decimal;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct SubledgerReconciliationEvaluation {
13 pub ar_reconciled: bool,
15 pub ar_gl_balance: Decimal,
17 pub ar_subledger_balance: Decimal,
19 pub ar_difference: Decimal,
21 pub ap_reconciled: bool,
23 pub ap_gl_balance: Decimal,
25 pub ap_subledger_balance: Decimal,
27 pub ap_difference: Decimal,
29 pub fa_asset_reconciled: bool,
31 pub fa_asset_gl_balance: Decimal,
33 pub fa_asset_subledger_balance: Decimal,
35 pub fa_asset_difference: Decimal,
37 pub fa_accum_depr_reconciled: bool,
39 pub fa_accum_depr_gl_balance: Decimal,
41 pub fa_accum_depr_subledger_balance: Decimal,
43 pub fa_accum_depr_difference: Decimal,
45 pub inventory_reconciled: bool,
47 pub inventory_gl_balance: Decimal,
49 pub inventory_subledger_balance: Decimal,
51 pub inventory_difference: Decimal,
53 pub completeness_score: f64,
55 pub subledgers_reconciled: usize,
57 pub subledgers_total: usize,
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct SubledgerData {
64 pub ar_gl_balance: Option<Decimal>,
66 pub ar_subledger_balance: Option<Decimal>,
68 pub ap_gl_balance: Option<Decimal>,
70 pub ap_subledger_balance: Option<Decimal>,
72 pub fa_asset_gl_balance: Option<Decimal>,
74 pub fa_asset_subledger_balance: Option<Decimal>,
76 pub fa_accum_depr_gl_balance: Option<Decimal>,
78 pub fa_accum_depr_subledger_balance: Option<Decimal>,
80 pub inventory_gl_balance: Option<Decimal>,
82 pub inventory_subledger_balance: Option<Decimal>,
84}
85
86pub struct SubledgerEvaluator {
88 tolerance: Decimal,
90}
91
92impl SubledgerEvaluator {
93 pub fn new(tolerance: Decimal) -> Self {
95 Self { tolerance }
96 }
97
98 pub fn evaluate(&self, data: &SubledgerData) -> EvalResult<SubledgerReconciliationEvaluation> {
100 let mut subledgers_reconciled = 0;
101 let mut subledgers_total = 0;
102
103 let (ar_reconciled, ar_gl, ar_sub, ar_diff) = self.check_reconciliation(
105 data.ar_gl_balance,
106 data.ar_subledger_balance,
107 &mut subledgers_reconciled,
108 &mut subledgers_total,
109 );
110
111 let (ap_reconciled, ap_gl, ap_sub, ap_diff) = self.check_reconciliation(
113 data.ap_gl_balance,
114 data.ap_subledger_balance,
115 &mut subledgers_reconciled,
116 &mut subledgers_total,
117 );
118
119 let (fa_asset_reconciled, fa_asset_gl, fa_asset_sub, fa_asset_diff) = self
121 .check_reconciliation(
122 data.fa_asset_gl_balance,
123 data.fa_asset_subledger_balance,
124 &mut subledgers_reconciled,
125 &mut subledgers_total,
126 );
127
128 let (fa_accum_reconciled, fa_accum_gl, fa_accum_sub, fa_accum_diff) = self
130 .check_reconciliation(
131 data.fa_accum_depr_gl_balance,
132 data.fa_accum_depr_subledger_balance,
133 &mut subledgers_reconciled,
134 &mut subledgers_total,
135 );
136
137 let (inv_reconciled, inv_gl, inv_sub, inv_diff) = self.check_reconciliation(
139 data.inventory_gl_balance,
140 data.inventory_subledger_balance,
141 &mut subledgers_reconciled,
142 &mut subledgers_total,
143 );
144
145 let completeness_score = if subledgers_total > 0 {
146 subledgers_reconciled as f64 / subledgers_total as f64
147 } else {
148 1.0 };
150
151 Ok(SubledgerReconciliationEvaluation {
152 ar_reconciled,
153 ar_gl_balance: ar_gl,
154 ar_subledger_balance: ar_sub,
155 ar_difference: ar_diff,
156 ap_reconciled,
157 ap_gl_balance: ap_gl,
158 ap_subledger_balance: ap_sub,
159 ap_difference: ap_diff,
160 fa_asset_reconciled,
161 fa_asset_gl_balance: fa_asset_gl,
162 fa_asset_subledger_balance: fa_asset_sub,
163 fa_asset_difference: fa_asset_diff,
164 fa_accum_depr_reconciled: fa_accum_reconciled,
165 fa_accum_depr_gl_balance: fa_accum_gl,
166 fa_accum_depr_subledger_balance: fa_accum_sub,
167 fa_accum_depr_difference: fa_accum_diff,
168 inventory_reconciled: inv_reconciled,
169 inventory_gl_balance: inv_gl,
170 inventory_subledger_balance: inv_sub,
171 inventory_difference: inv_diff,
172 completeness_score,
173 subledgers_reconciled,
174 subledgers_total,
175 })
176 }
177
178 fn check_reconciliation(
180 &self,
181 gl_balance: Option<Decimal>,
182 subledger_balance: Option<Decimal>,
183 reconciled_count: &mut usize,
184 total_count: &mut usize,
185 ) -> (bool, Decimal, Decimal, Decimal) {
186 match (gl_balance, subledger_balance) {
187 (Some(gl), Some(sub)) => {
188 *total_count += 1;
189 let diff = gl - sub;
190 let is_reconciled = diff.abs() <= self.tolerance;
191 if is_reconciled {
192 *reconciled_count += 1;
193 }
194 (is_reconciled, gl, sub, diff)
195 }
196 _ => (true, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO),
197 }
198 }
199}
200
201impl Default for SubledgerEvaluator {
202 fn default() -> Self {
203 Self::new(Decimal::new(1, 2)) }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_reconciled_subledgers() {
213 let data = SubledgerData {
214 ar_gl_balance: Some(Decimal::new(100000, 2)),
215 ar_subledger_balance: Some(Decimal::new(100000, 2)),
216 ap_gl_balance: Some(Decimal::new(50000, 2)),
217 ap_subledger_balance: Some(Decimal::new(50000, 2)),
218 ..Default::default()
219 };
220
221 let evaluator = SubledgerEvaluator::default();
222 let result = evaluator.evaluate(&data).unwrap();
223
224 assert!(result.ar_reconciled);
225 assert!(result.ap_reconciled);
226 assert_eq!(result.completeness_score, 1.0);
227 }
228
229 #[test]
230 fn test_unreconciled_subledger() {
231 let data = SubledgerData {
232 ar_gl_balance: Some(Decimal::new(100000, 2)),
233 ar_subledger_balance: Some(Decimal::new(99000, 2)), ..Default::default()
235 };
236
237 let evaluator = SubledgerEvaluator::default();
238 let result = evaluator.evaluate(&data).unwrap();
239
240 assert!(!result.ar_reconciled);
241 assert_eq!(result.ar_difference, Decimal::new(1000, 2));
242 }
243
244 #[test]
245 fn test_no_subledger_data() {
246 let data = SubledgerData::default();
247 let evaluator = SubledgerEvaluator::default();
248 let result = evaluator.evaluate(&data).unwrap();
249
250 assert_eq!(result.completeness_score, 1.0);
252 }
253}