Skip to content

Calibrate every head, unify the chip language, fix the modal layout - #269

Merged
rvnminers-A-and-N merged 3 commits into
mainfrom
feat/calibrate-all-heads-and-bench-ux
Jul 30, 2026
Merged

rvnminers-A-and-N merged 3 commits into
mainfrom
feat/calibrate-all-heads-and-bench-ux

Conversation

@rvnminers-A-and-N

Copy link
Copy Markdown
Collaborator

Closes #265, #266, #267, #268.

Calibration everywhere (#265)

Taste and Tox21 still ran on a flat 0.5 with only AUROC shown — the exact gap #261 closed for aroma and mouthfeel. Both now use the same out-of-fold calibration with a 50% precision floor, and /api/heads publishes threshold, precision, recall and confident_capable for all four modalities.

Taste heads were mildly shy, as expected with hundreds of positives each — sweet 0.33, umami 0.33, bitter 0.51 — and all six clear the floor at 0.50–0.87.

The Tox21 result is the one that matters. All twelve calibrated upward (NR-AR → 0.69, NR-AR-LBD → 0.65, NR-ER → 0.63), which means a flat 0.5 had them over-flagging. That's the opposite direction from the thin aroma heads and the more dangerous one: a caution flag that cries wolf teaches people to ignore the flags that matter.

Chip language + what each dimension IS (#266)

The four chip families had each been given a different border style at a different time — dashed, dotted, thicker, plus per-taste text recolouring — so they read as four accidents rather than one system. They now share a pill, size and border weight, with identity carried by a leading dot in the family's hue.

Each studio section gained a plain-language paragraph on what its dimension is and why it matters. Menthol is cold with no temperature change; capsaicin burns with no heat. That distinction is load-bearing and nothing in the UI had ever said it.

All modalities on cards (#267)

Substitute and neighbor cards showed taste and aroma but not mouthfeel. Read straight off the profile matrix — the mouthfeel columns were already there, so it costs no extra inference — and each sensation must clear its own calibrated threshold, so a card never shows something the modal would call indicative.

Modal layout (#268) — both bugs measured, not guessed

  • Mobile: .swap-grid used 1fr, which is minmax(auto,1fr), and auto won't shrink below min-content — so the cards rendered 468px wide inside a 358px modal. .modal-top already used minmax(0,1fr); .swap-grid didn't.
  • Desktop: the render path set an inline display:block on #results, and block boxes ignore gap — so the stylesheet's gap:14px had never applied and the Heads card sat flush against Substitutes.

Playwright probe at 390px and 1440px: no card overflows its container at either size, no two cards within 8px on desktop.

Glossary

HOW-IT-WORKS.md used CV-AUROC eleven times without defining it, and never mentioned threshold, precision or recall. A six-term glossary now sits at §0.5 before any number is used, mirrored as a card in the site's own How-it-works grid.

Also: five basic tastes, six taste headstasteless predicts documented absence of taste (how you find a clean carrier), not a sixth basic. Loose in both places before.

Verified: ruff clean, 43 tests passing. Needs sudo systemctl restart flavormancer — models changed.

…yout

Closes #265, #266, #267, #268.

CALIBRATION EVERYWHERE (#265). Taste and Tox21 still ran on a flat 0.5 with only AUROC shown —
the exact reporting gap #261 closed for aroma and mouthfeel. Both now use the same out-of-fold
calibration with a 50% precision floor, and /api/heads publishes threshold, precision, recall and
confident_capable for all four modalities.

The taste heads were mildly shy, as expected with hundreds of positives each (sweet 0.33, umami
0.33, bitter 0.51), and all six clear the floor at 0.50-0.87. The Tox21 result is the one that
matters: every one of the twelve calibrated UPWARD — NR-AR to 0.69, NR-AR-LBD 0.65, NR-ER 0.63 —
which means a flat 0.5 had them OVER-flagging. That is the opposite direction from the thin aroma
heads and the more dangerous one, because a caution flag that cries wolf teaches people to ignore
the flags that matter.

CHIP LANGUAGE (#266). Flavor, note, taste and mouthfeel chips had each been given a different
border STYLE at a different time — dashed, dotted, thicker, plus per-taste text recolouring — so
they read as four accidents rather than one system. They now share a pill, a size and a border
weight, and carry identity in a leading dot in the family's own hue. Each studio section also
gained a plain-language paragraph on what that dimension IS and why it matters: a flavor is the
whole percept, a note is what the nose reads, taste is the basics the tongue reads, mouthfeel is
touch reported by the trigeminal nerve. Menthol is cold without a temperature change; that
distinction is load-bearing and nothing in the UI had ever said it.

ALL MODALITIES ON CARDS (#267). Substitute and neighbor cards showed taste and aroma but not
mouthfeel. Read straight off the profile matrix, whose mouthfeel columns were already there, so
it costs no extra inference — and each sensation must clear its own calibrated threshold, so a
card never shows something the modal would call indicative.

MODAL LAYOUT (#268). Two real bugs, both measured rather than guessed:

  - Mobile: the swap grid used `1fr`, which is minmax(auto,1fr), and `auto` will not shrink below
    min-content — so the substitutes and neighbors cards rendered 468px wide inside a 358px modal.
    minmax(0,1fr) fixes it; .modal-top already had this and .swap-grid did not.
  - Desktop: #results is a flex column whose 14px gap separates the cards, but the render path set
    an inline display:block on it, and block boxes ignore `gap`. The Heads card had been sitting
    flush against the Substitutes card below it. Clearing the inline style lets the stylesheet win.

Verified with a Playwright probe at 390px and 1440px: no card overflows its container at either
size, and no two cards are within 8px of each other on desktop.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
HOW-IT-WORKS.md used CV-AUROC eleven times and never said what it is beyond a parenthetical, and
never mentioned threshold, precision or recall at all — the real explanation lived only in
ACCURACY.md, one click further than most readers go. A six-term glossary now sits at section 0.5,
before any number is used: head, AUROC (0.5 is a coin flip, 1.0 is never wrong), threshold (not a
flat 0.5 — 0.16 to 0.85, fitted per head), precision (when it says yes, how often is it right),
recall, out-of-fold, and confident vs indicative. Two sentences each.

The same glossary, compressed to a definition list, is now a card in the site's own How-it-works
grid, so it reaches someone who will never open the docs folder.

Also tightens the taste wording, which was loose in both places: there are FIVE basic tastes and
SIX taste heads. `tasteless` is a head that predicts the documented absence of taste — genuinely
useful, since it is how you find a clean carrier — but it is not a sixth basic taste, and calling
it one is the kind of small inaccuracy a flavorist would notice immediately.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
@rvnminers-A-and-N rvnminers-A-and-N added area:training Python dataset build + model training area:ui React workbench frontend documentation Improvements or additions to documentation enhancement New feature or request labels Jul 30, 2026
@rvnminers-A-and-N rvnminers-A-and-N added this to the M9 — Bench UX milestone Jul 30, 2026
…xcluded

Three stack questions kept resurfacing, so they are now answered in the one place someone would
look, with the reasoning rather than just the verdict.

Frontend is pinned to React 19 + Vite + TypeScript with Tailwind and shadcn/ui, explicitly NOT
MUI. shadcn is copy-in rather than import, so the components land in our own source tree where
they can be read and owned — which for a portfolio is the difference between showing you used a
component library and showing you can build one. Flavormancer also already has a visual identity
(the purple/teal gradient, the gilded scroll rods, the flask loader) that Material would flatten,
and the port would be spent overriding it. The trade is stated honestly: MUI wins when you need an
enterprise data-grid and date pickers tomorrow. This app is cards, chips, charts and a modal.

Node.js is scoped to build tooling and an optional TypeScript MCP server beside the Python one —
never a third backend. Two API stacks is breadth; three is sprawl, and it reads as indecision.

Laravel is deliberately absent and the reasoning is written down so nobody re-opens it: it is a
good fit for CRUD-and-content products and is used heavily elsewhere in this portfolio, but
Flavormancer is on-prem scientific computing. Adding a familiar framework that proves nothing new
would blur that. Choosing against your most comfortable stack when it does not fit is the point.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
@rvnminers-A-and-N
rvnminers-A-and-N merged commit 9b1790d into main Jul 30, 2026
4 checks passed
@rvnminers-A-and-N
rvnminers-A-and-N deleted the feat/calibrate-all-heads-and-bench-ux branch July 30, 2026 09:13
rvnminers-A-and-N added a commit that referenced this pull request Jul 30, 2026
…oc (#229, #26, #25) (#273)

CHIP LEGEND (#229). Four dimensions with four different meanings sit side by side in the studios,
and after the chip-system work in #269 the dot colour was the only thing distinguishing them —
which works only if you already know the code. A key now states it once, up front: what each
family means, and what solid / outline / hatched marking indicates (documented, predicted,
indicative). The hatched swatch matters most: it is the one that says "right less than half the
time — a hint, not an answer".

MEASURED PROPERTIES (#209, in progress). 73% of the enrichment table had no measured boiling
point, and the assumption was that the data did not exist. It does. build_properties.py reads
PubChem's property table, which only ever carries COMPUTED values; measured boiling and melting
points live in PUG-View under Experimental Properties, an endpoint this codebase had never called.
A sample of molecules with no BP found 8 of 8 had a PubChem record.

build_measured_properties.py crawls that endpoint. Parsing is the real work — values are free text
written by whoever took the measurement ("246 °C", "115-116 °C at 12 mm Hg", "410 °F") — so it
converts Fahrenheit, takes the midpoint of a range, and DISCARDS anything measured at reduced
pressure. A boiling point at 12 mmHg is not comparable to one at atmospheric, and silently mixing
them would corrupt the volatility ordering the formulation studio depends on. properties.parquet
already carries boiling_point_pressure_mmhg for exactly this reason.

Smoke test resolved 11 of 12 with values that check out (pyrrolidine 86.6 °C). The full crawl is
~7,200 molecules at roughly 57% hit rate and takes hours; it checkpoints every 100 so it can be
folded in whenever it finishes.

_by_skel() would have silently discarded all of it. It required an `inchikey` column, but both
backfill tables are keyed by `inchikey_skel` — so it returned an empty dict, with no error, and
every backfilled name and boiling point would simply never have appeared. It now accepts either.

DATA-PIPELINE.md (#26, #25). What every build script reads, the REAL column names in each table
read off the built artifacts rather than copied from the loader's hopes, and a clean-machine first
run in dependency order with timings and an end-to-end verification that checks a prediction
rather than just that the server started.

Two claims in the first draft were wrong and are corrected: the verification example printed
`.aroma.top[0]`, which is the highest-scoring CONFIDENT head and not the one you searched for; and
I had written that `n_sources` breaks label conflicts by weight of evidence. It does not — nothing
reads it, and conflicts resolve POSITIVE-WINS: one source calling a molecule sweet makes it sweet.
That is the right default for sparse partially-annotated sources, but it means a single mislabelled
source can assert a taste the others deny, which is worth knowing and is now written down.

Also resolves the [OBTAIN]/[VERIFY] markers: flavordb_taste.csv is permanently excluded
(NonCommercial), umami_list.csv is an unused nice-to-have, sweeteners_db.csv is present and drives
the intensity regressor.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:training Python dataset build + model training area:ui React workbench frontend documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

model(taste,safety): calibrate the taste and Tox21 heads like the aroma heads

1 participant