///|
/// Stable text exports for dashboards, audit logs, and command-line use.
///|
/// One serializable result row.
pub(all) struct ExportRow {
key : String
value : String
unit : String
status : String
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Export document with version and provenance.
pub(all) struct ExportDocument {
schema : String
generated_by : String
rows : Array[ExportRow]
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Create one row.
pub fn export_row(
key : String,
value : Double,
unit : String,
status : String,
) -> ExportRow {
{ key, value: value.to_string(), unit, status }
}
///|
/// Convert an analysis report to a stable row set.
pub fn analysis_export_rows(report : AnalysisReport) -> Array[ExportRow] {
[
export_row("raw_count", report.raw_count.to_double(), "beats", "ok"),
export_row(
"cleaned_count",
report.cleaned_intervals.length().to_double(),
"beats",
"ok",
),
export_row("mean_rr", report.metrics.mean_rr, "ms", "ok"),
export_row("mean_hr", report.metrics.mean_hr, "bpm", "ok"),
export_row("sdnn", report.metrics.sdnn, "ms", "ok"),
export_row("rmssd", report.metrics.rmssd, "ms", "ok"),
export_row("pnn50", report.metrics.pnn50, "%", "ok"),
export_row(
"frequency_total_power",
report.frequency.total_power,
"ms2",
"ok",
),
export_row(
"frequency_centroid",
report.frequency.spectral_centroid_hz,
"hz",
"ok",
),
export_row("sample_entropy", report.nonlinear.sample_entropy, "", "ok"),
export_row(
"feature_count",
report.feature_vector.length().to_double(),
"features",
"ok",
),
export_row(
"quality_ratio",
report.quality.clean_ratio,
"ratio",
if report.quality.is_low_quality {
"review"
} else {
"ok"
},
),
]
}
///|
/// Create a document for a report.
pub fn analysis_export_document(report : AnalysisReport) -> ExportDocument {
{
schema: "hrvkit.report.v1",
generated_by: "moonbit-hrvkit",
rows: analysis_export_rows(report),
}
}
///|
/// Escape a value for a simple key-value export.
pub fn export_escape(value : String) -> String {
let result = StringBuilder::new()
for character in value {
if character == '\\' {
result.write_string("\\\\")
} else if character == '"' {
result.write_string("\\\"")
} else if character == '\n' {
result.write_string("\\n")
} else {
result.write_char(character)
}
}
result.to_string()
}
///|
/// Export rows as a compact JSON-like line protocol without external state.
pub fn export_rows_ndjson(rows : Array[ExportRow]) -> String {
let result = StringBuilder::new()
for row in rows {
result.write_string("{\"key\":\"")
result.write_string(export_escape(row.key))
result.write_string("\",\"value\":\"")
result.write_string(export_escape(row.value))
result.write_string("\",\"unit\":\"")
result.write_string(export_escape(row.unit))
result.write_string("\",\"status\":\"")
result.write_string(export_escape(row.status))
result.write_string("\"}\n")
}
result.to_string()
}
///|
/// Export a document as a CSV table.
pub fn export_document_csv(document : ExportDocument) -> String {
let grid = [["key", "value", "unit", "status"]]
for row in document.rows {
grid.push([row.key, row.value, row.unit, row.status])
}
to_csv(grid)
}
///|
/// Export a report as a Markdown table.
pub fn analysis_report_markdown(report : AnalysisReport) -> String {
let result = StringBuilder::new()
result.write_string("# HRV analysis report\n\n")
result.write_string("Generated by moonbit-hrvkit.\n\n")
result.write_string(
"| Metric | Value | Unit | Status |\n| --- | ---: | --- | --- |\n",
)
for row in analysis_export_rows(report) {
result.write_string("| ")
result.write_string(row.key)
result.write_string(" | ")
result.write_string(row.value)
result.write_string(" | ")
result.write_string(row.unit)
result.write_string(" | ")
result.write_string(row.status)
result.write_string(" |\n")
}
result.to_string()
}
///|
/// Create a Markdown report with the quality summary and feature vector size.
pub fn analysis_report_markdown_with_quality(report : AnalysisReport) -> String {
let result = analysis_report_markdown(report)
result + "\nQuality: " + analysis_quality_summary(report) + "\n"
}
///|
/// Export a batch of observations as CSV.
pub fn session_observations_csv(
observations : Array[SessionObservation],
) -> String {
let grid = [
[
"session_id", "date", "duration_minutes", "rmssd", "sdnn", "mean_hr", "quality_score",
"training_load",
],
]
for observation in observations {
grid.push([
observation.session_id,
observation.date,
observation.duration_minutes.to_string(),
observation.rmssd.to_string(),
observation.sdnn.to_string(),
observation.mean_hr.to_string(),
observation.quality_score.to_string(),
observation.training_load.to_string(),
])
}
to_csv(grid)
}
///|
/// Add a provenance footer to a text export.
pub fn export_with_provenance(content : String, source : String) -> String {
content +
"\n# source=" +
export_escape(source) +
"\n# schema=hrvkit.report.v1\n"
}
///|
/// Return whether an export row contains a finite numeric value.
pub fn export_row_is_numeric(row : ExportRow) -> Bool {
let parsed = @strconv.from_str(row.value) catch { _ => 0.0 }
let failed = @strconv.from_str(row.value) catch { _ => -1.0 }
parsed == parsed && !parsed.is_inf() && row.value != "" && failed != -1.0
}
///|
/// Return the number of rows marked for review.
pub fn export_review_count(rows : Array[ExportRow]) -> Int {
let mut count = 0
for row in rows {
if row.status == "review" {
count += 1
}
}
count
}
///|
/// Return a compact one-line summary for logs.
pub fn export_log_line(report : AnalysisReport) -> String {
"hrvkit raw=" +
report.raw_count.to_string() +
" clean=" +
report.cleaned_intervals.length().to_string() +
" rmssd=" +
report.metrics.rmssd.to_string() +
" quality=" +
report.quality.clean_ratio.to_string()
}