The rep a free pose library was happy to call perfect
I remember the moment I stopped trusting rep counts. I had wired a stock pose library into a test harness, fed it a synthetic post-ACL squat session, and watched it log fifteen good squats in a row. Fifteen green ticks. The problem was rep 9. On rep 9 the knee was caving inward and the descent had gone soft, the exact compensation a physio watches for eight weeks after an ACL reconstruction. The library did not care. It had keypoints, it had a rep, it printed "good." That is the whole trap of exercise AI in one frame. Counting reps is free now. BlazePose and MoveNet will happily give you 33 keypoints at 30 frames a second and never once tell you the patient is re-injuring themselves.
I did not want to build a better pose model. Everyone already has the pose model. I wanted to build the layer that reads rep 9 the way a clinician would, and I wanted it to be plain enough that the clinician could cross-examine it. So in Kinetiq, our exercise-intelligence engine, rep 9 does not pass. The engine returns a knee-valgus flag with the arithmetic attached: valgus index 0.054 against a 0.03 threshold, tempo ratio 1.4 against a 1.25 limit, peak flexion 78.6 degrees. It calls it a re-injury risk and drops the rep quality to 4 out of 10 while its neighbours sit at 10. Maria, the patient in that session, is synthetic, a procedurally generated keypoint stream with planted form labels, not a real person. But the caving knee she is built to reproduce is the thing a real PT platform has to catch and today mostly does not.
Rep 9 in Maria's synthetic post-ACL session. The engine flags knee valgus (index 0.054, threshold 0.03) and a compensatory tempo (1.4 versus the 1.25 limit) and reports why, while the surrounding reps score a clean 10.
The week I decided the honest answer was "I don't know"
I spent a genuinely uncomfortable week deciding what the engine should do when it cannot see. Early on I had it interpolate through occlusion, because a gap in the timeline looked untidy and a filled-in number looked finished. Then I read the monocular knee-angle error figures again. A single camera gets you a mean absolute error of 9.3 to 21.9 degrees on knee angle (Nature Scientific Reports, 2025). If I am already carrying up to twenty degrees of uncertainty on a joint I can see, inventing a joint I cannot see is not a rounding convenience. It is a lie with a decimal point on it.
So I built a confidence gate and gave it permission to abstain. When Maria's hip keypoint drops below the visibility threshold on rep 12, the engine does not guess. It returns "not scored, hip not visible," greys the skeleton out, and fabricates no angle. On the labeled test set that behaviour is an invariant I check rather than assert: of the occluded reps, every one abstains and zero produce a fabricated angle. I find that far more reassuring than a dashboard that is never blank. A blank cell where a joint was hidden is the most honest pixel in the product, and it is the one I would defend hardest to a skeptical clinician.
Rep 12 abstains. The left hip keypoint fell below the visibility gate (confidence under 0.5), so the engine records "not scored, hip not visible" rather than inventing a flexion number it cannot support.
The same 80 degree squat, two different verdicts
I did not expect the profile flip to be the thing that convinced people, but it is. In the demo I select Maria, a 62-year-old eight weeks post-ACL, and a squat at roughly 80 degrees of depth scores good, because her flexion target is 75 degrees. Then I change one dropdown to Jordan, a 30-year-old corporate-wellness athlete, and the identical motion scores insufficient depth, because his target is 95. Same keypoints, same code, different verdict, because the clinical context changed. That is the part a one-size threshold can never do, and it is the part that made me stop thinking of this as a computer-vision problem and start thinking of it as a judgement problem. The rules are population-adaptive and clinician-configurable, keyed to age band, condition, and recovery week. A raw pose stream has no opinion about who is squatting. A physio has nothing but opinions about who is squatting, and they are usually right.
I keep the trust core deterministic on purpose. Every per-rep verdict is plain, unit-tested NumPy, so a clinician can ask "why was rep 9 flagged" and get "valgus 0.054 exceeded 0.03" rather than a shrug from a black box. There are two optional agents on top, a longitudinal monitor and a clinical scribe, but they only interpret and communicate the numbers the deterministic engine already produced, and any figure they try to introduce that is not in the evidence gets rejected. Agents advise. Code decides. For a company called Veriprajna, which means true wisdom, an engine you can interrogate has to beat one you have to trust.
Why the abstention is what makes the session billable
I did not realise until late that the discipline to say "I don't know" is also what makes the session worth money. Physical-therapy home-exercise adherence sits around 35 percent, and roughly 65 percent of patients abandon within the first month (Veriprajna WP29 research, 2026), while musculoskeletal problems cost employers about 3,591 dollars per employee per year (WP29 research, 2026). Clinicians can bill Remote Therapeutic Monitoring for the exercise data they gather, under the CMS RTM family CPT 98975 to 98981 plus the 2026 codes 98979 and 98985. But CMS does not reimburse raw coordinates. It wants device-gathered data tied to a treatment decision. A rep that was honestly abstained, and a rep 9 that was honestly flagged, are exactly that: evidence that a decision happened.
So Maria's session exports as a FHIR-shaped RTM report, structured JSON that mirrors the FHIR Observation and DocumentReference shapes rather than a validated EHR write. It records 13 verified reps, 1 flagged, 1 abstained, average quality 9.6 out of 10, and because of the valgus alert the determination comes back as needs-clinician-review, so a person looks before anything is billed. The flag is not a failure of the session. The flag, and the clinician looking at it, is the treatment decision the reimbursement is built on.
The session determination. Kinetiq marks Maria's session needs-clinician-review, maps it to the CMS RTM family (CPT 98975 to 98981, plus the 2026 codes 98979 and 98985), and offers the FHIR-shaped JSON export as the auditable record a clinician documents against.
What I keep coming back to
I keep coming back to that first frame of fifteen green ticks over a caving knee. Everything I have built since is an argument that the pose model was never the hard part. As pose estimation gets better, and it will, the caving knee gets easier to see, but the questions above it do not move: does this rep count for this patient, this week, and can you prove it to the person who pays for it. You can run the whole thing yourself, four honestly labeled scenarios and one real captured squat clip, keyless and with no camera, at veriprajna.com/demos/ai-biomechanics-exercise-verification.
Here is the question I have not fully answered for myself. In your own product, when the model cannot see the joint, does the interface show a confident number or an honest blank, and which one did your users actually ask for?