Building Kinetiq, an exercise-verification engine for physical therapy: the value is the confidence-gated, RTM-billable proof above the pose model.
Physical TherapyDigital HealthHealthcare AI

Any pose model will log 15 good squats on a knee that is caving. I built the AI that catches rep 9.

Ashutosh SinghalAshutosh SinghalJuly 8, 202613 min read

I built the first version of this demo to prove a pose model could count squats, and within a day I had proven the wrong thing. I fed it a synthetic post-ACL patient I called Maria, 62 years old, eight weeks out of knee reconstruction, fifteen prescribed bodyweight squats. The free pose library did exactly what free pose libraries do. It logged fifteen reps, counted them all, and moved on. Fifteen out of fifteen. A clean session.

The problem was rep 9. On rep 9 Maria's knee caves inward and she slows down to compensate, the exact motion a physical therapist watches for after an ACL reconstruction because it is how people re-tear the graft. The pose model saw a squat. It did not see a re-injury risk, because seeing that is not what a pose model is for. The rep it should have flagged was the rep it counted.

That was the moment the actual product got clear to me. I had been treating the pose estimation as the hard part. It is not the hard part anymore. It is free.

Pose estimation is free, so I stopped trying to compete with it

I spent the first week trying to build a better keypoint tracker, and it was the most wasted week of the project. BlazePose and MoveNet already run on a phone, at 30 frames per second, giving you a 33-keypoint skeleton for nothing. Every PT platform and corporate-wellness app already has this or can drop it in tomorrow. Competing there is competing to give away something that is already being given away.

What nobody hands you is the layer after the keypoints. Taking a raw pose stream and turning it into something a clinician can act on and a payer will reimburse. That is the layer I decided Kinetiq would build, end to end, and the thesis I kept coming back to is this.

Pose estimation is free. The value is the brain above it, and the billable proof.

So I threw away the tracker and built the interpretation engine instead. It takes the keypoint stream (synthetic for three of my test patients, and for a fourth, offline-extracted from a real captured squat clip so I could prove the pipeline runs on real footage), smooths it with a 1-Euro filter, gates it on confidence, segments reps by temporal self-similarity, and computes the features a clinician actually reasons about: joint-angle and range of motion, a knee-valgus index, a descent-to-ascent tempo ratio, movement smoothness as Log Dimensionless Jerk, and a left-right symmetry index. Plain, unit-tested NumPy. No model in the scoring path. You can build the whole thing at https://veriprajna.com/demos/ai-biomechanics-exercise-verification and watch it grade a session.

When I ran Maria back through this engine, rep 9 came back the way it should have the first time.

Kinetiq per-rep table for Maria, rep 9 flagged KNEE VALGUS with valgus index 0.054 above the 0.03 threshold and tempo 1.4 above 1.25, flagged as a re-injury risk while the surrounding reps score good
Rep 9, the one the raw counter called good: the engine returns knee valgus index 0.054 (at or above the 0.03 threshold) and tempo 1.4 (above 1.25), a re-injury-risk flag, not a fifteenth good rep.

The engine does not just say "bad rep." It writes the clause it checked: valgus 0.054 at or above 0.03, tempo 1.4 above 1.25. A clinician can cross-examine that. You cannot cross-examine a black box, and after ACL surgery you very much want to.

The rep I was tempted to fake

I remember exactly where I got greedy, and it was rep 12. On rep 12 of Maria's session the hip keypoint drops below the visibility gate. The tracker confidence on that joint falls too low to trust. For a few frames the engine simply does not know where her hip is.

The tempting move, the one that makes your benchmark look better, is to interpolate. Guess the hip position from the frames around it, compute an angle anyway, score the rep, keep the number clean. I wrote that interpolation. It worked. And then I read the monocular-accuracy literature and deleted it.

Single-camera knee-angle error runs a mean absolute error of 9.3 to 21.9 degrees (Nature Scientific Reports, 2025). That is the honest ceiling on this kind of measurement. If I am fabricating a hip position on a low-confidence frame and then reporting a joint angle off it, I am not measuring a knee, I am generating a plausible-looking number and calling it clinical. For a company named Veriprajna, which means true wisdom, that was not a close call.

