Skip to content

OPSIN resolver returns substituent radicals as compound structures (methyl -> [CH3]) #953

Description

@skearnes

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 radicalsmethyl,
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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions