The design works. Two things have to be decided before any of it gets built, and one of them is a form edit that is cheap today and expensive after the first client.
Five stages, one mandatory human gate in the middle. Why the two inputs stay separate until they are scored.
Open → 02 Intake pipelineWhat the 14-page questionnaire actually yields, split into what parses cleanly and what needs a model.
Open → 03 Lab pipelineBlood panel to structured markers against functional ranges. Mostly already built.
Open → 04 · THE CORE The weightingHow the two sides score against each other into a ranked Top Five. Rules decide, the model writes.
Open → 05 · DO THIS FIRST Fix the formTen questions to convert from free text to scales, and three symptoms the form never asks about.
Open → 06 Local renderingCanva is out. The document gets generated locally the same way the lab report already is.
Open →The shape
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.
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.
Intake pipeline
The questionnaire runs 14 pages. Splitting it by what a pipeline can actually use:
Parses cleanly today
| Domain | Fields | Feeds |
|---|---|---|
| Anthropometrics | DOB, height, weight | Macro calculation, the only source for it |
| Cycle | Stage, age at menarche, cycle length, days of bleeding, colour, clots yes/no, clot size, clot frequency, flow 1-10, spotting | Iron status, hormone patterns |
| Sleep | Bedtime, wake time, hours, night waking times, how they feel on waking | Blood sugar, cortisol, thyroid |
| Lifestyle | Stress 1-10, sitting hours, screen hours, movement yes/no | Nervous system, training plan |
| Diet pattern | Way of eating checkboxes, foods avoided table, water intake | Constrains every food recommendation |
| Exposures | Six-row past/present grid | Detox and inflammation priorities |
| Health history | Supplements table, medications table, birth control and HRT table, diagnoses table, supplement form preference | Interaction 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.
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.
Lab pipeline
~/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.
The weighting
This is the part you asked about, and the important decision is that it should not be a model.
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.
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 side | Intake 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
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.
Fix the form first
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
- 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.
Local rendering
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
| Page | Generated from |
|---|---|
| Macro targets and reasons | Calculated, 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 plate | Macro split plus dietary constraints from the foods-avoided table and way-of-eating checkboxes |
| Day of eating | Must 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 months | Priority rank becomes phase order. Priority one in month one. |
| Training plan | Current movement description plus priorities. Nervous system or adrenal priorities cap intensity. |
| Good mood foods | Food groups selected by the specific nutrient gaps the panel found |
| Nervous system support | Populated 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.
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
| Stage | Attacks | Current cost |
|---|---|---|
| Lab extraction plus correlation | Interpreting labs against the intake | 2 to 3 hours, the single biggest block |
| Generation and rendering | Building the protocol | 1 to 2 hours |
| Neither, yet | Recording the findings video | 1 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.