Kinetiq table showing rep 12 as NOT SCORED with the reason LHIP not visible, the hip keypoint below the visibility gate, and no angle fabricated for that rep
Rep 12: the hip keypoint drops below the visibility gate, so the verdict is "not scored, hip not visible." The skeleton greys out and no angle is invented for that frame.

So the engine abstains. It says, on screen, "rep not scored, hip not visible." It greys the skeleton out. It does not pretend.

We never guess joint angles from low-confidence keypoints.

I put that sentence on the screen because I wanted the discipline to be visible, not buried in a config file. Across the occluded reps in my labeled set, three of three abstained and zero angles were fabricated, and that is not a claim I assert, it is a unit-tested invariant. An occluded rep that got scored anyway would, by definition, be a fabricated angle. The test would catch it. Refusing to answer is a feature I had to fight my own instincts to keep.

Why does the same squat pass for one patient and fail for the next?

I almost shipped a single global threshold, and a physical therapist I showed an early build to talked me out of it in about thirty seconds. Her point was simple. An 80-degree squat from a woman eight weeks post-ACL is good progress. The same 80-degree squat from a healthy 30-year-old wellness client is a lazy rep. If your engine scores them the same, it does not understand rehab, it understands geometry.

So the rule engine became population-adaptive. Thresholds key to the patient profile: age band, condition, recovery week. Maria, post-ACL week 8, gets a flexion target of 75 degrees. Jordan, a 30-year-old corporate-wellness athlete with healthy knees, gets 95. I built a control into the demo to make the point undeniable: flip the profile dropdown, keep the exact same motion, and watch the verdict change.

Kinetiq showing Jordan, wellness athlete profile, the same roughly 80-degree squat scored insufficient depth (the on-screen shallow verdict) because flexion is below the 95-degree target, where Maria's post-ACL profile scored it good
The same roughly 80-degree depth that scored good for Maria (target 75 degrees) is scored insufficient depth for Jordan (target 95 degrees), shown on screen as the shallow verdict. One motion, two verdicts, because the thresholds adapt to the patient.

The first time I flipped that dropdown in front of someone and the whole column of verdicts changed under a motion that did not, I watched them get it. The same motion earns a different verdict by profile, and that is clinical judgement, not a bug. This is also the part of the thesis that does not age out. When pose estimation gets perfect, and it will, the sensor still cannot tell you that 80 degrees means one thing for a healing knee and another for a healthy one. That understanding lives in the layer above.

Agents advise, code decides

I did let an AI agent embarrass me once, and it is the reason the architecture looks the way it does. Everyone I pitched wanted the on-trend capability: an agent that reads the session and writes the clinical note, an agent that watches the trend and escalates. Fine. I wired up a Clinical Scribe and a Longitudinal Monitor over another patient, Eleanor, 70, post-knee-replacement, whose range of motion is quietly declining across her session history.

The first draft the scribe wrote was fluent and confident and contained a range-of-motion figure that was not in the evidence. It had rounded, or smoothed, or simply invented a number that read well. If I had trusted it, I would have shipped a clinical note with a fabricated measurement in it. That is the whole failure mode of putting a language model in a medical loop, and I had just watched it happen on my own screen.

The fix was to make the agents structurally incapable of it. Every figure an agent introduces is checked against the deterministic evidence. Any number that is not in the engine's output is rejected, and the deterministic template is shown instead. Both agents abstain entirely when there is no API key. They interpret and they communicate. They do not decide.

Kinetiq AI Scribe and Longitudinal Monitor panel for Eleanor, both labeled grounding-checked, the monitor reporting a declining range-of-motion trend across the session history and escalating for clinician review
The Longitudinal Monitor over Eleanor's synthetic session history: it detects the declining range-of-motion trend and drafts the proactive clinician escalation. Both agents are grounding-checked against the deterministic engine, so any figure not in the evidence is rejected before it reaches the note.
Agents advise, code decides.

That sentence is the load-bearing design decision of the whole demo. The trust core, every per-rep verdict and the billing determination, is plain unit-tested code with no language model anywhere near it. The agents sit on top, bounded and grounding-checked, doing the one thing they are genuinely good at, which is turning verified numbers into clinician-ready prose. The moment an agent gets a vote on the verdict, you have lost the auditability that was the entire point.

What a payer actually reimburses is a receipt, not a rep count

