Source code for cadrumo.domain.calculations.registry.record_design_coverage

"""Off-load-path record-design coverage and calculation-closure derivations.

:func:`calculation_closure_casilla_ids` and
``calculation_closure_legal_refs`` derive bounded closure projections from
a :class:`ModeloRevision`; ``DisenoCoverageReport`` remains the advisory
full-Dise�o inventory.
"""

from __future__ import annotations

from collections.abc import Callable

from ....core.casilla_id import CasillaId
from .bindings import binding_source_casilla_ids, binding_source_modelo
from .runtime_graph import expression_casilla_refs
from .schema import BindingDefinition, ModeloRevision
from .schema_surfaces import CasillaDefinition

# ---------------------------------------------------------------------------
# Calculation-completeness manifest derivation and Dise�o extraction
# (off-load-path)
# ---------------------------------------------------------------------------
#
# The derivations below run off the snapshot-build hot path; they are
# called only by manifest-authoring scripts, the off-load-path coverage
# report, and the drift re-verification test.
#
# - ``calculation_closure_casilla_ids`` enumerates a revision's calculation
#   closure as canonical ``casilla.id`` values only. Reference tokens that do
#   not name a declared casilla id remain unresolved and fail the manifest
#   derivation instead of being reinterpreted as display metadata.
#
# - ``calculation_closure_record_design_metadata`` projects that canonical
#   closure through each closure casilla's own registry ``(segmento, number)``
#   metadata. This metadata is reviewed against Dise�o where needed, while
#   the manifest row also carries the canonical ``casilla.id`` that consumers
#   resolve.
#
# - ``derive_calculation_completeness_casillas`` derives the
#   *calculation-completeness manifest* casilla set from that closure:
#   the modelo's calculation surface keyed on canonical ``casilla.id`` and
#   carrying the registry metadata each closure casilla declares. For a multi-segment modelo it optionally
#   verifies the derived record segments against the AEAT Dise�o de
#   Registros. This is the set the load-blocking completeness gate
#   enforces.
#
# - ``derive_diseno_coverage_casillas`` extracts the *full* Dise�o
#   casilla set � every five-digit casilla tag AEAT embeds in a field
#   description, accounting-statement data-entry fields included. It
#   parses the multi-megabyte Dise�o corpus and is the input to the
#   off-load-path advisory coverage report that inventories form-level
#   data coverage; it is NOT a load-blocking gate.

"""Match bounded bracketed field identifiers in record-design text.

The extractor accepts the identifier widths used by the authored record
design corpus and leaves unbracketed layouts to the coverage report. It
does not declare a model, field, or legal mapping.

The numeric width is bounded so unrelated bracketed amounts, identifiers,
and offsets are not mistaken for fields.
"""


def _binding_is_cross_modelo(binding: BindingDefinition, modelo_id: str) -> bool:
    """Return whether a binding names a foreign source modelo.

    A binding is *cross-modelo* when its typed selector helper reports a
    ``source_modelo`` that is not the modelo whose closure is being derived.
    A binding with no source modelo, or one set to ``modelo_id``, is a
    *within-modelo* binding: its ``source_casilla_ids`` / ``source_casilla_id``
    name casillas on the modelo being derived, and those casillas belong in
    the modelo's own calculation closure.
    """
    source_modelo = binding_source_modelo(binding)
    if source_modelo is None:
        return False
    return source_modelo != modelo_id


def _visit_formula_closure_tokens(
    revision: ModeloRevision,
    visit_token: Callable[[CasillaId], None],
) -> None:
    for formula in revision.formulas:
        visit_token(formula.target_casilla_id)
        for ref in expression_casilla_refs(formula.expression):
            visit_token(ref)


def _visit_expectation_closure_tokens(
    revision: ModeloRevision,
    visit_token: Callable[[CasillaId], None],
) -> None:
    for expectation in revision.verification_expectations:
        for ref in expectation.computed_casilla_ids:
            visit_token(ref)
        for ref in expectation.reconciliation_total_casilla_ids.values():
            visit_token(ref)


