datasynth_eval/coherence/
manufacturing.rs1use crate::error::EvalResult;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ManufacturingThresholds {
13 pub min_yield_consistency: f64,
15 pub min_sequence_valid: f64,
17 pub min_defect_rate_accuracy: f64,
19 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#[derive(Debug, Clone)]
36pub struct ProductionOrderData {
37 pub order_id: String,
39 pub actual_quantity: f64,
41 pub scrap_quantity: f64,
43 pub reported_yield: f64,
45 pub planned_cost: f64,
47 pub actual_cost: f64,
49}
50
51#[derive(Debug, Clone)]
53pub struct RoutingOperationData {
54 pub order_id: String,
56 pub sequence_number: u32,
58 pub start_timestamp: i64,
60}
61
62#[derive(Debug, Clone)]
64pub struct QualityInspectionData {
65 pub lot_id: String,
67 pub sample_size: u32,
69 pub defect_count: u32,
71 pub reported_defect_rate: f64,
73 pub characteristics_within_limits: u32,
75 pub total_characteristics: u32,
77}
78
79#[derive(Debug, Clone)]
81pub struct CycleCountData {
82 pub record_id: String,
84 pub book_quantity: f64,
86 pub counted_quantity: f64,
88 pub reported_variance: f64,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ManufacturingEvaluation {
95 pub yield_rate_consistency: f64,
97 pub avg_cost_variance_ratio: f64,
99 pub operation_sequence_valid: f64,
101 pub defect_rate_accuracy: f64,
103 pub characteristics_compliance: f64,
105 pub variance_calculation_accuracy: f64,
107 pub total_orders: usize,
109 pub total_inspections: usize,
111 pub total_cycle_counts: usize,
113 pub passes: bool,
115 pub issues: Vec<String>,
117}
118
119pub struct ManufacturingEvaluator {
121 thresholds: ManufacturingThresholds,
122}
123
124impl ManufacturingEvaluator {
125 pub fn new() -> Self {
127 Self {
128 thresholds: ManufacturingThresholds::default(),
129 }
130 }
131
132 pub fn with_thresholds(thresholds: ManufacturingThresholds) -> Self {
134 Self { thresholds }
135 }
136
137 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 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; }
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 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 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 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 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 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 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 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, 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, 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, },
374 RoutingOperationData {
375 order_id: "PO001".to_string(),
376 sequence_number: 20,
377 start_timestamp: 1000, },
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}