#![allow(clippy::unwrap_used)]
use datasynth_eval::{
AccountType, BalanceSheetEvaluator, BalanceSnapshot, CoherenceEvaluation,
DocumentChainEvaluator, DocumentReferenceData, EvaluationThresholds, ICMatchingData,
ICMatchingEvaluator, O2CChainData, P2PChainData, UnmatchedICItem,
};
use rust_decimal_macros::dec;
use std::collections::HashMap;
#[test]
fn test_ic_matching_within_tolerance() {
let evaluator = ICMatchingEvaluator::new(dec!(1.00));
let data = ICMatchingData {
total_pairs: 5,
matched_pairs: 4,
total_receivables: dec!(50000),
total_payables: dec!(49999.50),
unmatched_items: vec![UnmatchedICItem {
company: "1000".to_string(),
counterparty: "2000".to_string(),
amount: dec!(0.50), is_receivable: true,
}],
gross_volume: None,
net_settlement: None,
};
let result = evaluator.evaluate(&data).unwrap();
assert_eq!(result.within_tolerance_count, 1);
assert_eq!(result.outside_tolerance_count, 0);
assert_eq!(
result.discrepancy_count, 0,
"Within-tolerance items should not count as discrepancies"
);
}
#[test]
fn test_ic_matching_outside_tolerance() {
let evaluator = ICMatchingEvaluator::new(dec!(0.01));
let data = ICMatchingData {
total_pairs: 3,
matched_pairs: 1,
total_receivables: dec!(30000),
total_payables: dec!(25000),
unmatched_items: vec![UnmatchedICItem {
company: "1000".to_string(),
counterparty: "3000".to_string(),
amount: dec!(5000), is_receivable: true,
}],
gross_volume: None,
net_settlement: None,
};
let result = evaluator.evaluate(&data).unwrap();
assert_eq!(result.outside_tolerance_count, 1);
assert_eq!(result.discrepancy_count, 1);
assert_eq!(result.net_position, dec!(5000));
}
#[test]
fn test_ic_netting_efficiency() {
let evaluator = ICMatchingEvaluator::default();
let data = ICMatchingData {
total_pairs: 10,
matched_pairs: 10,
total_receivables: dec!(100000),
total_payables: dec!(100000),
unmatched_items: vec![],
gross_volume: Some(dec!(200000)),
net_settlement: Some(dec!(20000)), };
let result = evaluator.evaluate(&data).unwrap();
let efficiency = result.netting_efficiency.unwrap();
assert!(
(efficiency - 0.9).abs() < 0.01,
"Netting efficiency should be ~90%, got {efficiency}"
);
}
#[test]
fn test_complete_document_chains() {
let evaluator = DocumentChainEvaluator::new();
let p2p = vec![
P2PChainData {
is_complete: true,
has_po: true,
has_gr: true,
has_invoice: true,
has_payment: true,
three_way_match_passed: true,
},
P2PChainData {
is_complete: true,
has_po: true,
has_gr: true,
has_invoice: true,
has_payment: true,
three_way_match_passed: true,
},
];
let o2c = vec![O2CChainData {
is_complete: true,
has_so: true,
has_delivery: true,
has_invoice: true,
has_receipt: true,
credit_check_passed: true,
}];
let refs = DocumentReferenceData {
total_references: 10,
valid_references: 10,
orphan_count: 0,
};
let result = evaluator.evaluate(&p2p, &o2c, &refs).unwrap();
assert_eq!(result.p2p_completion_rate, 1.0);
assert_eq!(result.o2c_completion_rate, 1.0);
assert_eq!(result.p2p_three_way_match_rate, 1.0);
assert_eq!(result.reference_integrity_score, 1.0);
assert_eq!(result.broken_references, 0);
}
#[test]
fn test_incomplete_document_chains() {
let evaluator = DocumentChainEvaluator::new();
let p2p = vec![
P2PChainData {
is_complete: false,
has_po: true,
has_gr: true,
has_invoice: false, has_payment: false, three_way_match_passed: false,
},
P2PChainData {
is_complete: true,
has_po: true,
has_gr: true,
has_invoice: true,
has_payment: true,
three_way_match_passed: true,
},
];
let o2c: Vec<O2CChainData> = vec![];
let refs = DocumentReferenceData {
total_references: 8,
valid_references: 6,
orphan_count: 1,
};
let result = evaluator.evaluate(&p2p, &o2c, &refs).unwrap();
assert_eq!(result.p2p_completion_rate, 0.5);
assert_eq!(result.p2p_three_way_match_rate, 0.5);
assert_eq!(result.broken_references, 2);
assert!(result.reference_integrity_score < 1.0);
assert_eq!(result.o2c_completion_rate, 1.0);
}
#[test]
fn test_coherence_all_passing() {
let mut eval = CoherenceEvaluation::new();
let balance_evaluator = BalanceSheetEvaluator::default();
let mut balances = HashMap::new();
balances.insert(AccountType::Asset, dec!(1000));
balances.insert(AccountType::Liability, dec!(600));
balances.insert(AccountType::Equity, dec!(400));
let snapshot = BalanceSnapshot {
company_code: "C001".to_string(),
fiscal_year: 2024,
fiscal_period: 1,
balances,
};
eval.balance = Some(balance_evaluator.evaluate(&[snapshot]).unwrap());
let ic_evaluator = ICMatchingEvaluator::default();
let ic_data = ICMatchingData {
total_pairs: 5,
matched_pairs: 5,
total_receivables: dec!(50000),
total_payables: dec!(50000),
unmatched_items: vec![],
gross_volume: None,
net_settlement: None,
};
eval.intercompany = Some(ic_evaluator.evaluate(&ic_data).unwrap());
let doc_evaluator = DocumentChainEvaluator::new();
let p2p = vec![P2PChainData {
is_complete: true,
has_po: true,
has_gr: true,
has_invoice: true,
has_payment: true,
three_way_match_passed: true,
}];
let o2c = vec![O2CChainData {
is_complete: true,
has_so: true,
has_delivery: true,
has_invoice: true,
has_receipt: true,
credit_check_passed: true,
}];
let refs = DocumentReferenceData {
total_references: 5,
valid_references: 5,
orphan_count: 0,
};
eval.document_chain = Some(doc_evaluator.evaluate(&p2p, &o2c, &refs).unwrap());
let thresholds = EvaluationThresholds::default();
eval.check_thresholds(&thresholds);
assert!(
eval.passes,
"All evaluations passing should yield overall pass"
);
assert!(
eval.failures.is_empty(),
"No failures expected: {:?}",
eval.failures
);
}
#[test]
fn test_coherence_balance_failure_propagates() {
let mut eval = CoherenceEvaluation::new();
let balance_evaluator = BalanceSheetEvaluator::default();
let mut balances = HashMap::new();
balances.insert(AccountType::Asset, dec!(1000));
balances.insert(AccountType::Liability, dec!(500));
balances.insert(AccountType::Equity, dec!(200)); let snapshot = BalanceSnapshot {
company_code: "C001".to_string(),
fiscal_year: 2024,
fiscal_period: 1,
balances,
};
eval.balance = Some(balance_evaluator.evaluate(&[snapshot]).unwrap());
let thresholds = EvaluationThresholds::default();
eval.check_thresholds(&thresholds);
assert!(!eval.passes, "Imbalanced balance sheet should fail");
assert!(
eval.failures.iter().any(|f| f.contains("Balance sheet")),
"Should have balance sheet failure message"
);
}
#[test]
fn test_coherence_ic_failure_propagates() {
let mut eval = CoherenceEvaluation::new();
let ic_evaluator = ICMatchingEvaluator::default();
let ic_data = ICMatchingData {
total_pairs: 10,
matched_pairs: 2, total_receivables: dec!(100000),
total_payables: dec!(80000),
unmatched_items: vec![],
gross_volume: None,
net_settlement: None,
};
eval.intercompany = Some(ic_evaluator.evaluate(&ic_data).unwrap());
let thresholds = EvaluationThresholds::default();
eval.check_thresholds(&thresholds);
assert!(
!eval.passes,
"Low IC match rate should fail (20% < threshold)"
);
assert!(
eval.failures.iter().any(|f| f.contains("IC match rate")),
"Should have IC match rate failure"
);
}