Source code for aeat.domain.calculations.registry._support_matrix

"""Typed per-modelo support-matrix registry.

:class:`~domain.calculations.registry.ModeloEntry` is the first-class typed
roll-up of "what does modelo X actually support", derived entirely from the loaded
:class:`~domain.calculations.registry.ModeloDefinition` /
:class:`~domain.calculations.registry.ModeloRevision` records — never
hand-maintained. It composes existing registry primitives rather than
re-implementing them:

* calc-grade / manifest / export-format / extractor detection mirrors
  ``dev.registry.matrix`` (contributor-facing capability probe);
* rename tracking reads the revision's already-declared
  :class:`~domain.calculations.registry.CasillaContinuidadEvolutionDefinition`
  entries (the ``casilla_continuidad_evolutions`` field);
* deprecation policy reads the revision's already-declared
  :class:`~domain.calculations.registry.SupportRemovalDecisionDefinition`
  entries (the ``support_removal_decisions`` mapping);
* portal-compatibility tracking reads the revision's declared
  :class:`~domain.calculations.registry.LiveCrossReferenceDecision` entries
  (surface kind and evidence tier).

Coverage honesty (``no-silent-under-declaration``): a modelo missing a
capability, rename record, deprecation decision, or portal cross-reference
reports an explicit empty/False value, never a fabricated positive.

See Also:
    :func:`~domain.calculations.registry.build_support_matrix`
        Pure builder that folds the
        :class:`~domain.calculations.registry.ValidatedRegistryAuthority` into
        typed rows.
    :class:`~domain.calculations.registry._query_reports.ModeloSupportMatrixReport`
        Query-service envelope returned by
        :meth:`~domain.calculations.registry.RegistryQueryService.support_matrix`.
    :func:`~application.modelo.registry_support_matrix`
        Application facade used by CLI discovery without re-reading registry
        authority directly.
"""

from __future__ import annotations

from datetime import date

from pydantic import BaseModel, ConfigDict

from ._authority import ValidatedRegistryAuthority
from ._ids import ModeloId, RevisionId
from ._record_design_coverage import calculation_closure_casilla_ids
from ._schema import CalculationClass, EvidenceTier, ModeloDefinition, ModeloRevision
from ._schema_surfaces import CasillaContinuidadEvolutionDefinition

__all__ = [
    "ModeloEntry",
    "ModeloPortalCompatibilityRef",
    "ModeloRenameRecord",
    "ModeloSupportRemovalRecord",
    "build_support_matrix",
]


def _latest_revision(modelo: ModeloDefinition) -> ModeloRevision:
    """Return the revision with the most recent ``valid_from`` for ``modelo``.

    A modelo always declares at least one revision
    (:meth:`~domain.calculations.registry.ModeloDefinition._validate_revisions`
    enforces this at load time), so the max is always well-defined.
    """
    return max(modelo.revisions.values(), key=lambda revision: revision.valid_from)


def _calculation_closure_casilla_ids(revision: ModeloRevision, modelo_id: str):
    return calculation_closure_casilla_ids(revision, modelo_id)


