Skip to main content

datasynth_eval/coherence/
manufacturing.rs

1//! Manufacturing evaluator.
2//!
3//! Validates manufacturing data coherence including yield rate consistency,
4//! cost variance, operation sequencing, quality inspection accuracy,
5//! and cycle count variance calculations.
6
7use crate::error::EvalResult;
8use serde::{Deserialize, Serialize};
9
10/// Thresholds for manufacturing evaluation.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ManufacturingThresholds {
13    /// Minimum yield rate consistency (fraction of orders with valid yield).
14    pub min_yield_consistency: f64,
15    /// Minimum fraction of orders with valid operation sequences.
16    pub min_sequence_valid: f64,
17    /// Minimum defect rate calculation accuracy.
18    pub min_defect_rate_accuracy: f64,
19    /// Minimum variance calculation accuracy for cycle counts.
20    pub min_variance_accuracy: f64,
21}
22
23impl Default for ManufacturingThresholds {
24    fn default() -> Self {
25        Self {
26            min_yield_consistency: 0.95,
27            min_sequence_valid: 0.99,
28            min_defect_rate_accuracy: 0.99,
29            min_variance_accuracy: 0.99,
30        }
31    }
32}
33
34/// Production order data for validation.
35#[derive(Debug, Clone)]
36pub struct ProductionOrderData {
37    /// Order identifier.
38    pub order_id: String,
39    /// Actual output quantity.
40    pub actual_quantity: f64,
41    /// Scrap quantity.
42    pub scrap_quantity: f64,
43    /// Reported yield rate.
44    pub reported_yield: f64,
45    /// Planned cost.
46    pub planned_cost: f64,
47    /// Actual cost.
48    pub actual_cost: f64,
49}
50
51/// Routing operation data for sequence validation.
52#[derive(Debug, Clone)]
53pub struct RoutingOperationData {
54    /// Parent order identifier.
55    pub order_id: String,
56    /// Operation sequence number.
57    pub sequence_number: u32,
58    /// Operation start timestamp (epoch seconds).
59    pub start_timestamp: i64,
60}
61
62/// Quality inspection data.
63#[derive(Debug, Clone)]
64pub struct QualityInspectionData {
65    /// Inspection lot identifier.
66    pub lot_id: String,
67    /// Sample size.
68    pub sample_size: u32,
69    /// Defect count.
70    pub defect_count: u32,
71    /// Reported defect rate.
72    pub reported_defect_rate: f64,
73    /// Characteristics within specification limits.
74    pub characteristics_within_limits: u32,
75    /// Total characteristics inspected.
76    pub total_characteristics: u32,
77}
78
79/// Cycle count data.
80#[derive(Debug, Clone)]
81pub struct CycleCountData {
82    /// Count record identifier.
83    pub record_id: String,
84    /// Book quantity.
85    pub book_quantity: f64,
86    /// Counted quantity.
87    pub counted_quantity: f64,
88    /// Reported variance.
89    pub reported_variance: f64,
90}
91
92/// Results of manufacturing evaluation.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ManufacturingEvaluation {
95    /// Yield rate consistency: fraction of orders with correct yield calculation.
96    pub yield_rate_consistency: f64,
97    /// Average cost variance ratio |actual - planned| / planned.
98    pub avg_cost_variance_ratio: f64,
99    /// Operation sequence validity: fraction with ascending sequence numbers and dates.
100    pub operation_sequence_valid: f64,
101    /// Defect rate accuracy: fraction of inspections with correct defect rate.
102    pub defect_rate_accuracy: f64,
103    /// Characteristics compliance: fraction of characteristics within limits.
104    pub characteristics_compliance: f64,
105    /// Variance calculation accuracy: fraction of cycle counts with correct variance.
106    pub variance_calculation_accuracy: f64,
107    /// Total production orders evaluated.
108    pub total_orders: usize,
109    /// Total inspections evaluated.
110    pub total_inspections: usize,
111    /// Total cycle counts evaluated.
112    pub total_cycle_counts: usize,
113    /// Overall pass/fail.
114    pub passes: bool,
115    /// Issues found.
116    pub issues: Vec<String>,
117}
118
119/// Evaluator for manufacturing coherence.
120pub struct ManufacturingEvaluator {
121    thresholds: ManufacturingThresholds,
122}
123
124impl ManufacturingEvaluator {
125    /// Create a new evaluator with default thresholds.
126    pub fn new() -> Self {
127        Self {
128            thresholds: ManufacturingThresholds::default(),
129        }
130    }
131
132    /// Create with custom thresholds.
133    pub fn with_thresholds(thresholds: ManufacturingThresholds) -> Self {
134        Self { thresholds }
135    }
136
137    /// Evaluate manufacturing data.
138    pub fn evaluate(
139        &self,
140        orders: &[ProductionOrderData],
141        operations: &[RoutingOperationData],
142        inspections: &[QualityInspectionData],
143        cycle_counts: &[CycleCountData],
144    ) -> EvalResult<ManufacturingEvaluation> {
145        let mut issues = Vec::new();
146
147        // 1. Yield rate consistency: yield = actual / (actual + scrap)
148        let yield_ok = orders
149            .iter()
150            .filter(|o| {
151                let total = o.actual_quantity + o.scrap_quantity;
152                if total <= 0.0 {
153                    return true; // Skip zero-output orders
154                }
155                let expected_yield = o.actual_quantity / total;
156                (o.reported_yield - expected_yield).abs() <= 0.001
157            })
158            .count();
159        let yield_rate_consistency = if orders.is_empty() {
160            1.0
161        } else {
162            yield_ok as f64 / orders.len() as f64
163        };
164
165        // 2. Cost variance
166        let cost_variances: Vec<f64> = orders
167            .iter()
168            .filter(|o| o.planned_cost > 0.0)
169            .map(|o| (o.actual_cost - o.planned_cost).abs() / o.planned_cost)
170            .collect();
171        let avg_cost_variance_ratio = if cost_variances.is_empty() {
172            0.0
173        } else {
174            cost_variances.iter().sum::<f64>() / cost_variances.len() as f64
175        };
176
177        // 3. Operation sequencing: group by order, check ascending
178        let mut order_ops: std::collections::HashMap<&str, Vec<&RoutingOperationData>> =
179            std::collections::HashMap::new();
180        for op in operations {
181            order_ops.entry(op.order_id.as_str()).or_default().push(op);
182        }
183        let total_order_groups = order_ops.len();
184        let seq_valid = order_ops
185            .values()
186            .filter(|ops| {
187                let mut sorted = ops.to_vec();
188                sorted.sort_by_key(|o| o.sequence_number);
189                // Check sequence numbers are ascending and timestamps non-decreasing
190                sorted.windows(2).all(|w| {
191                    w[0].sequence_number < w[1].sequence_number
192                        && w[0].start_timestamp <= w[1].start_timestamp
193                })
194            })
195            .count();
196        let operation_sequence_valid = if total_order_groups == 0 {
197            1.0
198        } else {
199            seq_valid as f64 / total_order_groups as f64
200        };
201
202        // 4. Quality inspection: defect_rate = defect_count / sample_size
203        let defect_ok = inspections
204            .iter()
205            .filter(|insp| {
206                if insp.sample_size == 0 {
207                    return true;
208                }
209                let expected_rate = insp.defect_count as f64 / insp.sample_size as f64;
210                (insp.reported_defect_rate - expected_rate).abs() <= 0.001
211            })
212            .count();
213        let defect_rate_accuracy = if inspections.is_empty() {
214            1.0
215        } else {
216            defect_ok as f64 / inspections.len() as f64
217        };
218
219        // Characteristics compliance
220        let total_chars: u32 = inspections.iter().map(|i| i.total_characteristics).sum();
221        let within_chars: u32 = inspections
222            .iter()
223            .map(|i| i.characteristics_within_limits)
224            .sum();
225        let characteristics_compliance = if total_chars == 0 {
226            1.0
227        } else {
228            within_chars as f64 / total_chars as f64
229        };
230
231        // 5. Cycle count variance: variance = counted - book
232        let variance_ok = cycle_counts
233            .iter()
234            .filter(|cc| {
235                let expected_variance = cc.counted_quantity - cc.book_quantity;
236                (cc.reported_variance - expected_variance).abs() <= 0.01
237            })
238            .count();
239        let variance_calculation_accuracy = if cycle_counts.is_empty() {
240            1.0
241        } else {
242            variance_ok as f64 / cycle_counts.len() as f64
243        };
244
245        // Check thresholds
246        if yield_rate_consistency < self.thresholds.min_yield_consistency {
247            issues.push(format!(
248                "Yield consistency {:.3} < {:.3}",
249                yield_rate_consistency, self.thresholds.min_yield_consistency
250            ));
251        }
252        if operation_sequence_valid < self.thresholds.min_sequence_valid {
253            issues.push(format!(
254                "Operation sequence validity {:.3} < {:.3}",
255                operation_sequence_valid, self.thresholds.min_sequence_valid
256            ));
257        }
258        if defect_rate_accuracy < self.thresholds.min_defect_rate_accuracy {
259            issues.push(format!(
260                "Defect rate accuracy {:.3} < {:.3}",
261                defect_rate_accuracy, self.thresholds.min_defect_rate_accuracy
262            ));
263        }
264        if variance_calculation_accuracy < self.thresholds.min_variance_accuracy {
265            issues.push(format!(
266                "Variance calculation accuracy {:.3} < {:.3}",
267                variance_calculation_accuracy, self.thresholds.min_variance_accuracy
268            ));
269        }
270
271        let passes = issues.is_empty();
272
273        Ok(ManufacturingEvaluation {
274            yield_rate_consistency,
275            avg_cost_variance_ratio,
276            operation_sequence_valid,
277            defect_rate_accuracy,
278            characteristics_compliance,
279            variance_calculation_accuracy,
280            total_orders: orders.len(),
281            total_inspections: inspections.len(),
282            total_cycle_counts: cycle_counts.len(),
283            passes,
284            issues,
285        })
286    }
287}
288
289impl Default for ManufacturingEvaluator {
290    fn default() -> Self {
291        Self::new()
292    }
293}
294
295#[cfg(test)]
296#[allow(clippy::unwrap_used)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn test_valid_manufacturing_data() {
302        let evaluator = ManufacturingEvaluator::new();
303        let orders = vec![ProductionOrderData {
304            order_id: "PO001".to_string(),
305            actual_quantity: 90.0,
306            scrap_quantity: 10.0,
307            reported_yield: 0.9, // 90 / (90+10) = 0.9
308            planned_cost: 10_000.0,
309            actual_cost: 10_500.0,
310        }];
311
312        let operations = vec![
313            RoutingOperationData {
314                order_id: "PO001".to_string(),
315                sequence_number: 10,
316                start_timestamp: 1000,
317            },
318            RoutingOperationData {
319                order_id: "PO001".to_string(),
320                sequence_number: 20,
321                start_timestamp: 2000,
322            },
323        ];
324
325        let inspections = vec![QualityInspectionData {
326            lot_id: "LOT001".to_string(),
327            sample_size: 100,
328            defect_count: 5,
329            reported_defect_rate: 0.05,
330            characteristics_within_limits: 95,
331            total_characteristics: 100,
332        }];
333
334        let cycle_counts = vec![CycleCountData {
335            record_id: "CC001".to_string(),
336            book_quantity: 100.0,
337            counted_quantity: 98.0,
338            reported_variance: -2.0,
339        }];
340
341        let result = evaluator
342            .evaluate(&orders, &operations, &inspections, &cycle_counts)
343            .unwrap();
344        assert!(result.passes);
345        assert_eq!(result.yield_rate_consistency, 1.0);
346        assert_eq!(result.defect_rate_accuracy, 1.0);
347    }
348
349    #[test]
350    fn test_wrong_yield() {
351        let evaluator = ManufacturingEvaluator::new();
352        let orders = vec![ProductionOrderData {
353            order_id: "PO001".to_string(),
354            actual_quantity: 90.0,
355            scrap_quantity: 10.0,
356            reported_yield: 0.5, // Wrong, should be 0.9
357            planned_cost: 10_000.0,
358            actual_cost: 10_000.0,
359        }];
360
361        let result = evaluator.evaluate(&orders, &[], &[], &[]).unwrap();
362        assert!(!result.passes);
363    }
364
365    #[test]
366    fn test_out_of_order_operations() {
367        let evaluator = ManufacturingEvaluator::new();
368        let operations = vec![
369            RoutingOperationData {
370                order_id: "PO001".to_string(),
371                sequence_number: 10,
372                start_timestamp: 2000, // Later timestamp but earlier sequence
373            },
374            RoutingOperationData {
375                order_id: "PO001".to_string(),
376                sequence_number: 20,
377                start_timestamp: 1000, // Earlier timestamp but later sequence
378            },
379        ];
380
381        let result = evaluator.evaluate(&[], &operations, &[], &[]).unwrap();
382        assert_eq!(result.operation_sequence_valid, 0.0);
383    }
384
385    #[test]
386    fn test_empty_data() {
387        let evaluator = ManufacturingEvaluator::new();
388        let result = evaluator.evaluate(&[], &[], &[], &[]).unwrap();
389        assert!(result.passes);
390    }
391}