///|
/// Protocol planning and compliance helpers for repeatable recordings.
///|
/// A protocol step shown to an operator.
pub(all) struct ProtocolStep {
ordinal : Int
name : String
duration_seconds : Double
instruction : String
required : Bool
} derive(FromJson, ToJson, Debug, Eq)
///|
/// A complete operator protocol.
pub(all) struct ProtocolPlan {
name : String
version : String
steps : Array[ProtocolStep]
target_beats : Int
target_duration_seconds : Double
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Completion state for a protocol step.
pub(all) struct ProtocolStepResult {
ordinal : Int
observed_seconds : Double
completed : Bool
deviation_seconds : Double
note : String
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Protocol completion report.
pub(all) struct ProtocolRunReport {
plan_name : String
completed_steps : Int
required_steps : Int
completion_ratio : Double
duration_error_seconds : Double
passed : Bool
results : Array[ProtocolStepResult]
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Make a resting morning protocol plan.
pub fn morning_protocol_plan() -> ProtocolPlan {
{
name: "morning-resting-hrv",
version: "1.0",
steps: [
{
ordinal: 1,
name: "settle",
duration_seconds: 120.0,
instruction: "sit quietly and breathe normally",
required: true,
},
{
ordinal: 2,
name: "record",
duration_seconds: 300.0,
instruction: "record a continuous RR stream",
required: true,
},
{
ordinal: 3,
name: "review",
duration_seconds: 30.0,
instruction: "review signal quality before scoring",
required: true,
},
],
target_beats: 300,
target_duration_seconds: 300.0,
}
}
///|
/// Make a short field protocol plan.
pub fn field_protocol_plan() -> ProtocolPlan {
{
name: "field-60-second-hrv",
version: "1.0",
steps: [
{
ordinal: 1,
name: "settle",
duration_seconds: 30.0,
instruction: "remain still",
required: true,
},
{
ordinal: 2,
name: "record",
duration_seconds: 60.0,
instruction: "record a clean RR stream",
required: true,
},
],
target_beats: 60,
target_duration_seconds: 60.0,
}
}
///|
/// Return the total planned duration.
pub fn protocol_plan_duration(plan : ProtocolPlan) -> Double {
sum_values(plan.steps.map(fn(step) { step.duration_seconds }))
}
///|
/// Find a step by ordinal.
pub fn protocol_step(plan : ProtocolPlan, ordinal : Int) -> ProtocolStep? {
for step in plan.steps {
if step.ordinal == ordinal {
return Some(step)
}
}
None
}
///|
/// Validate step results against a plan.
pub fn evaluate_protocol_run(
plan : ProtocolPlan,
results : Array[ProtocolStepResult],
tolerance_seconds : Double,
) -> ProtocolRunReport {
let tolerance = if tolerance_seconds < 0.0 { 0.0 } else { tolerance_seconds }
let mut completed = 0
let mut required = 0
for step in plan.steps {
if step.required {
required += 1
}
}
for result in results {
if result.completed && result.deviation_seconds.abs() <= tolerance {
completed += 1
}
}
let completion = if required == 0 {
1.0
} else {
completed.to_double() / required.to_double()
}
let observed = sum_values(results.map(fn(result) { result.observed_seconds }))
{
plan_name: plan.name,
completed_steps: completed,
required_steps: required,
completion_ratio: completion,
duration_error_seconds: observed - plan.target_duration_seconds,
passed: completion >= 1.0,
results,
}
}
///|
/// Generate missing step results with actionable notes.
pub fn missing_protocol_results(
plan : ProtocolPlan,
results : Array[ProtocolStepResult],
) -> Array[ProtocolStepResult] {
let output = []
for step in plan.steps {
let mut found = false
for result in results {
if result.ordinal == step.ordinal {
found = true
}
}
if found {
for result in results {
if result.ordinal == step.ordinal {
output.push(result)
}
}
} else {
output.push({
ordinal: step.ordinal,
observed_seconds: 0.0,
completed: false,
deviation_seconds: -step.duration_seconds,
note: "step was not recorded",
})
}
}
output
}
///|
/// Return a human-readable next action.
pub fn protocol_next_action(
plan : ProtocolPlan,
results : Array[ProtocolStepResult],
) -> String {
for step in plan.steps {
let mut completed = false
for result in results {
if result.ordinal == step.ordinal && result.completed {
completed = true
}
}
if !completed {
return "complete step " +
step.ordinal.to_string() +
": " +
step.instruction
}
}
"protocol complete; review quality before publishing"
}
///|
/// Return a protocol feature vector.
pub fn protocol_run_feature_vector(report : ProtocolRunReport) -> Array[Double] {
[
report.completed_steps.to_double(),
report.required_steps.to_double(),
report.completion_ratio,
report.duration_error_seconds,
if report.passed {
1.0
} else {
0.0
},
]
}
///|
/// Return whether a plan is internally consistent.
pub fn protocol_plan_is_valid(plan : ProtocolPlan) -> Bool {
if plan.steps.length() == 0 ||
plan.target_beats <= 0 ||
plan.target_duration_seconds <= 0.0 {
return false
}
for i in 0..