///|
/// Control limits for a stable sequence of observed values.
pub(all) struct ControlLimits {
center : Double
lower : Double
upper : Double
sensitivity : Double
}
///|
/// Observation analysis combining specification and process views.
pub(all) struct ObservationAnalysis {
feature_id : String
count : Int
summary : BatchSummary
within_control_limit_count : Int
outlier_count : Int
mean_residual : Double
trend : String
repeat_consistent : Bool
}
///|
/// Select observations belonging to a feature.
pub fn observations_for(
feature~ : Feature,
observations~ : Array[Observation],
) -> Array[Observation] {
let result = []
for observation in observations {
if observation.feature_id == feature.id {
result.push(observation)
}
}
result
}
///|
/// Return values belonging to a feature in observation order.
pub fn observation_values_for(
feature~ : Feature,
observations~ : Array[Observation],
) -> Array[Double] {
let result = []
for observation in observations_for(feature~, observations~) {
result.push(observation.value)
}
result
}
///|
/// Return signed residuals from nominal values.
pub fn residuals(feature~ : Feature, values~ : Array[Double]) -> Array[Double] {
let result = []
for value in values {
result.push(signed_deviation(feature, value))
}
result
}
///|
/// Build symmetric control limits around the observed mean.
pub fn control_limits(
values~ : Array[Double],
sensitivity~ : Double,
) -> ControlLimits {
let center = mean(values)
let spread = population_stddev(values)
let width = spread * sensitivity.abs()
{ center, lower: center - width, upper: center + width, sensitivity }
}
///|
/// Return flags for values outside control limits.
pub fn outlier_flags(
values~ : Array[Double],
limits~ : ControlLimits,
) -> Array[Bool] {
let result = []
for value in values {
result.push(value < limits.lower || value > limits.upper)
}
result
}
///|
/// Count values inside control limits.
pub fn within_control_limit_count(
values~ : Array[Double],
limits~ : ControlLimits,
) -> Int {
let mut result = 0
for value in values {
if value >= limits.lower && value <= limits.upper {
result += 1
}
}
result
}
///|
/// Check repeatability against the full bilateral tolerance width.
pub fn repeated_measurement_consistent(
feature~ : Feature,
values : Array[Double],
) -> Bool {
values.length() <= 1 || range(values) <= (feature.upper - feature.lower).abs()
}
///|
/// Analyze feature observations with process and specification metrics.
pub fn analyze_observations(
feature~ : Feature,
observations~ : Array[Observation],
uncertainty~ : Double,
) -> ObservationAnalysis {
let selected = observations_for(feature~, observations~)
let values = observation_values_for(feature~, observations~)
let summary = summarize_batch(feature~, observations=selected)
let limits = control_limits(values~, sensitivity=3.0)
let flags = outlier_flags(values~, limits~)
let mut outlier_count = 0
for flag in flags {
if flag {
outlier_count += 1
}
}
let mut residual_total = 0.0
for residual in residuals(feature~, values~) {
residual_total += residual
}
let mean_residual = if values.length() == 0 {
0.0
} else {
residual_total / values.length().to_double()
}
let updated_capability = capability_indices(feature~, values~, uncertainty~)
let adjusted_summary = BatchSummary::{
feature_id: summary.feature_id,
count: summary.count,
pass_count: summary.pass_count,
fail_count: summary.fail_count,
unmeasured_count: summary.unmeasured_count,
mean: summary.mean,
minimum: summary.minimum,
maximum: summary.maximum,
range: summary.range,
standard_deviation: summary.standard_deviation,
defect_rate: summary.defect_rate,
capability: updated_capability,
}
{
feature_id: feature.id,
count: values.length(),
summary: adjusted_summary,
within_control_limit_count: within_control_limit_count(values~, limits~),
outlier_count,
mean_residual,
trend: batch_trend(values),
repeat_consistent: repeated_measurement_consistent(feature~, values),
}
}
///|
/// Return a compact risk label for an analysis.
pub fn analysis_risk(analysis : ObservationAnalysis) -> RiskLevel {
if analysis.outlier_count > 0 || analysis.summary.fail_count > 0 {
RiskLevel::High
} else if !analysis.repeat_consistent || !analysis.summary.capability.adequate {
RiskLevel::Medium
} else {
RiskLevel::Low
}
}