///|
/// A sustained heart-rate episode.
pub(all) struct RateEpisode {
start : Int
end : Int
peak_bpm : Double
minimum_bpm : Double
average_bpm : Double
duration_seconds : Double
kind : String
} derive(FromJson, ToJson, Debug, Eq)
///|
/// A contiguous cluster of artifacts.
pub(all) struct ArtifactCluster {
start : Int
end : Int
count : Int
ratio : Double
severity : String
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Return whether a heart rate is within a closed zone.
pub fn heart_rate_in_zone(
rate : Double,
lower_bpm : Double,
upper_bpm : Double,
) -> Bool {
rate >= lower_bpm && rate <= upper_bpm
}
///|
/// Detect sustained tachycardia or bradycardia episodes.
pub fn detect_rate_episodes(
intervals : Array[Double],
lower_bpm : Double,
upper_bpm : Double,
minimum_beats : Int,
) -> Array[RateEpisode] {
let result = []
let rates = intervals_to_heart_rate(intervals)
let minimum = if minimum_beats < 1 { 1 } else { minimum_beats }
let mut start = -1
let mut kind = ""
for i in 0.. upper_bpm {
"tachycardia"
} else {
""
}
if current_kind != "" && current_kind == kind {
()
} else if current_kind != "" {
if start >= 0 && i - start >= minimum {
result.push(make_rate_episode(intervals, rates, start, i, kind))
}
start = i
kind = current_kind
} else {
if start >= 0 && i - start >= minimum {
result.push(make_rate_episode(intervals, rates, start, i, kind))
}
start = -1
kind = ""
}
}
if start >= 0 && rates.length() - start >= minimum {
result.push(
make_rate_episode(intervals, rates, start, rates.length(), kind),
)
}
result
}
///|
/// Construct one episode summary from a half-open interval.
fn make_rate_episode(
intervals : Array[Double],
rates : Array[Double],
start : Int,
end : Int,
kind : String,
) -> RateEpisode {
let values = []
let mut peak = rates[start]
let mut minimum = rates[start]
let mut duration = 0.0
for i in start.. peak {
peak = rates[i]
}
if rates[i] < minimum {
minimum = rates[i]
}
duration += intervals[i] / 1000.0
}
{
start,
end,
peak_bpm: peak,
minimum_bpm: minimum,
average_bpm: mean_value(values),
duration_seconds: duration,
kind,
}
}
///|
/// Detect clusters of invalid values in an RR stream.
pub fn detect_artifact_clusters(
intervals : Array[Double],
config : HrvConfig,
minimum_count : Int,
) -> Array[ArtifactCluster] {
let result = []
let minimum = if minimum_count < 1 { 1 } else { minimum_count }
let mut start = -1
let mut count = 0
for i in 0..= 0 && count >= minimum {
result.push(make_artifact_cluster(start, i, count, intervals.length()))
}
start = -1
count = 0
} else {
if start < 0 {
start = i
}
count += 1
}
}
if start >= 0 && count >= minimum {
result.push(
make_artifact_cluster(
start,
intervals.length(),
count,
intervals.length(),
),
)
}
result
}
///|
/// Create one artifact cluster with a severity grade.
fn make_artifact_cluster(
start : Int,
end : Int,
count : Int,
total : Int,
) -> ArtifactCluster {
let ratio = if total == 0 {
0.0
} else {
count.to_double() / total.to_double()
}
{
start,
end,
count,
ratio,
severity: if ratio >= 0.25 {
"critical"
} else if ratio >= 0.10 {
"warning"
} else {
"minor"
},
}
}
///|
/// Calculate the amount of time spent in a heart-rate zone.
pub fn time_in_heart_rate_zone(
intervals : Array[Double],
lower_bpm : Double,
upper_bpm : Double,
) -> Double {
let rates = intervals_to_heart_rate(intervals)
let mut seconds = 0.0
for i in 0.. Double {
let start = episode.end
let end = if start + observation_beats > intervals.length() {
intervals.length()
} else {
start + observation_beats
}
if end - start <= 1 {
0.0
} else {
let values = []
for i in start.. Double {
let mut duration = 0.0
for interval in intervals {
if classify_interval(interval, config) is Normal {
duration += interval / 1000.0
}
}
duration
}