Status: private, never distributed · Code: available on request (src-ai/groq-vision.ts, src-ai/meal-photo-analyze-route.ts, eval/eval-meal-photo.ts, eval/dataset-metadata-SAMPLE.csv)
The short version. This is a continuous glucose monitoring dashboard with a vision model bolted on top, built for a single household and never sold. It is on this site for one reason: it is the project where I had to decide, in code, where the line sits between a wellness tool and a regulated medical device, and then build the thing that keeps the software on the correct side of that line when a model tries to cross it.
Device readings arrive from Dexcom Share and are stored, trended and displayed: time in range, GMI, coefficient of variation, a 24-hour modal day, a heatmap. Separately, you photograph a meal and a vision model estimates its carbohydrate and calorie content, so the estimate can sit alongside the sensor trace.
Nothing about that is unusual. What is unusual is what happens when the model is asked to go one step further.
Where does a display tool stop and a medical device start?
The answer is statutory, and I went and read it rather than guessing. Software that solely transfers, stores, converts formats and displays medical device data is not a device — FD&C Act §520(o)(1)(D), added by the Cures Act, with the classification at 21 CFR 880.6310. Manufacturer obligations attach on commercial distribution (21 CFR 807.3(d)), and 21 CFR 807.65(f) exempts software built solely for analysis and never introduced into commerce. FDA's own enforcement-discretion examples name historical trending and comparison of vital signs.
So a dashboard that shows a trend is outside the regime. The moment it tells you what to do about the trend, it is Clinical Decision Support, and the analysis changes completely.
Four features were specified, costed, and then permanently killed for crossing that line:
They are not on a backlog. They are on a written never list, reviewed every sprint, because a feature that changes your regulatory posture is not a feature you defer.
A red line in a document is a preference. This one runs in production, in both directions, and it is the artifact I would most want a governance panel to look at.
Going in. Eight patterns detect a dose-shaped question from the user — "how much insulin", "what's my correction dose", "should I bolus". The response is one fixed sentence, verbatim, every time: "I can help estimate carbs, but I can't recommend insulin. Use your care plan or pump calculator." A safety flag is written to the audit trail.
Coming out. Eight broader patterns scan every text field the model wrote before any of it reaches a screen. A sentence containing dose language is surgically removed and the rest of the field is kept, because discarding the whole answer teaches users to route around the filter. Two critical flags null out the numeric estimate entirely and replace it with a refusal.
Underneath both. The event schema has no dose field at all. Even if both filters missed, the model has nowhere to put a number that means units of a drug.
And on every screen. One disclaimer constant, one mandatory injection point: "This is a wellness estimate, not medical advice. Carb counts are best guesses based on your photo. Verify before dosing. Follow your care team's plan. If you feel unwell, contact your care team or 911."
The filters are tested against adversarial strings, including ones written specifically to slip past them.
HIPAA does not apply here, and that is a finding, not a shrug.
HIPAA binds covered entities and business associates — health plans, clearinghouses and certain providers — at 45 CFR §160.103 and §164. HHS guidance is explicit that the rules do not follow health information into an organisation that is not covered. A single-household tool operated by no covered entity sits outside the scope, and sharing data with a household caregiver does not introduce one.
The part that matters for the job is the next sentence, not that one. I wrote down what would reverse it. Distribute it commercially, host anyone outside the household, or operate it on behalf of a provider, and the analysis moves — into the FTC Health Breach Notification Rule, Washington's My Health My Data Act, and California's CMIA and CCPA. Those triggers are recorded next to the determination, so nobody has to re-derive them the day the product changes shape.
Not covered is not the same as no obligation. Because HIPAA did not attach, a substitute floor was written against NIST, OWASP and FTC health-app guidance: minimise what is collected and kept, per-caregiver visibility controls, explicit opt-in before any continuous sharing. In the code that means photo bytes live in request-scoped memory and are never written to disk or any store, a visible child's face raises a flag with plain-language copy saying no server copy is kept, and the training corpus is excluded from both git and the deploy.
The store is a Google Sheet, which sounds casual and is the part I get asked about most. It is not casual, and the access design is the thing I would walk a panel through.
Nothing human-facing ever touches the store directly. Access is a Google service account authenticating by signed JWT, scoped to one spreadsheet ID. The account's private key exists only as a deploy-time environment variable. It is not in the repository, not in the client bundle, and the sheet is never link-shared. Revoking access is deleting one row in the sheet's share list, which is a property worth having when the alternative is rotating a database password everything depends on.
The write path has its own separate secret. The scheduled sync endpoint checks a bearer token before it does anything at all and returns 401 without it, so a public URL is not a public write. A lock row is claimed before each run, so two overlapping runs cannot double-insert. Every attempt, successful or not, appends to a sync log.
External consumers get a projection, not the store. When something outside the app needs current readings, it does not get the sheet. It gets a small recent tab, four columns wide, overwritten on every sync, deliberately bounded so it can be handed out whole without handing out history. That is the difference between sharing data and sharing a view of data, and it is a decision that has to be made before someone asks, not during.
Caregiver sessions cannot write, by construction rather than by check. A caregiver's token carries who they are and what they may see, signed, so the boundary is enforced from the token itself without a lookup on every request. In this version every caregiver is read-only: observers observe. Existing write routes rejected caregivers the day the feature landed, with no per-route change, because they were already asking a question that only an owner session can answer.
What is not protected, stated plainly. The training corpus is excluded from both version control and the deploy, so 5,845 images never leave the machine they were prepared on. But the app is still a single shared login with no multi-tenancy, and that is a known ceiling rather than a finished design. The multi-role replacement is specified and partly built, and the module carrying the limitation says so in its own header rather than in a document nobody opens.
The vision estimate is scored against laboratory ground truth: a 5,845-image nutrition dataset with a 5,007-row label file. Mean absolute percentage error, median percentage error, in-range hit rate, dated runs, a published target.
It missed the target. Badly, and repeatedly.
So the photo feature is behind an environment kill switch, defaults to off, and is restricted to one operator "until validated". The measurement is the gate, and the gate is closed. I also made the scoring harder on purpose in one direction and fairer in another: the corpus runs without the scale reference the real flow uses, which understates accuracy, and answers where the model correctly declined to guess were pulled out of the error statistic into their own retake rate, because scoring an appropriate "I don't know" as 100% wrong punishes the exact behaviour I want.
There is also a failure in the record. A commit body once carried evaluation numbers written before the evaluation finished. It was amended with the real, mixed results and the incident stayed in the notes rather than being cleaned up.
Every governance function eventually faces the same handful of moments, and this one project contains all of them.
The last one is the transferable one. In a health system the equivalent moment is a model whose behaviour has drifted away from the policy that approved it, and the only safe move is to raise it rather than quietly re-fit the policy to the model.
GG · Applied AI Engineering portfolio · synced to youdontneedmy.help design