///|
/// Baseline state used by repeated morning measurements.
pub(all) struct MorningBaselineState {
count : Int
center : Double
deviation : Double
lower : Double
upper : Double
trend_slope : Double
latest_status : String
missing_days : Int
} derive(FromJson, ToJson, Debug, Eq)
///|
/// An explainable alert emitted for a morning series.
pub(all) struct MorningAlert {
date : String
severity : String
code : String
message : String
value : Double
baseline : Double
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Build a baseline state from the historical values before today.
pub fn baseline_state(
history : Array[MorningMeasurement],
today_index : Int,
config : HrvConfig,
) -> MorningBaselineState {
let values = []
let dates = []
let bound = if today_index < 0 {
0
} else if today_index > history.length() {
history.length()
} else {
today_index
}
for i in 0.. robust.upper {
"High"
} else {
"Normal"
}
{
count: values.length(),
center: robust.center,
deviation: robust.spread,
lower: robust.lower,
upper: robust.upper,
trend_slope: trend.slope,
latest_status: status,
missing_days: missing_calendar_days(dates),
}
}
///|
/// Detect low/high RMSSD and date-quality alerts.
pub fn detect_morning_alerts(
history : Array[MorningMeasurement],
config : HrvConfig,
) -> Array[MorningAlert] {
let result = []
for i in 0.. 1 && measurement.rmssd < state.lower {
result.push({
date: measurement.date,
severity: if measurement.rmssd < state.lower - state.deviation {
"critical"
} else {
"warning"
},
code: "rmssd_below_baseline",
message: "RMSSD is below the personal morning range",
value: measurement.rmssd,
baseline: state.center,
})
} else if state.count > 1 && measurement.rmssd > state.upper {
result.push({
date: measurement.date,
severity: "info",
code: "rmssd_above_baseline",
message: "RMSSD is above the personal morning range",
value: measurement.rmssd,
baseline: state.center,
})
}
if parse_date(measurement.date) is None {
result.push({
date: measurement.date,
severity: "error",
code: "invalid_date",
message: "Morning measurement date is not YYYY-MM-DD",
value: measurement.rmssd,
baseline: state.center,
})
}
}
result
}
///|
/// Produce a robust status for the most recent measurement.
pub fn latest_morning_status(
history : Array[MorningMeasurement],
config : HrvConfig,
) -> MorningBaselineState {
baseline_state(history, history.length(), config)
}
///|
/// Return a seven-day exponentially weighted RMSSD baseline.
pub fn ewma_morning_baseline(
history : Array[MorningMeasurement],
alpha : Double,
) -> Array[Double] {
let values = []
for measurement in history {
values.push(measurement.rmssd)
}
exponential_moving_average(values, alpha)
}
///|
/// Score the current history using a robust baseline and trend.
pub fn score_latest_morning(
history : Array[MorningMeasurement],
config : HrvConfig,
) -> ReadinessScore {
if history.length() == 0 {
return calculate_readiness(
0.0,
calculate_robust_baseline([], 3.5, config.normal_range_sd_factor),
0.0,
0.0,
)
}
let prior = []
for i in 0..<(history.length() - 1) {
prior.push(history[i].rmssd)
}
let baseline = calculate_robust_baseline(
prior,
3.5,
config.normal_range_sd_factor,
)
let trend = fit_linear_trend(prior).slope
calculate_readiness(history[history.length() - 1].rmssd, baseline, trend, 1.0)
}