HMM Clinic · Protocol automation

Intake plus labs, weighted

How the onboarding questionnaire and the blood panel get analysed separately, scored against each other, and turned into a finished protocol. Written against the real form and the real Canva template, not a sketch.

2026-08-11 · design only, nothing built yet

← Overview

The shape

0 PB form submitted ──────────┐ lab PDF arrives ────────────┤ both trigger n8n on the NAS │ 1a intake extraction ─────────┤ local model, structured JSON out 1b lab extraction ────────────┘ existing ~/hmm-lab-report, OptimalDX ranges │ 2 correlation engine ────────┤ DETERMINISTIC SCORING, no model │ ranked priorities + evidence both sides ▼ 3 ══ PRACTITIONER GATE ══ Maria reviews, reorders, overrides │ 4 generation ────────────────┤ macros calculated, prose written, supplements mapped │ 5 render ────────────────────┘ into the protocol template

Why the two inputs stay separate until stage 2

If you hand a model the labs and the symptoms together and ask for an interpretation, it will rationalise. It finds a story that fits both, and it finds one whether or not the evidence supports it. Extracting each side blind to the other means the convergence at stage 2 is real information rather than a narrative the model constructed.

It also means each extractor can be tested independently against known inputs, which is the only way to know whether this thing works.

Stage 3 is not optional

The practitioner gate is what keeps a credentialed human as the practitioner of record. The machine proposes a ranked list with its evidence shown; Maria accepts, reorders, or overrides, and her edits are captured. Without this the product is an algorithm giving health advice under Jes's brand, which is a different thing legally and a worse thing clinically.

It is also where the training data comes from. Every override Maria makes is a labelled correction, and after twenty or thirty of them you can see exactly where the scoring is wrong.

← Overview

Intake pipeline

The questionnaire runs 14 pages. Splitting it by what a pipeline can actually use:

Parses cleanly today

DomainFieldsFeeds
AnthropometricsDOB, height, weightMacro calculation, the only source for it
CycleStage, age at menarche, cycle length, days of bleeding, colour, clots yes/no, clot size, clot frequency, flow 1-10, spottingIron status, hormone patterns
SleepBedtime, wake time, hours, night waking times, how they feel on wakingBlood sugar, cortisol, thyroid
LifestyleStress 1-10, sitting hours, screen hours, movement yes/noNervous system, training plan
Diet patternWay of eating checkboxes, foods avoided table, water intakeConstrains every food recommendation
ExposuresSix-row past/present gridDetox and inflammation priorities
Health historySupplements table, medications table, birth control and HRT table, diagnoses table, supplement form preferenceInteraction checks, supplement selection

Needs a model to extract

Top five health concerns, goal statement, relationship with food, foods that worsen symptoms, digestive difficulties, primary stressors, coping tools, support system, hobbies, movement description, alcohol, smoking, substances, trauma and life experiences, and the full typical-day walkthrough.

The typical-day walkthrough is the single richest field in the document. It contains meal timing, caffeine, supplement timing, work rhythm and wind-down behaviour in one paragraph, and it is worth a dedicated extraction pass rather than being lumped in with everything else.

The problem field group. Sugar cravings, salt cravings, excessive hunger, low appetite, tiredness after meals, constipation days per week, diarrhoea days per week and bowel movement frequency are all open text boxes. These are among the highest-signal blood-sugar and thyroid questions on the form, and as written they produce answers like "sometimes" and "a lot lately" that cannot be scored or compared between clients. See Fix the form first.

Output

One JSON object per client: demographics, a symptom vector with severities, dietary constraints, current supplements and medications, cycle profile, lifestyle profile, and the client's own stated priorities. That last one matters, because the client's top five concerns and the clinical top five are different lists and both belong in the report.

← Overview

Lab pipeline

Mostly already built. ~/hmm-lab-report/ is scaffolded against OptimalDX and carries scope-of-practice term handling. Verify its current state before rebuilding anything; this stage is the closest to done.

The panel is the PMDD Comprehensive from DHA Laboratories plus four additions: Insulin, GlycoMark, TPO Ab and Thyroglobulin Ab.

What extraction has to produce per marker

  • Value and units
  • Standard reference range as the lab reported it
  • Functional or optimal range, which is the whole point. A result inside the standard range but outside the functional range is most of what this product sells.
  • Direction and magnitude of deviation, normalised so markers are comparable

Derived values, computed not read

Several of the strongest signals are ratios rather than single markers, and they have to be calculated at extraction time so the scoring engine can use them directly. Triglyceride to HDL ratio. Percent transferrin saturation. Free T3 to reverse T3. Sodium to potassium. Neutrophil to lymphocyte ratio.