def _visit_binding_closure_tokens(
    revision: ModeloRevision,
    modelo_id: str,
    visit_token: Callable[[CasillaId], None],
) -> None:
    for binding in revision.bindings:
        if _binding_is_cross_modelo(binding, modelo_id):
            continue
        for token in binding_source_casilla_ids(binding):
            visit_token(token)


def _walk_calculation_closure(
    revision: ModeloRevision,
    modelo_id: str,
    *,
    visit_token: Callable[[CasillaId], None],
    visit_endpoint: Callable[[CasillaDefinition], None],
) -> None:
    """Walk the within-modelo calculation closure, dispatching each member.

    Shared by :func:`calculation_closure_casilla_ids` and
    ``calculation_closure_record_design_metadata``; ``visit_endpoint`` receives every
    formula/binding endpoint casilla and ``visit_token`` every referenced
    casilla token (formula targets, transitive expression refs,
    verification-expectation operands, and within-modelo binding source
    coordinates).
    """
    for casilla in revision.casillas:
        if casilla.formula is not None or casilla.binding is not None:
            visit_endpoint(casilla)
    _visit_formula_closure_tokens(revision, visit_token)
    _visit_expectation_closure_tokens(revision, visit_token)
    _visit_binding_closure_tokens(revision, modelo_id, visit_token)


[docs] def calculation_closure_casilla_ids(revision: ModeloRevision, modelo_id: str) -> frozenset[CasillaId]: """Return canonical casilla ids in a revision's calculation closure. The *calculation closure* is the set of casillas the cross-connecting calculation engine traverses **within this modelo revision**: - every ``formula.target_casilla_id`` casilla; - every casilla referenced inside any ``formula.expression``, walked transitively via the runtime-graph ``expression_casilla_refs`` walker; - every casilla that declares a ``formula`` (a computed endpoint) or a ``binding`` (a bound endpoint) � the engine-visible casillas; - every verification-expectation operand casilla (``computed_casilla_ids`` and the ``reconciliation_total_casilla_ids`` targets); - every *within-modelo* binding ``source_casilla_ids`` / ``source_casilla_id`` selector casilla, and every *within-modelo* the relation-prefill provider's ``source_casilla_id``. A binding ``source_casilla_ids`` / ``source_casilla_id`` selector � and a the relation-prefill provider's ``source_casilla_id`` � is excluded from this closure **only when it is genuinely cross-modelo**: when the selector explicitly names a ``source_modelo`` that differs from ``modelo_id``. A cross-modelo selector's ``source_casilla_ids`` / ``source_casilla_id`` name casillas on that *foreign* modelo, not on the modelo whose closure is being derived; the cross-modelo edge enters the current modelo through the *bound* casilla � the current-modelo casilla that declares the binding (or, for a relation, ``relation.target_binding``) � which is already counted above as a binding endpoint. Folding a foreign-modelo casilla id into this closure would make the completeness gate demand it from the wrong modelo's registry. A selector that omits ``source_modelo`` or sets it equal to ``modelo_id`` is a *within-modelo* selector: a ``previous_filing`` self-binding or a ``previous_period`` self-relation names a casilla on the modelo being derived, so that casilla is a genuine closure member and is kept. References are not normalised through record-design metadata. A formula, binding, relation, or verification token must already be the canonical ``casilla.id`` for this revision. A reference token that matches no declared casilla id is kept verbatim so the manifest derivation and registry validation fail loudly on the unresolved canonical reference. Args: revision: The :class:`ModeloRevision` whose formula and binding graph is walked to derive the closure. modelo_id: The AEAT modelo identifier used to exclude cross-modelo selector casillas from the closure. """ closure: set[CasillaId] = set() _walk_calculation_closure( revision, modelo_id, visit_token=closure.add, visit_endpoint=lambda casilla: closure.add(casilla.id), ) return frozenset(closure)
__all__ = [ "calculation_closure_casilla_ids", ]