Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions training/predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -1290,15 +1290,29 @@ def _taste_auroc(t):
meta = _TASTE_META.get(t) if isinstance(_TASTE_META, dict) else None
return meta.get("auroc") if isinstance(meta, dict) else None

def _cal(meta, h):
"""The head's published calibration: where its bar sits, how precise it is there, and
whether it may be shown as confident. Surfaced so the catalog is auditable — a head that
fires at 0.16 and is right 6% of the time should say so on its own row, not only inside a
molecule read."""
m = meta.get(h, {})
return {"threshold": m.get("threshold"), "precision": m.get("cv_precision"),
"recall": m.get("cv_recall"), "n_pos": m.get("n_pos"),
"confident_capable": m.get("confident_capable", True)}

def _aroma(a):
return {"head": a, "auroc": _AROMA_META.get(a, {}).get("auroc"), "desc": AROMA_DESC.get(a)}
return {"head": a, "auroc": _AROMA_META.get(a, {}).get("auroc"),
"desc": AROMA_DESC.get(a), **_cal(_AROMA_META, a)}

# mouthfeel = the aroma heads tagged mouthfeel (cooling/pungent) + the dedicated mouthfeel heads
mouthfeel = [_aroma(a) for a in aroma_heads if a in _MOUTHFEEL_HEADS]
mouthfeel += [{"head": h, "auroc": _MOUTHFEEL_META.get(h, {}).get("auroc"), "desc": AROMA_DESC.get(h)}
mouthfeel += [{"head": h, "auroc": _MOUTHFEEL_META.get(h, {}).get("auroc"),
"desc": AROMA_DESC.get(h), **_cal(_MOUTHFEEL_META, h)}
for h in mouthfeel_heads]
return {
"taste": [{"head": t, "auroc": _taste_auroc(t)} for t in taste_heads],
# taste heads keep a flat 0.5: hundreds of positives each, so they were never shy
"taste": [{"head": t, "auroc": _taste_auroc(t), "threshold": 0.5,
"confident_capable": True} for t in taste_heads],
"aroma": [_aroma(a) for a in aroma_heads],
"mouthfeel": mouthfeel,
"safety": [{"head": t, "auroc": _TOX_META.get(t, {}).get("auroc"),
Expand Down
23 changes: 17 additions & 6 deletions training/workbench.html
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@
/* a head that fires but is right <50% of the time out-of-fold: shown, never sold as confident */
.ameter.indicative .afill{background-image:repeating-linear-gradient(45deg,transparent,transparent 3px,rgba(0,0,0,.28) 3px,rgba(0,0,0,.28) 6px)}
.ameter.indicative .aval b{font-weight:600;opacity:.75;font-style:italic}
.hbar.indicative .hb-fill{background-image:repeating-linear-gradient(45deg,transparent,transparent 3px,rgba(0,0,0,.28) 3px,rgba(0,0,0,.28) 6px)}
.hb-ind{font-family:var(--mono);font-size:9px;letter-spacing:.06em;text-transform:uppercase;opacity:.6;margin-left:6px;border:1px solid currentColor;border-radius:3px;padding:0 3px}
.arow{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:4px}
.aname{font-weight:600;font-size:13px;text-transform:capitalize}
.aval{font-family:var(--mono);font-size:12px;color:var(--muted)}
Expand Down Expand Up @@ -1867,20 +1869,29 @@ <h4>Software &amp; type</h4>
}

// one compact head bar (name · track · value · AUROC) — shared by the mouthfeel + safety groups
function headBar(name, score, auroc, color, title){
const pct=Math.round((score||0)*100), on=score>=0.5;
// `cal` carries the head's published calibration when it has one: {threshold, precision,
// indicative}. Tox heads aren't calibrated and fall back to a flat 0.5, which is correct for them —
// they have hundreds of positives each and were never shy.
function headBar(name, score, auroc, color, title, cal){
const thr = (cal && cal.threshold!=null) ? cal.threshold : 0.5;
const pct=Math.round((score||0)*100), on=(score||0)>=thr;
const ind = !!(cal && cal.indicative);
const au = auroc!=null ? ` <span class="hb-au">· AUROC ${auroc}</span>` : '';
return `<div class="hbar${on?'':' dim'}"${title?` title="${title.replace(/"/g,'&quot;')}"`:''}>`+
`<span class="hb-name">${name}</span>`+
// show WHERE the bar sits and how precise the head is there — a head firing at 16% should say so
const cl = (cal && cal.threshold!=null)
? ` <span class="hb-au">· fires ≥${Math.round(thr*100)}%${cal.precision!=null?` · ${Math.round(cal.precision*100)}% precise`:''}</span>` : '';
return `<div class="hbar${on?'':' dim'}${ind?' indicative':''}"${title?` title="${title.replace(/"/g,'&quot;')}"`:''}>`+
`<span class="hb-name">${name}${ind?'<span class="hb-ind">indicative</span>':''}</span>`+
`<span class="hb-track"><span class="hb-fill" style="width:${pct}%;background:${color}"></span></span>`+
`<span class="hb-val">${pct}%${au}</span></div>`;
`<span class="hb-val">${pct}%${au}${cl}</span></div>`;
}
// Mouthfeel (chemesthesis) — the 5 trained trigeminal heads, ranked, each with its CV-AUROC
function renderMouthfeel(mf){
const grp=$('mouthfeelGroup'), box=$('mouthfeelBars');
const ds=(mf&&mf.descriptors)||[];
if(!ds.length){ if(grp) grp.style.display='none'; return; }
box.innerHTML = ds.map(d=> headBar(d.sensation, d.score, d.auroc, aromaColor(d.sensation), d.desc)).join('');
box.innerHTML = ds.map(d=> headBar(d.sensation, d.score, d.auroc, aromaColor(d.sensation), d.desc,
{threshold:d.threshold, precision:d.precision, indicative:d.indicative})).join('');
grp.style.display='block';
}
// Safety — the 12 Tox21 caution-only assay heads, most-active first, each with its CV-AUROC
Expand Down
Loading