orthonym.rules.parent_ranking#

Note

Internal API. Names and behaviour may change between releases.

a phase (P4-b): the Blue-Book-ranked parent-candidate SET.

WHY THIS MODULE EXISTS#

Production is early parent commit: each handler selects its own parent internally and adds exactly ONE candidate to the pool, and the pool’s ranking path is dead (selection_mode='first_applicable', assembly/candidate_pool.py:135, and the module says so at :953-955). So there was never a candidate set to filter — building the ranked set is the work. See internal notes Part A1/A3 and Part G.

WHAT THE BLUE BOOK ACTUALLY LICENSES (derivation Part B1 + B1a)#

There is no escape clause conditioning the choice of parent on whether a name for it can be constructed. Every / criterion is a property of the structure or of the candidate name string; nameability is never among them:

the Blue Book — SENIORITY ORDER FOR PARENT STRUCTURES — “When there is a choice, the senior parent structure is chosen by applying the following criteria, in order, until a decision is reached. These criteria must always be applied before those applicable to rings and ring systems (see and to chains (see.”

the Blue Book — Selection between a ring and a chain as parent hydride — “Within the same heteroatom class and for the same number of characteristic groups cited as the principal characteristic group, a ring is always selected as the parent hydride to construct a preferred IUPAC name. In general nomenclature, a ring or a chain can be the parent hydride.”

Consequently a fall-through to a lower-ranked parent is never a PIN. Its correct status is fixed by:

the Blue Book — INTRODUCTION — “… Preferred IUPAC names are generated under the condition that the name of the parent structure and the names of all or part of components are preferred IUPAC names. When this condition is not fulfilled and when the names of components are acceptable for general nomenclature, the resulting names of the compounds are acceptable only for general nomenclature.”

PIN status is therefore conditional and compositional. A rank>0 emission is a general IUPAC name (the Blue Book) — T3/T4, never is_pin: True. namer. name_tiered already enforces that for every source == 'general_engine' emission; record_parent_fallthrough makes the reason auditable rather than merely correct.

But tier 2 is not the floor. the Blue Book marks discarded names not, and those are “no longer recommended” — emitting one is an accuracy defect, not a label. is_bluebook_discarded_name is the fail-closed veto for that.

CASCADE SHAPE, AND WHY IS NOT A CROSS-CLASS TERM HERE#

applies only “If the criteria of through … do not effect a

choice” (the Blue Book). For a ring-vs-chain pair always effects a choice (ring wins, the Blue Book “always”), so the cross-class (a)–(l) list can only ever fire within a class — where both existing scorers already implement it (ring_system_score [27]/[28]; chain_score elements 3/4). That is why this module needs no new comparator, and it is a derivation result, not an omission.

Ranking key (all terms “lower is senior”, so sorted ⇒ most senior first):

k0 -pcg_count (the Blue Book) max principal-characteristic

groups — precedes everything, ring or chain

k1 -senior_atom_rank (the Blue Book) senior skeletal atom, in

‘s OWN element order (N>P>…>O>S>…>C)

k2 0 ring / 1 chain + (the Blue Book) ring always wins k3 within-class score rings: (a)–(g) then type then

(a),(b) — ring_system_score, verified BB-faithful (derivation Part H1). chains: chain_score’s order (Part H2).

k4 structural tiebreak alphanumerical order is the Blue Book’s own

last resort (the Blue Book); the chain scorer already applies it (chains._p45_alpha_key). This final term makes the order TOTAL using RDKit canonical ranks, which are invariant to SMILES spelling — never atom-input or set/dict iteration order (determinism_new must stay 0).

⚠ FOUR DISTINCT ELEMENT SEQUENCES (derivation Part B8)., (c), (g)/(f) and (c) are four different orders; sharing one table is a correctness bug. This module uses P_44_1_2_ELEMENT_RANK for k1 only — that table implements and nothing else.

RANK 1 IS THE INCUMBENT, BY CONSTRUCTION#

Rank 0 of the returned list is always the parent production already committed to (select_principal_ring_system / features.principal_chain). That makes the fall-through purely additive: it can only ever try parents that today’s code never reaches, so best-effort output is byte-identical whenever rank 0 still passes its gates, and the PIN path never runs this code at all (general_fallback defaults False, namer.py:1815).

class orthonym.rules.parent_ranking.ParentCandidate(kind, atoms, rank, key, is_incumbent)#

Bases: object

One candidate parent structure, with its Blue-Book rank.

atoms is sorted for a ring system and ordered/oriented for a chain (the chain’s numbering direction is part of the candidate). rank 0 is the incumbent — the parent production already chose.

kind: str#
atoms: Tuple[int, ...]#
rank: int#
key: Tuple[Any, ...]#
is_incumbent: bool#
orthonym.rules.parent_ranking.is_bluebook_discarded_name(name)#

True if name is one the Blue Book marks not (the Blue Book).

Exact match over the lowercase/whitespace-collapsed name, against the corpus extracted by scripts/extract_bluebook_not_corpus.py. Exact-only is deliberate: this predicate may only ever suppress an emission, so a substring or prefix rule (which could veto a correct name) is unacceptable while a miss (the veto not firing) is merely a known limitation.

orthonym.rules.parent_ranking.enumerate_parent_candidates(mol, features)#

All ring systems and all skeletal chains — NO nameability pre-filter.

Returns (ring_systems, chains, n_chains_dropped). The Blue Book’s cascade never consults nameability (derivation Part B1), so a candidate is dropped here only for combinatorial reasons, and the count is returned so the caller can log it rather than let a silent cap read as full coverage.

orthonym.rules.parent_ranking.rank_parent_candidates(mol, features)#

The Blue-Book-ranked parent-candidate set, most senior first.

Rank 0 is the parent production already committed to (see the module docstring); ranks 1..N are the candidates today’s code never reaches, ordered by the cascade with a spelling-invariant final tiebreak.

orthonym.rules.parent_ranking.features_for_candidate(features, candidate)#

A shallow features copy whose parent is candidate.

The three ring producers all resolve their parent through select_principal_ring_system(mol, features.ring_systems), which returns its single element verbatim when the list has length 1 (ring_selection.py:785-786) — so restricting ring_systems is enough to steer them, with no producer change at all. The chain producer reads principal_chain / chain_is_parent / atom_to_locant.

Returns features itself for the incumbent, so rank 0 is byte-identical.