orthonym.assembly.candidate_pool#

Note

Internal API. Names and behaviour may change between releases.

CandidatePool: the candidate names of one assemble_name call, the gates that void a candidate, and the choice of the winner.

WHAT IT DOES#

A handler that names the molecule calls pool.add(name, handler_id, features,...). add scores the name with coverage_scoring.compute_confidence, applies the two gates that void a candidate, and stores the survivor:

  • RATIO_REJECT_FLOOR (every handler): a candidate whose ratio factor is below the floor is voided, unless the producer measured a complete atom partition (coverage_measured=True).

  • the Tier-B gate (the ring_b handlers of HANDLER_POLICIES): a candidate whose confidence is below CONFIDENCE_GATE_THRESHOLD is voided.

A voided candidate returns None and the caller falls through to the next handler.

best returns the candidate of the first handler whose policy is direct_return; when none fired it returns the first candidate added. The inner handlers run in dispatch order (assembly/inner_dispatch.py) and the first one that succeeds returns its own result, so the winner of a pool is the first name that a handler produced. The three Tier-A ring handlers (complex_ring, heterocycle, benzene) are the exception: handlers/tier_a_ring.py adds up to three candidates and ranks them with coverage_scoring.select_best_candidate (highest confidence, then handler priority, then alphanumerical order of the name).

The pool does not rank parent skeletons by the criteria. That ranking is rules/p44_scorer.py (select_parent_unified), and the ranking of finished names is assembly/offer_pool.py (rank_offers, select_offer, select_rt_passing), which namer.py consumes.

FIELDS SET AFTER SCORING#

parent_atom_indices, ring_info, tree, node_scores and tree_rewritten are set on the candidate after compute_confidence has returned and never enter it: the confidence is a function of the name, the handler and the features alone, so passing an atom set or a tree cannot change it. factors['parent_correctness'] is recorded after scoring in the same way; ParentCorrectnessScorer is bound once at module load (not imported per add call).

POOL STACK#

assemble_name is recursive (nested substituent and fragment namings, N-oxide, decomposition). Each call pushes a fresh pool with push_pool and pops it in a finally clause with pop_pool; get_current_pool returns the top. A recursive call therefore never adds to its caller’s pool. The stack is thread-local, so concurrent runners keep independent stacks.

class orthonym.assembly.candidate_pool.HandlerPolicy(handler_id, tier, priority, direct_return=False)#

Bases: object

Declarative policy for one handler’s pool participation.

Fields:

handler_id: Handler name (matches HANDLER_PRIORITY keys). tier: ‘ring_a’ | ‘ring_b’ | ‘chain’ | ‘direct_return’. priority: Tiebreak priority (pulled from HANDLER_PRIORITY). direct_return: True for Tier B and direct-return handlers

(best returns the first such candidate); False for the Tier A ring handlers (ranked by select_best_candidate) and chain.

add applies the module constant CONFIDENCE_GATE_THRESHOLD to the ring_b handlers; a policy carries no threshold of its own.

handler_id: str#
tier: str#
priority: int#
direct_return: bool = False#
class orthonym.assembly.candidate_pool.CandidatePool#

Bases: object

Holds scored candidate names from one assemble_name invocation.

best returns the candidate of the first direct_return handler, else the first candidate added (module docstring).

parent_correctness is recorded in add through the module-level ParentCorrectnessScorer binding; it is computed after the confidence, so it does not enter cand.confidence.

Tier B gate semantics: for a ring_b handler, add returns None (gate-rejected) when cand.confidence is below CONFIDENCE_GATE_THRESHOLD (0.40), and the caller falls through to the next handler.

Direct-return handlers: add also sets self._direct_return_winner (the first direct-return candidate), which best returns.

add(name, handler_id, features, parent_atom_indices=None, ring_info=None, tree=None, coverage_measured=False)#

Score and add a candidate. Returns the candidate, or None if gate-rejected (Tier B handlers only).

BYTE-IDENTICAL CONTRACT (RESEARCH Risk 1):

parent_atom_indices is set POST-HOC on the returned CandidateName (NOT passed into compute_confidence). This guarantees confidence is byte-identical to current composer.py code.

Parameters:
  • name (str) – The IUPAC name produced by the handler.

  • handler_id (str) – Key into HANDLER_POLICIES. Unknown handler_id is accepted with no policy (no gate, no direct-return flag).

  • features (Any) – MolecularFeatures (passed through to compute_confidence and to ParentCorrectnessScorer).

  • parent_atom_indices (Set[int] | None) – Optional set of atom indices comprising the parent structure. Used by ParentCorrectnessScorer; does NOT affect compute_confidence (Risk 1).

  • coverage_measured (bool) – True when the producer MEASURED a complete atom partition for name (every heavy atom bound to one of its fragments; _w2_atom_coverage_verdict). The RATIO_REJECT_FLOOR is a character-count stand-in for coverage, so it is skipped then: numerical-term chain names are short by design (‘octacontane’, ratio 0.092). Does not affect the confidence.

Returns:

The added CandidateName on success. None if Tier B gate rejected (cand.confidence < CONFIDENCE_GATE_THRESHOLD).

Return type:

CandidateName | None

best()#

Return the winning candidate, or None when the pool is empty.

The candidate of the first direct_return handler wins (the same result as the sequential “if X applies: return X” dispatch of assemble_name); when no such handler added a candidate, the first candidate added wins. For example, the Tier-A complex_ring handler (direct_return False) may add ‘1-methoxypyrene’ and the polycyclic handler (direct_return True) then add ‘2-methoxypyrene’ for the same molecule: best returns the polycyclic candidate, not _candidates[0].

all_candidates()#

Return all collected candidates (for logging/diagnostics).

orthonym.assembly.candidate_pool.push_pool(features=None)#

Push a fresh pool onto the per-thread stack and return it.

Called by assemble_name prologue (composer.py). MUST be paired with pop_pool in a finally clause so the pool is popped on every exit path (normal return, exception, early return statements).

(code review 2026-05-30): when features is provided, the a phase

controller flag/oracle are bound NOW (construction) off that authoritative per-call features object, instead of being lifted lazily off the first add call. features=None (get_current_pool auto-push, tests) leaves the pool’s flag at its safe default and the add fallback binds it later.

Returns the new pool that is now the active cascade scope for the currently-executing assemble_name call.

orthonym.assembly.candidate_pool.pop_pool()#

Pop the top pool from the per-thread stack.

Called by assemble_name epilogue (composer.py, in finally clause). Safe to call when the stack is empty (no-op) — defensive against any code path that pops without a matching push.

orthonym.assembly.candidate_pool.get_current_pool()#

Return the top of the per-thread pool stack — the active cascade for the currently-executing assemble_name call.

Auto-pushes a fresh pool if the stack is empty. This covers two cases: (1) a handler called get_current_pool outside an assemble_name scope (e.g., from a unit test that invokes pool.add directly), and (2) backward compatibility with any legacy caller that expected the pre-fix lazy-init behavior.

orthonym.assembly.candidate_pool.clear_pool(features=None)#

BACKWARD-COMPAT: replace the top of the stack with a fresh pool.

Pre-fix code (Plan 03) called clear_pool in assemble_name’s prologue to reset state. Post-fix, push_pool is the right primitive (the wrapping try/finally handles pop). This alias preserves the call-site signature so any handler still calling clear_pool directly does not break.

Behavior: if the stack is empty, push a fresh pool (same as pre-fix lazy init); if non-empty, replace the top so the current cascade restarts cleanly without affecting outer cascades on the stack.

(code review 2026-05-30): binds the a phase controller flag/oracle

off features at construction (see push_pool) when provided.