Skip to content

resolve_name silently accepts the first CID when PubChem returns several #952

Description

@skearnes

Describe the bug

resolvers.resolve_name treats a multi-answer PubChem response as if it were a single
answer, and silently keeps whichever CID PubChem happened to list first.

_pubchem_resolve requests .../property/IsomericSMILES/txt. When a name matches
several CIDs, PUG REST returns one SMILES per line. _fetch_text returns the whole
body, and resolve_name then does:

if smiles and Chem.MolFromSmiles(smiles) is not None:
    return smiles, resolver

Chem.MolFromSmiles parses the first line of a multi-line string and discards the rest
(whitespace terminates a SMILES, and the remainder is read as the name field), so the
guard passes and the multi-line body is returned as if it were one structure. The
SMILES identifier written by resolve_names is then an arbitrary choice among the
candidates, with details = "NAME resolved by the PubChem API" claiming no ambiguity.

To Reproduce

$ curl -s "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/II/property/IsomericSMILES/txt"
CCC(C)C(C(=O)NC(C(C)C)C(=O)O)N
CC[C@H](C)[C@@H](C(=O)N[C@@H]([C@@H](C)CC)C(=O)O)N
>>> from ord_schema.resolvers import resolve_name
>>> resolve_name("name", "II")
('CCC(C)C(C(=O)NC(C(C)C)C(=O)O)N', 'PubChem API')   # the second candidate is dropped silently

IV behaves the same way. Both were found while auditing name resolution over the ORD
name-only compounds; they are real strings in the corpus, used as roman-numeral compound
labels rather than as names.

Expected behavior

A response carrying more than one structure is ambiguous and the resolver has no basis
for choosing between them. It should be treated as a non-answer and fall through to the
next resolver, the same way an unparseable body already does — matching the reasoning
already recorded in the comment in resolve_name about not writing a structural
identifier that later passes cannot correct.

Concretely: split the body on newlines in _pubchem_resolve and raise _ResolverError
(or return "") when it yields more than one non-empty line.

Additional context

Found while measuring a local PubChem name index against the live service. A resolver
that quietly picks one of several candidate structures is worse than one that declines,
because the written SMILES identifier counts as structural and masks the compound from
every later resolution pass.

🤖 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