///|
/// A stable key/value row for human-readable metric exports.
pub(all) struct MetricRow {
key : String
value : String
unit : String
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Convert core time-domain metrics into a stable table.
pub fn metrics_rows(metrics : HrvMetrics) -> Array[MetricRow] {
[
{ key: "mean_rr", value: metrics.mean_rr.to_string(), unit: "ms" },
{ key: "mean_hr", value: metrics.mean_hr.to_string(), unit: "bpm" },
{ key: "sdnn", value: metrics.sdnn.to_string(), unit: "ms" },
{ key: "rmssd", value: metrics.rmssd.to_string(), unit: "ms" },
{ key: "pnn50", value: metrics.pnn50.to_string(), unit: "%" },
{ key: "pnn_custom", value: metrics.pnn_custom.to_string(), unit: "%" },
{
key: "total_beats",
value: metrics.quality.total_beats.to_string(),
unit: "beats",
},
{
key: "valid_beats",
value: metrics.quality.valid_beats.to_string(),
unit: "beats",
},
{
key: "ectopic_beats",
value: metrics.quality.ectopic_beats.to_string(),
unit: "beats",
},
{
key: "clean_ratio",
value: metrics.quality.clean_ratio.to_string(),
unit: "ratio",
},
]
}
///|
/// Export a diagnostic as CSV.
pub fn diagnostic_csv(diagnostic : SignalDiagnostic) -> String {
to_csv([
["metric", "value"],
["sample_count", diagnostic.sample_count.to_string()],
["duration_seconds", diagnostic.duration_seconds.to_string()],
["valid_ratio", diagnostic.valid_ratio.to_string()],
["artifact_ratio", diagnostic.artifact_ratio.to_string()],
["mean_rr", diagnostic.mean_rr.to_string()],
["median_rr", diagnostic.median_rr.to_string()],
["mean_hr", diagnostic.mean_hr.to_string()],
["hr_range", diagnostic.hr_range.to_string()],
["rmssd", diagnostic.rmssd.to_string()],
["sdnn", diagnostic.sdnn.to_string()],
["drift_slope", diagnostic.drift_slope.to_string()],
["gap_candidates", diagnostic.gap_candidates.to_string()],
["grade", diagnostic.grade],
])
}
///|
/// Export a full report's headline metrics as CSV.
pub fn report_headline_csv(report : AnalysisReport) -> String {
to_csv([
["metric", "value", "unit"],
["raw_count", report.raw_count.to_string(), "beats"],
["clean_count", report.cleaned_intervals.length().to_string(), "beats"],
["mean_rr", report.metrics.mean_rr.to_string(), "ms"],
["mean_hr", report.metrics.mean_hr.to_string(), "bpm"],
["sdnn", report.metrics.sdnn.to_string(), "ms"],
["rmssd", report.metrics.rmssd.to_string(), "ms"],
["poincare_sd1", report.poincare.sd1.to_string(), "ms"],
["poincare_sd2", report.poincare.sd2.to_string(), "ms"],
["lf_hf_ratio", report.frequency.lf_hf_ratio.to_string(), "ratio"],
["sample_entropy", report.nonlinear.sample_entropy.to_string(), "nats"],
["quality_grade", quality_grade(report.raw_validation), ""],
])
}
///|
/// Format the report for a compact terminal summary.
pub fn report_summary(report : AnalysisReport) -> String {
"HRV report: \{report.raw_count} raw beats, \{report.cleaned_intervals.length()} cleaned; mean HR \{report.metrics.mean_hr.to_string()} bpm; RMSSD \{report.metrics.rmssd.to_string()} ms; quality \{quality_grade(report.raw_validation)}"
}