datasynth_eval/coherence/
country_packs.rs1use crate::error::EvalResult;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct CountryPackThresholds {
13 pub min_rate_validity: f64,
15 pub min_format_validity: f64,
17}
18
19impl Default for CountryPackThresholds {
20 fn default() -> Self {
21 Self {
22 min_rate_validity: 0.99,
23 min_format_validity: 0.99,
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct TaxRateData {
31 pub rate_name: String,
33 pub rate: f64,
35}
36
37#[derive(Debug, Clone)]
39pub struct ApprovalLevelData {
40 pub level: u32,
42 pub threshold: f64,
44}
45
46#[derive(Debug, Clone)]
48pub struct HolidayData {
49 pub name: String,
51 pub activity_multiplier: f64,
53}
54
55#[derive(Debug, Clone)]
57pub struct CountryPackData {
58 pub country_code: String,
60 pub tax_rates: Vec<TaxRateData>,
62 pub approval_levels: Vec<ApprovalLevelData>,
64 pub holidays: Vec<HolidayData>,
66 pub iban_length: Option<u32>,
68 pub fiscal_year_start_month: Option<u32>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct CountryPackEvaluation {
75 pub tax_rate_validity: f64,
77 pub approval_order_validity: f64,
79 pub holiday_multiplier_validity: f64,
81 pub iban_length_validity: f64,
83 pub fiscal_year_validity: f64,
85 pub total_packs: usize,
87 pub total_tax_rates: usize,
89 pub total_holidays: usize,
91 pub passes: bool,
93 pub issues: Vec<String>,
95}
96
97pub struct CountryPackEvaluator {
99 thresholds: CountryPackThresholds,
100}
101
102impl CountryPackEvaluator {
103 pub fn new() -> Self {
105 Self {
106 thresholds: CountryPackThresholds::default(),
107 }
108 }
109
110 pub fn with_thresholds(thresholds: CountryPackThresholds) -> Self {
112 Self { thresholds }
113 }
114
115 pub fn evaluate(&self, packs: &[CountryPackData]) -> EvalResult<CountryPackEvaluation> {
117 let mut issues = Vec::new();
118
119 let all_rates: Vec<&TaxRateData> = packs.iter().flat_map(|p| p.tax_rates.iter()).collect();
121 let rate_ok = all_rates
122 .iter()
123 .filter(|r| (0.0..=1.0).contains(&r.rate))
124 .count();
125 let tax_rate_validity = if all_rates.is_empty() {
126 1.0
127 } else {
128 rate_ok as f64 / all_rates.len() as f64
129 };
130
131 let order_ok = packs
133 .iter()
134 .filter(|p| {
135 if p.approval_levels.len() <= 1 {
136 return true;
137 }
138 let mut sorted = p.approval_levels.clone();
139 sorted.sort_by_key(|a| a.level);
140 sorted.windows(2).all(|w| w[0].threshold <= w[1].threshold)
141 })
142 .count();
143 let approval_order_validity = if packs.is_empty() {
144 1.0
145 } else {
146 order_ok as f64 / packs.len() as f64
147 };
148
149 let all_holidays: Vec<&HolidayData> =
151 packs.iter().flat_map(|p| p.holidays.iter()).collect();
152 let holiday_ok = all_holidays
153 .iter()
154 .filter(|h| (0.0..=1.0).contains(&h.activity_multiplier))
155 .count();
156 let holiday_multiplier_validity = if all_holidays.is_empty() {
157 1.0
158 } else {
159 holiday_ok as f64 / all_holidays.len() as f64
160 };
161
162 let ibans: Vec<u32> = packs.iter().filter_map(|p| p.iban_length).collect();
164 let iban_ok = ibans.iter().filter(|&&l| (15..=34).contains(&l)).count();
165 let iban_length_validity = if ibans.is_empty() {
166 1.0
167 } else {
168 iban_ok as f64 / ibans.len() as f64
169 };
170
171 let fy_months: Vec<u32> = packs
173 .iter()
174 .filter_map(|p| p.fiscal_year_start_month)
175 .collect();
176 let fy_ok = fy_months.iter().filter(|&&m| (1..=12).contains(&m)).count();
177 let fiscal_year_validity = if fy_months.is_empty() {
178 1.0
179 } else {
180 fy_ok as f64 / fy_months.len() as f64
181 };
182
183 if tax_rate_validity < self.thresholds.min_rate_validity {
185 issues.push(format!(
186 "Tax rate validity {:.4} < {:.4}",
187 tax_rate_validity, self.thresholds.min_rate_validity
188 ));
189 }
190 if approval_order_validity < self.thresholds.min_format_validity {
191 issues.push(format!(
192 "Approval level ordering validity {:.4} < {:.4}",
193 approval_order_validity, self.thresholds.min_format_validity
194 ));
195 }
196 if holiday_multiplier_validity < self.thresholds.min_rate_validity {
197 issues.push(format!(
198 "Holiday multiplier validity {:.4} < {:.4}",
199 holiday_multiplier_validity, self.thresholds.min_rate_validity
200 ));
201 }
202 if iban_length_validity < self.thresholds.min_format_validity {
203 issues.push(format!(
204 "IBAN length validity {:.4} < {:.4}",
205 iban_length_validity, self.thresholds.min_format_validity
206 ));
207 }
208 if fiscal_year_validity < self.thresholds.min_format_validity {
209 issues.push(format!(
210 "Fiscal year month validity {:.4} < {:.4}",
211 fiscal_year_validity, self.thresholds.min_format_validity
212 ));
213 }
214
215 let passes = issues.is_empty();
216
217 Ok(CountryPackEvaluation {
218 tax_rate_validity,
219 approval_order_validity,
220 holiday_multiplier_validity,
221 iban_length_validity,
222 fiscal_year_validity,
223 total_packs: packs.len(),
224 total_tax_rates: all_rates.len(),
225 total_holidays: all_holidays.len(),
226 passes,
227 issues,
228 })
229 }
230}
231
232impl Default for CountryPackEvaluator {
233 fn default() -> Self {
234 Self::new()
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_valid_country_pack() {
244 let evaluator = CountryPackEvaluator::new();
245 let packs = vec![CountryPackData {
246 country_code: "DE".to_string(),
247 tax_rates: vec![
248 TaxRateData {
249 rate_name: "standard_vat".to_string(),
250 rate: 0.19,
251 },
252 TaxRateData {
253 rate_name: "reduced_vat".to_string(),
254 rate: 0.07,
255 },
256 ],
257 approval_levels: vec![
258 ApprovalLevelData {
259 level: 1,
260 threshold: 1000.0,
261 },
262 ApprovalLevelData {
263 level: 2,
264 threshold: 5000.0,
265 },
266 ApprovalLevelData {
267 level: 3,
268 threshold: 25000.0,
269 },
270 ],
271 holidays: vec![
272 HolidayData {
273 name: "New Year".to_string(),
274 activity_multiplier: 0.0,
275 },
276 HolidayData {
277 name: "Christmas Eve".to_string(),
278 activity_multiplier: 0.3,
279 },
280 ],
281 iban_length: Some(22),
282 fiscal_year_start_month: Some(1),
283 }];
284
285 let result = evaluator.evaluate(&packs).unwrap();
286 assert!(result.passes);
287 assert_eq!(result.total_packs, 1);
288 assert_eq!(result.total_tax_rates, 2);
289 assert_eq!(result.total_holidays, 2);
290 }
291
292 #[test]
293 fn test_invalid_tax_rate() {
294 let evaluator = CountryPackEvaluator::new();
295 let packs = vec![CountryPackData {
296 country_code: "XX".to_string(),
297 tax_rates: vec![TaxRateData {
298 rate_name: "bad_rate".to_string(),
299 rate: 1.5, }],
301 approval_levels: vec![],
302 holidays: vec![],
303 iban_length: None,
304 fiscal_year_start_month: None,
305 }];
306
307 let result = evaluator.evaluate(&packs).unwrap();
308 assert!(!result.passes);
309 assert!(result.issues.iter().any(|i| i.contains("Tax rate")));
310 }
311
312 #[test]
313 fn test_unordered_approval_levels() {
314 let evaluator = CountryPackEvaluator::new();
315 let packs = vec![CountryPackData {
316 country_code: "XX".to_string(),
317 tax_rates: vec![],
318 approval_levels: vec![
319 ApprovalLevelData {
320 level: 1,
321 threshold: 5000.0,
322 },
323 ApprovalLevelData {
324 level: 2,
325 threshold: 1000.0, },
327 ],
328 holidays: vec![],
329 iban_length: None,
330 fiscal_year_start_month: None,
331 }];
332
333 let result = evaluator.evaluate(&packs).unwrap();
334 assert!(!result.passes);
335 assert!(result.issues.iter().any(|i| i.contains("Approval level")));
336 }
337
338 #[test]
339 fn test_invalid_iban_length() {
340 let evaluator = CountryPackEvaluator::new();
341 let packs = vec![CountryPackData {
342 country_code: "XX".to_string(),
343 tax_rates: vec![],
344 approval_levels: vec![],
345 holidays: vec![],
346 iban_length: Some(10), fiscal_year_start_month: None,
348 }];
349
350 let result = evaluator.evaluate(&packs).unwrap();
351 assert!(!result.passes);
352 assert!(result.issues.iter().any(|i| i.contains("IBAN length")));
353 }
354
355 #[test]
356 fn test_invalid_fiscal_year_month() {
357 let evaluator = CountryPackEvaluator::new();
358 let packs = vec![CountryPackData {
359 country_code: "XX".to_string(),
360 tax_rates: vec![],
361 approval_levels: vec![],
362 holidays: vec![],
363 iban_length: None,
364 fiscal_year_start_month: Some(13), }];
366
367 let result = evaluator.evaluate(&packs).unwrap();
368 assert!(!result.passes);
369 assert!(result.issues.iter().any(|i| i.contains("Fiscal year")));
370 }
371
372 #[test]
373 fn test_invalid_holiday_multiplier() {
374 let evaluator = CountryPackEvaluator::new();
375 let packs = vec![CountryPackData {
376 country_code: "XX".to_string(),
377 tax_rates: vec![],
378 approval_levels: vec![],
379 holidays: vec![HolidayData {
380 name: "Bad Holiday".to_string(),
381 activity_multiplier: 1.5, }],
383 iban_length: None,
384 fiscal_year_start_month: None,
385 }];
386
387 let result = evaluator.evaluate(&packs).unwrap();
388 assert!(!result.passes);
389 assert!(result
390 .issues
391 .iter()
392 .any(|i| i.contains("Holiday multiplier")));
393 }
394
395 #[test]
396 fn test_empty_data() {
397 let evaluator = CountryPackEvaluator::new();
398 let result = evaluator.evaluate(&[]).unwrap();
399 assert!(result.passes);
400 }
401}