From 2237c199c1153499fc29c65de7197c5e57e8d950 Mon Sep 17 00:00:00 2001 From: Shane Maynard Date: Fri, 31 Jul 2026 12:40:03 -0400 Subject: [PATCH] Add per-user learned machine profile; fix methodology honesty issues Replaces the flat additive batch-size correction with a per-user multiplicative pace factor learned from each account's own logged roasts (shared/learn.js, GET /api/machine-profile), plus a lot-scoped "last refine" auto-suggestion. Also fixes the reference data being framed as "your own roasts" on a now-multi-user product, and reclassifies the drying/Maillard sanity checks as informational since they're algebraically derived from the DTR check rather than independent (yellow = 0.56 x first crack, not entered separately). Bug fixes found by an adversarial Opus review of the first pass: - Unicode minus sign (U+2212) broke duration parsing against the app's own generated refine-suggestion text - Printed sanity-checks table still showed a bare pass/fail glyph for the now-informational drying/Maillard rows - Printed time-ledger box didn't show the pace multiplication step, so it stopped reconciling by hand once pace != 1 - Field 6.4 (manual batch correction) was double-counted: excluded from the learned-pace fit but added back after the multiplication - Learned pace had no outlier rejection or hard clamp - computeLedger had no test coverage - Batch-size help copy overstated what the pace factor models (it's a single blanket ratio, not conditioned on batch weight) A follow-up Opus pass also caught the per-user profile cache surviving logout/account-switch in a shared browser; fixed by sweeping it alongside the existing plan-draft cleanup. Co-Authored-By: Claude Sonnet 5 --- public/app.css | 26 +++++ public/index.html | 69 +++++++++++-- public/js/field-help-content.js | 172 ++++++++++++++++++++++---------- public/js/field-help.js | 4 +- public/js/lot-picker.js | 70 ++++++++++++- public/js/main.js | 84 ++++++++++++++-- public/worksheet.css | 1 + server/app.js | 56 +++++++++++ shared/learn.js | 132 ++++++++++++++++++++++++ shared/ledger.js | 30 +++++- shared/time.js | 7 +- test/learn.test.js | 170 +++++++++++++++++++++++++++++++ test/ledger.test.js | 116 +++++++++++++++++++++ test/machine-profile.test.js | 145 +++++++++++++++++++++++++++ 14 files changed, 999 insertions(+), 83 deletions(-) create mode 100644 shared/learn.js create mode 100644 test/learn.test.js create mode 100644 test/ledger.test.js create mode 100644 test/machine-profile.test.js diff --git a/public/app.css b/public/app.css index 3d4c1f7..4a082a1 100644 --- a/public/app.css +++ b/public/app.css @@ -1625,6 +1625,28 @@ body.show-fids .field[data-fid]::after { .check-tile.fail .check-why { display: block; } +.check-tile.informational .check-why { + display: block; +} +.check-tile.informational .check-band { + font-style: italic; +} +.refine-suggestion { + margin-top: 6px; + padding: 8px 10px; + border-radius: var(--radius-sm); + border: 1px solid var(--ember-soft); + background: var(--ember-soft); + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 6px; +} +.refine-suggestion span { + font-size: 11.5px; + color: var(--ink-2); + line-height: 1.4; +} .dock-note { font-size: 10.5px; color: var(--ink-3); @@ -2796,6 +2818,10 @@ body.show-fids .field[data-fid]::after { td.passcell.fail::before { content: "✗ "; } + td.passcell.informational::before { + content: "— "; + color: var(--mid); + } } /* ─── Legacy .opt styling reused by the (hidden-on-screen) print worksheet's own radios ── */ diff --git a/public/index.html b/public/index.html index 8ecfde5..4b05dd1 100644 --- a/public/index.html +++ b/public/index.html @@ -499,7 +499,17 @@ >0 the first time you roast this coffee. Afterwards, carry "One change next batch" here — this is how the plan learns. +