///|
/// One detected artifact with enough context for audit logs.
pub(all) struct ArtifactEvent {
index : Int
value : Double
kind : IntervalDisposition
replacement : Double
confidence : Double
left_neighbor : Double
right_neighbor : Double
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Result of an auditable cleaning pass.
pub(all) struct CleaningResult {
intervals : Array[Double]
quality : QualityReport
validation : IntervalValidation
events : Array[ArtifactEvent]
changed_count : Int
max_correction : Double
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Return a valid neighbor on the left, if any.
fn left_valid_value(
intervals : Array[Double],
index : Int,
config : HrvConfig,
) -> Double? {
let mut i = index - 1
while i >= 0 {
if classify_interval(intervals[i], config) is Normal {
return Some(intervals[i])
}
i -= 1
}
None
}
///|
/// Return a valid neighbor on the right, if any.
fn right_valid_value(
intervals : Array[Double],
index : Int,
config : HrvConfig,
) -> Double? {
let mut i = index + 1
while i < intervals.length() {
if classify_interval(intervals[i], config) is Normal {
return Some(intervals[i])
}
i += 1
}
None
}
///|
/// Interpolate an artifact from the nearest valid neighbors.
pub fn interpolate_artifact(
intervals : Array[Double],
index : Int,
config : HrvConfig,
) -> Double {
let left = left_valid_value(intervals, index, config)
let right = right_valid_value(intervals, index, config)
match (left, right) {
(Some(l), Some(r)) => (l + r) / 2.0
(Some(l), None) => l
(None, Some(r)) => r
(None, None) => config.min_rr
}
}
///|
/// Detect artifacts and record the replacement used by a cleaning method.
pub fn inspect_cleaning(
intervals : Array[Double],
cleaning_method : CleaningMethod,
config : HrvConfig,
) -> CleaningResult {
let (cleaned, quality) = clean_rr_intervals(
intervals, cleaning_method, config,
)
let validation = validate_intervals(intervals, config)
let events = []
let mut changed_count = 0
let mut max_correction = 0.0
let mut output_index = 0
let removes = match cleaning_method {
Remove => true
_ => false
}
for i in 0.. 0.0 {
changed_count += 1
if correction > max_correction {
max_correction = correction
}
}
output_index += 1
}
} else {
let replacement = if cleaning_method is Remove {
0.0
} else if output_index < cleaned.length() {
cleaned[output_index]
} else {
interpolate_artifact(intervals, i, config)
}
let confidence = if replacement == 0.0 {
0.0
} else {
1.0 /
(
1.0 +
absolute_difference(intervals[i], replacement) / replacement.abs()
)
}
let left = left_valid_value(intervals, i, config)
let right = right_valid_value(intervals, i, config)
let left_value = match left {
Some(value) => value
None => 0.0
}
let right_value = match right {
Some(value) => value
None => 0.0
}
events.push({
index: i,
value: intervals[i],
kind: disposition,
replacement,
confidence,
left_neighbor: left_value,
right_neighbor: right_value,
})
if !removes {
changed_count += 1
}
if replacement > 0.0 {
let correction = absolute_difference(intervals[i], replacement)
if correction > max_correction {
max_correction = correction
}
}
if !removes {
output_index += 1
}
}
}
{
intervals: cleaned,
quality,
validation,
events,
changed_count,
max_correction,
}
}
///|
/// Replace isolated invalid observations while preserving valid runs.
pub fn repair_invalid_intervals(
intervals : Array[Double],
config : HrvConfig,
) -> Array[Double] {
let result = []
for i in 0.. Array[Double] {
let result = []
if radius < 0 {
return result
}
for i in 0.. intervals.length() {
intervals.length()
} else {
i + radius + 1
}
for j in start.. Array[Double] {
let result = []
let center = median_value(intervals)
let mad = median_absolute_deviation(intervals)
let scale = if mad == 0.0 { 0.0 } else { mad * 1.4826 }
let fence = (if multiplier < 0.0 { 0.0 } else { multiplier }) * scale
for value in intervals {
if (scale == 0.0 && value == center) ||
(scale != 0.0 && absolute_difference(value, center) <= fence) {
result.push(value)
}
}
result
}
///|
/// Return a compact cleaning feature vector.
pub fn cleaning_feature_vector(result : CleaningResult) -> Array[Double] {
[
result.quality.clean_ratio,
result.events.length().to_double(),
result.changed_count.to_double(),
result.max_correction,
result.validation.duplicates.to_double(),
result.validation.monotonic_breaks.to_double(),
result.validation.too_short.to_double(),
result.validation.too_long.to_double(),
result.validation.non_finite.to_double(),
result.validation.median,
]
}