"""Core revision record-section validation helpers.
Validates the primary record sections declared on a
:class:`~domain.calculations.registry.ModeloRevision`: casillas, formulas,
parameters, bindings, and extraction profiles. Each validator checks local
reference closure plus legal/source grounding through
:class:`~domain.calculations.registry._validate_evidence.EvidenceValidator`.
See Also:
:func:`~domain.calculations.registry._validate_revision_sections.validate_revision_definition`
Per-revision dispatcher that invokes these record-section validators.
:mod:`~domain.calculations.registry._validate_formulas`
Formula-expression validation used by the formula section.
:mod:`~domain.calculations.registry._validate_extraction_profiles`
Extraction-profile artefact and specimen gates used by this module.
"""
from __future__ import annotations
from collections.abc import Mapping
from pathlib import Path
from ._binding_selector_utils import selector_as_dict
from ._bindings import (
is_layout_binding_selector,
validate_binding_selector_shape,
)
from ._ids import BindingId, CasillaId, RelationId
from ._schema import DataBindingDefinition, FormulaDefinition, LegalReference, ModeloRevision, SourceReference
from ._validate_evidence import EvidenceValidator
from ._validate_extraction_profiles import (
validate_bbox_anchor_consistency,
validate_declaracion_pdf_round_trip_gate,
validate_declaracion_pdf_specimen_gate,
validate_dotted_callable,
validate_extraction_profile_artefacts,
)
from ._validate_formulas import validate_formula_expression
from ._validate_helpers import _missing_refs
from ._validate_revision_identity import _duplicates
from ._validate_revision_rules import validate_dated_values
_CASILLA_METADATA_SOURCE_TIERS = ("official_source_guidance", "layout_authority")
[docs]
def validate_casilla_section(
failures: list[str],
*,
prefix: str,
revision: ModeloRevision,
formulas: Mapping[str, FormulaDefinition],
bindings: set[BindingId],
export_field_ids: set[str],
legal_refs: Mapping[str, LegalReference],
source_refs: Mapping[str, SourceReference],
evidence: EvidenceValidator,
) -> None:
"""Append casilla metadata, formula, binding, and export-ref failures.
The :class:`~domain.calculations.registry.ModeloRevision` supplies
:class:`~domain.calculations.registry.CasillaDefinition` rows. Each
casilla, constraint, and alias must be legally/source grounded; formula,
binding, alternate-binding, and export-field references must point at ids in
the shared revision-validation context.
"""
for casilla in revision.casillas:
owner = f"casilla {casilla.id}"
failures.extend(_missing_refs(prefix, owner, casilla.legal_refs, legal_refs, "legal"))
failures.extend(_missing_refs(prefix, owner, casilla.source_refs, source_refs, "source"))
failures.extend(
evidence.require_any_source_tier(prefix, owner, casilla.source_refs, _CASILLA_METADATA_SOURCE_TIERS)
)
if casilla.constraints is not None:
constraint_owner = f"casilla {casilla.id} constraints"
failures.extend(
_missing_refs(prefix, constraint_owner, casilla.constraints.legal_refs, legal_refs, "legal"),
)
failures.extend(
_missing_refs(prefix, constraint_owner, casilla.constraints.source_refs, source_refs, "source"),
)
failures.extend(
evidence.require_any_source_tier(
prefix,
constraint_owner,
casilla.constraints.source_refs,
_CASILLA_METADATA_SOURCE_TIERS,
),
)
for alias in casilla.aliases:
alias_owner = f"casilla {casilla.id} alias {alias.label!r}"
failures.extend(_missing_refs(prefix, alias_owner, alias.legal_refs, legal_refs, "legal"))
failures.extend(_missing_refs(prefix, alias_owner, alias.source_refs, source_refs, "source"))
failures.extend(
evidence.require_any_source_tier(
prefix,
alias_owner,
alias.source_refs,
_CASILLA_METADATA_SOURCE_TIERS,
),
)
if casilla.formula is not None and casilla.formula not in formulas:
failures.append(f"{prefix}: casilla {casilla.id!r} references unknown formula {casilla.formula!r}")
if (
casilla.formula is not None
and casilla.formula in formulas
and formulas[casilla.formula].target_casilla_id != casilla.id
):
failures.append(
f"{prefix}: casilla {casilla.id!r} references formula {casilla.formula!r} "
f"targeting {formulas[casilla.formula].target_casilla_id!r}",
)
if casilla.binding is not None and casilla.binding not in bindings:
failures.append(f"{prefix}: casilla {casilla.id!r} references unknown binding {casilla.binding!r}")
for binding in casilla.alternate_bindings:
if binding not in bindings:
failures.append(
f"{prefix}: casilla {casilla.id!r} references unknown alternate binding {binding!r}",
)
for export_ref in casilla.export_refs:
if export_ref not in export_field_ids:
failures.append(f"{prefix}: casilla {casilla.id!r} references unknown export field {export_ref!r}")
[docs]
def validate_parameter_section(
failures: list[str],
*,
prefix: str,
revision: ModeloRevision,
legal_refs: Mapping[str, LegalReference],
source_refs: Mapping[str, SourceReference],
evidence: EvidenceValidator,
) -> None:
"""Append parameter reference, citation, and dated-value failures.
The :class:`~domain.calculations.registry.ModeloRevision` supplies
:class:`~domain.calculations.registry.ParameterDefinition` rows. Each
parameter must carry known legal/source refs, official-source citations, and
valid dated-value windows.
"""
for parameter in revision.parameters:
owner = f"parameter {parameter.id}"
failures.extend(_missing_refs(prefix, owner, parameter.legal_refs, legal_refs, "legal"))
failures.extend(_missing_refs(prefix, owner, parameter.source_refs, source_refs, "source"))
failures.extend(evidence.require_source_tier(prefix, owner, parameter.source_refs, "official_source_guidance"))
failures.extend(
evidence.validate_source_citations(
prefix,
owner,
parameter.source_refs,
parameter.source_citations,
"official_source_guidance",
),
)
failures.extend(validate_dated_values(prefix, parameter.id, parameter.values))
[docs]
def validate_binding_section(
failures: list[str],
*,
prefix: str,
revision: ModeloRevision,
legal_refs: Mapping[str, LegalReference],
source_refs: Mapping[str, SourceReference],
evidence: EvidenceValidator,
) -> None:
"""Append binding selector-shape, reference, and evidence failures.
The :class:`~domain.calculations.registry.ModeloRevision` supplies
:class:`~domain.calculations.registry.DataBindingDefinition` rows.
Selectors are validated through the single binding-selector contract; layout
bindings require layout-authority evidence, while other bindings require
official-source guidance and source citations.
"""
for binding in revision.bindings:
failures.extend(f"{prefix}: {fail}" for fail in validate_binding_selector_shape(binding))
for binding in revision.bindings:
owner = f"binding {binding.id}"
failures.extend(_missing_refs(prefix, owner, binding.legal_refs, legal_refs, "legal"))
failures.extend(_missing_refs(prefix, owner, binding.source_refs, source_refs, "source"))
if _is_layout_binding(binding):
failures.extend(evidence.require_source_tier(prefix, owner, binding.source_refs, "layout_authority"))
else:
failures.extend(
evidence.require_source_tier(prefix, owner, binding.source_refs, "official_source_guidance"),
)
failures.extend(
evidence.validate_source_citations(
prefix,
owner,
binding.source_refs,
binding.source_citations,
"official_source_guidance",
),
)
# The per-source op/fact invariants for every family (invoice, counterpart,
# the four ledger families, the four detail-record families, withholding, and
# previous_filing) are run by the single ``validate_binding_selector_shape``
# dispatch loop above. The prior separate try/except per-family calls here
# were a redundant second validation path; one path now covers every family.
def _is_layout_binding(binding: DataBindingDefinition) -> bool:
"""Layout-binding predicate, delegated to the typed manual_input shape."""
return is_layout_binding_selector(selector_as_dict(binding))