///|
pub fn measure_region(
  region : Component,
  unit : String,
  scale : Double,
) -> Array[Measurement] {
  let area = Double::from_int(region.area) * scale * scale
  [
    { name: "area", value: area, unit, decision: Pass, deviation: 0.0 },
    {
      name: "perimeter",
      value: region.perimeter * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "width",
      value: bounding_width(region.bounds) * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "height",
      value: bounding_height(region.bounds) * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
  ]
}

///|
pub fn measure_polygon(
  points : Array[Point],
  unit : String,
  scale : Double,
) -> Array[Measurement] {
  let metrics = polygon_metrics(points)
  [
    {
      name: "area",
      value: metrics.area * scale * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "perimeter",
      value: metrics.perimeter * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "circularity",
      value: metrics.circularity,
      unit: "ratio",
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "rectangularity",
      value: metrics.rectangularity,
      unit: "ratio",
      decision: Pass,
      deviation: 0.0,
    },
  ]
}

///|
pub fn measure_circle(
  circle : Circle,
  unit : String,
  scale : Double,
) -> Array[Measurement] {
  [
    {
      name: "diameter",
      value: measure_diameter(circle) * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "radius",
      value: circle.radius * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "roundness",
      value: circle.rms_error * scale,
      unit,
      decision: Pass,
      deviation: 0.0,
    },
  ]
}

///|
pub fn measure_line_feature(
  line : Line,
  points : Array[Point],
  unit : String,
) -> Array[Measurement] {
  let residuals = line_residuals(line, points)
  [
    {
      name: "line-mean-error",
      value: mean_value(residuals),
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "line-max-error",
      value: maximum_value(residuals),
      unit,
      decision: Pass,
      deviation: 0.0,
    },
    {
      name: "line-repeatability",
      value: repeatability(residuals),
      unit,
      decision: Pass,
      deviation: 0.0,
    },
  ]
}

///|
pub fn apply_tolerances(
  measurements : Array[Measurement],
  tolerances : Array[Tolerance],
) -> Array[Measurement] {
  let result : Array[Measurement] = []
  for i in 0.. PipelineResult {
  let diagnostics : Array[PipelineDiagnostic] = []
  let mut confidence = 1.0
  let mut accepted = true
  for measurement in measurements {
    confidence *= 1.0 - decision_penalty(measurement.decision) / 2.0
    if measurement.decision == Fail {
      accepted = false
    }
  }
  { measurements, diagnostics, accepted, confidence }
}

///|
pub fn suite_merge(
  first : PipelineResult,
  second : PipelineResult,
) -> PipelineResult {
  let measurements = first.measurements
  measurements.append(second.measurements)
  let diagnostics = first.diagnostics
  diagnostics.append(second.diagnostics)
  {
    measurements,
    diagnostics,
    accepted: first.accepted && second.accepted,
    confidence: first.confidence * second.confidence,
  }
}

///|
pub fn measurement_names(measurements : Array[Measurement]) -> Array[String] {
  measurements.map(fn(measurement) { measurement.name })
}

///|
pub fn measurement_values(measurements : Array[Measurement]) -> Array[Double] {
  measurements.map(fn(measurement) { measurement.value })
}

///|
pub fn measurement_by_name(
  measurements : Array[Measurement],
  name : String,
) -> Measurement? {
  for measurement in measurements {
    if measurement.name == name {
      return Some(measurement)
    }
  }
  None
}

///|
pub fn suite_statistics(result : PipelineResult) -> StatsSummary {
  describe_samples(measurement_values(result.measurements))
}

///|
pub fn suite_decision(result : PipelineResult) -> MeasurementDecision {
  pipeline_decision(result)
}

///|
pub fn suite_csv(result : PipelineResult) -> String {
  render_measurements_csv(result.measurements)
}

///|
pub fn suite_markdown(result : PipelineResult, part_id : String) -> String {
  render_pipeline_markdown(result, part_id)
}

///|
pub fn stable_measurement_order(
  measurements : Array[Measurement],
) -> Array[Measurement] {
  let result : Array[Measurement] = []
  for measurement in measurements {
    let mut index = 0
    while index < result.length() && result[index].name <= measurement.name {
      index += 1
    }
    result.insert(index, measurement)
  }
  result
}

///|
pub fn measurement_delta(
  first : Array[Measurement],
  second : Array[Measurement],
) -> Array[Measurement] {
  let result : Array[Measurement] = []
  for i in 0..