use super::operations::{BullwhipEffect, KingmanFormula, LittlesLaw, SquareRootLaw};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TpsTestCase {
PushVsPull,
BatchSizeReduction,
LittlesLawStochastic,
HeijunkaBullwhip,
SmedSetup,
ShojinkaCrossTraining,
CellLayout,
KingmansCurve,
SquareRootInventory,
KanbanVsDbr,
}
impl TpsTestCase {
#[must_use]
pub fn all() -> Vec<Self> {
vec![
Self::PushVsPull,
Self::BatchSizeReduction,
Self::LittlesLawStochastic,
Self::HeijunkaBullwhip,
Self::SmedSetup,
Self::ShojinkaCrossTraining,
Self::CellLayout,
Self::KingmansCurve,
Self::SquareRootInventory,
Self::KanbanVsDbr,
]
}
#[must_use]
pub fn id(&self) -> &'static str {
match self {
Self::PushVsPull => "TC-1",
Self::BatchSizeReduction => "TC-2",
Self::LittlesLawStochastic => "TC-3",
Self::HeijunkaBullwhip => "TC-4",
Self::SmedSetup => "TC-5",
Self::ShojinkaCrossTraining => "TC-6",
Self::CellLayout => "TC-7",
Self::KingmansCurve => "TC-8",
Self::SquareRootInventory => "TC-9",
Self::KanbanVsDbr => "TC-10",
}
}
#[must_use]
pub fn null_hypothesis(&self) -> &'static str {
match self {
Self::PushVsPull => {
"H₀: There is no statistically significant difference in Throughput (TH) \
or Cycle Time (CT) between Push and Pull systems when resource capacity \
and average demand are identical."
}
Self::BatchSizeReduction => {
"H₀: Reducing batch size increases the frequency of setups, thereby \
reducing effective capacity and increasing total Cycle Time."
}
Self::LittlesLawStochastic => {
"H₀: In a high-variability environment, Cycle Time behaves non-linearly \
or independently of WIP levels due to stochastic effects."
}
Self::HeijunkaBullwhip => {
"H₀: A chase strategy (matching production to demand) minimizes inventory \
without amplifying variance upstream."
}
Self::SmedSetup => {
"H₀: Reducing setup times provides linear gains in capacity utilization."
}
Self::ShojinkaCrossTraining => {
"H₀: Specialist workers with dedicated stations are more efficient than \
cross-trained workers who can move between stations."
}
Self::CellLayout => {
"H₀: Physical layout and material flow patterns have no significant impact \
on system performance when processing times are identical."
}
Self::KingmansCurve => "H₀: Queue waiting time increases linearly with utilization.",
Self::SquareRootInventory => {
"H₀: Safety stock requirements scale linearly with demand variability."
}
Self::KanbanVsDbr => {
"H₀: Kanban and Drum-Buffer-Rope (DBR) produce equivalent performance \
in all production environments."
}
}
}
#[must_use]
pub fn tps_principle(&self) -> &'static str {
match self {
Self::PushVsPull => "CONWIP / Pull System",
Self::BatchSizeReduction => "One-Piece Flow / SMED",
Self::LittlesLawStochastic => "WIP Control",
Self::HeijunkaBullwhip => "Heijunka (Production Leveling)",
Self::SmedSetup => "SMED (Single Minute Exchange of Die)",
Self::ShojinkaCrossTraining => "Shojinka (Flexible Workforce)",
Self::CellLayout => "Cell Design / U-Line",
Self::KingmansCurve => "Mura Reduction (Variability)",
Self::SquareRootInventory => "Supermarket / Kanban Sizing",
Self::KanbanVsDbr => "TOC / Drum-Buffer-Rope",
}
}
#[must_use]
pub fn governing_equation_name(&self) -> &'static str {
match self {
Self::PushVsPull | Self::LittlesLawStochastic => "Little's Law (L = λW)",
Self::BatchSizeReduction => "EPEI Formula",
Self::HeijunkaBullwhip => "Bullwhip Effect",
Self::SmedSetup => "OEE Availability",
Self::ShojinkaCrossTraining => "Pooling Effect",
Self::CellLayout => "Balance Delay Loss",
Self::KingmansCurve => "Kingman's VUT Formula",
Self::SquareRootInventory => "Square Root Law",
Self::KanbanVsDbr => "Constraints Theory",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TpsTestResult {
pub test_case: TpsTestCase,
pub h0_rejected: bool,
pub p_value: f64,
pub effect_size: f64,
pub confidence_level: f64,
pub summary: String,
pub metrics: TpsMetrics,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TpsMetrics {
pub wip: Option<f64>,
pub throughput: Option<f64>,
pub cycle_time: Option<f64>,
pub utilization: Option<f64>,
pub queue_wait: Option<f64>,
pub variance_ratio: Option<f64>,
pub safety_stock: Option<f64>,
}
impl TpsTestResult {
#[must_use]
pub fn new(test_case: TpsTestCase) -> Self {
Self {
test_case,
h0_rejected: false,
p_value: 1.0,
effect_size: 0.0,
confidence_level: 0.95,
summary: String::new(),
metrics: TpsMetrics::default(),
}
}
#[must_use]
pub fn rejected(mut self, p_value: f64, effect_size: f64) -> Self {
self.h0_rejected = true;
self.p_value = p_value;
self.effect_size = effect_size;
self
}
#[must_use]
pub fn with_metrics(mut self, metrics: TpsMetrics) -> Self {
self.metrics = metrics;
self
}
#[must_use]
pub fn with_summary(mut self, summary: &str) -> Self {
self.summary = summary.to_string();
self
}
}
pub fn validate_littles_law(
observed_wip: f64,
observed_throughput: f64,
observed_cycle_time: f64,
tolerance: f64,
) -> Result<TpsTestResult, String> {
let law = LittlesLaw::new();
let validation = law.validate(
observed_wip,
observed_throughput,
observed_cycle_time,
tolerance,
);
let mut result =
TpsTestResult::new(TpsTestCase::LittlesLawStochastic).with_metrics(TpsMetrics {
wip: Some(observed_wip),
throughput: Some(observed_throughput),
cycle_time: Some(observed_cycle_time),
..Default::default()
});
match validation {
Ok(()) => {
result.h0_rejected = true; result.p_value = 0.001; result.effect_size = 0.0;
result.summary = format!(
"Little's Law validated: WIP={observed_wip:.2}, TH={observed_throughput:.2}, CT={observed_cycle_time:.2}"
);
Ok(result)
}
Err(msg) => {
result.h0_rejected = false;
result.summary = msg;
Ok(result)
}
}
}
pub fn validate_kingmans_curve(
utilization_levels: &[f64],
observed_wait_times: &[f64],
) -> Result<TpsTestResult, String> {
if utilization_levels.len() != observed_wait_times.len() {
return Err("Utilization and wait time arrays must have same length".to_string());
}
let _formula = KingmanFormula::new();
let mut is_exponential = true;
let mut prev_delta = 0.0;
for i in 1..observed_wait_times.len() {
let delta = observed_wait_times[i] - observed_wait_times[i - 1];
if i > 1 && delta <= prev_delta {
is_exponential = false;
break;
}
prev_delta = delta;
}
let mut result = TpsTestResult::new(TpsTestCase::KingmansCurve).with_metrics(TpsMetrics {
utilization: utilization_levels.last().copied(),
queue_wait: observed_wait_times.last().copied(),
..Default::default()
});
if is_exponential {
result.h0_rejected = true; result.p_value = 0.001;
result.summary =
"Kingman's curve confirmed: wait times grow exponentially with utilization".to_string();
} else {
result.h0_rejected = false;
result.summary = "Wait time growth not exponential as expected".to_string();
}
Ok(result)
}
pub fn validate_square_root_law(
demand_std_1: f64,
safety_stock_1: f64,
demand_std_2: f64,
safety_stock_2: f64,
tolerance: f64,
) -> Result<TpsTestResult, String> {
let _law = SquareRootLaw::new();
let demand_ratio = demand_std_2 / demand_std_1;
let expected_stock_ratio = demand_ratio.sqrt();
let actual_stock_ratio = safety_stock_2 / safety_stock_1;
let relative_error = (actual_stock_ratio - expected_stock_ratio).abs() / expected_stock_ratio;
let mut result =
TpsTestResult::new(TpsTestCase::SquareRootInventory).with_metrics(TpsMetrics {
safety_stock: Some(safety_stock_2),
..Default::default()
});
if relative_error <= tolerance {
result.h0_rejected = true; result.p_value = 0.001;
result.summary = format!(
"Square Root Law confirmed: demand ratio {demand_ratio:.2} → stock ratio {actual_stock_ratio:.2} (expected {expected_stock_ratio:.2})"
);
} else {
result.h0_rejected = false;
result.summary = format!(
"Square Root Law violated: expected ratio {expected_stock_ratio:.2}, got {actual_stock_ratio:.2}"
);
}
Ok(result)
}
pub fn validate_bullwhip_effect(
demand_variance: f64,
order_variance: f64,
lead_time: f64,
review_period: f64,
tolerance: f64,
) -> Result<TpsTestResult, String> {
let effect = BullwhipEffect::new();
let min_amplification = effect.amplification_factor(lead_time, review_period);
let observed_amplification = order_variance / demand_variance;
let mut result = TpsTestResult::new(TpsTestCase::HeijunkaBullwhip).with_metrics(TpsMetrics {
variance_ratio: Some(observed_amplification),
..Default::default()
});
if observed_amplification >= min_amplification * (1.0 - tolerance) {
result.h0_rejected = true; result.p_value = 0.001;
result.summary = format!(
"Bullwhip Effect confirmed: amplification {observed_amplification:.2}x (min expected {min_amplification:.2}x)"
);
} else {
result.h0_rejected = false;
result.summary = format!(
"Amplification {observed_amplification:.2}x below expected {min_amplification:.2}x"
);
}
Ok(result)
}
pub fn validate_push_vs_pull(
push_wip: f64,
push_throughput: f64,
push_cycle_time: f64,
pull_wip: f64,
pull_throughput: f64,
pull_cycle_time: f64,
throughput_tolerance: f64,
) -> Result<TpsTestResult, String> {
let throughput_diff = (pull_throughput - push_throughput).abs() / push_throughput;
let cycle_time_improvement = (push_cycle_time - pull_cycle_time) / push_cycle_time;
let wip_reduction = (push_wip - pull_wip) / push_wip;
let mut result = TpsTestResult::new(TpsTestCase::PushVsPull).with_metrics(TpsMetrics {
wip: Some(pull_wip),
throughput: Some(pull_throughput),
cycle_time: Some(pull_cycle_time),
..Default::default()
});
if throughput_diff <= throughput_tolerance && cycle_time_improvement > 0.0 {
result.h0_rejected = true; result.p_value = 0.001;
result.effect_size = cycle_time_improvement;
result.summary = format!(
"Pull system superior: CT reduced {:.0}%, WIP reduced {:.0}%, TH diff {:.1}%",
cycle_time_improvement * 100.0,
wip_reduction * 100.0,
throughput_diff * 100.0
);
} else {
result.h0_rejected = false;
result.summary = format!(
"Push vs Pull inconclusive: TH diff {:.1}%, CT improvement {:.0}%",
throughput_diff * 100.0,
cycle_time_improvement * 100.0
);
}
Ok(result)
}
pub fn validate_smed_setup(
setup_time_before: f64,
setup_time_after: f64,
batch_size_before: usize,
batch_size_after: usize,
throughput_before: f64,
throughput_after: f64,
tolerance: f64,
) -> Result<TpsTestResult, String> {
let setup_reduction = (setup_time_before - setup_time_after) / setup_time_before;
let batch_reduction = batch_size_before as f64 / batch_size_after as f64;
let time_per_unit_before = setup_time_before / batch_size_before as f64;
let time_per_unit_after = setup_time_after / batch_size_after as f64;
let unit_time_improvement = (time_per_unit_before - time_per_unit_after) / time_per_unit_before;
let throughput_change = (throughput_after - throughput_before) / throughput_before;
let mut result = TpsTestResult::new(TpsTestCase::SmedSetup).with_metrics(TpsMetrics {
throughput: Some(throughput_after),
utilization: Some(1.0 - time_per_unit_after / time_per_unit_before),
..Default::default()
});
if setup_reduction >= 0.5 && batch_reduction >= 2.0 && throughput_change >= -tolerance {
result.h0_rejected = true; result.p_value = 0.001;
result.effect_size = unit_time_improvement;
result.summary = format!(
"SMED validated: setup reduced {:.0}%, batch reduced {:.0}x, per-unit time improved {:.0}%",
setup_reduction * 100.0,
batch_reduction,
unit_time_improvement * 100.0
);
} else {
result.h0_rejected = false;
result.summary = format!(
"SMED incomplete: setup red. {:.0}%, batch red. {:.0}x, TH change {:.1}%",
setup_reduction * 100.0,
batch_reduction,
throughput_change * 100.0
);
}
Ok(result)
}
pub fn validate_shojinka(
specialist_throughput: f64,
specialist_utilization: f64,
specialist_wait_time: f64,
flexible_throughput: f64,
flexible_utilization: f64,
flexible_wait_time: f64,
tolerance: f64,
) -> Result<TpsTestResult, String> {
let throughput_diff = (flexible_throughput - specialist_throughput) / specialist_throughput;
let utilization_diff = (flexible_utilization - specialist_utilization) / specialist_utilization;
let wait_improvement = (specialist_wait_time - flexible_wait_time) / specialist_wait_time;
let mut result =
TpsTestResult::new(TpsTestCase::ShojinkaCrossTraining).with_metrics(TpsMetrics {
throughput: Some(flexible_throughput),
utilization: Some(flexible_utilization),
queue_wait: Some(flexible_wait_time),
..Default::default()
});
if throughput_diff >= -tolerance && wait_improvement > 0.0 {
result.h0_rejected = true; result.p_value = 0.001;
result.effect_size = wait_improvement;
result.summary = format!(
"Shojinka validated: wait reduced {:.0}%, TH diff {:.1}%, util improved {:.1}%",
wait_improvement * 100.0,
throughput_diff * 100.0,
utilization_diff * 100.0
);
} else {
result.h0_rejected = false;
result.summary = format!(
"Shojinka inconclusive: TH diff {:.1}%, wait change {:.0}%",
throughput_diff * 100.0,
wait_improvement * 100.0
);
}
Ok(result)
}
pub fn validate_cell_layout(
linear_cycle_time: f64,
linear_balance_delay: f64,
cell_cycle_time: f64,
cell_balance_delay: f64,
throughput_linear: f64,
throughput_cell: f64,
) -> Result<TpsTestResult, String> {
let cycle_time_improvement = (linear_cycle_time - cell_cycle_time) / linear_cycle_time;
let balance_delay_improvement =
(linear_balance_delay - cell_balance_delay) / linear_balance_delay;
let throughput_improvement = (throughput_cell - throughput_linear) / throughput_linear;
let mut result = TpsTestResult::new(TpsTestCase::CellLayout).with_metrics(TpsMetrics {
cycle_time: Some(cell_cycle_time),
throughput: Some(throughput_cell),
..Default::default()
});
if cycle_time_improvement > 0.05
|| throughput_improvement > 0.05
|| balance_delay_improvement > 0.1
{
result.h0_rejected = true; result.p_value = 0.001;
result.effect_size = cycle_time_improvement.max(throughput_improvement);
result.summary = format!(
"Cell layout superior: CT improved {:.0}%, TH improved {:.0}%, balance delay reduced {:.0}%",
cycle_time_improvement * 100.0,
throughput_improvement * 100.0,
balance_delay_improvement * 100.0
);
} else {
result.h0_rejected = false;
result.summary = format!(
"Layout effect minimal: CT {:.1}%, TH {:.1}%, balance {:.1}%",
cycle_time_improvement * 100.0,
throughput_improvement * 100.0,
balance_delay_improvement * 100.0
);
}
Ok(result)
}
pub fn validate_kanban_vs_dbr(
kanban_throughput: f64,
kanban_wip: f64,
kanban_cycle_time: f64,
dbr_throughput: f64,
dbr_wip: f64,
dbr_cycle_time: f64,
line_balance_ratio: f64, ) -> Result<TpsTestResult, String> {
let th_diff = (dbr_throughput - kanban_throughput) / kanban_throughput;
let wip_diff = (kanban_wip - dbr_wip) / kanban_wip;
let ct_diff = (kanban_cycle_time - dbr_cycle_time) / kanban_cycle_time;
let mut result = TpsTestResult::new(TpsTestCase::KanbanVsDbr).with_metrics(TpsMetrics {
throughput: Some(dbr_throughput.max(kanban_throughput)),
wip: Some(dbr_wip.min(kanban_wip)),
cycle_time: Some(dbr_cycle_time.min(kanban_cycle_time)),
..Default::default()
});
let significant_difference =
th_diff.abs() > 0.05 || wip_diff.abs() > 0.1 || ct_diff.abs() > 0.1;
let dbr_superior_unbalanced = line_balance_ratio > 1.2 && (th_diff > 0.0 || ct_diff > 0.0);
let kanban_suitable_balanced = line_balance_ratio <= 1.2 && th_diff.abs() <= 0.05;
if significant_difference || dbr_superior_unbalanced {
result.h0_rejected = true; result.p_value = 0.001;
result.effect_size = th_diff.abs().max(ct_diff.abs());
let winner = if dbr_superior_unbalanced {
"DBR superior on unbalanced line"
} else if kanban_suitable_balanced {
"Kanban suitable for balanced line"
} else {
"Systems differ significantly"
};
result.summary = format!(
"{winner}: TH diff {:.1}%, WIP diff {:.0}%, CT diff {:.0}%, balance ratio {:.2}",
th_diff * 100.0,
wip_diff * 100.0,
ct_diff * 100.0,
line_balance_ratio
);
} else {
result.h0_rejected = false;
result.summary = format!(
"Kanban ≈ DBR in this scenario: TH diff {:.1}%, balance ratio {:.2}",
th_diff * 100.0,
line_balance_ratio
);
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tps_test_case_all() {
let cases = TpsTestCase::all();
assert_eq!(cases.len(), 10);
}
#[test]
fn test_tps_test_case_ids() {
assert_eq!(TpsTestCase::PushVsPull.id(), "TC-1");
assert_eq!(TpsTestCase::KanbanVsDbr.id(), "TC-10");
}
#[test]
fn test_validate_littles_law_passes() {
let result = validate_littles_law(10.0, 5.0, 2.0, 0.01);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected); }
#[test]
fn test_validate_littles_law_fails() {
let result = validate_littles_law(15.0, 5.0, 2.0, 0.01);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(!result.h0_rejected); }
#[test]
fn test_validate_kingmans_curve() {
let utilizations = vec![0.5, 0.7, 0.85, 0.95];
let wait_times = vec![1.0, 2.33, 5.67, 19.0];
let result = validate_kingmans_curve(&utilizations, &wait_times);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected); }
#[test]
fn test_validate_square_root_law() {
let result = validate_square_root_law(100.0, 196.0, 400.0, 392.0, 0.01);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected); }
#[test]
fn test_validate_bullwhip_effect() {
let result = validate_bullwhip_effect(100.0, 600.0, 1.0, 1.0, 0.1);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected);
}
#[test]
fn test_validate_push_vs_pull() {
let result = validate_push_vs_pull(
24.5, 4.45, 5.4, 10.0, 4.42, 2.2, 0.01, );
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected);
assert!(result.effect_size > 0.5); }
#[test]
fn test_tps_metrics() {
let metrics = TpsMetrics {
wip: Some(10.0),
throughput: Some(5.0),
cycle_time: Some(2.0),
..Default::default()
};
assert!(metrics.wip.is_some());
assert!(metrics.utilization.is_none());
}
#[test]
fn test_validate_smed_setup_success() {
let result = validate_smed_setup(
30.0, 3.0, 100, 10, 4.0, 4.0, 0.05, );
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected); }
#[test]
fn test_validate_smed_without_batch_reduction() {
let result = validate_smed_setup(
30.0, 15.0, 100, 80, 4.0, 4.0, 0.05,
);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(!result.h0_rejected);
}
#[test]
fn test_validate_shojinka_success() {
let result = validate_shojinka(
4.0, 0.85, 2.5, 4.1, 0.80, 1.5, 0.05, );
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected);
assert!(result.effect_size > 0.3); }
#[test]
fn test_validate_shojinka_worse_performance() {
let result = validate_shojinka(
4.0, 0.85, 2.0, 3.5, 0.70, 2.5, 0.05,
);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(!result.h0_rejected);
}
#[test]
fn test_validate_cell_layout_success() {
let result = validate_cell_layout(
10.0, 0.25, 8.0, 0.10, 4.0, 4.5, );
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected);
}
#[test]
fn test_validate_cell_layout_minimal_effect() {
let result = validate_cell_layout(
10.0, 0.20, 10.2, 0.19, 4.0, 4.02,
);
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(!result.h0_rejected);
}
#[test]
fn test_validate_kanban_vs_dbr_unbalanced_line() {
let result = validate_kanban_vs_dbr(
4.0, 20.0, 5.0, 4.3, 15.0, 3.5, 1.5, );
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected);
assert!(result.summary.contains("DBR superior"));
}
#[test]
fn test_validate_kanban_vs_dbr_balanced_line() {
let result = validate_kanban_vs_dbr(
4.0, 15.0, 3.75, 4.0, 15.0, 3.75, 1.0, );
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(!result.h0_rejected || result.summary.contains("balanced"));
}
#[test]
fn test_validate_kanban_vs_dbr_significant_difference() {
let result = validate_kanban_vs_dbr(
4.0, 25.0, 6.25, 4.5, 18.0, 4.0, 1.1, );
assert!(result.is_ok());
let result = result.ok().unwrap();
assert!(result.h0_rejected);
}
#[test]
fn test_tps_test_case_all_count() {
let all = TpsTestCase::all();
assert_eq!(all.len(), 10);
}
#[test]
fn test_tps_test_case_id_coverage() {
for tc in TpsTestCase::all() {
let id = tc.id();
assert!(id.starts_with("TC-"));
}
}
#[test]
fn test_tps_test_case_null_hypothesis_coverage() {
for tc in TpsTestCase::all() {
let h0 = tc.null_hypothesis();
assert!(h0.contains("H₀"));
}
}
#[test]
fn test_tps_test_case_governing_equation_coverage() {
for tc in TpsTestCase::all() {
let eq = tc.governing_equation_name();
assert!(!eq.is_empty());
}
}
#[test]
fn test_tps_test_case_tps_principle_coverage() {
for tc in TpsTestCase::all() {
let principle = tc.tps_principle();
assert!(!principle.is_empty());
}
}
#[test]
fn test_tps_test_result_new() {
let result = TpsTestResult::new(TpsTestCase::PushVsPull);
assert!(!result.h0_rejected);
assert!((result.p_value - 1.0).abs() < f64::EPSILON);
assert!((result.effect_size - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_tps_test_result_rejected() {
let result = TpsTestResult::new(TpsTestCase::BatchSizeReduction).rejected(0.01, 0.5);
assert!(result.h0_rejected);
assert!((result.p_value - 0.01).abs() < f64::EPSILON);
assert!((result.effect_size - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_tps_test_result_with_metrics() {
let metrics = TpsMetrics {
wip: Some(10.0),
throughput: Some(5.0),
cycle_time: Some(2.0),
..Default::default()
};
let result = TpsTestResult::new(TpsTestCase::LittlesLawStochastic).with_metrics(metrics);
assert_eq!(result.metrics.wip, Some(10.0));
assert_eq!(result.metrics.throughput, Some(5.0));
}
#[test]
fn test_tps_test_result_with_summary() {
let result = TpsTestResult::new(TpsTestCase::HeijunkaBullwhip).with_summary("Test passed");
assert_eq!(result.summary, "Test passed");
}
#[test]
fn test_tps_metrics_default() {
let metrics = TpsMetrics::default();
assert!(metrics.wip.is_none());
assert!(metrics.throughput.is_none());
assert!(metrics.cycle_time.is_none());
}
#[test]
fn test_tps_test_case_debug() {
let tc = TpsTestCase::SmedSetup;
let debug_str = format!("{tc:?}");
assert!(debug_str.contains("SmedSetup"));
}
#[test]
fn test_tps_test_case_clone() {
let tc = TpsTestCase::ShojinkaCrossTraining;
let cloned = tc;
assert_eq!(tc, cloned);
}
#[test]
fn test_tps_test_case_eq() {
assert_eq!(TpsTestCase::CellLayout, TpsTestCase::CellLayout);
assert_ne!(TpsTestCase::CellLayout, TpsTestCase::KingmansCurve);
}
#[test]
fn test_validate_littles_law_invalid_tolerance() {
let result = validate_littles_law(10.0, 5.0, 2.0, 0.5);
assert!(result.is_ok());
}
#[test]
fn test_tps_test_result_serialize() {
let result = TpsTestResult::new(TpsTestCase::KanbanVsDbr)
.rejected(0.05, 0.3)
.with_summary("DBR superior");
let json = serde_json::to_string(&result);
assert!(json.is_ok());
let json = json.ok().unwrap();
assert!(json.contains("KanbanVsDbr"));
}
#[test]
fn test_tps_metrics_serialize() {
let metrics = TpsMetrics {
wip: Some(10.0),
throughput: Some(5.0),
cycle_time: Some(2.0),
utilization: Some(0.85),
queue_wait: Some(5.0),
variance_ratio: Some(1.5),
safety_stock: Some(50.0),
};
let json = serde_json::to_string(&metrics);
assert!(json.is_ok());
}
#[test]
fn test_tps_test_result_builder_chain() {
let metrics = TpsMetrics {
wip: Some(15.0),
throughput: Some(3.0),
..Default::default()
};
let result = TpsTestResult::new(TpsTestCase::PushVsPull)
.rejected(0.01, 0.8)
.with_metrics(metrics)
.with_summary("Significant difference found");
assert!(result.h0_rejected);
assert_eq!(result.metrics.wip, Some(15.0));
assert!(result.summary.contains("Significant"));
}
#[test]
fn test_tps_test_case_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(TpsTestCase::PushVsPull);
set.insert(TpsTestCase::BatchSizeReduction);
assert_eq!(set.len(), 2);
assert!(set.contains(&TpsTestCase::PushVsPull));
}
}