1use super::chart::{ChartPoint, ControlLimits, ViolationType};
14
15pub trait RunRule {
20 fn check(&self, points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)>;
25}
26
27pub struct WesternElectricRules;
37
38pub struct NelsonRules;
59
60fn zone_widths(limits: &ControlLimits) -> (f64, f64) {
68 let sigma = (limits.ucl - limits.cl) / 3.0;
69 (sigma, 2.0 * sigma)
70}
71
72fn check_rule1(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
76 let mut violations = Vec::new();
77 for point in points {
78 if point.value > limits.ucl || point.value < limits.lcl {
79 violations.push((point.index, ViolationType::BeyondLimits));
80 }
81 }
82 violations
83}
84
85fn check_rule2(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
89 let mut violations = Vec::new();
90 if points.len() < 9 {
91 return violations;
92 }
93
94 let cl = limits.cl;
95 let sides: Vec<i8> = points
98 .iter()
99 .map(|p| {
100 if p.value > cl {
101 1
102 } else if p.value < cl {
103 -1
104 } else {
105 0
106 }
107 })
108 .collect();
109
110 let mut run_length = 1_usize;
111 for i in 1..sides.len() {
112 if sides[i] != 0 && sides[i] == sides[i - 1] {
113 run_length += 1;
114 } else {
115 run_length = 1;
116 }
117 if run_length >= 9 {
118 violations.push((points[i].index, ViolationType::NineOneSide));
119 }
120 }
121 violations
122}
123
124fn check_rule3(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
128 let _ = limits; let mut violations = Vec::new();
130 if points.len() < 6 {
131 return violations;
132 }
133
134 let dirs: Vec<i8> = points
136 .windows(2)
137 .map(|w| {
138 if w[1].value > w[0].value {
139 1
140 } else if w[1].value < w[0].value {
141 -1
142 } else {
143 0
144 }
145 })
146 .collect();
147
148 let mut run_length = 1_usize;
149 for i in 1..dirs.len() {
150 if dirs[i] != 0 && dirs[i] == dirs[i - 1] {
151 run_length += 1;
152 } else {
153 run_length = 1;
154 }
155 if run_length >= 5 {
158 violations.push((points[i + 1].index, ViolationType::SixTrend));
161 }
162 }
163 violations
164}
165
166fn check_rule4(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
170 let _ = limits;
171 let mut violations = Vec::new();
172 if points.len() < 14 {
173 return violations;
174 }
175
176 let dirs: Vec<i8> = points
178 .windows(2)
179 .map(|w| {
180 if w[1].value > w[0].value {
181 1
182 } else if w[1].value < w[0].value {
183 -1
184 } else {
185 0
186 }
187 })
188 .collect();
189
190 let mut alt_length = 1_usize;
192 for i in 1..dirs.len() {
193 if dirs[i] != 0 && dirs[i - 1] != 0 && dirs[i] == -dirs[i - 1] {
194 alt_length += 1;
195 } else {
196 alt_length = 1;
197 }
198 if alt_length >= 13 {
202 violations.push((points[i + 1].index, ViolationType::FourteenAlternating));
203 }
204 }
205 violations
206}
207
208fn check_rule5(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
212 let mut violations = Vec::new();
213 if points.len() < 3 {
214 return violations;
215 }
216
217 let (_, two_sigma) = zone_widths(limits);
218 let upper_2s = limits.cl + two_sigma;
219 let lower_2s = limits.cl - two_sigma;
220
221 for i in 2..points.len() {
222 let window = &points[i - 2..=i];
223
224 let above_count = window.iter().filter(|p| p.value > upper_2s).count();
226 if above_count >= 2 {
227 violations.push((points[i].index, ViolationType::TwoOfThreeBeyond2Sigma));
228 continue;
229 }
230
231 let below_count = window.iter().filter(|p| p.value < lower_2s).count();
233 if below_count >= 2 {
234 violations.push((points[i].index, ViolationType::TwoOfThreeBeyond2Sigma));
235 }
236 }
237 violations
238}
239
240fn check_rule6(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
244 let mut violations = Vec::new();
245 if points.len() < 5 {
246 return violations;
247 }
248
249 let (one_sigma, _) = zone_widths(limits);
250 let upper_1s = limits.cl + one_sigma;
251 let lower_1s = limits.cl - one_sigma;
252
253 for i in 4..points.len() {
254 let window = &points[i - 4..=i];
255
256 let above_count = window.iter().filter(|p| p.value > upper_1s).count();
258 if above_count >= 4 {
259 violations.push((points[i].index, ViolationType::FourOfFiveBeyond1Sigma));
260 continue;
261 }
262
263 let below_count = window.iter().filter(|p| p.value < lower_1s).count();
265 if below_count >= 4 {
266 violations.push((points[i].index, ViolationType::FourOfFiveBeyond1Sigma));
267 }
268 }
269 violations
270}
271
272fn check_rule7(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
276 let mut violations = Vec::new();
277 if points.len() < 15 {
278 return violations;
279 }
280
281 let (one_sigma, _) = zone_widths(limits);
282 let upper_1s = limits.cl + one_sigma;
283 let lower_1s = limits.cl - one_sigma;
284
285 let mut run_length = 0_usize;
286 for point in points {
287 if point.value >= lower_1s && point.value <= upper_1s {
288 run_length += 1;
289 } else {
290 run_length = 0;
291 }
292 if run_length >= 15 {
293 violations.push((point.index, ViolationType::FifteenWithin1Sigma));
294 }
295 }
296 violations
297}
298
299fn check_rule8(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
303 let mut violations = Vec::new();
304 if points.len() < 8 {
305 return violations;
306 }
307
308 let (one_sigma, _) = zone_widths(limits);
309 let upper_1s = limits.cl + one_sigma;
310 let lower_1s = limits.cl - one_sigma;
311
312 let mut run_length = 0_usize;
313 for point in points {
314 if point.value > upper_1s || point.value < lower_1s {
316 run_length += 1;
317 } else {
318 run_length = 0;
319 }
320 if run_length >= 8 {
321 violations.push((point.index, ViolationType::EightBeyond1Sigma));
322 }
323 }
324 violations
325}
326
327impl RunRule for WesternElectricRules {
332 fn check(&self, points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
336 let mut results = Vec::new();
337 results.extend(check_rule1(points, limits));
338 results.extend(check_rule2(points, limits));
339 results.extend(check_rule5(points, limits));
340 results.extend(check_rule6(points, limits));
341 results.sort_by_key(|&(idx, _)| idx);
342 results
343 }
344}
345
346impl RunRule for NelsonRules {
347 fn check(&self, points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
349 let mut results = Vec::new();
350 results.extend(check_rule1(points, limits));
351 results.extend(check_rule2(points, limits));
352 results.extend(check_rule3(points, limits));
353 results.extend(check_rule4(points, limits));
354 results.extend(check_rule5(points, limits));
355 results.extend(check_rule6(points, limits));
356 results.extend(check_rule7(points, limits));
357 results.extend(check_rule8(points, limits));
358 results.sort_by_key(|&(idx, _)| idx);
359 results
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 fn make_points(values: &[f64]) -> Vec<ChartPoint> {
369 values
370 .iter()
371 .enumerate()
372 .map(|(i, &v)| ChartPoint {
373 value: v,
374 index: i,
375 violations: Vec::new(),
376 })
377 .collect()
378 }
379
380 #[test]
383 fn test_rule1_point_above_ucl() {
384 let limits = ControlLimits {
385 ucl: 30.0,
386 cl: 25.0,
387 lcl: 20.0,
388 };
389 let points = make_points(&[25.0, 31.0, 25.0]);
390 let violations = check_rule1(&points, &limits);
391 assert_eq!(violations.len(), 1);
392 assert_eq!(violations[0].0, 1);
393 assert_eq!(violations[0].1, ViolationType::BeyondLimits);
394 }
395
396 #[test]
397 fn test_rule1_point_below_lcl() {
398 let limits = ControlLimits {
399 ucl: 30.0,
400 cl: 25.0,
401 lcl: 20.0,
402 };
403 let points = make_points(&[25.0, 19.0, 25.0]);
404 let violations = check_rule1(&points, &limits);
405 assert_eq!(violations.len(), 1);
406 assert_eq!(violations[0].0, 1);
407 }
408
409 #[test]
410 fn test_rule1_on_limit_is_not_violation() {
411 let limits = ControlLimits {
412 ucl: 30.0,
413 cl: 25.0,
414 lcl: 20.0,
415 };
416 let points = make_points(&[30.0, 20.0]);
417 let violations = check_rule1(&points, &limits);
418 assert!(violations.is_empty());
419 }
420
421 #[test]
424 fn test_rule2_nine_above() {
425 let limits = ControlLimits {
426 ucl: 30.0,
427 cl: 25.0,
428 lcl: 20.0,
429 };
430 let values: Vec<f64> = (0..9).map(|_| 26.0).collect();
432 let points = make_points(&values);
433 let violations = check_rule2(&points, &limits);
434 assert_eq!(violations.len(), 1);
435 assert_eq!(violations[0].1, ViolationType::NineOneSide);
436 }
437
438 #[test]
439 fn test_rule2_eight_not_enough() {
440 let limits = ControlLimits {
441 ucl: 30.0,
442 cl: 25.0,
443 lcl: 20.0,
444 };
445 let values: Vec<f64> = (0..8).map(|_| 26.0).collect();
446 let points = make_points(&values);
447 let violations = check_rule2(&points, &limits);
448 assert!(violations.is_empty());
449 }
450
451 #[test]
452 fn test_rule2_nine_below() {
453 let limits = ControlLimits {
454 ucl: 30.0,
455 cl: 25.0,
456 lcl: 20.0,
457 };
458 let values: Vec<f64> = (0..9).map(|_| 24.0).collect();
459 let points = make_points(&values);
460 let violations = check_rule2(&points, &limits);
461 assert_eq!(violations.len(), 1);
462 }
463
464 #[test]
467 fn test_rule3_six_increasing() {
468 let limits = ControlLimits {
469 ucl: 30.0,
470 cl: 25.0,
471 lcl: 20.0,
472 };
473 let points = make_points(&[20.0, 21.0, 22.0, 23.0, 24.0, 25.0]);
474 let violations = check_rule3(&points, &limits);
475 assert_eq!(violations.len(), 1);
476 assert_eq!(violations[0].1, ViolationType::SixTrend);
477 }
478
479 #[test]
480 fn test_rule3_six_decreasing() {
481 let limits = ControlLimits {
482 ucl: 30.0,
483 cl: 25.0,
484 lcl: 20.0,
485 };
486 let points = make_points(&[30.0, 29.0, 28.0, 27.0, 26.0, 25.0]);
487 let violations = check_rule3(&points, &limits);
488 assert_eq!(violations.len(), 1);
489 }
490
491 #[test]
492 fn test_rule3_five_not_enough() {
493 let limits = ControlLimits {
494 ucl: 30.0,
495 cl: 25.0,
496 lcl: 20.0,
497 };
498 let points = make_points(&[20.0, 21.0, 22.0, 23.0, 24.0]);
499 let violations = check_rule3(&points, &limits);
500 assert!(violations.is_empty());
501 }
502
503 #[test]
506 fn test_rule4_fourteen_alternating() {
507 let limits = ControlLimits {
508 ucl: 30.0,
509 cl: 25.0,
510 lcl: 20.0,
511 };
512 let values: Vec<f64> = (0..14)
514 .map(|i| if i % 2 == 0 { 24.0 } else { 26.0 })
515 .collect();
516 let points = make_points(&values);
517 let violations = check_rule4(&points, &limits);
518 assert_eq!(violations.len(), 1);
519 assert_eq!(violations[0].1, ViolationType::FourteenAlternating);
520 }
521
522 #[test]
523 fn test_rule4_thirteen_not_enough() {
524 let limits = ControlLimits {
525 ucl: 30.0,
526 cl: 25.0,
527 lcl: 20.0,
528 };
529 let values: Vec<f64> = (0..13)
530 .map(|i| if i % 2 == 0 { 24.0 } else { 26.0 })
531 .collect();
532 let points = make_points(&values);
533 let violations = check_rule4(&points, &limits);
534 assert!(violations.is_empty());
535 }
536
537 #[test]
540 fn test_rule5_two_of_three_above() {
541 let limits = ControlLimits {
542 ucl: 28.0, cl: 25.0,
544 lcl: 22.0,
545 };
546 let points = make_points(&[27.5, 25.0, 27.5]);
549 let violations = check_rule5(&points, &limits);
550 assert_eq!(violations.len(), 1);
551 assert_eq!(violations[0].1, ViolationType::TwoOfThreeBeyond2Sigma);
552 }
553
554 #[test]
555 fn test_rule5_two_of_three_below() {
556 let limits = ControlLimits {
557 ucl: 28.0,
558 cl: 25.0,
559 lcl: 22.0,
560 };
561 let points = make_points(&[22.5, 25.0, 22.5]);
563 let violations = check_rule5(&points, &limits);
564 assert_eq!(violations.len(), 1);
565 }
566
567 #[test]
570 fn test_rule6_four_of_five_above() {
571 let limits = ControlLimits {
572 ucl: 28.0, cl: 25.0,
574 lcl: 22.0,
575 };
576 let points = make_points(&[26.5, 26.5, 25.0, 26.5, 26.5]);
578 let violations = check_rule6(&points, &limits);
579 assert_eq!(violations.len(), 1);
580 assert_eq!(violations[0].1, ViolationType::FourOfFiveBeyond1Sigma);
581 }
582
583 #[test]
586 fn test_rule7_fifteen_within() {
587 let limits = ControlLimits {
588 ucl: 28.0, cl: 25.0,
590 lcl: 22.0,
591 };
592 let values: Vec<f64> = (0..15).map(|i| 24.5 + (i as f64 % 3.0) * 0.25).collect();
594 let points = make_points(&values);
595 let violations = check_rule7(&points, &limits);
596 assert_eq!(violations.len(), 1);
597 assert_eq!(violations[0].1, ViolationType::FifteenWithin1Sigma);
598 }
599
600 #[test]
603 fn test_rule8_eight_beyond() {
604 let limits = ControlLimits {
605 ucl: 28.0, cl: 25.0,
607 lcl: 22.0,
608 };
609 let points = make_points(&[27.0, 23.0, 27.0, 23.0, 27.0, 23.0, 27.0, 23.0]);
611 let violations = check_rule8(&points, &limits);
612 assert_eq!(violations.len(), 1);
613 assert_eq!(violations[0].1, ViolationType::EightBeyond1Sigma);
614 }
615
616 #[test]
617 fn test_rule8_seven_not_enough() {
618 let limits = ControlLimits {
619 ucl: 28.0,
620 cl: 25.0,
621 lcl: 22.0,
622 };
623 let points = make_points(&[27.0, 23.0, 27.0, 23.0, 27.0, 23.0, 27.0]);
624 let violations = check_rule8(&points, &limits);
625 assert!(violations.is_empty());
626 }
627
628 #[test]
631 fn test_western_electric_combines_four_rules() {
632 let limits = ControlLimits {
633 ucl: 30.0,
634 cl: 25.0,
635 lcl: 20.0,
636 };
637 let points = make_points(&[25.0, 31.0, 25.0]);
639 let we = WesternElectricRules;
640 let violations = we.check(&points, &limits);
641 assert!(!violations.is_empty());
642 assert!(violations
643 .iter()
644 .any(|(_, v)| *v == ViolationType::BeyondLimits));
645 }
646
647 #[test]
650 fn test_nelson_detects_trend() {
651 let limits = ControlLimits {
652 ucl: 30.0,
653 cl: 25.0,
654 lcl: 20.0,
655 };
656 let points = make_points(&[20.0, 21.0, 22.0, 23.0, 24.0, 25.0]);
657 let nelson = NelsonRules;
658 let violations = nelson.check(&points, &limits);
659 assert!(violations
660 .iter()
661 .any(|(_, v)| *v == ViolationType::SixTrend));
662 }
663
664 #[test]
665 fn test_nelson_no_violations_in_random_data() {
666 let limits = ControlLimits {
667 ucl: 28.0,
668 cl: 25.0,
669 lcl: 22.0,
670 };
671 let points = make_points(&[25.5, 24.8, 25.2, 24.9, 25.1]);
673 let nelson = NelsonRules;
674 let violations = nelson.check(&points, &limits);
675 assert!(violations.is_empty());
676 }
677
678 #[test]
679 fn test_nelson_rule2_continuation() {
680 let limits = ControlLimits {
682 ucl: 30.0,
683 cl: 25.0,
684 lcl: 20.0,
685 };
686 let values: Vec<f64> = (0..10).map(|_| 26.0).collect();
687 let points = make_points(&values);
688 let violations = check_rule2(&points, &limits);
689 assert_eq!(violations.len(), 2);
691 assert_eq!(violations[0].0, 8);
692 assert_eq!(violations[1].0, 9);
693 }
694
695 #[test]
696 fn test_rule5_not_triggered_mixed_sides() {
697 let limits = ControlLimits {
698 ucl: 28.0,
699 cl: 25.0,
700 lcl: 22.0,
701 };
702 let points = make_points(&[27.5, 25.0, 22.5]);
705 let violations = check_rule5(&points, &limits);
706 assert!(violations.is_empty());
707 }
708
709 #[test]
710 fn test_rule7_fourteen_not_enough() {
711 let limits = ControlLimits {
712 ucl: 28.0,
713 cl: 25.0,
714 lcl: 22.0,
715 };
716 let values: Vec<f64> = (0..14).map(|_| 25.5).collect();
717 let points = make_points(&values);
718 let violations = check_rule7(&points, &limits);
719 assert!(violations.is_empty());
720 }
721}