///|
fn csv_escape(value : String) -> String {
if value.contains(",") || value.contains("\"") || value.contains("\n") {
"\"" + value.replace(old="\"", new="\"\"") + "\""
} else {
value
}
}
///|
pub fn render_batch_csv(batch : BatchRun) -> String {
render_measurements_csv(batch.records)
}
///|
pub fn render_batch_markdown(batch : BatchRun) -> String {
let summary = batch.summary()
let out = StringBuilder()
out.write_string("# Batch Measurement Report\n\n")
out.write_string("Batch: ")
out.write_string(batch.id)
out.write_string("\n\n")
out.write_string("- Total: ")
out.write_string(summary.total.to_string())
out.write_string("\n- Accepted: ")
out.write_string((summary.pass + summary.warn).to_string())
out.write_string("\n- Rejected: ")
out.write_string(summary.fail.to_string())
out.write_string("\n- Acceptance rate: ")
out.write_string(summary.acceptance_rate.to_string())
out.write_string(
"\n\n| Name | Value | Unit | Decision | Deviation |\n| --- | ---: | --- | --- | ---: |\n",
)
for measurement in batch.records {
out.write_string("| ")
out.write_string(csv_escape(measurement.name))
out.write_string(" | ")
out.write_string(measurement.value.to_string())
out.write_string(" | ")
out.write_string(measurement.unit)
out.write_string(" | ")
out.write_string(decision_name(measurement.decision))
out.write_string(" | ")
out.write_string(measurement.deviation.to_string())
out.write_string(" |\n")
}
out.to_string()
}
///|
fn decision_name(decision : MeasurementDecision) -> String {
match decision {
Pass => "PASS"
Warn => "WARN"
Fail => "FAIL"
}
}
///|
pub fn render_pipeline_markdown(
result : PipelineResult,
part_id : String,
) -> String {
let out = StringBuilder()
out.write_string("# Pipeline Result\n\nPart: ")
out.write_string(part_id)
out.write_string("\n\nStatus: ")
out.write_string(decision_name(pipeline_decision(result)))
out.write_string("\nConfidence: ")
out.write_string(result.confidence.to_string())
out.write_string("\n\n")
if result.measurements.is_empty() {
out.write_string("No measurements were produced.\n")
} else {
out.write_string(
"| Item | Value | Unit | Decision |\n| --- | ---: | --- | --- |\n",
)
for measurement in result.measurements {
out.write_string("| ")
out.write_string(measurement.name)
out.write_string(" | ")
out.write_string(measurement.value.to_string())
out.write_string(" | ")
out.write_string(measurement.unit)
out.write_string(" | ")
out.write_string(decision_name(measurement.decision))
out.write_string(" |\n")
}
}
if !result.diagnostics.is_empty() {
out.write_string("\n## Diagnostics\n")
for diagnostic in result.diagnostics {
out.write_string("- [")
out.write_string(decision_name(diagnostic.severity))
out.write_string("] ")
out.write_string(diagnostic.stage)
out.write_string(": ")
out.write_string(diagnostic.message)
out.write_string("\n")
}
}
out.to_string()
}
///|
pub fn render_quality_markdown(summary : QualitySummary) -> String {
let out = StringBuilder()
out.write_string("## Quality\n\n")
out.write_string("| Metric | Value |\n| --- | ---: |\n")
out.write_string("| Signal to noise | ")
out.write_string(summary.signal_to_noise.to_string())
out.write_string(" |\n| Edge count | ")
out.write_string(summary.edge_count.to_string())
out.write_string(" |\n| Confidence | ")
out.write_string(summary.confidence.to_string())
out.write_string(" |\n| Stable | ")
out.write_string(summary.stable.to_string())
out.write_string(" |\n")
for note in summary.notes {
out.write_string("\n> ")
out.write_string(note)
}
out.to_string()
}
///|
pub fn render_diagnostics_csv(events : Array[DiagnosticEvent]) -> String {
let out = StringBuilder()
out.write_string("code,stage,detail,value\n")
for event in events {
out.write_string(csv_escape(event.code))
out.write_string(",")
out.write_string(csv_escape(event.stage))
out.write_string(",")
out.write_string(csv_escape(event.detail))
out.write_string(",")
out.write_string(event.value.to_string())
out.write_string("\n")
}
out.to_string()
}
///|
pub fn render_calibration_markdown(fit : CalibrationFit) -> String {
let summary = residual_summary(fit)
let out = StringBuilder()
out.write_string("## Calibration\n\n")
out.write_string("- RMS error: ")
out.write_string(fit.rms_error.to_string())
out.write_string("\n")
out.write_string("- Maximum error: ")
out.write_string(fit.max_error.to_string())
out.write_string("\n")
out.write_string("- Residual median: ")
out.write_string(summary.median.to_string())
out.write_string("\n")
out.write_string("- Residual P95: ")
out.write_string(summary.percentile(0.95).to_string())
out.write_string("\n")
out.to_string()
}
///|
pub fn render_component_table(components : Array[Component]) -> String {
let out = StringBuilder()
out.write_string(
"| Label | Area | Perimeter | Circularity | Centroid |\n| ---: | ---: | ---: | ---: | --- |\n",
)
for component in components {
out.write_string("| ")
out.write_string(component.label.to_string())
out.write_string(" | ")
out.write_string(component.area.to_string())
out.write_string(" | ")
out.write_string(component.perimeter.to_string())
out.write_string(" | ")
out.write_string(component.circularity.to_string())
out.write_string(" | (")
out.write_string(component.centroid.x.to_string())
out.write_string(", ")
out.write_string(component.centroid.y.to_string())
out.write_string(") |\n")
}
out.to_string()
}
///|
pub fn render_histogram_text(histogram : Histogram) -> String {
let out = StringBuilder()
let span = histogram.maximum - histogram.minimum
for i in 0..