[docs] class ModeloRenameRecord(BaseModel): """One declared per-ejercicio casilla continuity evolution. Projects a :class:`~domain.calculations.registry.CasillaContinuidadEvolutionDefinition` already declared on the revision — this record never invents rename history; it surfaces what the registry already tracks per continuity chain (``continuidad_id``). Attributes: continuidad_id: The stable continuity chain identifier a casilla's number/label is tracked under across revisions. from_revision: Revision id the evolution starts from. to_revision: Revision id the evolution lands on. evolution_kind: The declared nature of the change (e.g. ``"label_evolved"``, ``"repurposed"``, ``"retired"``). """ model_config = ConfigDict(frozen=True) continuidad_id: str from_revision: RevisionId to_revision: RevisionId evolution_kind: str
[docs] class ModeloSupportRemovalRecord(BaseModel): """One declared deprecation decision for a modelo revision. Projects a :class:`~domain.calculations.registry.SupportRemovalDecisionDefinition` already declared on the revision. Attributes: subject_type: The kind of surface removed from filing-grade support (e.g. ``"export_layout"``, ``"live_cross_reference"``). subject_id: The identifier of the removed surface. reason: The declared regulatory/technical reason for removal. evidence_note: Human-readable evidence backing the decision. """ model_config = ConfigDict(frozen=True) subject_type: str subject_id: str reason: str evidence_note: str
[docs] class ModeloPortalCompatibilityRef(BaseModel): """One declared AEAT-portal cross-reference for a modelo revision. Projects a :class:`~domain.calculations.registry.LiveCrossReferenceDecision` already declared on the revision — the registry's own record of which live AEAT surface the modelo has been cross-checked against and under what evidence tier. Attributes: id: Stable registry identifier for the cross-reference decision. surface: The live AEAT surface kind cross-checked (e.g. ``"public_read_surface"``, ``"integration_test_service"``). evidence_tier: The declared evidence tier backing the cross-reference. """ model_config = ConfigDict(frozen=True) id: str surface: str evidence_tier: EvidenceTier
[docs] class ModeloEntry(BaseModel): """First-class typed support-matrix record for one modelo. Derived entirely from the loaded registry authority — every field is a direct read or fold over the modelo's latest revision, never a hand-maintained value. See :func:`~domain.calculations.registry.build_support_matrix`. Attributes: modelo_id: The AEAT modelo identifier (e.g. ``"303"``). title: Human-readable display name from the registry. calculation_class: The modelo's declared calculation role (``"filing"``, ``"informative"``, or ``"summary"``). revision_count: Number of registry revisions declared for this modelo. latest_revision_id: The id of the revision this entry's capabilities are probed against (the revision with the most recent ``valid_from``). latest_revision_valid_from: That revision's applicability start date. supported_revision_ids: Every declared revision id, oldest ``valid_from`` first. calc_grade: Whether the latest revision's calculation closure is non-empty (has at least one formula/binding/verification wiring the engine traverses). has_completeness_manifest: Whether the latest revision declares a calculation-completeness manifest. has_fixed_width_export: Whether the latest revision registers a ``fixed_width`` (fichero-BOE) export layout. has_xml_dictionary_export: Whether the latest revision registers an ``xml_dictionary`` export layout. has_extractor: Whether the latest revision registers at least one extraction profile. extraction_profile_count: Count of extraction profiles on the latest revision. renames: Declared per-ejercicio casilla continuity evolutions on the latest revision. deprecations: Declared support-removal (deprecation) decisions on the latest revision. portal_compatibility_refs: Declared AEAT-portal cross-references on the latest revision. is_deprecated: ``True`` when the latest revision declares at least one support-removal decision. """ model_config = ConfigDict(frozen=True) modelo_id: ModeloId title: str calculation_class: CalculationClass revision_count: int latest_revision_id: RevisionId latest_revision_valid_from: date supported_revision_ids: tuple[RevisionId, ...] calc_grade: bool has_completeness_manifest: bool has_fixed_width_export: bool has_xml_dictionary_export: bool has_extractor: bool extraction_profile_count: int renames: tuple[ModeloRenameRecord, ...] deprecations: tuple[ModeloSupportRemovalRecord, ...] portal_compatibility_refs: tuple[ModeloPortalCompatibilityRef, ...] @property def is_deprecated(self) -> bool: """Return whether the latest revision declares a support-removal decision.""" return bool(self.deprecations)
def _rename_record(evolution: CasillaContinuidadEvolutionDefinition) -> ModeloRenameRecord: return ModeloRenameRecord( continuidad_id=str(evolution.continuidad_id), from_revision=evolution.from_revision, to_revision=evolution.to_revision, evolution_kind=evolution.evolution_kind, ) def _entry_for_modelo(modelo: ModeloDefinition) -> ModeloEntry: revision = _latest_revision(modelo) closure = _calculation_closure_casilla_ids(revision, modelo.id) export_formats = {layout.format for layout in revision.export_layouts} supported_revision_ids = tuple( item.id for item in sorted(modelo.revisions.values(), key=lambda item: (item.valid_from, str(item.id))) ) renames = tuple(_rename_record(evolution) for evolution in revision.casilla_continuidad_evolutions) deprecations = tuple( ModeloSupportRemovalRecord( subject_type=decision.subject_type, subject_id=decision.subject_id, reason=decision.reason, evidence_note=decision.evidence_note, ) for decision in revision.support_removal_decisions ) portal_refs = tuple( ModeloPortalCompatibilityRef( id=str(decision.id), surface=decision.surface, evidence_tier=decision.evidence_tier, ) for decision in revision.live_cross_references ) return ModeloEntry( modelo_id=modelo.id, title=modelo.title, calculation_class=modelo.calculation_class, revision_count=len(modelo.revisions), latest_revision_id=revision.id, latest_revision_valid_from=revision.valid_from, supported_revision_ids=supported_revision_ids, calc_grade=bool(closure), has_completeness_manifest=revision.completeness_manifest is not None, has_fixed_width_export="fixed_width" in export_formats, has_xml_dictionary_export="xml_dictionary" in export_formats, has_extractor=bool(revision.extraction_profiles), extraction_profile_count=len(revision.extraction_profiles), renames=renames, deprecations=deprecations, portal_compatibility_refs=portal_refs, )
[docs] def build_support_matrix(authority: ValidatedRegistryAuthority) -> tuple[ModeloEntry, ...]: """Probe every modelo in ``authority`` and return its typed support row. Args: authority: The :class:`~domain.calculations.registry.ValidatedRegistryAuthority` to probe. Returns: Every modelo's :class:`~domain.calculations.registry.ModeloEntry`, sorted by ``modelo_id``. """ entries = (_entry_for_modelo(modelo) for modelo in authority.modelos) return tuple(sorted(entries, key=lambda entry: entry.modelo_id))