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
Describe the bug
resolvers.resolve_nametreats a multi-answer PubChem response as if it were a singleanswer, and silently keeps whichever CID PubChem happened to list first.
_pubchem_resolverequests.../property/IsomericSMILES/txt. When a name matchesseveral CIDs, PUG REST returns one SMILES per line.
_fetch_textreturns the wholebody, and
resolve_namethen does:Chem.MolFromSmilesparses 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
SMILESidentifier written byresolve_namesis then an arbitrary choice among thecandidates, with
details = "NAME resolved by the PubChem API"claiming no ambiguity.To Reproduce
IVbehaves the same way. Both were found while auditing name resolution over the ORDname-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_nameabout not writing a structuralidentifier that later passes cannot correct.
Concretely: split the body on newlines in
_pubchem_resolveand 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