GlycoMark reads backwards. Low GlycoMark indicates postprandial glucose excursions, so the deviation logic is inverted relative to almost every other marker on the panel. Worth encoding explicitly rather than discovering it in a report that went to a client.
← Overview

The weighting

This is the part you asked about, and the important decision is that it should not be a model.

The lab half already exists. body_systems.py and health_concerns.py in ~/hmm-lab-report/ already score 35 categories against the panel: 11 body systems including Thyroid Function with TPO and Thyroglobulin antibodies already mapped, plus Adrenal, Inflammation, 7 nutrient-status groups and 16 patient-facing health concerns with copy already written in wellness-coach scope. _score_system() returns a 0-100 score, a likelihood band, which markers were found and missing, and a direction arrow per marker. That is the evidence trail this design needs, already emitted.

So the genuinely new work is narrower than it looks: an intake scorer of the same shape, a convergence step, and a reduction from 35 scored categories down to the five a client actually reads.

The SOP names ten candidate clinical priorities. Each one gets a scoring rule with two independent sides.

priority_score = lab_signal + intake_signal + convergence_bonus lab_signal = weighted sum of that priority's markers, by deviation intake_signal = weighted sum of that priority's symptom fields, by severity convergence = large bonus when BOTH sides fire divergence = flag for the practitioner, NOT a score penalty

Why rules and not a model

Deterministic scoring is reproducible, auditable, and cannot invent a finding. The same client data produces the same ranked list every time, and when Maria disagrees you can point at the exact rule that produced it and change that rule. A model asked to rank priorities will produce a plausible list that varies run to run and cannot be corrected in any durable way.

The model writes the prose. The rules pick the priorities. That split is the whole design.

Worked example: blood sugar dysregulation

Lab sideIntake side
Fasting glucose, HbA1c, fasting insulin, GlycoMark, triglycerides, HDL, triglyceride to HDL ratio Sugar cravings, tiredness after meals, excessive hunger, waking between 1 and 3am, meal frequency and timing, way of eating

Insulin high, GlycoMark low, and the client reports sugar cravings plus post-meal fatigue plus 1-3am waking. Both sides fire, convergence applies, and this lands at the top of the list with the evidence for it printed on both sides. That is a finding you can defend to a client and to a practitioner.

Worked example: iron status

The cycle section of the questionnaire is unusually well built for this. Flow rated 1 to 10, clot size, clot frequency and days of bleeding give a genuine menstrual blood loss estimate, and that sits directly against ferritin, percent saturation, haemoglobin, MCV and RDW. Heavy flow with large frequent clots plus low ferritin is one of the cleanest convergences the panel can produce, and it is a finding that changes a woman's life. Keep those questions exactly as they are.

Divergence is a feature

Do not let one-sided evidence auto-promote. Abnormal labs with no matching symptoms, or clear symptoms against a clean panel, both mean something worth a practitioner's attention. Neither should be silently ranked as a top priority. Surface them in a separate "needs your eyes" list at the practitioner gate.

What comes out

Ranked priorities with a confidence band, the specific markers and specific questionnaire answers behind each one, the divergence list, and the client's own stated concerns held alongside for comparison. The gap between what she thinks is wrong and what the data says is itself worth showing her.

← Overview

Fix the form first

Cheap now. Expensive after the first client.

Every question changed after clients start filling this in means either re-parsing history or living with two incompatible data shapes forever. These edits cost an afternoon in Practice Better today.

Convert from free text to a scale

All eight of these are currently open boxes. A 0 to 3 frequency scale (never, occasionally, often, daily) makes each one directly scoreable, and none of them lose anything by being constrained.

  • Do you often feel excessively hungry
  • Do you have little or no appetite
  • Do you often crave sugar
  • Do you often crave salt
  • Do you often feel tired after meals
  • Do you experience digestive difficulties (keep the free-text box as a follow-up, but add checkboxes for bloating, gas, constipation, diarrhoea, reflux)

And these two should be plain numbers, not prose:

  • Constipation days per week
  • Diarrhoea days per week

Bowel movement frequency should be a picker rather than a sentence: more than three per day, one to three per day, every other day, twice a week, less than twice a week.

Three symptoms the form never asks about

All three are core to priorities the SOP explicitly lists, and without them those priorities can only ever score on the lab side, which means they can never converge.
  • Temperature intolerance. Cold hands and feet, or running hot. Central to thyroid.
  • Hair, skin and nail changes. Hair shedding, brittle nails, dry skin. Thyroid, iron and protein status all read here.
  • Mood and cognition, structured. The form asks about stress and about the cycle, but never asks directly and comparably about low mood, anxiety, irritability or brain fog. On a product whose name contains the word mood, that is a gap.

