1use super::operations::{BullwhipEffect, KingmanFormula, LittlesLaw, SquareRootLaw};
30use serde::{Deserialize, Serialize};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub enum TpsTestCase {
35 PushVsPull,
37 BatchSizeReduction,
39 LittlesLawStochastic,
41 HeijunkaBullwhip,
43 SmedSetup,
45 ShojinkaCrossTraining,
47 CellLayout,
49 KingmansCurve,
51 SquareRootInventory,
53 KanbanVsDbr,
55}
56
57impl TpsTestCase {
58 #[must_use]
60 pub fn all() -> Vec<Self> {
61 vec![
62 Self::PushVsPull,
63 Self::BatchSizeReduction,
64 Self::LittlesLawStochastic,
65 Self::HeijunkaBullwhip,
66 Self::SmedSetup,
67 Self::ShojinkaCrossTraining,
68 Self::CellLayout,
69 Self::KingmansCurve,
70 Self::SquareRootInventory,
71 Self::KanbanVsDbr,
72 ]
73 }
74
75 #[must_use]
77 pub fn id(&self) -> &'static str {
78 match self {
79 Self::PushVsPull => "TC-1",
80 Self::BatchSizeReduction => "TC-2",
81 Self::LittlesLawStochastic => "TC-3",
82 Self::HeijunkaBullwhip => "TC-4",
83 Self::SmedSetup => "TC-5",
84 Self::ShojinkaCrossTraining => "TC-6",
85 Self::CellLayout => "TC-7",
86 Self::KingmansCurve => "TC-8",
87 Self::SquareRootInventory => "TC-9",
88 Self::KanbanVsDbr => "TC-10",
89 }
90 }
91
92 #[must_use]
94 pub fn null_hypothesis(&self) -> &'static str {
95 match self {
96 Self::PushVsPull => {
97 "H₀: There is no statistically significant difference in Throughput (TH) \
98 or Cycle Time (CT) between Push and Pull systems when resource capacity \
99 and average demand are identical."
100 }
101 Self::BatchSizeReduction => {
102 "H₀: Reducing batch size increases the frequency of setups, thereby \
103 reducing effective capacity and increasing total Cycle Time."
104 }
105 Self::LittlesLawStochastic => {
106 "H₀: In a high-variability environment, Cycle Time behaves non-linearly \
107 or independently of WIP levels due to stochastic effects."
108 }
109 Self::HeijunkaBullwhip => {
110 "H₀: A chase strategy (matching production to demand) minimizes inventory \
111 without amplifying variance upstream."
112 }
113 Self::SmedSetup => {
114 "H₀: Reducing setup times provides linear gains in capacity utilization."
115 }
116 Self::ShojinkaCrossTraining => {
117 "H₀: Specialist workers with dedicated stations are more efficient than \
118 cross-trained workers who can move between stations."
119 }
120 Self::CellLayout => {
121 "H₀: Physical layout and material flow patterns have no significant impact \
122 on system performance when processing times are identical."
123 }
124 Self::KingmansCurve => "H₀: Queue waiting time increases linearly with utilization.",
125 Self::SquareRootInventory => {
126 "H₀: Safety stock requirements scale linearly with demand variability."
127 }
128 Self::KanbanVsDbr => {
129 "H₀: Kanban and Drum-Buffer-Rope (DBR) produce equivalent performance \
130 in all production environments."
131 }
132 }
133 }
134
135 #[must_use]
137 pub fn tps_principle(&self) -> &'static str {
138 match self {
139 Self::PushVsPull => "CONWIP / Pull System",
140 Self::BatchSizeReduction => "One-Piece Flow / SMED",
141 Self::LittlesLawStochastic => "WIP Control",
142 Self::HeijunkaBullwhip => "Heijunka (Production Leveling)",
143 Self::SmedSetup => "SMED (Single Minute Exchange of Die)",
144 Self::ShojinkaCrossTraining => "Shojinka (Flexible Workforce)",
145 Self::CellLayout => "Cell Design / U-Line",
146 Self::KingmansCurve => "Mura Reduction (Variability)",
147 Self::SquareRootInventory => "Supermarket / Kanban Sizing",
148 Self::KanbanVsDbr => "TOC / Drum-Buffer-Rope",
149 }
150 }
151
152 #[must_use]
154 pub fn governing_equation_name(&self) -> &'static str {
155 match self {
156 Self::PushVsPull | Self::LittlesLawStochastic => "Little's Law (L = λW)",
157 Self::BatchSizeReduction => "EPEI Formula",
158 Self::HeijunkaBullwhip => "Bullwhip Effect",
159 Self::SmedSetup => "OEE Availability",
160 Self::ShojinkaCrossTraining => "Pooling Effect",
161 Self::CellLayout => "Balance Delay Loss",
162 Self::KingmansCurve => "Kingman's VUT Formula",
163 Self::SquareRootInventory => "Square Root Law",
164 Self::KanbanVsDbr => "Constraints Theory",
165 }
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct TpsTestResult {
172 pub test_case: TpsTestCase,
174 pub h0_rejected: bool,
176 pub p_value: f64,
178 pub effect_size: f64,
180 pub confidence_level: f64,
182 pub summary: String,
184 pub metrics: TpsMetrics,
186}
187
188#[derive(Debug, Clone, Default, Serialize, Deserialize)]
190pub struct TpsMetrics {
191 pub wip: Option<f64>,
193 pub throughput: Option<f64>,
195 pub cycle_time: Option<f64>,
197 pub utilization: Option<f64>,
199 pub queue_wait: Option<f64>,
201 pub variance_ratio: Option<f64>,
203 pub safety_stock: Option<f64>,
205}
206
207impl TpsTestResult {
208 #[must_use]
210 pub fn new(test_case: TpsTestCase) -> Self {
211 Self {
212 test_case,
213 h0_rejected: false,
214 p_value: 1.0,
215 effect_size: 0.0,
216 confidence_level: 0.95,
217 summary: String::new(),
218 metrics: TpsMetrics::default(),
219 }
220 }
221
222 #[must_use]
224 pub fn rejected(mut self, p_value: f64, effect_size: f64) -> Self {
225 self.h0_rejected = true;
226 self.p_value = p_value;
227 self.effect_size = effect_size;
228 self
229 }
230
231 #[must_use]
233 pub fn with_metrics(mut self, metrics: TpsMetrics) -> Self {
234 self.metrics = metrics;
235 self
236 }
237
238 #[must_use]
240 pub fn with_summary(mut self, summary: &str) -> Self {
241 self.summary = summary.to_string();
242 self
243 }
244}
245
246pub fn validate_littles_law(
254 observed_wip: f64,
255 observed_throughput: f64,
256 observed_cycle_time: f64,
257 tolerance: f64,
258) -> Result<TpsTestResult, String> {
259 let law = LittlesLaw::new();
260 let validation = law.validate(
261 observed_wip,
262 observed_throughput,
263 observed_cycle_time,
264 tolerance,
265 );
266
267 let mut result =
268 TpsTestResult::new(TpsTestCase::LittlesLawStochastic).with_metrics(TpsMetrics {
269 wip: Some(observed_wip),
270 throughput: Some(observed_throughput),
271 cycle_time: Some(observed_cycle_time),
272 ..Default::default()
273 });
274
275 match validation {
276 Ok(()) => {
277 result.h0_rejected = true; result.p_value = 0.001; result.effect_size = 0.0;
280 result.summary = format!(
281 "Little's Law validated: WIP={observed_wip:.2}, TH={observed_throughput:.2}, CT={observed_cycle_time:.2}"
282 );
283 Ok(result)
284 }
285 Err(msg) => {
286 result.h0_rejected = false;
287 result.summary = msg;
288 Ok(result)
289 }
290 }
291}
292
293pub fn validate_kingmans_curve(
300 utilization_levels: &[f64],
301 observed_wait_times: &[f64],
302) -> Result<TpsTestResult, String> {
303 if utilization_levels.len() != observed_wait_times.len() {
304 return Err("Utilization and wait time arrays must have same length".to_string());
305 }
306
307 let _formula = KingmanFormula::new();
308
309 let mut is_exponential = true;
311 let mut prev_delta = 0.0;
312
313 for i in 1..observed_wait_times.len() {
314 let delta = observed_wait_times[i] - observed_wait_times[i - 1];
315 if i > 1 && delta <= prev_delta {
316 is_exponential = false;
317 break;
318 }
319 prev_delta = delta;
320 }
321
322 let mut result = TpsTestResult::new(TpsTestCase::KingmansCurve).with_metrics(TpsMetrics {
323 utilization: utilization_levels.last().copied(),
324 queue_wait: observed_wait_times.last().copied(),
325 ..Default::default()
326 });
327
328 if is_exponential {
329 result.h0_rejected = true; result.p_value = 0.001;
331 result.summary =
332 "Kingman's curve confirmed: wait times grow exponentially with utilization".to_string();
333 } else {
334 result.h0_rejected = false;
335 result.summary = "Wait time growth not exponential as expected".to_string();
336 }
337
338 Ok(result)
339}
340
341pub fn validate_square_root_law(
348 demand_std_1: f64,
349 safety_stock_1: f64,
350 demand_std_2: f64,
351 safety_stock_2: f64,
352 tolerance: f64,
353) -> Result<TpsTestResult, String> {
354 let _law = SquareRootLaw::new();
355
356 let demand_ratio = demand_std_2 / demand_std_1;
358 let expected_stock_ratio = demand_ratio.sqrt();
359 let actual_stock_ratio = safety_stock_2 / safety_stock_1;
360
361 let relative_error = (actual_stock_ratio - expected_stock_ratio).abs() / expected_stock_ratio;
362
363 let mut result =
364 TpsTestResult::new(TpsTestCase::SquareRootInventory).with_metrics(TpsMetrics {
365 safety_stock: Some(safety_stock_2),
366 ..Default::default()
367 });
368
369 if relative_error <= tolerance {
370 result.h0_rejected = true; result.p_value = 0.001;
372 result.summary = format!(
373 "Square Root Law confirmed: demand ratio {demand_ratio:.2} → stock ratio {actual_stock_ratio:.2} (expected {expected_stock_ratio:.2})"
374 );
375 } else {
376 result.h0_rejected = false;
377 result.summary = format!(
378 "Square Root Law violated: expected ratio {expected_stock_ratio:.2}, got {actual_stock_ratio:.2}"
379 );
380 }
381
382 Ok(result)
383}
384
385pub fn validate_bullwhip_effect(
392 demand_variance: f64,
393 order_variance: f64,
394 lead_time: f64,
395 review_period: f64,
396 tolerance: f64,
397) -> Result<TpsTestResult, String> {
398 let effect = BullwhipEffect::new();
399 let min_amplification = effect.amplification_factor(lead_time, review_period);
400 let observed_amplification = order_variance / demand_variance;
401
402 let mut result = TpsTestResult::new(TpsTestCase::HeijunkaBullwhip).with_metrics(TpsMetrics {
403 variance_ratio: Some(observed_amplification),
404 ..Default::default()
405 });
406
407 if observed_amplification >= min_amplification * (1.0 - tolerance) {
409 result.h0_rejected = true; result.p_value = 0.001;
411 result.summary = format!(
412 "Bullwhip Effect confirmed: amplification {observed_amplification:.2}x (min expected {min_amplification:.2}x)"
413 );
414 } else {
415 result.h0_rejected = false;
416 result.summary = format!(
417 "Amplification {observed_amplification:.2}x below expected {min_amplification:.2}x"
418 );
419 }
420
421 Ok(result)
422}
423
424pub fn validate_push_vs_pull(
431 push_wip: f64,
432 push_throughput: f64,
433 push_cycle_time: f64,
434 pull_wip: f64,
435 pull_throughput: f64,
436 pull_cycle_time: f64,
437 throughput_tolerance: f64,
438) -> Result<TpsTestResult, String> {
439 let throughput_diff = (pull_throughput - push_throughput).abs() / push_throughput;
440 let cycle_time_improvement = (push_cycle_time - pull_cycle_time) / push_cycle_time;
441 let wip_reduction = (push_wip - pull_wip) / push_wip;
442
443 let mut result = TpsTestResult::new(TpsTestCase::PushVsPull).with_metrics(TpsMetrics {
444 wip: Some(pull_wip),
445 throughput: Some(pull_throughput),
446 cycle_time: Some(pull_cycle_time),
447 ..Default::default()
448 });
449
450 if throughput_diff <= throughput_tolerance && cycle_time_improvement > 0.0 {
452 result.h0_rejected = true; result.p_value = 0.001;
454 result.effect_size = cycle_time_improvement;
455 result.summary = format!(
456 "Pull system superior: CT reduced {:.0}%, WIP reduced {:.0}%, TH diff {:.1}%",
457 cycle_time_improvement * 100.0,
458 wip_reduction * 100.0,
459 throughput_diff * 100.0
460 );
461 } else {
462 result.h0_rejected = false;
463 result.summary = format!(
464 "Push vs Pull inconclusive: TH diff {:.1}%, CT improvement {:.0}%",
465 throughput_diff * 100.0,
466 cycle_time_improvement * 100.0
467 );
468 }
469
470 Ok(result)
471}
472
473pub fn validate_smed_setup(
485 setup_time_before: f64,
486 setup_time_after: f64,
487 batch_size_before: usize,
488 batch_size_after: usize,
489 throughput_before: f64,
490 throughput_after: f64,
491 tolerance: f64,
492) -> Result<TpsTestResult, String> {
493 let setup_reduction = (setup_time_before - setup_time_after) / setup_time_before;
495 let batch_reduction = batch_size_before as f64 / batch_size_after as f64;
496
497 let time_per_unit_before = setup_time_before / batch_size_before as f64;
499 let time_per_unit_after = setup_time_after / batch_size_after as f64;
500 let unit_time_improvement = (time_per_unit_before - time_per_unit_after) / time_per_unit_before;
501
502 let throughput_change = (throughput_after - throughput_before) / throughput_before;
504
505 let mut result = TpsTestResult::new(TpsTestCase::SmedSetup).with_metrics(TpsMetrics {
506 throughput: Some(throughput_after),
507 utilization: Some(1.0 - time_per_unit_after / time_per_unit_before),
508 ..Default::default()
509 });
510
511 if setup_reduction >= 0.5 && batch_reduction >= 2.0 && throughput_change >= -tolerance {
513 result.h0_rejected = true; result.p_value = 0.001;
515 result.effect_size = unit_time_improvement;
516 result.summary = format!(
517 "SMED validated: setup reduced {:.0}%, batch reduced {:.0}x, per-unit time improved {:.0}%",
518 setup_reduction * 100.0,
519 batch_reduction,
520 unit_time_improvement * 100.0
521 );
522 } else {
523 result.h0_rejected = false;
524 result.summary = format!(
525 "SMED incomplete: setup red. {:.0}%, batch red. {:.0}x, TH change {:.1}%",
526 setup_reduction * 100.0,
527 batch_reduction,
528 throughput_change * 100.0
529 );
530 }
531
532 Ok(result)
533}
534
535pub fn validate_shojinka(
545 specialist_throughput: f64,
546 specialist_utilization: f64,
547 specialist_wait_time: f64,
548 flexible_throughput: f64,
549 flexible_utilization: f64,
550 flexible_wait_time: f64,
551 tolerance: f64,
552) -> Result<TpsTestResult, String> {
553 let throughput_diff = (flexible_throughput - specialist_throughput) / specialist_throughput;
554 let utilization_diff = (flexible_utilization - specialist_utilization) / specialist_utilization;
555 let wait_improvement = (specialist_wait_time - flexible_wait_time) / specialist_wait_time;
556
557 let mut result =
558 TpsTestResult::new(TpsTestCase::ShojinkaCrossTraining).with_metrics(TpsMetrics {
559 throughput: Some(flexible_throughput),
560 utilization: Some(flexible_utilization),
561 queue_wait: Some(flexible_wait_time),
562 ..Default::default()
563 });
564
565 if throughput_diff >= -tolerance && wait_improvement > 0.0 {
567 result.h0_rejected = true; result.p_value = 0.001;
569 result.effect_size = wait_improvement;
570 result.summary = format!(
571 "Shojinka validated: wait reduced {:.0}%, TH diff {:.1}%, util improved {:.1}%",
572 wait_improvement * 100.0,
573 throughput_diff * 100.0,
574 utilization_diff * 100.0
575 );
576 } else {
577 result.h0_rejected = false;
578 result.summary = format!(
579 "Shojinka inconclusive: TH diff {:.1}%, wait change {:.0}%",
580 throughput_diff * 100.0,
581 wait_improvement * 100.0
582 );
583 }
584
585 Ok(result)
586}
587
588pub fn validate_cell_layout(
603 linear_cycle_time: f64,
604 linear_balance_delay: f64,
605 cell_cycle_time: f64,
606 cell_balance_delay: f64,
607 throughput_linear: f64,
608 throughput_cell: f64,
609) -> Result<TpsTestResult, String> {
610 let cycle_time_improvement = (linear_cycle_time - cell_cycle_time) / linear_cycle_time;
611 let balance_delay_improvement =
612 (linear_balance_delay - cell_balance_delay) / linear_balance_delay;
613 let throughput_improvement = (throughput_cell - throughput_linear) / throughput_linear;
614
615 let mut result = TpsTestResult::new(TpsTestCase::CellLayout).with_metrics(TpsMetrics {
616 cycle_time: Some(cell_cycle_time),
617 throughput: Some(throughput_cell),
618 ..Default::default()
619 });
620
621 if cycle_time_improvement > 0.05
623 || throughput_improvement > 0.05
624 || balance_delay_improvement > 0.1
625 {
626 result.h0_rejected = true; result.p_value = 0.001;
628 result.effect_size = cycle_time_improvement.max(throughput_improvement);
629 result.summary = format!(
630 "Cell layout superior: CT improved {:.0}%, TH improved {:.0}%, balance delay reduced {:.0}%",
631 cycle_time_improvement * 100.0,
632 throughput_improvement * 100.0,
633 balance_delay_improvement * 100.0
634 );
635 } else {
636 result.h0_rejected = false;
637 result.summary = format!(
638 "Layout effect minimal: CT {:.1}%, TH {:.1}%, balance {:.1}%",
639 cycle_time_improvement * 100.0,
640 throughput_improvement * 100.0,
641 balance_delay_improvement * 100.0
642 );
643 }
644
645 Ok(result)
646}
647
648pub fn validate_kanban_vs_dbr(
661 kanban_throughput: f64,
662 kanban_wip: f64,
663 kanban_cycle_time: f64,
664 dbr_throughput: f64,
665 dbr_wip: f64,
666 dbr_cycle_time: f64,
667 line_balance_ratio: f64, ) -> Result<TpsTestResult, String> {
669 let th_diff = (dbr_throughput - kanban_throughput) / kanban_throughput;
670 let wip_diff = (kanban_wip - dbr_wip) / kanban_wip;
671 let ct_diff = (kanban_cycle_time - dbr_cycle_time) / kanban_cycle_time;
672
673 let mut result = TpsTestResult::new(TpsTestCase::KanbanVsDbr).with_metrics(TpsMetrics {
674 throughput: Some(dbr_throughput.max(kanban_throughput)),
675 wip: Some(dbr_wip.min(kanban_wip)),
676 cycle_time: Some(dbr_cycle_time.min(kanban_cycle_time)),
677 ..Default::default()
678 });
679
680 let significant_difference =
682 th_diff.abs() > 0.05 || wip_diff.abs() > 0.1 || ct_diff.abs() > 0.1;
683
684 let dbr_superior_unbalanced = line_balance_ratio > 1.2 && (th_diff > 0.0 || ct_diff > 0.0);
686 let kanban_suitable_balanced = line_balance_ratio <= 1.2 && th_diff.abs() <= 0.05;
688
689 if significant_difference || dbr_superior_unbalanced {
690 result.h0_rejected = true; result.p_value = 0.001;
692 result.effect_size = th_diff.abs().max(ct_diff.abs());
693
694 let winner = if dbr_superior_unbalanced {
695 "DBR superior on unbalanced line"
696 } else if kanban_suitable_balanced {
697 "Kanban suitable for balanced line"
698 } else {
699 "Systems differ significantly"
700 };
701
702 result.summary = format!(
703 "{winner}: TH diff {:.1}%, WIP diff {:.0}%, CT diff {:.0}%, balance ratio {:.2}",
704 th_diff * 100.0,
705 wip_diff * 100.0,
706 ct_diff * 100.0,
707 line_balance_ratio
708 );
709 } else {
710 result.h0_rejected = false;
711 result.summary = format!(
712 "Kanban ≈ DBR in this scenario: TH diff {:.1}%, balance ratio {:.2}",
713 th_diff * 100.0,
714 line_balance_ratio
715 );
716 }
717
718 Ok(result)
719}
720
721#[cfg(test)]
722mod tests {
723 use super::*;
724
725 #[test]
726 fn test_tps_test_case_all() {
727 let cases = TpsTestCase::all();
728 assert_eq!(cases.len(), 10);
729 }
730
731 #[test]
732 fn test_tps_test_case_ids() {
733 assert_eq!(TpsTestCase::PushVsPull.id(), "TC-1");
734 assert_eq!(TpsTestCase::KanbanVsDbr.id(), "TC-10");
735 }
736
737 #[test]
738 fn test_validate_littles_law_passes() {
739 let result = validate_littles_law(10.0, 5.0, 2.0, 0.01);
741 assert!(result.is_ok());
742 let result = result.ok().unwrap();
743 assert!(result.h0_rejected); }
745
746 #[test]
747 fn test_validate_littles_law_fails() {
748 let result = validate_littles_law(15.0, 5.0, 2.0, 0.01);
750 assert!(result.is_ok());
751 let result = result.ok().unwrap();
752 assert!(!result.h0_rejected); }
754
755 #[test]
756 fn test_validate_kingmans_curve() {
757 let utilizations = vec![0.5, 0.7, 0.85, 0.95];
759 let wait_times = vec![1.0, 2.33, 5.67, 19.0]; let result = validate_kingmans_curve(&utilizations, &wait_times);
762 assert!(result.is_ok());
763 let result = result.ok().unwrap();
764 assert!(result.h0_rejected); }
766
767 #[test]
768 fn test_validate_square_root_law() {
769 let result = validate_square_root_law(100.0, 196.0, 400.0, 392.0, 0.01);
771 assert!(result.is_ok());
772 let result = result.ok().unwrap();
773 assert!(result.h0_rejected); }
775
776 #[test]
777 fn test_validate_bullwhip_effect() {
778 let result = validate_bullwhip_effect(100.0, 600.0, 1.0, 1.0, 0.1);
781 assert!(result.is_ok());
782 let result = result.ok().unwrap();
783 assert!(result.h0_rejected);
784 }
785
786 #[test]
787 fn test_validate_push_vs_pull() {
788 let result = validate_push_vs_pull(
790 24.5, 4.45, 5.4, 10.0, 4.42, 2.2, 0.01, );
794 assert!(result.is_ok());
795 let result = result.ok().unwrap();
796 assert!(result.h0_rejected);
797 assert!(result.effect_size > 0.5); }
799
800 #[test]
801 fn test_tps_metrics() {
802 let metrics = TpsMetrics {
803 wip: Some(10.0),
804 throughput: Some(5.0),
805 cycle_time: Some(2.0),
806 ..Default::default()
807 };
808
809 assert!(metrics.wip.is_some());
810 assert!(metrics.utilization.is_none());
811 }
812
813 #[test]
818 fn test_validate_smed_setup_success() {
819 let result = validate_smed_setup(
821 30.0, 3.0, 100, 10, 4.0, 4.0, 0.05, );
826 assert!(result.is_ok());
827 let result = result.ok().unwrap();
828 assert!(result.h0_rejected); }
834
835 #[test]
836 fn test_validate_smed_without_batch_reduction() {
837 let result = validate_smed_setup(
839 30.0, 15.0, 100, 80, 4.0, 4.0, 0.05,
842 );
843 assert!(result.is_ok());
844 let result = result.ok().unwrap();
845 assert!(!result.h0_rejected);
847 }
848
849 #[test]
854 fn test_validate_shojinka_success() {
855 let result = validate_shojinka(
857 4.0, 0.85, 2.5, 4.1, 0.80, 1.5, 0.05, );
861 assert!(result.is_ok());
862 let result = result.ok().unwrap();
863 assert!(result.h0_rejected);
864 assert!(result.effect_size > 0.3); }
866
867 #[test]
868 fn test_validate_shojinka_worse_performance() {
869 let result = validate_shojinka(
871 4.0, 0.85, 2.0, 3.5, 0.70, 2.5, 0.05,
874 );
875 assert!(result.is_ok());
876 let result = result.ok().unwrap();
877 assert!(!result.h0_rejected);
878 }
879
880 #[test]
885 fn test_validate_cell_layout_success() {
886 let result = validate_cell_layout(
888 10.0, 0.25, 8.0, 0.10, 4.0, 4.5, );
892 assert!(result.is_ok());
893 let result = result.ok().unwrap();
894 assert!(result.h0_rejected);
895 }
896
897 #[test]
898 fn test_validate_cell_layout_minimal_effect() {
899 let result = validate_cell_layout(
901 10.0, 0.20, 10.2, 0.19, 4.0, 4.02,
903 );
904 assert!(result.is_ok());
905 let result = result.ok().unwrap();
906 assert!(!result.h0_rejected);
907 }
908
909 #[test]
914 fn test_validate_kanban_vs_dbr_unbalanced_line() {
915 let result = validate_kanban_vs_dbr(
917 4.0, 20.0, 5.0, 4.3, 15.0, 3.5, 1.5, );
921 assert!(result.is_ok());
922 let result = result.ok().unwrap();
923 assert!(result.h0_rejected);
924 assert!(result.summary.contains("DBR superior"));
925 }
926
927 #[test]
928 fn test_validate_kanban_vs_dbr_balanced_line() {
929 let result = validate_kanban_vs_dbr(
931 4.0, 15.0, 3.75, 4.0, 15.0, 3.75, 1.0, );
935 assert!(result.is_ok());
936 let result = result.ok().unwrap();
937 assert!(!result.h0_rejected || result.summary.contains("balanced"));
939 }
940
941 #[test]
942 fn test_validate_kanban_vs_dbr_significant_difference() {
943 let result = validate_kanban_vs_dbr(
945 4.0, 25.0, 6.25, 4.5, 18.0, 4.0, 1.1, );
949 assert!(result.is_ok());
950 let result = result.ok().unwrap();
951 assert!(result.h0_rejected);
952 }
953
954 #[test]
959 fn test_tps_test_case_all_count() {
960 let all = TpsTestCase::all();
961 assert_eq!(all.len(), 10);
962 }
963
964 #[test]
965 fn test_tps_test_case_id_coverage() {
966 for tc in TpsTestCase::all() {
967 let id = tc.id();
968 assert!(id.starts_with("TC-"));
969 }
970 }
971
972 #[test]
973 fn test_tps_test_case_null_hypothesis_coverage() {
974 for tc in TpsTestCase::all() {
975 let h0 = tc.null_hypothesis();
976 assert!(h0.contains("H₀"));
977 }
978 }
979
980 #[test]
981 fn test_tps_test_case_governing_equation_coverage() {
982 for tc in TpsTestCase::all() {
983 let eq = tc.governing_equation_name();
984 assert!(!eq.is_empty());
985 }
986 }
987
988 #[test]
989 fn test_tps_test_case_tps_principle_coverage() {
990 for tc in TpsTestCase::all() {
991 let principle = tc.tps_principle();
992 assert!(!principle.is_empty());
993 }
994 }
995
996 #[test]
997 fn test_tps_test_result_new() {
998 let result = TpsTestResult::new(TpsTestCase::PushVsPull);
999 assert!(!result.h0_rejected);
1000 assert!((result.p_value - 1.0).abs() < f64::EPSILON);
1001 assert!((result.effect_size - 0.0).abs() < f64::EPSILON);
1002 }
1003
1004 #[test]
1005 fn test_tps_test_result_rejected() {
1006 let result = TpsTestResult::new(TpsTestCase::BatchSizeReduction).rejected(0.01, 0.5);
1007 assert!(result.h0_rejected);
1008 assert!((result.p_value - 0.01).abs() < f64::EPSILON);
1009 assert!((result.effect_size - 0.5).abs() < f64::EPSILON);
1010 }
1011
1012 #[test]
1013 fn test_tps_test_result_with_metrics() {
1014 let metrics = TpsMetrics {
1015 wip: Some(10.0),
1016 throughput: Some(5.0),
1017 cycle_time: Some(2.0),
1018 ..Default::default()
1019 };
1020 let result = TpsTestResult::new(TpsTestCase::LittlesLawStochastic).with_metrics(metrics);
1021 assert_eq!(result.metrics.wip, Some(10.0));
1022 assert_eq!(result.metrics.throughput, Some(5.0));
1023 }
1024
1025 #[test]
1026 fn test_tps_test_result_with_summary() {
1027 let result = TpsTestResult::new(TpsTestCase::HeijunkaBullwhip).with_summary("Test passed");
1028 assert_eq!(result.summary, "Test passed");
1029 }
1030
1031 #[test]
1032 fn test_tps_metrics_default() {
1033 let metrics = TpsMetrics::default();
1034 assert!(metrics.wip.is_none());
1035 assert!(metrics.throughput.is_none());
1036 assert!(metrics.cycle_time.is_none());
1037 }
1038
1039 #[test]
1040 fn test_tps_test_case_debug() {
1041 let tc = TpsTestCase::SmedSetup;
1042 let debug_str = format!("{tc:?}");
1043 assert!(debug_str.contains("SmedSetup"));
1044 }
1045
1046 #[test]
1047 fn test_tps_test_case_clone() {
1048 let tc = TpsTestCase::ShojinkaCrossTraining;
1049 let cloned = tc;
1050 assert_eq!(tc, cloned);
1051 }
1052
1053 #[test]
1054 fn test_tps_test_case_eq() {
1055 assert_eq!(TpsTestCase::CellLayout, TpsTestCase::CellLayout);
1056 assert_ne!(TpsTestCase::CellLayout, TpsTestCase::KingmansCurve);
1057 }
1058
1059 #[test]
1060 fn test_validate_littles_law_invalid_tolerance() {
1061 let result = validate_littles_law(10.0, 5.0, 2.0, 0.5);
1063 assert!(result.is_ok());
1064 }
1065
1066 #[test]
1067 fn test_tps_test_result_serialize() {
1068 let result = TpsTestResult::new(TpsTestCase::KanbanVsDbr)
1069 .rejected(0.05, 0.3)
1070 .with_summary("DBR superior");
1071
1072 let json = serde_json::to_string(&result);
1073 assert!(json.is_ok());
1074 let json = json.ok().unwrap();
1075 assert!(json.contains("KanbanVsDbr"));
1076 }
1077
1078 #[test]
1079 fn test_tps_metrics_serialize() {
1080 let metrics = TpsMetrics {
1081 wip: Some(10.0),
1082 throughput: Some(5.0),
1083 cycle_time: Some(2.0),
1084 utilization: Some(0.85),
1085 queue_wait: Some(5.0),
1086 variance_ratio: Some(1.5),
1087 safety_stock: Some(50.0),
1088 };
1089
1090 let json = serde_json::to_string(&metrics);
1091 assert!(json.is_ok());
1092 }
1093
1094 #[test]
1095 fn test_tps_test_result_builder_chain() {
1096 let metrics = TpsMetrics {
1097 wip: Some(15.0),
1098 throughput: Some(3.0),
1099 ..Default::default()
1100 };
1101 let result = TpsTestResult::new(TpsTestCase::PushVsPull)
1102 .rejected(0.01, 0.8)
1103 .with_metrics(metrics)
1104 .with_summary("Significant difference found");
1105
1106 assert!(result.h0_rejected);
1107 assert_eq!(result.metrics.wip, Some(15.0));
1108 assert!(result.summary.contains("Significant"));
1109 }
1110
1111 #[test]
1112 fn test_tps_test_case_hash() {
1113 use std::collections::HashSet;
1114 let mut set = HashSet::new();
1115 set.insert(TpsTestCase::PushVsPull);
1116 set.insert(TpsTestCase::BatchSizeReduction);
1117 assert_eq!(set.len(), 2);
1118 assert!(set.contains(&TpsTestCase::PushVsPull));
1119 }
1120}