///|
/// Supplier risk tier used to set incoming inspection intensity.
pub(all) enum SupplierRiskTier {
SupplierApproved
SupplierMonitored
SupplierRestricted
SupplierDisqualified
} derive(Debug, Eq)
///|
/// Supplier master record with quality, delivery, and continuity indicators.
pub struct ReliabilitySupplier {
supplier_id : String
name : String
tier : SupplierRiskTier
quality_ppm : Double
on_time_rate : Double
audit_score : Double
lead_time : Double
lead_time_variability : Double
annual_spend : Double
single_source : Bool
}
///|
pub fn reliability_supplier(
supplier_id : String,
name : String,
tier : SupplierRiskTier,
quality_ppm : Double,
on_time_rate : Double,
audit_score : Double,
lead_time : Double,
lead_time_variability : Double,
annual_spend : Double,
single_source : Bool,
) -> ReliabilitySupplier {
if quality_ppm < 0.0 ||
on_time_rate < 0.0 ||
on_time_rate > 1.0 ||
audit_score < 0.0 ||
audit_score > 1.0 ||
lead_time < 0.0 ||
lead_time_variability < 0.0 ||
annual_spend < 0.0 {
abort("invalid supplier record")
}
{
supplier_id,
name,
tier,
quality_ppm,
on_time_rate,
audit_score,
lead_time,
lead_time_variability,
annual_spend,
single_source,
}
}
///|
pub fn reliability_supplier_quality_rate(
supplier : ReliabilitySupplier,
) -> Double {
1.0 / (1.0 + supplier.quality_ppm / 1000.0)
}
///|
pub fn reliability_supplier_delivery_score(
supplier : ReliabilitySupplier,
) -> Double {
supplier.on_time_rate *
@math.exp(-supplier.lead_time_variability / (1.0 + supplier.lead_time))
}
///|
pub fn reliability_supplier_continuity_score(
supplier : ReliabilitySupplier,
) -> Double {
let concentration_penalty = if supplier.single_source { 0.25 } else { 0.0 }
(supplier.audit_score * (1.0 - concentration_penalty)).max(0.0)
}
///|
pub fn reliability_supplier_risk_score(
supplier : ReliabilitySupplier,
) -> Double {
let quality = reliability_supplier_quality_rate(supplier)
let delivery = reliability_supplier_delivery_score(supplier)
let continuity = reliability_supplier_continuity_score(supplier)
(0.5 * quality + 0.3 * delivery + 0.2 * continuity).max(0.0).min(1.0)
}
///|
pub fn reliability_supplier_should_escalate(
supplier : ReliabilitySupplier,
minimum_score : Double,
) -> Bool {
reliability_supplier_risk_score(supplier) < minimum_score ||
supplier.tier is SupplierRestricted ||
supplier.tier is SupplierDisqualified
}
///|
pub fn reliability_supplier_recommended_sample_fraction(
supplier : ReliabilitySupplier,
) -> Double {
match supplier.tier {
SupplierApproved => 0.05
SupplierMonitored => 0.2
SupplierRestricted => 0.5
SupplierDisqualified => 1.0
}
}
///|
/// A received lot and its production risk exposure.
pub struct ReliabilitySupplierLot {
lot_id : String
supplier_id : String
part_id : Int
quantity : Int
unit_cost : Double
received_at : Double
promised_at : Double
criticality : Double
traceability_complete : Bool
}
///|
pub fn reliability_supplier_lot(
lot_id : String,
supplier_id : String,
part_id : Int,
quantity : Int,
unit_cost : Double,
received_at : Double,
promised_at : Double,
criticality : Double,
traceability_complete : Bool,
) -> ReliabilitySupplierLot {
if part_id < 0 ||
quantity < 0 ||
unit_cost < 0.0 ||
received_at < 0.0 ||
promised_at < 0.0 ||
criticality < 0.0 {
abort("invalid supplier lot")
}
{
lot_id,
supplier_id,
part_id,
quantity,
unit_cost,
received_at,
promised_at,
criticality,
traceability_complete,
}
}
///|
pub fn reliability_supplier_lot_total_value(
lot : ReliabilitySupplierLot,
) -> Double {
lot.quantity.to_double() * lot.unit_cost
}
///|
pub fn reliability_supplier_lot_is_late(lot : ReliabilitySupplierLot) -> Bool {
lot.received_at > lot.promised_at
}
///|
pub fn reliability_supplier_lot_delay(lot : ReliabilitySupplierLot) -> Double {
(lot.received_at - lot.promised_at).max(0.0)
}
///|
pub fn reliability_supplier_lot_risk(
lot : ReliabilitySupplierLot,
supplier : ReliabilitySupplier,
) -> Double {
let lateness = reliability_supplier_lot_delay(lot)
let traceability = if lot.traceability_complete { 0.0 } else { 1.0 }
let quality_risk = 1.0 - reliability_supplier_risk_score(supplier)
(lot.criticality *
(quality_risk + traceability + lateness / (1.0 + lot.promised_at))).max(0.0)
}
///|
/// Incoming inspection result for a lot.
pub struct ReliabilityIncomingInspection {
inspection_id : Int
lot_id : String
sample_size : Int
defect_count : Int
critical_defects : Int
inspected_value : Double
accepted : Bool
inspection_time : Double
}
///|
pub fn reliability_incoming_inspection(
inspection_id : Int,
lot_id : String,
sample_size : Int,
defect_count : Int,
critical_defects : Int,
inspected_value : Double,
accepted : Bool,
inspection_time : Double,
) -> ReliabilityIncomingInspection {
if inspection_id < 0 ||
sample_size < 0 ||
defect_count < 0 ||
critical_defects < 0 ||
defect_count > sample_size ||
critical_defects > defect_count ||
inspected_value < 0.0 ||
inspection_time < 0.0 {
abort("invalid incoming inspection")
}
{
inspection_id,
lot_id,
sample_size,
defect_count,
critical_defects,
inspected_value,
accepted,
inspection_time,
}
}
///|
pub fn reliability_incoming_defect_rate(
inspection : ReliabilityIncomingInspection,
) -> Double {
if inspection.sample_size == 0 {
0.0
} else {
inspection.defect_count.to_double() / inspection.sample_size.to_double()
}
}
///|
pub fn reliability_incoming_critical_rate(
inspection : ReliabilityIncomingInspection,
) -> Double {
if inspection.sample_size == 0 {
0.0
} else {
inspection.critical_defects.to_double() / inspection.sample_size.to_double()
}
}
///|
pub fn reliability_incoming_yield(
inspection : ReliabilityIncomingInspection,
) -> Double {
1.0 - reliability_incoming_defect_rate(inspection)
}
///|
pub fn reliability_incoming_cost_per_unit(
inspection : ReliabilityIncomingInspection,
) -> Double {
if inspection.sample_size == 0 {
0.0
} else {
inspection.inspected_value / inspection.sample_size.to_double()
}
}
///|
/// Attribute sampling plan derived from supplier risk and lot criticality.
pub struct ReliabilityIncomingPlan {
sample_size : Int
acceptance_number : Int
rejection_number : Int
inspection_fraction : Double
rationale : String
}
///|
pub fn reliability_incoming_plan(
lot_size : Int,
supplier : ReliabilitySupplier,
criticality : Double,
) -> ReliabilityIncomingPlan {
if lot_size < 0 || criticality < 0.0 {
abort("invalid incoming plan")
}
let fraction = (reliability_supplier_recommended_sample_fraction(supplier) *
(0.5 + criticality.min(1.0))).min(1.0)
let sample = (lot_size.to_double() * fraction)
.ceil()
.to_int()
.min(lot_size)
.max(0)
let acceptance = if supplier.tier is SupplierApproved { 1 } else { 0 }
let rationale = if supplier.single_source {
"single-source continuity protection"
} else {
"risk-based incoming inspection"
}
{
sample_size: sample,
acceptance_number: acceptance,
rejection_number: acceptance + 1,
inspection_fraction: fraction,
rationale,
}
}
///|
pub fn reliability_incoming_plan_expected_cost(
plan : ReliabilityIncomingPlan,
inspection_cost_per_unit : Double,
failure_cost : Double,
defect_rate : Double,
lot_size : Int,
) -> Double {
if inspection_cost_per_unit < 0.0 ||
failure_cost < 0.0 ||
defect_rate < 0.0 ||
lot_size < 0 {
abort("invalid incoming plan cost")
}
plan.sample_size.to_double() * inspection_cost_per_unit +
lot_size.to_double() *
defect_rate *
failure_cost *
(1.0 - plan.inspection_fraction)
}
///|
pub fn reliability_incoming_plan_is_full_inspection(
plan : ReliabilityIncomingPlan,
) -> Bool {
plan.inspection_fraction >= 1.0
}
///|
/// Monthly quality scorecard used in supplier business reviews.
pub struct ReliabilitySupplierScorecard {
supplier_id : String
lots : Int
inspected_units : Int
defect_units : Int
rejected_lots : Int
total_value : Double
on_time_deliveries : Int
late_deliveries : Int
audit_score : Double
}
///|
pub fn reliability_supplier_scorecard(
supplier_id : String,
lots : Int,
inspected_units : Int,
defect_units : Int,
rejected_lots : Int,
total_value : Double,
on_time_deliveries : Int,
late_deliveries : Int,
audit_score : Double,
) -> ReliabilitySupplierScorecard {
if lots < 0 ||
inspected_units < 0 ||
defect_units < 0 ||
rejected_lots < 0 ||
total_value < 0.0 ||
on_time_deliveries < 0 ||
late_deliveries < 0 ||
audit_score < 0.0 ||
audit_score > 1.0 {
abort("invalid supplier scorecard")
}
{
supplier_id,
lots,
inspected_units,
defect_units,
rejected_lots,
total_value,
on_time_deliveries,
late_deliveries,
audit_score,
}
}
///|
pub fn reliability_scorecard_defect_rate(
scorecard : ReliabilitySupplierScorecard,
) -> Double {
if scorecard.inspected_units == 0 {
0.0
} else {
scorecard.defect_units.to_double() / scorecard.inspected_units.to_double()
}
}
///|
pub fn reliability_scorecard_rejection_rate(
scorecard : ReliabilitySupplierScorecard,
) -> Double {
if scorecard.lots == 0 {
0.0
} else {
scorecard.rejected_lots.to_double() / scorecard.lots.to_double()
}
}
///|
pub fn reliability_scorecard_delivery_rate(
scorecard : ReliabilitySupplierScorecard,
) -> Double {
let deliveries = scorecard.on_time_deliveries + scorecard.late_deliveries
if deliveries == 0 {
0.0
} else {
scorecard.on_time_deliveries.to_double() / deliveries.to_double()
}
}
///|
pub fn reliability_scorecard_quality_score(
scorecard : ReliabilitySupplierScorecard,
) -> Double {
let defect = (1.0 - reliability_scorecard_defect_rate(scorecard)).max(0.0)
let rejection = (1.0 - reliability_scorecard_rejection_rate(scorecard)).max(
0.0,
)
let delivery = reliability_scorecard_delivery_rate(scorecard)
(0.5 * defect + 0.2 * rejection + 0.2 * delivery + 0.1 * scorecard.audit_score).min(
1.0,
)
}
///|
pub fn reliability_scorecard_recommendation(
scorecard : ReliabilitySupplierScorecard,
) -> SupplierRiskTier {
let score = reliability_scorecard_quality_score(scorecard)
if score >= 0.9 {
SupplierRiskTier::SupplierApproved
} else if score >= 0.7 {
SupplierRiskTier::SupplierMonitored
} else if score >= 0.4 {
SupplierRiskTier::SupplierRestricted
} else {
SupplierRiskTier::SupplierDisqualified
}
}
///|
pub fn reliability_supplier_portfolio_score(
suppliers : Array[ReliabilitySupplier],
) -> Double {
if suppliers.is_empty() {
0.0
} else {
let spend = suppliers.fold(init=0.0, (sum, supplier) => {
sum + supplier.annual_spend
})
if spend == 0.0 {
suppliers.fold(init=0.0, (sum, supplier) => {
sum + reliability_supplier_risk_score(supplier)
}) /
suppliers.length().to_double()
} else {
suppliers.fold(init=0.0, (sum, supplier) => {
sum + reliability_supplier_risk_score(supplier) * supplier.annual_spend
}) /
spend
}
}
}
///|
pub fn reliability_supplier_restricted_ids(
suppliers : Array[ReliabilitySupplier],
threshold : Double,
) -> Array[String] {
suppliers.filter_map(supplier => {
if reliability_supplier_should_escalate(supplier, threshold) {
Some(supplier.supplier_id)
} else {
None
}
})
}
///|
pub fn reliability_supplier_concentration(
suppliers : Array[ReliabilitySupplier],
) -> Double {
if suppliers.is_empty() {
0.0
} else {
let total = suppliers.fold(init=0.0, (sum, supplier) => {
sum + supplier.annual_spend
})
if total == 0.0 {
0.0
} else {
let largest = suppliers.fold(init=0.0, (best, supplier) => {
best.max(supplier.annual_spend)
})
largest / total
}
}
}
///|
pub fn reliability_supplier_diversification_score(
suppliers : Array[ReliabilitySupplier],
) -> Double {
1.0 - reliability_supplier_concentration(suppliers)
}
///|
pub fn reliability_supplier_portfolio_risk(
suppliers : Array[ReliabilitySupplier],
) -> Double {
let quality = reliability_supplier_portfolio_score(suppliers)
let diversity = reliability_supplier_diversification_score(suppliers)
(1.0 - (0.8 * quality + 0.2 * diversity)).max(0.0).min(1.0)
}
///|
pub fn reliability_supplier_resilience_actions(
suppliers : Array[ReliabilitySupplier],
) -> Array[String] {
let actions = Array::new()
if reliability_supplier_concentration(suppliers) > 0.6 {
actions.push("qualify a second source for the highest-spend part")
}
if reliability_supplier_portfolio_score(suppliers) < 0.7 {
actions.push("open supplier corrective-action plans")
}
for supplier in suppliers {
if supplier.lead_time_variability > supplier.lead_time {
actions.push("review safety stock for a variable-lead-time supplier")
}
}
actions
}
///|
pub fn reliability_supply_chain_checksum(
suppliers : Array[ReliabilitySupplier],
scorecards : Array[ReliabilitySupplierScorecard],
) -> Double {
reliability_supplier_portfolio_score(suppliers) * 100.0 +
reliability_supplier_portfolio_risk(suppliers) * 10.0 +
scorecards.fold(init=0.0, (sum, scorecard) => {
sum + reliability_scorecard_quality_score(scorecard)
})
}