# Weight model cross-check against an independent Hall-model implementation (2026-09-24)

**Result:** the planner's live weight model (`window.WeightModel`, build 22.4.0-rc3, grainoftruth.life, read
2026-09-24) reproduces an independent, published implementation of Kevin Hall's adult body-weight model almost
exactly. Across 6 adult profiles and 3 intake scenarios (18 one-year runs):

- Baseline maintenance intake is identical in all 6 profiles (e.g. 2,749 kcal/day vs 2,749).
- 17 of 18 trajectories agree within 0.3 lb at day 30, 90, 180 and 364.
- Largest gap: 1.49 lb at one year (1.6% of the 58 lb loss) for a lean 23-year-old man eating 1,000 kcal/day
  below maintenance, whose projected weight reaches a BMI of about 13. That is an extreme case where the two
  implementations clamp or floor differently.
- A steady +0.05 to +0.18 lb offset on losses is consistent with the reference applying the intake change one day
  later than the planner (day 1 vs day 0).

**What this does and doesn't show.** It shows the planner's equations are numerically equivalent to the published
Hall adult model as implemented in the `bw` R package (Camacho-García-Formenti & Zepeda-Tello, 2018, v1.0.0,
CRAN archive; built from Hall et al., *Lancet* 2011). It does not show agreement with real people's weight change;
the model's own accuracy is established in the literature (e.g. NIH BWP vs 3,703 bariatric-program patients,
*Sci Rep* 2023). It also does not test the planner's extensions beyond the reference: sodium/ECF, carbohydrate
percentage changes, and the activity-change input (the reference package has no activity-change input, so
scenario 4 was not compared).

**Implication for product claims.** Chat's 2026-09-19 wording ("informed by Hall/NIDDK research ... not shown to be
equivalent to the NIH tool") can be strengthened to something like: *"Grain of Truth's weight projections use the
published Hall dynamic energy-balance model; in our checks they match an independent research implementation of
that model to within 0.3 lb over a year for typical adults."* Still no NIH affiliation or endorsement claim.
Have counsel glance at wording before any public use.

## Method

1. Planner side: in the live page, `WeightModel.initialize(profile, plan)` for baseline intake, then
   `WeightModel.simulate(profile, plan, 365)` with a constant daily intake of baseline − 500, − 1,000 and + 300
   kcal, activity change 0%, carbohydrate % and sodium held at baseline. The six profiles are the planner's own
   validation-harness profiles (M23, F35, M39, F52, M65, F28).
2. Reference side: R 4.3.3, `bw` 1.0.0 (CRAN archive; plotting/survey helpers stripped, model C++ untouched),
   `adult_weight(kg, m, age, sex, EIchange = constant, fat = kg × body-fat %, PAL, pcarb_base, days = 365)`.
3. Compare weight (lb) at day 30, 90, 180 and 364. Script: `validation/bwrun.R`; outputs `bw_out.csv`
   (reference) and `got.csv` (planner).

## Results (lb)

| Profile | Scenario | Planner d30 / d90 / d180 / d364 | Reference d30 / d90 / d180 / d364 | Max gap |
|---|---|---|---|---|
| M23 (154.3 lb, PAL 1.6) | −500 | 148.97 / 141.84 / 133.90 / 125.31 | 148.90 / 141.79 / 133.87 / 125.30 | 0.07 |
| M23 | −1000 | 143.46 / 128.30 / 111.18 / 96.72 | 143.31 / 128.18 / 110.91 / 95.23 | 1.49 |
| M23 | +300 | 157.42 / 161.39 / 165.72 / 171.01 | 157.46 / 161.42 / 165.74 / 171.02 | 0.04 |
| F35 (145 lb, PAL 1.5) | −500 | 140.05 / 133.66 / 125.62 / 113.96 | 139.97 / 133.59 / 125.56 / 113.93 | 0.08 |
| F35 | −1000 | 134.97 / 121.82 / 104.74 / 80.14 | 134.79 / 121.65 / 104.61 / 80.08 | 0.18 |
| F35 | +300 | 147.92 / 151.61 / 156.16 / 162.86 | 147.96 / 151.65 / 156.19 / 162.88 | 0.04 |
| M39 (245 lb, PAL 1.35) | −500 | 240.75 / 234.81 / 226.81 / 213.36 | 240.70 / 234.76 / 226.77 / 213.33 | 0.05 |
| M39 | −1000 | 236.44 / 224.43 / 208.01 / 179.96 | 236.33 / 224.32 / 207.92 / 179.90 | 0.11 |
| M39 | +300 | 247.53 / 251.03 / 255.70 / 263.51 | 247.56 / 251.06 / 255.72 / 263.53 | 0.03 |
| F52 (190 lb, PAL 1.3) | −500 | 185.33 / 179.33 / 171.16 / 157.17 | 185.25 / 179.26 / 171.09 / 157.12 | 0.08 |
| F52 | −1000 | 180.55 / 168.39 / 151.60 / 122.30 | 180.37 / 168.23 / 151.45 / 122.19 | 0.18 |
| F52 | +300 | 192.76 / 196.30 / 201.06 / 209.16 | 192.80 / 196.34 / 201.09 / 209.18 | 0.04 |
| M65 (180 lb, PAL 1.45) | −500 | 175.27 / 168.93 / 160.76 / 148.37 | 175.20 / 168.86 / 160.71 / 148.34 | 0.07 |
| M65 | −1000 | 170.43 / 157.42 / 140.19 / 113.79 | 170.28 / 157.28 / 140.07 / 113.73 | 0.15 |
| M65 | +300 | 182.80 / 186.48 / 191.12 / 198.22 | 182.84 / 186.51 / 191.14 / 198.23 | 0.04 |
| F28 (160 lb, PAL 1.75) | −500 | 155.32 / 149.16 / 141.85 / 132.41 | 155.26 / 149.11 / 141.81 / 132.39 | 0.06 |
| F28 | −1000 | 150.55 / 137.84 / 122.42 / 103.15 | 150.41 / 137.73 / 122.34 / 103.12 | 0.14 |
| F28 | +300 | 162.76 / 166.33 / 170.48 / 176.04 | 162.80 / 166.35 / 170.49 / 176.05 | 0.04 |

## Next checks (not done)

1. The same six profiles against the live NIH Body Weight Planner (niddk.nih.gov/bwp) by hand: 18 runs,
   about an hour, would close "equivalent to the NIH tool" directly.
2. Activity change: compare the planner's `activityChangePct` against the BWP's activity-change input.
3. Real-person check: Geremy's own 30-day log (predicted vs actual 7-day-average weight), per the robustness plan.
4. Put this comparison into the repo's test suite as a fixture (the 18 reference trajectories, ±0.3 lb), so a
   future code change can't silently drift the model.
