orthonym.metrics.coverage_metrics#

Note

Internal API. Names and behaviour may change between releases.

Task 0.3 — the corrected three-metric coverage instrument.

Pure classifiers + aggregator. NO Java and NO Orthonym naming happen in this module: the round-trip is performed by an injected callable (name_to_smiles: str -> Optional[str]), so the unit tests exercise every branch with a dict-backed fake OPSIN. The production runner (scripts/coverage_metrics.py) supplies the real OPSIN subprocess.

Corrected metric contract (binds the ship gate — see the master plan “Metric definitions”):

  • coverage = emitted / total. “Emitted” = Orthonym produced a real name (NOT errors.is_failure_name).

  • of_emitted_rt = round-trip-valid / emitted. A name is RT-valid iff OPSIN PARSES it AND the parsed structure matches the input under the chosen matcher.

  • confidently_wrong = emitted AND OPSIN-parses AND structure-MISMATCH, as a fraction of TOTAL. OPSIN-unparseable emissions are their OWN bucket (``unparseable``), never counted as wrong — this correction is what flips the ship gate relative to the earlier conflated metric.

  • unparseable = emitted AND OPSIN-does-not-parse, fraction of TOTAL.

  • abs_rt_valid = coverage × of_emitted_rt = RT-valid / total. This is the ~99% target axis.

Every metric is reported under BOTH matchers (they are NOT interchangeable):

  • parity — the most lenient lens, a forgiving round-trip comparison. Full standardization of both sides (fragment-parent → normalize → reionize → uncharge → canonical tautomer) then canonical-SMILES equality. This forgives (a) charge/protonation state, (b) tautomers broadly — RDKit’s canonical tautomer merges keto-enol AND amide/imidol shifts, wider than InChI mobile-H — and (c) stereo, because CanonicalTautomer normalizes away stereo descriptors. It answers “does the name describe the right CONSTITUTION, ignoring charge, tautomer and stereo specificity?”

  • strict — the Orthonym house matcher: InChIKey skeleton block (formula + connectivity + mobile-H, stereo- AND charge-insensitive) PLUS net formal charge. This is exactly the constitutional key. It is charge-SENSITIVE (catches a dropped/added charge the parity lens forgives) and tautomer-tolerant only to InChI’s mobile-H scope (it does NOT merge keto-enol), while remaining stereo-insensitive.

Stereo caveat (documented, not a bug). BOTH matchers forgive stereo, so neither flags a stereo INVERSION (name says S, input is R) — it counts as RT-valid. This is by design and consistent with the RT gate being stereo-insensitive; stereo-inversion residual wrongness is owned by the C.1 stratified residual-sampling audit, not by these automated matchers. The upshot: of-emitted-RT is a (small) OVER-estimate to the extent stereo inversions occur. Crediting stereo OMISSION as a match is intended (the tier is allowed to emit constitution-only names).

pin_pct (curated gold-gate pass rate) is a DIFFERENT population and is carried through verbatim if supplied; it is never derived here and never presented as commensurable with coverage.

class orthonym.metrics.coverage_metrics.Matcher(*values)#

Bases: str, Enum

PARITY = 'parity'#
STRICT = 'strict'#
class orthonym.metrics.coverage_metrics.RowClassification(*values)#

Bases: str, Enum

ABSTAINED = 'abstained'#
RT_VALID = 'rt_valid'#
CONFIDENTLY_WRONG = 'confidently_wrong'#
UNPARSEABLE = 'unparseable'#
class orthonym.metrics.coverage_metrics.RowResult(smiles: str, name: str | None, emitted: bool, opsin_parsed: bool | None, parity: orthonym.metrics.coverage_metrics.RowClassification | None, strict: orthonym.metrics.coverage_metrics.RowClassification | None)#

Bases: object

smiles: str#
name: str | None#
emitted: bool#
opsin_parsed: bool | None#
parity: RowClassification | None#
strict: RowClassification | None#
orthonym.metrics.coverage_metrics.classify_row(smiles, name, name_to_smiles)#

Classify one (input, emitted-name) pair under both matchers.

A single OPSIN call is made per emitted name; the parsed SMILES is then scored by each matcher (the parse/unparseable axis is matcher-invariant, only the match/mismatch split differs).

orthonym.metrics.coverage_metrics.aggregate(rows, pin_pct=None)#

Aggregate row results into the corrected three-metric report under both matchers. pin_pct (curated-gold gate pass rate, a DIFFERENT population) is carried verbatim when supplied; never derived here.

orthonym.metrics.coverage_metrics.parity_match(input_smiles, opsin_smiles)#

True iff the two structures agree under the parity matcher (charge/tautomer-forgiving, stereo-comparing). False if either side cannot be standardized (fail-CLOSED for the metric — an unstandardizable emission is not credited as a match).

orthonym.metrics.coverage_metrics.strict_match(input_smiles, opsin_smiles)#

True iff InChIKey skeleton block AND net formal charge agree (the constitutional key). False if either side cannot be hashed.