Two more worth adding while you are in there: headaches or migraines including cycle timing, and dizziness on standing, which reads on iron, blood pressure and adrenal status and takes one line.

One structural addition

Ask when the last blood draw was and whether the client was fasted, and capture cycle day at draw. Several markers on this panel move across the cycle, and interpreting them without knowing where in her cycle she was is guessing.

← Overview

Local rendering

Decided 2026-08-11: generate the document locally, same as the lab report. Canva is out.

That removes the whole vendor question. Nothing leaves the network, the layout is version-controlled instead of living in one person's Canva account, and it reuses a renderer that already exists rather than a Brand Template conversion nobody has done. The cost is rebuilding Maria's eight-page layout in code once.

Her template is still the specification. Eight pages, and the structure is good: macro targets with a reason under each, building your plate, small sustainable changes, a day of eating, your next six months, a training plan Monday through Sunday, good mood foods as food-group tiles, and nervous system support. Treat it as the design brief for the renderer.

What has to be generated per page

PageGenerated from
Macro targets and reasonsCalculated, not written. Height, weight, age and activity from intake give the baseline; the top priorities adjust it. The "reason for the number above" is prose tied to the priority that drove the adjustment.
Building your plateMacro split plus dietary constraints from the foods-avoided table and way-of-eating checkboxes
Day of eatingMust hit the macro targets and respect every avoided food. This is the page most likely to embarrass you if it is generated carelessly.
Next six monthsPriority rank becomes phase order. Priority one in month one.
Training planCurrent movement description plus priorities. Nervous system or adrenal priorities cap intensity.
Good mood foodsFood groups selected by the specific nutrient gaps the panel found
Nervous system supportPopulated only when that priority makes the top five

What it builds on

generate_report.py in ~/hmm-lab-report/ is a 73KB ReportLab renderer with its own assets/ directory, already producing branded client-facing PDFs in HMM's look. The protocol becomes a second document type in that same codebase, not a new project.

Two things come along for free. scope_of_practice.py already enforces the wellness-coach term map programmatically, so protocol copy gets the same treatment the lab report gets. And the whole thing runs where the client data already is, so there is no new place for it to leak.

The Canva template stops being infrastructure and becomes a mockup. That is a better role for it. Maria can keep iterating on layout in a tool she is fast in, and the renderer follows her design rather than depending on the file staying intact and correctly fielded.
← Overview

Build sequence

Ordered so each stage is useful on its own, and so nothing gets built twice.

  • Decide local rendering versus Canva. Settled 2026-08-11: local, in the existing lab-report codebase.
  • Fix the questionnaire. Practice Better edits only. No code. Blocks everything downstream and gets more expensive every day it waits.
  • Add the four new markers to biomarkers.json. Insulin is already there; GlycoMark, TPO Abs and Thyroglobulin Abs need optimal ranges defined, and GlycoMark needs its inverted logic encoded so low reads as a problem.
  • Build the intake extractor. Questionnaire to structured JSON, with the typical-day walkthrough as its own pass. This is the biggest genuinely new piece.
  • Build the intake scorer, mirroring _score_system() so both halves emit the same shape and the convergence step stays trivial.
  • Write the convergence rules and the 35-to-5 reduction with Maria at the table. The weights are her clinical judgement, not an engineering decision. Nervous system, sleep and lifestyle score from intake only, since no blood marker reaches them.
  • Build the practitioner review gate before building any generation. Maria should be reviewing machine-ranked priorities against her own on real clients well before the machine writes a word of client-facing text.
  • Protocol renderer last, as a second document type in generate_report.py, following Maria's Canva layout as the design brief.

What each stage takes off the nine hours

StageAttacksCurrent cost
Lab extraction plus correlationInterpreting labs against the intake2 to 3 hours, the single biggest block
Generation and renderingBuilding the protocol1 to 2 hours
Neither, yetRecording the findings video1 to 2 hours

The video is untouched by any of this, and once the other two are automated it becomes the largest remaining block. A generated script keyed to the top five priorities is the obvious follow-on, and it is a much smaller build than anything above.

Four hours is reachable. Ten minutes ordering labs, roughly forty-five minutes reviewing and correcting machine-ranked priorities, twenty minutes editing generated protocol text, and the video. That is the difference between $33 an hour and $75 an hour for the practitioner, at no additional cost to HMM.