Describe the bug
resolvers._opsin_resolve accepts substituent (radical) SMILES from the OPSIN web
service, and resolve_names writes them onto compounds as SMILES identifiers. A
compound named methyl gets the structure [CH3].
OPSIN parses substituent prefixes as radicals, and the EBI web service the resolver
calls has that behavior enabled:
$ curl -s https://www.ebi.ac.uk/opsin/ws/methyl.smi
[CH3]
$ curl -s https://www.ebi.ac.uk/opsin/ws/bromo.smi
[Br]
$ curl -s https://www.ebi.ac.uk/opsin/ws/Boc.smi
[C](=O)OC(C)(C)C
RDKit parses all three, so the Chem.MolFromSmiles(smiles) is not None guard in
resolve_name passes and the fragment is returned as the compound's structure.
To Reproduce
>>> from ord_schema.resolvers import resolve_name
>>> resolve_name("name", "methyl")
('[CH3]', 'OPSIN')
(PubChem and CIR both decline methyl, so OPSIN is what answers.)
Expected behavior
A substituent is not a compound. _opsin_resolve should decline a name that parses only
as a radical, and fall through.
The cheapest check is on the parsed molecule rather than the string: reject when the Mol
has any atom with unpaired electrons (atom.GetNumRadicalElectrons()), which is what
distinguishes [CH3] from a legitimately radical-free structure. Note that a plain
[Br] or [OH] also arrives this way, so a string-level check for square brackets is
not sufficient.
Additional context
Measured over the ORD name-only compounds (52,651 unique names). Of the 1,663 names
OPSIN resolved in that audit, 456 (27%) are substituent radicals — methyl,
ethyl, benzyl, bromo, chloro, amino, hydroxy, acetyl, carbonyl, Boc,
nitro, diazo, vinyl — covering 3,793 compound rows that would receive a
fragment as their structure.
The same measurement pins the behavior to the web service's configuration rather than to
OPSIN itself: running the OPSIN jar locally through py2opsin with its default settings
resolves 1,222 of those names and reproduces the extra 456 only when
allow_radicals=True is passed (1,678 hits, with the same SMILES on every name both
produce). So the EBI service runs with radicals allowed.
🤖 Generated with Claude Code
Describe the bug
resolvers._opsin_resolveaccepts substituent (radical) SMILES from the OPSIN webservice, and
resolve_nameswrites them onto compounds asSMILESidentifiers. Acompound named
methylgets the structure[CH3].OPSIN parses substituent prefixes as radicals, and the EBI web service the resolver
calls has that behavior enabled:
RDKit parses all three, so the
Chem.MolFromSmiles(smiles) is not Noneguard inresolve_namepasses and the fragment is returned as the compound's structure.To Reproduce
(PubChem and CIR both decline
methyl, so OPSIN is what answers.)Expected behavior
A substituent is not a compound.
_opsin_resolveshould decline a name that parses onlyas a radical, and fall through.
The cheapest check is on the parsed molecule rather than the string: reject when the Mol
has any atom with unpaired electrons (
atom.GetNumRadicalElectrons()), which is whatdistinguishes
[CH3]from a legitimately radical-free structure. Note that a plain[Br]or[OH]also arrives this way, so a string-level check for square brackets isnot sufficient.
Additional context
Measured over the ORD name-only compounds (52,651 unique names). Of the 1,663 names
OPSIN resolved in that audit, 456 (27%) are substituent radicals —
methyl,ethyl,benzyl,bromo,chloro,amino,hydroxy,acetyl,carbonyl,Boc,nitro,diazo,vinyl— covering 3,793 compound rows that would receive afragment as their structure.
The same measurement pins the behavior to the web service's configuration rather than to
OPSIN itself: running the OPSIN jar locally through
py2opsinwith its default settingsresolves 1,222 of those names and reproduces the extra 456 only when
allow_radicals=Trueis passed (1,678 hits, with the same SMILES on every name bothproduce). So the EBI service runs with radicals allowed.
🤖 Generated with Claude Code