Skip to content

Commit 39c01f5

Browse files
fix(predict): rename Tox21 models dict — it clobbered the structural-alert SMARTS (#61)
I introduced a name collision in #60: the new Tox21 models dict was named `_TOX`, the same name as the existing structural-alert SMARTS dict. So `_tox_alerts()` iterated RandomForest objects instead of SMARTS patterns — silently emptying the structural-alert safety screen when no tox models were present (the CI case, which is why CI missed it) and crashing predict() when they were (surfaced once the demo box had trained tox_models/). Fix: the tox models dict is now `_TOX_MODELS`; the structural-alert `_TOX` is intact. Adds a regression test (nitrobenzene must trip the 'aromatic nitro' alert). Verified end-to-end with the populated GRAS + properties tables: menthol BP 215 °C + in-GRAS, nitrobenzene not-in-GRAS + nitro alert, tox heads loaded. 21 tests pass. Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
1 parent 556ba4a commit 39c01f5

2 files changed

Lines changed: 12 additions & 4 deletions

File tree

tests/test_predict.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,11 @@ def test_predict_includes_tox_screen():
2929
out = predict.predict("CCO")
3030
assert "tox_screen" in out["safety"]
3131
assert isinstance(out["safety"]["tox_screen"]["available"], bool)
32+
33+
34+
def test_safety_structural_alert_fires():
35+
# regression: a nitro aromatic must trip the structural tox-alert screen. (The tox-models
36+
# dict was accidentally named _TOX, clobbering the structural-alert SMARTS dict; this
37+
# silently emptied structural_alerts with no models and crashed with them.)
38+
out = predict.predict("O=[N+]([O-])c1ccccc1") # nitrobenzene
39+
assert out["safety"]["structural_alerts"] # non-empty

training/predict.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ def _ik1(*smiles):
187187

188188
# Caution-only toxicity-assay heads (Tox21, public domain). Loaded if trained.
189189
# INDICATIVE in-vitro signals — never a toxicity determination.
190-
_TOX = {}
190+
_TOX_MODELS = {}
191191
_TOX_DIR = Path("tox_models")
192192
if _TOX_DIR.exists():
193193
for p in _TOX_DIR.glob("*_rf.joblib"):
194-
_TOX[p.stem.replace("_rf", "")] = joblib.load(p)
194+
_TOX_MODELS[p.stem.replace("_rf", "")] = joblib.load(p)
195195

196196
# Known-label lookup: ground truth for molecules we actually have data on. This
197197
# is how the salty/sour data works as a FLAG without a model — if a queried
@@ -623,12 +623,12 @@ def predict_aroma(smiles, top_k=8):
623623
def predict_tox(mol, threshold=0.5):
624624
"""Caution-only in-vitro tox-assay activity (Tox21 models). INDICATIVE flags for
625625
review — NEVER a toxicity/safety determination. Honest/empty if heads untrained."""
626-
if not _TOX:
626+
if not _TOX_MODELS:
627627
return {"available": False,
628628
"note": "tox heads not trained — run train_tox.py (Tox21, public domain)"}
629629
x = _fp(mol)
630630
assays = []
631-
for name, clf in sorted(_TOX.items()):
631+
for name, clf in sorted(_TOX_MODELS.items()):
632632
p = round(float(clf.predict_proba(x)[0, 1]), 3)
633633
assays.append({"assay": name, "meaning": _TOX_MEANING.get(name, name), "probability": p})
634634
flags = [a["assay"] for a in assays if a["probability"] >= threshold]

0 commit comments

Comments
 (0)