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.olate_with_senior_neutral_group(mol, sites, neutral_smi, style)#
The name of a mono-anion whose centre is the oxygen or sulfur of a hydroxy or thiol group and whose neutral form has a group of a class senior to the hydroxy compounds, or ‘’.
“SENIORITY ORDER FOR CLASSES” (the Blue Book), Table 4.1: class 4 ‘Anions’ is
senior to class 7 ‘Acids’ and to every class below it, so the anion is the parent and its group is the principal characteristic group, as for ‘5-carboxypentanoate (PIN)’ (:40997), whose acid group is cited as a prefix. “Anions derived from hydroxy compounds” (:41011): the centre is named by the suffix ‘olate’ or ‘thiolate’, and “Partial salts of polyols and their chalcogen analogues are named substitutively on the basis of the corresponding anion” (:28203): ‘sodium 3-hydroxypropan-1-olate (PIN)’.
route_chargednames the neutral form and gives its suffix the ending of the anion; that fails where the neutral name does not end in the hydroxy suffix, because a senior neutral group is its principal group (the arsonous acid of ‘4-(dihydroxyarsanyl)-2-nitrophenol’ is the parent of ‘(4-hydroxy-3-nitrophenyl)arsonous acid’). Here the neutral form is named again with the hydroxy or thiol group forced as the principal group (the senior group becomes a prefix), by the PIN tier’s machinery alone (_reenter_forced_at_pin_tier), and that name takes the ending. Only for one anion, no cation and no radical, and only when the anion’s group is the only hydroxy or thiol group of the molecule: another one would be a second suffix, or a prefix of a name the two forms do not share, and it is left to the routes that name it. The name is read back by its full InChIKey before it is returned.Built, for a benzene ring, a chain, an aliphatic ring, a heteroaromatic ring and an amino acid: the carboxylic, sulfonic, phosphonic, arsonic and stibonic acid groups, an amide, a nitrile, an aldehyde, a ketone and an acid halide, and on a benzene ring an ester (‘4-(methoxycarbonyl)phenolate’,,:31696). Declined, because the producer that names the neutral form decides the principal group of its own: an ester on a chain (‘3-methoxy-3-oxopropan-1-olate’ is the PIN shape,:31709, and no producer cites an ester as ‘alkoxy…oxo’ on a chain whose principal group is a sulfonic acid, an aminium or an olate: the neutral ‘methyl 3-sulfopropanoate’ is declined too), a lactone, a fused or polycyclic ring system, and a selenium or tellurium centre (
classify_aniondoes not know those anions). The caller then names the anion as it did before.
- 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_ring_cation_azanide(mol, cation_idx, anion_idx)#
“Anionic and cationic centers on different parent structures” (the Blue Book): the ANION is the parent and the cationic part is prefixed to it: a ring N+ with an exocyclic anionic nitrogen (the N-imide ylides,:42499) is the azanide of its group, the ring cation a prefix:42290, ‘pyridin-1-ium-1-yl (preferred prefix)’), with the group the nitrogen carries cited beside it ,:7272: the first cited prefix of a mononuclear parent has no enclosing marks):
C1=CC=[N+](C=C1)[NH-] -> (pyridin-1-ium-1-yl)azanide C1(=CC=CC=C1)[N-][N+]1=CC=CC=C1 -> phenyl(pyridin-1-ium-1-yl)azanide C1=CC=C[N+]1=[N-] -> (1H-pyrrol-1-ium-1-ylidene)azanide
The ring cation’s prefix is
substituent_naming.cation_to_prefix(ring_cation_hydro.ring_cation_prefix: one numbering, free valence at the cation); the azanide of a primary amine anion is the book’s alternative to its ‘aminide’, see_azanide_of_substituent), and the Blue Book gives no ring N-imide example, so the name is recorded as no PIN. Read back to the canonical SMILES ofmol(charges on the same atoms,_charge_sites_rt_ok); ‘’ on any decline.
- 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``.A parent with more than one kind of substitutable position (
toluene,pyridine, any parent that carries other groups) cannot leave the amine locant out,: it is named as the azanide of its substituent group (_azanide_of_substituent). FULL-InChIKey RT-gated (0-wrong). 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')#