"""document_ir.py — the surface-neutral, GEOMETRY-CARRYING document intermediate. This is the pivot of the whole efferent projector. A DocumentIR is NOT a text tree. It is a projection of a meaning-geometry region that carries, at every leaf, BOTH: * the realized surface text (``Block.sentences``) — what a TEXT projector reads, * the source geometry (``Block.provenance``) — what a MUSIC / IMAGE / VIDEO projector reads. Because the IR holds the geometry, not just the words, the SAME plan -> realize -> cohere pipeline drives every surface. A markdown projector renders the sentences; a music projector reads the provenance edges (salience, importance, polarity, relation) and maps them onto a symbolic-music surface; an image/video projector (documented seam) would read the same geometry. Nothing in this module invents content. Every :class:`Provenance` points at a real engram node id and a real relation. That is the faithfulness contract made structural: a claim with no provenance cannot exist in the IR. """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any # --------------------------------------------------------------------------- # # Provenance — the geometry an emitted claim traces to. FAITHFULNESS is here. # --------------------------------------------------------------------------- # @dataclass class Provenance: """One geometry edge behind one realized claim. ``kind`` distinguishes a FACT (a structural edge asserted by the geometry, spoken as fact) from an INTERPRETATION (something attributed, spoken with attribution) — the facts-as-facts + interpretations-attributed discipline (memory 80927e26). ``polarity`` is SACRED: a negated edge stays negated. """ subj_id: str | None # source engram node id of the subject subject: str | None # normalized subject surface relation: str # predicate lemma (e.g. "use", "contain", "be") obj: str | None # normalized object / complement surface polarity: str = "aff" # "aff" | "neg" (SACRED — never silently flipped) confidence: float = 0.0 # extraction confidence in [0,1] node_id: str | None = None # engram node the claim was extracted from kind: str = "fact" # "fact" | "interpretation" importance: float = 0.0 # source node importance (drives music/emphasis) salience: float = 0.0 # source node salience def trace(self) -> str: arrow = "-->" if self.polarity == "aff" else "--NOT-->" return (f"[{(self.node_id or '?')[:8]}] {self.subject!r} {arrow}" f"{self.relation} {self.obj!r} (conf {self.confidence:.2f})") @dataclass class Block: """A passage: one or more faithful sentences + the geometry they trace to. ``sentences`` and ``provenance`` are index-aligned where possible: sentence ``i`` was realized from ``provenance[i]``. A COHERE transition sentence with no new geometry carries a provenance whose ``kind == "connective"`` so the audit can see it introduced no new claim. """ sentences: list[str] = field(default_factory=list) provenance: list[Provenance] = field(default_factory=list) role: str = "body" # "body" | "lead" | "transition" def text(self) -> str: return " ".join(s.rstrip(". ") + "." for s in self.sentences if s.strip()) @dataclass class Section: heading: str level: int = 2 # markdown heading level / outline depth blocks: list[Block] = field(default_factory=list) seed_ids: list[str] = field(default_factory=list) # geometry nodes of section summary: str = "" # one-line grounded gloss (for pptx bullets / TOC) def all_provenance(self) -> list[Provenance]: out: list[Provenance] = [] for b in self.blocks: out.extend(b.provenance) return out @dataclass class DocumentIR: """The surface-neutral document. Built ONCE, projected to ANY surface.""" title: str subtitle: str = "" sections: list[Section] = field(default_factory=list) seed_id: str | None = None # the geometry region root format_spec: dict[str, Any] = field(default_factory=dict) # requested shape meta: dict[str, Any] = field(default_factory=dict) # -- geometry facets (what non-text projectors consume) ----------------- # def all_provenance(self) -> list[Provenance]: out: list[Provenance] = [] for s in self.sections: out.extend(s.all_provenance()) return out def claim_count(self) -> int: return sum(1 for p in self.all_provenance() if p.kind in ("fact", "interpretation")) def ungrounded_count(self) -> int: """Claims with no traceable node — MUST be zero for a faithful doc.""" return sum(1 for p in self.all_provenance() if p.kind in ("fact", "interpretation") and not p.node_id)