""" Hurdle Step rubric scorer — pure function, no model calls. FMS Hurdle Step Criteria (bilateral — score each side, report lower): - Score 3: hips/knees/ankles aligned, minimal trunk movement, dowel/posture stable, no contact with hurdle. - Score 2: movement completed with compensation (trunk lean, loss of alignment). - Score 1: contact with hurdle, loss of balance, or inability to maintain alignment. - Score 0: PAIN — never auto-scored. """ from __future__ import annotations from formscout.types import BiomechFeatures, ScoreResult def score_hurdle_step(features: BiomechFeatures) -> ScoreResult: """Pure rubric scorer for hurdle step.""" angles = features.angles alignments = features.alignments has_hip_flex = "step_hip_flexion_deg" in angles if not has_hip_flex: return ScoreResult( score=1, rationale="Insufficient data: hip flexion not measurable", confidence=0.3, notes="missing key measurements", ) trunk_stable = alignments.get("trunk_stable", False) stance_extended = alignments.get("stance_knee_extended", False) hip_flex = angles.get("step_hip_flexion_deg", 0) rationale_parts = [] # Score 3: good hip flexion, trunk stable, stance solid if hip_flex > 90 and trunk_stable and stance_extended: score = 3 rationale_parts.append("Hip flexion adequate, trunk stable, stance knee extended") elif hip_flex > 70 or (trunk_stable and stance_extended): score = 2 if not trunk_stable: rationale_parts.append("trunk lean detected") if not stance_extended: rationale_parts.append("stance knee flexion") if hip_flex <= 90: rationale_parts.append(f"hip flexion {hip_flex:.0f}° (borderline)") rationale_parts.insert(0, "Movement completed with compensation") else: score = 1 rationale_parts.append("Unable to maintain alignment") if not trunk_stable: rationale_parts.append("significant trunk lean") if not stance_extended: rationale_parts.append("stance knee collapse") confidence = features.confidence * 0.85 return ScoreResult( score=score, rationale="; ".join(rationale_parts), confidence=confidence, notes="", )