Skip to content

aroma: expand to 164 heads across a multi-industry corpus - #208

Merged
rvnminers-A-and-N merged 3 commits into
mainfrom
aroma-164-heads-multi-industry
Jul 25, 2026
Merged

rvnminers-A-and-N merged 3 commits into
mainfrom
aroma-164-heads-multi-industry

Conversation

@rvnminers-A-and-N

Copy link
Copy Markdown
Collaborator

Summary

Grows the aroma model from the food-only baseline to 164 odor-descriptor heads (170 total trained heads with the 6 taste heads), and reframes the corpus as multi-industry: one head set serves every "Mancer" mode. Flavormancer is the general flavor-chemistry base; Beveragemancer / Aromamancer (fragrance) / Vapemancer (inhalation) / Oilmancer (essential oils) specialize on top of it. Edibility is a per-molecule food-safe flag (open-gov registers), not corpus membership — so the corpus can include the fragrance space needed to read structure→smell well.

Closes #201.

What changed

Model / data

  • New heads span food (celery, maple, saffron, citrus subtypes, herbs/spices), fragrance/aroma-only (aldehydic, amber, oakmoss, vetiver, sandalwood, myrrh, labdanum, …), and floral/fruit subtypes — each clears CV-AUROC ≥ 0.70.
  • The diffuse musty percept is redistributed into corky (its distinct cork-taint scaffold, 0.98) + earthy/mushroom (sharpening earthy 0.88→0.95). The redundant tuberose head is consolidated into its white-floral neighbours (jasmine/gardenia/ylang/champaca), nearly doubling their example counts and de-inflating small-n scores.
  • Deterministic builds: a persistent resolve_cache.json ends the PubChem-flakiness that used to churn marginal (n≈10) heads build-to-build (e.g. cocoa once cratered 10→4 from one flaky build).

Surfaces (kept in sync)

  • Canonical per-descriptor blurbs now live once in predict.AROMA_DESC and flow to /api/aroma, /api/predict (now includes aroma), the workbench meter help, the MCP server, and the skill. The note picker offers every trained head.
  • Docs updated to 164 heads / 8,830 enriched molecules. AROMA-AUDIT.md documents the honesty rails and provenance (below).

Honesty rails (so the count never over-claims)

  • Subtype/structural heads (floral subtypes, lactonic) clear on shared scaffolds — flagged as slices of space we already cover, not orthogonal percepts.
  • ~1.00-AUROC heads (wintergreen, oakmoss, myrrh, cinnamon, musky) are narrow single-scaffold classes. Tested: adding diverse examples keeps them at 1.00 → the percept is the chemical family (easy separation, not fixable overfitting). Real robustness needs the GNN (research(model): GNN for structure→property vs the RandomForest heads #199).
  • The count is inherently ±2–3 at the margin — ~15 heads sit right at the 10-positive / 0.70-AUROC boundary.
  • musty does not ship (diffuse, no shared substructure).

Provenance

Food heads: HSDB (public domain) + open-gov food registers. Fragrance/aroma-only associations: general public flavor & fragrance chemistry facts (some supplied by Claude Opus 4.8), resolved to public-domain PubChem structures — non-copyrightable (Feist v. Rural); no proprietary compilation (Good Scents / Leffingwell / FlavorDB) is a source. A spot-verification + IP/provenance review is the standing pre-commercial gate (documented in SOURCES.md, not advertised in the UI).

Testing

  • 35 passed, 0 skipped (unit + Playwright e2e against the live 164-head app).
  • Every training-positive molecule predicts its own head; all 164 heads load and serve with descriptions attached.

Curated CSVs remain private (gitignored); framework + schema samples ship.

Follow-ups (not blocking)

Grow the aroma model from the food-only baseline to 164 odor-descriptor
heads (170 total with the 6 taste heads), serving one head set across every
"Mancer" mode. Flavormancer is the general flavor-chemistry base; the other
modes specialize on top of it — Beveragemancer, Aromamancer (fragrance),
Vapemancer (inhalation), Oilmancer (essential oils) — each adding its own
characteristics and tools. Edibility is a per-molecule food-safe FLAG (open-gov
registers), not corpus membership, so the corpus can include the fragrance
space needed to read structure->smell well.

Model / data
- New heads span food (celery, maple, saffron, citrus subtypes, herbs/spices),
  fragrance/aroma-only (aldehydic, amber, oakmoss, vetiver, sandalwood, myrrh,
  labdanum, ...), and floral/fruit subtypes — each clears CV-AUROC >= 0.70.
- Redistribute the diffuse "musty" percept into corky (distinct cork-taint
  scaffold) + earthy/mushroom (which it sharpens); consolidate the redundant
  tuberose head into its white-floral neighbours, de-inflating their small-n
  scores.
- Deterministic builds: persistent resolve_cache.json ends the PubChem-flakiness
  that churned marginal heads build-to-build.

Surfaces (kept in sync)
- Canonical per-descriptor blurbs live once in predict.AROMA_DESC and flow to
  /api/aroma, /api/predict (now includes aroma), the workbench meter help, the
  MCP server, and the skill. Note picker offers every trained head.
- Docs updated to 164 heads / 8,830 enriched molecules; AROMA-AUDIT documents
  the honesty rails (subtype/structural heads, ~1.00-AUROC single-scaffold
  classes, the count's +/-2 margin) and the fragrance-association provenance
  (public flavor/fragrance chemistry facts, some via Claude Opus 4.8; non-
  copyrightable per Feist; IP review noted in docs as the pre-commercial gate).

Curated CSVs remain private (gitignored); framework + schema samples ship.
Tests: 35 passed, 0 skipped (unit + e2e against the live 164-head app).

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
Remove redundant duplicate VOCAB entries (gardenia/ylang/magnolia/cucumber)
introduced across the head-expansion passes. Values were identical, so this is
a pure lint fix — dict keys dedupe, so the built dataset and the 164 trained
heads are unchanged.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
…2e gate

Ruff 0.16.0 broadened its default rule set (bandit S, bugbear B, SIM, FURB,
PLR, ...), surfacing ~82 findings repo-wide. CI installs ruff unpinned, so keep
it latest and fix the debt properly rather than pin or blanket-ignore.

Fixed outright (no noqa):
- try/except/pass  -> contextlib.suppress(), reasons preserved (23)
- try/except/continue in loops -> sentinel + contextlib.suppress (7)
- one blind-except return-fallback -> suppress + sentinel
- mutable default args -> None sentinels (B006); bare open() -> context
  managers (SIM115); nested with -> combined (SIM117); v==v NaN trick ->
  math.isnan / np.isnan (PLR0124); collapsed nested ifs (SIM102);
  dict() -> literals, next(iter()), tuple startswith, import sorting,
  regex-flag aliases, unused-noqa removal.

Kept as documented noqa (25, all with reasons): blind-except (BLE001) blocks
that do real fallback work — retry-with-backoff, log-and-skip, return-partial.
For best-effort network/RDKit/parquet code, catch-all-and-degrade is the correct
defensive choice; narrowing to named types would risk crashing on an unlisted
exception. No blanket config ignore, so future ruff rules still surface to triage.

e2e: the session-scoped models_present gate now polls with a 3-minute deadline
(was a single 15s query that could time out under load and cascade-skip all 9
model-dependent tests). Full suite now runs 0 skipped. (Remaining full-e2e
flakiness under back-to-back load is a single-worker-server artifact; those tests
pass in isolation and are not in the PR-blocking smoke set.)

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

data(aroma): chase near-miss heads + unlock skipped descriptors

1 participant