The commercial reason I built any of this is a thing I learned reading CMS billing rules, which is not how I expected to spend a month. Clinicians can bill Remote Therapeutic Monitoring under CPT 98975-98981, plus the 2026 codes 98979 and 98985, and the 2026 qualifying threshold dropped to as little as 2 days and 10 minutes of data (Veriprajna WP29 research, 2026). But CMS does not reimburse raw coordinates. It requires device-gathered data tied to a treatment decision. A pile of keypoints is not billable. A documented, auditable determination is.

That gap is not small, because the underlying problem is not small. PT home-exercise adherence sits around 35 percent, and 65 percent of patients abandon their program within the first month (Veriprajna WP29 research, 2026). Self-report overstates compliance, so the clinician is often billing and treating on data they cannot trust. On the employer side, musculoskeletal conditions cost roughly 3,591 dollars per employee per year, an estimated 36 percent of MSK surgeries are considered unnecessary (90 billion dollars), and more than half of employees resist sharing health data over privacy (Veriprajna WP29 research, 2026). Verified, privacy-safe, billable exercise data is the thing everyone in that chain is missing.

So the last thing the engine does per session is decide: billable, needs clinician review, or insufficient device data, and it exports a FHIR-shaped session report. Per-rep observations, the threshold clause checked for each, the session rollup, the RTM determination, and the documentation fields CMS asks for. I say FHIR-shaped, never FHIR-validated, because it is structured JSON that mirrors the Observation and DocumentReference field shapes, not a payload posted to a live EHR. I emit it. I do not pretend to POST it.

Maria's session, with the rep 9 valgus flag, does not auto-bill. It routes to a clinician first, because a re-injury signal should be seen by a human before it becomes a charge. That is the honest nuance I kept insisting on: billable does not mean all-good, it means documented well enough to stand behind.

Kinetiq benchmark summary: 100 percent verdict agreement on 25 clear cases, 100 percent on 10 borderline cases within one sigma of a threshold, 3 of 3 occluded reps abstained with 0 fabricated angles, and 3 of 4 sessions auto-billable with 1 routed to review
The benchmark on the fixed labeled synthetic set: 100 percent verdict agreement on 25 clear cases and on 10 borderline cases (within about one sigma of a threshold), 3 of 3 occluded reps abstained with 0 fabricated angles, and across 4 sessions, 3 auto-billable and 1 routed to review.

The numbers on that panel are the ones I care about, and I want to be precise about their scope. Ground truth here is the verdict implied by the planted physical parameters versus the profile thresholds, computed independently of the engine, and then the reps are rendered to keypoints with measurement noise added after the labels are fixed, so the engine has to recover the verdict through the noise. On that set the agreement is 100 percent on 25 clear cases and 100 percent on 10 borderline cases sitting within about one standard deviation of a threshold. That is a discrimination result on a labeled synthetic set, not an open-world guarantee, and the borderline cases are the ones that would flip under naive thresholding, which is exactly why I report them separately. These are durable measures: task accuracy, automation throughput, and honesty. They do not age out when the pose model improves, because none of them is a pose-model error rate.

The question I am left with

I came into this thinking I was building a computer-vision product, and I am ending it convinced I built an accountability product that happens to start with a camera. The interesting work was never getting the keypoints. It was deciding what the system is allowed to claim, when it must abstain, whose thresholds apply, and what a clinician can put their license behind. If you want to see the engine grade a caving knee, refuse an occluded rep, and export the receipt, it runs keyless at https://veriprajna.com/demos/ai-biomechanics-exercise-verification.

And if you would rather watch it than read me describe it, here is the whole thing running end to end.

The question I keep turning over, and the one I would genuinely like other builders to argue with me about, is this. As the sensors asymptote to perfect, the temptation will be to let the model decide more, because it will so rarely be wrong. But rarely-wrong is exactly the condition under which a fabricated number does the most damage, because you have stopped checking. So where, in your own systems, is the line you refuse to let the model cross, and can you point to the code that enforces it?

Related Research

Build Your AI with Confidence.

Partner with a team that has deep experience in building the next generation of enterprise AI. Let us help you design, build, and deploy an AI strategy you can trust.

Veriprajna Deep Tech Consultancy specializes in building safety-critical AI systems for healthcare, finance, and regulatory domains. Our architectures are validated against established protocols with comprehensive compliance documentation.