orthonym.assembly.name_tree_to_string#

Note

Internal API. Names and behaviour may change between releases.

a phase Pass-2 serializer (DECOMP-02 + internal notes).

Single source of truth for NameTreeNode -> str going forward. Plan-02 substrate ships this module ALONGSIDE the legacy _assemble_fragments path at composer.py:7591 — the legacy path STAYS until commit 03-10 thinning per internal notes incremental-migration discipline. Plan-02/03 first-wave handlers return NamingResult(name=<existing string>, tree=None,...); the legacy path is what actually produces those names. The Pass-2 serializer here is exercised by --dump-tree integration tests in Plan-04 and by + handlers that populate trees explicitly.

Contract per internal notes + internal notes-DECOMP.md:

  • If node.fragment_legacy is not None: delegate to the legacy assembly path by wrapping the fragment list as _assemble_fragments([node.fragment_legacy], style). This is the first-wave compatibility mode that lets a phase ship byte-identical even before any handler emits a real tree.

  • If node.fragment_legacy is None AND node.parent_stem is populated: assemble the name from the explicit fields in the order:

    stereo + prefixes + indicated_h + parent_stem + locants
           + unsaturation_locants + suffix
    
    applying multiplicative-prefix and parenthesization rules per

    +.

  • If node.fragment_legacy is None AND node.parent_stem == "": raise an explicit NameTreeSerializerError — this is a malformed tree per DECOMP-02 honest-fail-on-data.

Byte-identical contract (DECOMP-03): for every input node whose fragment_legacy round-trips through the legacy path, the output of name_tree_to_string(node, style) MUST equal the corresponding _assemble_fragments(fragments, style) output.

Anti-pattern hygiene: - -05 / -23: no postprocessor band-aid on inner-dispatch

output; the explicit-field branch below assembles deterministically from the IR fields.

  • -27: NameTreeNode field shape is locked — adding new fields to the serializer here without a corresponding NameTreeNode field addition is a Rule 4 architectural decision.

References: - internal notes-DECOMP.md — Pass-2 serializer contract. - 160-internal notes / Question 3 — explicit two-pass IR + serializer. - internal notes — analog: composer.py:7591-7748 _assemble_fragments. - internal notes § “Name-Tree IR Semantic Contract” — collision detection

per IUPAC; suffix-prefix locant priority; unsaturation infix.

orthonym.assembly.name_tree_to_string.name_tree_to_string(node, style='pin')#

a phase + IUPAC Pass-2 serializer.

Composition order:

[stereo] + [prefixes (alpha-sorted; recursively serialized)] +
[parent_stem] + [indicated_h] + [unsaturation_infix] + [suffix]
Parameters:
  • node (NameTreeNode) – The NameTreeNode to serialize.

  • style (str) – Naming style (“pin”, “general”, “cas”); forwarded to the legacy assembler when fragment_legacy is set.

Returns:

IUPAC name string per internal notes byte-identical contract.

Raises:

NameTreeSerializerError – if node.parent_stem == "" AND node.fragment_legacy is None (malformed tree per DECOMP-02 honest-fail-on-data).

Return type:

str

exception orthonym.assembly.name_tree_to_string.NameTreeSerializerError#

Bases: ValueError

Raised when a malformed NameTreeNode is passed to name_tree_to_string.

Per DECOMP-02 honest-fail-on-data (internal notes): a node with parent_stem == "" and fragment_legacy is None cannot serialize deterministically. The fix is upstream in the handler that produced the malformed node, not a band-aid string default.