|
| 1 | +"""Deterministic rule / lookup logic — no trained models or data files required. |
| 2 | +
|
| 3 | +These cover the rule layer the .NET product (M2) will port: sour/salty calls, |
| 4 | +allergen labeling, and the documented dangerous-mixture screen. |
| 5 | +""" |
| 6 | +from rdkit import Chem |
| 7 | + |
| 8 | +import predict |
| 9 | + |
| 10 | + |
| 11 | +def _mol(smiles): |
| 12 | + return Chem.MolFromSmiles(smiles) |
| 13 | + |
| 14 | + |
| 15 | +def test_sour_fires_on_acid(): |
| 16 | + out = predict._sour(_mol("CC(=O)O")) # acetic acid |
| 17 | + assert out["sour"] is True |
| 18 | + assert out["sour_reason"] |
| 19 | + |
| 20 | + |
| 21 | +def test_sour_silent_on_nonacid(): |
| 22 | + out = predict._sour(_mol("c1ccccc1")) # benzene |
| 23 | + assert out["sour"] is False |
| 24 | + assert out["sour_reason"] == [] |
| 25 | + |
| 26 | + |
| 27 | +def test_salty_inorganic_salt(): |
| 28 | + out = predict._salty(_mol("[Na+].[Cl-]")) # NaCl |
| 29 | + assert out["salty"] is True |
| 30 | + assert "inorganic" in out["salty_reason"] |
| 31 | + |
| 32 | + |
| 33 | +def test_salty_defers_to_organic_anion(): |
| 34 | + # monosodium glutamate — cation present, but the organic anion owns the taste |
| 35 | + out = predict._salty(_mol("C(CC(=O)[O-])C(C(=O)O)N.[Na+]")) |
| 36 | + assert out["salty"] is False |
| 37 | + assert "organic anion" in out["salty_reason"] |
| 38 | + |
| 39 | + |
| 40 | +def test_salty_silent_on_single_fragment(): |
| 41 | + out = predict._salty(_mol("OC1OC(CO)C(O)C(O)C1O")) # glucose |
| 42 | + assert out["salty"] is False |
| 43 | + |
| 44 | + |
| 45 | +def test_labeling_shape_and_negative(): |
| 46 | + out = predict.labeling(_mol("O=Cc1ccc(O)c(OC)c1")) # vanillin — not declarable |
| 47 | + assert out["eu_declarable_allergen"] is False |
| 48 | + assert out["allergen_name"] is None |
| 49 | + |
| 50 | + |
| 51 | +def test_check_mixture_empty_is_wellformed(): |
| 52 | + out = predict.check_mixture([]) |
| 53 | + assert out["active_hazards"] == [] |
| 54 | + assert out["conditional_hazards"] == [] |
| 55 | + assert "disclaimer" in out |
| 56 | + |
| 57 | + |
| 58 | +def test_check_mixture_flags_nitrosamine(): |
| 59 | + # sodium nitrite + a secondary amine -> documented nitrosamine hazard |
| 60 | + out = predict.check_mixture(["[Na+].[O-]N=O", "CNC"]) |
| 61 | + hazards = out["active_hazards"] + out["conditional_hazards"] |
| 62 | + assert any("nitrosamine" in h["possible_product"].lower() for h in hazards) |
0 commit comments