Skip to main content

datasynth_eval/coherence/
document_chain.rs

1//! Document chain completeness evaluation.
2//!
3//! Validates that P2P (Procure-to-Pay) and O2C (Order-to-Cash) document
4//! chains are complete and maintain referential integrity.
5
6use crate::error::EvalResult;
7use serde::{Deserialize, Serialize};
8
9/// Results of document chain evaluation.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct DocumentChainEvaluation {
12    /// Total P2P chains.
13    pub p2p_total: usize,
14    /// Complete P2P chains.
15    pub p2p_complete: usize,
16    /// P2P completion rate.
17    pub p2p_completion_rate: f64,
18    /// P2P chains with PO created.
19    pub p2p_po_created: usize,
20    /// P2P chains with GR created.
21    pub p2p_gr_created: usize,
22    /// P2P chains with invoice created.
23    pub p2p_invoice_created: usize,
24    /// P2P chains with payment created.
25    pub p2p_payment_created: usize,
26    /// P2P chains where three-way match passed.
27    pub p2p_three_way_match_passed: usize,
28    /// P2P three-way match rate.
29    pub p2p_three_way_match_rate: f64,
30    /// Total O2C chains.
31    pub o2c_total: usize,
32    /// Complete O2C chains.
33    pub o2c_complete: usize,
34    /// O2C completion rate.
35    pub o2c_completion_rate: f64,
36    /// O2C chains with SO created.
37    pub o2c_so_created: usize,
38    /// O2C chains with delivery created.
39    pub o2c_delivery_created: usize,
40    /// O2C chains with invoice created.
41    pub o2c_invoice_created: usize,
42    /// O2C chains with receipt created.
43    pub o2c_receipt_created: usize,
44    /// O2C chains where credit check passed.
45    pub o2c_credit_check_passed: usize,
46    /// O2C credit check pass rate.
47    pub o2c_credit_check_rate: f64,
48    /// Number of orphan documents (no chain reference).
49    pub orphan_documents: usize,
50    /// Number of broken references.
51    pub broken_references: usize,
52    /// Reference integrity score (0.0-1.0).
53    pub reference_integrity_score: f64,
54}
55
56/// Input for P2P chain evaluation.
57#[derive(Debug, Clone)]
58pub struct P2PChainData {
59    /// Whether chain is complete (all documents present).
60    pub is_complete: bool,
61    /// Whether PO was created.
62    pub has_po: bool,
63    /// Whether GR was created.
64    pub has_gr: bool,
65    /// Whether invoice was created.
66    pub has_invoice: bool,
67    /// Whether payment was created.
68    pub has_payment: bool,
69    /// Whether three-way match passed.
70    pub three_way_match_passed: bool,
71}
72
73/// Input for O2C chain evaluation.
74#[derive(Debug, Clone)]
75pub struct O2CChainData {
76    /// Whether chain is complete.
77    pub is_complete: bool,
78    /// Whether SO was created.
79    pub has_so: bool,
80    /// Whether delivery was created.
81    pub has_delivery: bool,
82    /// Whether invoice was created.
83    pub has_invoice: bool,
84    /// Whether receipt was created.
85    pub has_receipt: bool,
86    /// Whether credit check passed.
87    pub credit_check_passed: bool,
88}
89
90/// Document reference data.
91#[derive(Debug, Clone)]
92pub struct DocumentReferenceData {
93    /// Total document references.
94    pub total_references: usize,
95    /// Valid references (target document exists).
96    pub valid_references: usize,
97    /// Orphan documents (no incoming references).
98    pub orphan_count: usize,
99}
100
101/// Evaluator for document chain completeness.
102pub struct DocumentChainEvaluator;
103
104impl DocumentChainEvaluator {
105    /// Create a new evaluator.
106    pub fn new() -> Self {
107        Self
108    }
109
110    /// Evaluate document chains.
111    pub fn evaluate(
112        &self,
113        p2p_chains: &[P2PChainData],
114        o2c_chains: &[O2CChainData],
115        references: &DocumentReferenceData,
116    ) -> EvalResult<DocumentChainEvaluation> {
117        // P2P analysis
118        let p2p_total = p2p_chains.len();
119        let p2p_complete = p2p_chains.iter().filter(|c| c.is_complete).count();
120        let p2p_completion_rate = if p2p_total > 0 {
121            p2p_complete as f64 / p2p_total as f64
122        } else {
123            1.0
124        };
125
126        let p2p_po_created = p2p_chains.iter().filter(|c| c.has_po).count();
127        let p2p_gr_created = p2p_chains.iter().filter(|c| c.has_gr).count();
128        let p2p_invoice_created = p2p_chains.iter().filter(|c| c.has_invoice).count();
129        let p2p_payment_created = p2p_chains.iter().filter(|c| c.has_payment).count();
130        let p2p_three_way_match_passed = p2p_chains
131            .iter()
132            .filter(|c| c.three_way_match_passed)
133            .count();
134        let p2p_three_way_match_rate = if p2p_total > 0 {
135            p2p_three_way_match_passed as f64 / p2p_total as f64
136        } else {
137            1.0
138        };
139
140        // O2C analysis
141        let o2c_total = o2c_chains.len();
142        let o2c_complete = o2c_chains.iter().filter(|c| c.is_complete).count();
143        let o2c_completion_rate = if o2c_total > 0 {
144            o2c_complete as f64 / o2c_total as f64
145        } else {
146            1.0
147        };
148
149        let o2c_so_created = o2c_chains.iter().filter(|c| c.has_so).count();
150        let o2c_delivery_created = o2c_chains.iter().filter(|c| c.has_delivery).count();
151        let o2c_invoice_created = o2c_chains.iter().filter(|c| c.has_invoice).count();
152        let o2c_receipt_created = o2c_chains.iter().filter(|c| c.has_receipt).count();
153        let o2c_credit_check_passed = o2c_chains.iter().filter(|c| c.credit_check_passed).count();
154        let o2c_credit_check_rate = if o2c_total > 0 {
155            o2c_credit_check_passed as f64 / o2c_total as f64
156        } else {
157            1.0
158        };
159
160        // Reference integrity
161        let broken_references = references.total_references - references.valid_references;
162        let reference_integrity_score = if references.total_references > 0 {
163            references.valid_references as f64 / references.total_references as f64
164        } else {
165            1.0
166        };
167
168        Ok(DocumentChainEvaluation {
169            p2p_total,
170            p2p_complete,
171            p2p_completion_rate,
172            p2p_po_created,
173            p2p_gr_created,
174            p2p_invoice_created,
175            p2p_payment_created,
176            p2p_three_way_match_passed,
177            p2p_three_way_match_rate,
178            o2c_total,
179            o2c_complete,
180            o2c_completion_rate,
181            o2c_so_created,
182            o2c_delivery_created,
183            o2c_invoice_created,
184            o2c_receipt_created,
185            o2c_credit_check_passed,
186            o2c_credit_check_rate,
187            orphan_documents: references.orphan_count,
188            broken_references,
189            reference_integrity_score,
190        })
191    }
192}
193
194impl Default for DocumentChainEvaluator {
195    fn default() -> Self {
196        Self::new()
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_complete_p2p_chains() {
206        let p2p_chains = vec![
207            P2PChainData {
208                is_complete: true,
209                has_po: true,
210                has_gr: true,
211                has_invoice: true,
212                has_payment: true,
213                three_way_match_passed: true,
214            },
215            P2PChainData {
216                is_complete: true,
217                has_po: true,
218                has_gr: true,
219                has_invoice: true,
220                has_payment: true,
221                three_way_match_passed: true,
222            },
223        ];
224
225        let o2c_chains = vec![];
226        let references = DocumentReferenceData {
227            total_references: 10,
228            valid_references: 10,
229            orphan_count: 0,
230        };
231
232        let evaluator = DocumentChainEvaluator::new();
233        let result = evaluator
234            .evaluate(&p2p_chains, &o2c_chains, &references)
235            .unwrap();
236
237        assert_eq!(result.p2p_total, 2);
238        assert_eq!(result.p2p_complete, 2);
239        assert_eq!(result.p2p_completion_rate, 1.0);
240        assert_eq!(result.p2p_three_way_match_rate, 1.0);
241    }
242
243    #[test]
244    fn test_incomplete_chains() {
245        let p2p_chains = vec![P2PChainData {
246            is_complete: false,
247            has_po: true,
248            has_gr: true,
249            has_invoice: false,
250            has_payment: false,
251            three_way_match_passed: false,
252        }];
253
254        let o2c_chains = vec![];
255        let references = DocumentReferenceData {
256            total_references: 5,
257            valid_references: 4,
258            orphan_count: 1,
259        };
260
261        let evaluator = DocumentChainEvaluator::new();
262        let result = evaluator
263            .evaluate(&p2p_chains, &o2c_chains, &references)
264            .unwrap();
265
266        assert_eq!(result.p2p_completion_rate, 0.0);
267        assert_eq!(result.broken_references, 1);
268        assert_eq!(result.reference_integrity_score, 0.8);
269    }
270}