orthonym.rules.charged_router#
Note
Internal API. Names and behaviour may change between releases.
The single mandatory parent-selection chokepoint for charged species.
a phase Plan 03 (CHOKE-01, CHOKE-02). ROOT CAUSE of the flat round-trip
wall (V20 audit): every charged class (alkoxide / phenolate / carbanion /
thiolate / carbenium / onium / diazonium / aminium-fallback / radical) decided
its own parent through a parallel carbon-counting stub that ignores
connectivity, substituents and unsaturation — the literal heptanolate bug
(CCCCCCC[O-] -> heptanolate, dropping the locant and every substituent).
Those stubs BYPASS the sound select_parent cascade for 91% of wrong-parent
failures.
This module makes the chokepoint STRUCTURAL: route_charged(mol, style) is the
ONE funnel every charged dispatch handler delegates to. It GENERALIZES the proven
_name_oxoacid_anion template (ions.py:911, the 169.5 fix that
round-trips):
neutralize the chosen fragment -> re-enter the FULL pipeline
Orthonym(style, _disable_opsin_validity_gate=True).name(neutral_smi)-> re-apply the class-correct ionic suffix viaapply_ion_suffix_to_name.
A correct parent auto-corrects the locants (95% co-occurrence) on the same molecule — this is the multi-defect-collapsing fix, not a per-class patch.
- The four IUPAC-2013 guards (SYNTHESIS-authoritative-cascade.md; /
/, applied IN ORDER:
- GUARD 1 FG-class-before-suffix (the
heptanolatefix). A-S(=O)2-O-is an acid anion ->
-sulfonate, NOT a hydroxy anion ->-olate.classify_anionpicks the FG class; the per-classallowed_suffixessubset gates the textual seam so a sulfonate stem can NEVER mis-fire to-olate.- GUARD 2 ionic-center-count-first a-c / a-b). On a multi-center
ion the parent maximizes anionic /
ide/uidecenter count BEFORE length is considered (dicarboxylate dianions). Realized by neutralizing ALL same-sign centers and letting the re-entered pipeline name the multi-suffix parent (butanedioate), exactly as the proven anion seam already does.- GUARD 3 skeletal-charge element seniority d / c):
N > P > As > Sb > Bi > Si > Ge > Sn > Pb > B > Al > Ga > In > Tl > O > S > Se > Te > C. When the charge sits on a skeletal heteroatom the senior element bearing it is the parent, not the longest carbon chain.
- GUARD 4 zwitterion anion-is-parent override. The anion is FORCED as
the parent; a separable cation, the betaine quaternary ammonium) is demoted to a structured
(…azaniumyl)substituent prefix (substituent_naming.cation_to_prefix); a skeletal cation , a ring N+ of pyridinium-2-carboxylate) is deferred to the legacy path (the cumulative ium+ate suffix is out of scope this plan). 169.6-04 (was a Plan-03 detect-and-defer seam).
- GUARD 1 FG-class-before-suffix (the
Returns ‘’ on any failure / out-of-scope shape (metal complex, multi-fragment salt, zwitterion, malformed re-entered parent) so the caller falls through to the existing retained/legacy cascade — preserving the byte-identical no-crash contract.
NO carbon-counting. NO .replace. NO molecule-specific branch. The router is
GENERAL (fix-methodology.md).
- orthonym.rules.charged_router.classify_charged_pcg(mol, sites)#
Charge-first PCG classifier (.3, /). Runs on the ORIGINAL (un-neutralized) mol. Returns the FG-name to FORCE as the principal characteristic group (a detect_functional_groups KEY: ‘carboxylic_acid’, ‘sulfonic_acid’, ‘sulfinic_acid’, ‘phosphonic_acid’), or None when the charged class has NO neutral FG anchor (carbanion/alkoxide/thiolate/ aminide/phenolate -> handled by the.2 emit_parent_hydride_cumulative_ suffix primitive / the existing suffix seam, NOT this override) or when a different route owns the molecule.
- Ordering (; anions / cations / zwitterion / radical):
radical present -> None radical>anion>cation; the -yl primitive owns it)
mixed-sign (zwitterion) -> None _route_zwitterion owns it)
anion(s): senior ionized acid class per -> its acid-FG key
cation(s) only -> None (the aminium / class-keyed cation transforms own it)
The forced FG MUST be a features.functional_groups key (namer.py:1821) or it silently no-ops; for a class with no neutral FG anchor return None. the Blue Book (radical>anion>cation seniority).
- orthonym.rules.charged_router.emit_secondary_amine_azanide(mol, anion_idx, style)#
(the Blue Book,
acetylazanide (PIN)): a DISUBSTITUTED azanide R-[N-]-R’ — a deprotonated secondary amide / sulfonamide / secondary amine named as azanide (NH2-) carrying two N-substituent prefixes. point (3) disallows the...aminidemethod-(1) form for amides, so azanide IS the PIN for the acyl/sulfonyl arms; for a pure secondary-amine arm the PIN is technically...aminide, and this azanide form is then a correct RT-verified best-effort — either way the emitted structure is exactly the input.Names each N-arm as a substituent prefix (
name_substituent_fragment), alphabetises, escalates the enclosing marks viaapply_enclosing_marks), and appendsazanide. RT-gated on the FULL InChIKey (_cation_name_rt_ok) — fail closed to ‘’ (never a wrong or label-dropping name). Scoped to the ACYCLIC secondary-N-anion shape: a non-aromatic N-, formal charge -1, no H, exactly two heavy neighbours that fall into DISJOINT fragments once the two N-bonds are severed (a ring bridging both arms back to N is an in-ring N-anion, a different class). A trisubstituted N- (azanediide) is out of scope here.
- orthonym.rules.charged_router.emit_primary_amine_anide(mol, anion_idx, style)#
(the Blue Book,
benzenaminide (PIN)): a PRIMARY amine anion R-[NH-] named on the SYSTEMATIC amine parent, converting the-aminesuffix to-aminide.The default ionize seam converts
amine->``aminide`` on the re-entered NEUTRAL name; that already covers a CHAIN amine (methanaminide,ethanaminide, because the neutral is the systematicmethanamineetc.). But an AROMATIC / ring primary amine re-enters as the RETAINED headlineaniline– which carries no-aminesuffix token to convert, and the engine never emits the systematicbenzenamine(rules/benzene.pyreturnsaniline;rules/radicals.pyfails closed on exactly this gap). So build the systematic amine parent here: delete the amine N, name the remaining parent hydride (`` ->benzene), appendamine(eliding the parent’s trailinge), then convertamine->``aminide``.FULL-InChIKey RT-gated (0-wrong): a substituted / heteroatom parent whose bare (locant-omitted) amine spelling does NOT round-trip –
toluenamine,pyridinamine– is REJECTED and the caller abstains. Scope: a single ACYCLIC primary-amine N (charge -1, exactly one H, exactly one heavy neighbour, a carbon) whose parent fragment (N removed) is one connected component the pipeline can name. Fail-closed (‘’) otherwise. Placed AFTER the ionize seam so it only ever converts a would-be ABSTAIN (never renames an already-valid aminide).
- orthonym.rules.charged_router.emit_acylium(mol, cation_idx, style)#
(BB 41623 PIN): name an acylium cation R-C(+)=O.
Reconstructs the ACID (adds an -OH back onto the C+, restoring R-COOH), names it, then applies the acid->acylium class-keyed transform (‘carboxylic acid’->’carbonylium’, ‘oic acid’->’oylium’, ‘ic acid’-> ‘ylium’;
apply_ion_suffix_to_name/_CLASS_KEYED_CATION_TRANSFORMS ['acylium']). Deliberately NOT the genericadd_h_for_cationpath used for a plain carbenium ylium – THAT restores an ALDEHYDE (R-CHO), not an acid, which would misname ‘acetylium’ as an aldehyde-ylium.Mirrors
_name_diazonium(sever/reconstruct, name, class-keyed transform), but ADDS an atom (the reconstructed -OH) instead of severing one. Returns ‘’ on any decline – the caller’s generic cascade then runs (add_h_for_cationthere cannot mis-fire either: an aldehyde name never ends in an acid suffix, soapply_ion_suffix_to_namealso returns ‘’ on that fallback path – fail-closed, never a wrong name).
- orthonym.rules.charged_router.route_charged(mol, style='pin')#