cadrumo.domain.calculations.registry.schema_verification module

What a filed return is reconciled against, what must hold for it, and the folds.

Verification asks two questions of a filed modelo. First, do the numbers AEAT received agree with the numbers this application computes? A revision answers that by declaring verification expectations, and this module owns both halves of that answer — the per-expectation declaration and the snapshot-wide fold the living modelo reconciliation surface consumes. Second, do the filing’s values satisfy the cross-casilla invariants the form’s law implies? A revision answers that by declaring verification predicates, and this module owns that declaration together with the closed operator vocabulary the registry-build validator checks every predicate expression against.

Both questions live here because they are the two layers of one verification strategy and neither is meaningful alone: an expectation says which casillas are compared, a predicate says which relations between casillas must hold, and a filing is granted VERIFICADO_COMPLETO only when both are satisfied. The operator vocabulary travels with the predicate model rather than with the validator that reads it, because the model’s own docstring is where each operator’s semantics are documented — separating the two would document one concept in two modules.

The three casilla axes are deliberately distinct and are the reason the two models belong together. computed_casilla_ids are the coverage-gated reconciliation targets: fail to reconcile enough of them and the filing is NEEDS_REVIEW. reconcile_when_present_casilla_ids are reconciled when the filing prints them but are excluded from the coverage denominator, so enrolling a situational casilla can never lower coverage and flip a legitimate filing’s verdict. externally_grounded_casilla_ids is orthogonal to both: of the casillas a filing reconciles, which are backed by an AEAT-authoritative oracle rather than only by this application’s own engine — the difference between a number that agrees with itself and a number checked against the authority.

Those relationships are invariants, not conventions, so they are enforced on the declaration: each tuple is unique, the when-present set is disjoint from the computed set, and the externally-grounded set is a subset of their union. A casilla claimed as externally grounded but reconciled by nothing would advertise oracle backing for a value no filing ever compares.

RegistryVerificationPolicy is the fold of those declarations across a whole snapshot, and it is a frozen dataclass rather than a registry model because nothing authors it: it is derived by RegistrySnapshot.verification_policy() from the expectations a revision already declared. Folding it once here keeps the application surface from re-deriving the union, the strictest tolerance, and the strictest coverage floor at each call site, where the three could quietly disagree.

READ_ONLY_HTTP_METHODS: Final[frozenset[str]]

The HTTP methods that cannot change state on an AEAT surface.

Not an enum: HTTP methods are an external vocabulary this codebase does not close, and declaring one here would assert a completeness the registry has no authority over. What is closed is THIS set – the methods a read surface may use – and it was written out here and again as a private set in remote_state_guard, where the read-only mandate is enforced. Two copies of a security allowlist is one copy too many.

SIMULATOR_HTTP_METHODS: Final[frozenset[str]]

Read-only methods plus POST, which a simulator may accept.

Derived from the read-only set rather than relisted, so a method admitted to the read-only allowlist cannot be accidentally withheld from the simulator one – and, more importantly, so a method removed from read-only cannot silently survive here.

class ProfilePredicateOp(*values)[source]

Bases: StrEnum

The comparison a profile predicate performs.

EQUALS
NOT_EQUALS
class WorkbookFormulaCoverage(*values)[source]

Bases: StrEnum

How much of a parity workbook’s behaviour the reference actually covers.

FORMULA_FORM
STATIC_LAYOUT
RECORD_DESIGN_LAYOUT
UNSUPPORTED_BINARY_XLS
class VerificationFindingKind(*values)[source]

Bases: StrEnum

Whether a failed verification predicate blocks or merely advises.

Upper-case because these are the tokens the registry declares, and a finding kind that renders differently from its declaration is a finding an operator cannot grep for in the source that produced it.

BLOCKING_RULE
ADVISORY
class LiveVerificationSurface(*values)[source]

Bases: StrEnum

The kind of AEAT surface a live cross-reference verifies against.

Declared because six tokens were compared as bare strings across this module and its two neighbours, and two of the groupings below were written out three times each. The surface annotations stay str deliberately: a member compares equal to its token, so nothing about validation changes here, and retyping a validated field is a separate decision from naming the vocabulary.

OPEN_SIMULATOR
INTEGRATION_TEST_SERVICE
AUTHENTICATED_SIMULATOR
PUBLIC_READ_SURFACE
AUTHENTICATED_READ_SURFACE
STATIC_OFFICIAL_DOCUMENTATION
READ_SURFACES: Final[frozenset[LiveVerificationSurface]]

Surfaces that read live AEAT data rather than a simulator or a static document.

Written out at three sites in this module before this existed. These are the surfaces that must refuse synthetic data and are restricted to safe HTTP methods, so a surface added to this set inherits both rules at once instead of one of them.

SIMULATOR_SURFACES: Final[frozenset[LiveVerificationSurface]]

Surfaces that are not AEAT production infrastructure.

class ProfilePredicateDefinition(**data)[source]

Bases: RegistryModel

Declare one profile condition that controls verification applicability.

field: str
op: ProfilePredicateOpField
value: ProfileFactValue
explanation: str
legal_refs: LegalRefs
source_refs: SourceRefs
class LiveCrossReferenceDecision(**data)[source]

Bases: RegistryModel

Declare a resolved live cross-reference and its supporting evidence.

id: CrossReferenceId
evidence_tier: EvidenceTierField
surface: LiveVerificationSurfaceField
guard_policy_id: str
allowed_hosts: tuple[str, ...]
allowed_methods: tuple[str, ...]
forbidden_actions: tuple[str, ...]
synthetic_data_allowed: bool
requires_authentication: bool
requires_aeat_authorization: bool
legal_refs: LegalRefs
source_refs: SourceRefs
oracle_id: OracleId | None
applicability_condition_mode: ConditionModeField
applicability_predicates: tuple[ProfilePredicateDefinition, ...]
class WorkbookParityReference(**data)[source]

Bases: RegistryModel

Declare the workbook evidence used for an executable parity check.

id: WorkbookParityRefId
workbook_source: SourceRefId
fixture_id: WorkbookFixtureId
formula_coverage: WorkbookFormulaCoverageField
runner_required: bool
output_cells: Annotated[Mapping[WorkbookOutputId, WorkbookCellRefStr], FROZEN_MAPPING]
tolerance: DecimalValue
legal_refs: LegalRefs
source_refs: SourceRefs
class VerificationRoundingCode(*values)[source]

Bases: StrEnum

Closed rounding vocabulary for a verification expectation’s comparison.

Declared here rather than reusing RegistryRoundingCode: that enum is the FORMULA rounding authority and deliberately has no “do not round” mode, because every computed casilla is rounded by some legal rule. A verification expectation is a different axis – it says how the filed and computed values are brought into comparable form before the tolerance is applied – and none (compare the raw values) is a legitimate member there. Widening the formula enum to admit it would let a formula declare a mode the law never grants.

rounding was a bare str, so rounding = "bogus" was accepted at registry build and produced exactly the same policy and verifier behaviour as the valid declaration – a legal verification obligation could drift from the runtime authority with nothing to notice.

MONEY_2
NONE
class DiscrepancyCause(*values)[source]

Bases: StrEnum

Closed vocabulary of causes a verification discrepancy may be attributed to.

EXTRACTION_UNRELIABLE
UNMODELLED_RULE
ROUNDING
CORRECTNESS_DIVERGENCE
class VerificationExpectationDefinition(**data)[source]

Bases: RegistryModel

Declare one expected relationship between calculated and filed casillas.

id: VerificationExpectationId
computed_casilla_ids: tuple[CasillaId, ...]
reconcile_when_present_casilla_ids: tuple[CasillaId, ...]
externally_grounded_casilla_ids: tuple[CasillaId, ...]
reconciliation_total_casilla_ids: Annotated[Mapping[SettlementDirectionField, CasillaId], FROZEN_MAPPING]
tolerance: DecimalValue
rounding: VerificationRoundingCodeValue
min_coverage: Annotated[DecimalValue, Field(ge=UNIT_PROPORTION_MIN, le=UNIT_PROPORTION_MAX)]
discrepancy_causes: tuple[DiscrepancyCauseValue, ...]
legal_refs: LegalRefs
source_refs: SourceRefs
fold_reconciliation_total_casilla_ids(expectations)[source]

Fold every expectation’s reconciliation-total casillas into one mapping.

The canonical fold for this axis. Three surfaces need it — the verification policy, the filing subview and the result summary — and each previously open-coded its own loop with a different tie-break, so the same revision could in principle name one casilla as the ingresar total on one surface and a different one on another.

AMBIGUITY IS REFUSED RATHER THAN RESOLVED. The other folded axes have a defensible ordering — union for a set, strictest for a tolerance — but there is no “stricter” of two casilla ids, so any tie-break here would be an invention rather than a rule, and whichever surface adopted it first would silently become the authority. Two expectations naming DIFFERENT casillas for one kind is a registry-authoring fault, so it raises; naming the same casilla twice is harmless and folds to one entry.

Raises:

RegistryValidationError – When two expectations declare different casilla ids for the same reconciliation kind.

Return type:

Mapping[SettlementDirection, TypeAliasType]

Parameters:

expectations (Iterable[VerificationExpectationDefinition])

class RegistryVerificationPolicy(expectation_ids, reconciliation_total_casilla_ids, computed_casilla_ids, reconcile_when_present_casilla_ids, externally_grounded_casilla_ids, tolerance, min_coverage, rounding_codes=frozenset({}), discrepancy_causes=frozenset({}))[source]

Bases: object

Folded verification policy across a snapshot’s verification expectations.

Owns the registry-grounded projection (union of computed casilla ids, the union of reconcile-when-present casilla ids, the strictest tolerance, the strictest coverage floor) so application consumers do not re-derive the fold.

computed_casilla_ids are the coverage-gated reconciliation targets: a filing that fails to reconcile them below min_coverage is NEEDS_REVIEW. reconcile_when_present_casilla_ids are value-reconciled when the filing prints them (a filed-vs-computed divergence surfaces a discrepancy) but are excluded from the coverage denominator, so enrolling a situational casilla can never lower coverage and flip a legitimate filing’s verdict.

externally_grounded_casilla_ids is the third, orthogonal axis: of the casillas a filing reconciles (computed_casilla_ids or reconcile_when_present_casilla_ids), which have an AEAT-authoritative independent oracle expected value backing their reconciliation, rather than only the app’s own engine.

Parameters:
expectation_ids: tuple[TypeAliasType, ...]
reconciliation_total_casilla_ids: Mapping[Annotated[SettlementDirection], TypeAliasType]

The folded reconciliation-total casillas, from fold_reconciliation_total_casilla_ids(). Carried on the policy so a consumer that already holds one does not re-derive the fold.

computed_casilla_ids: frozenset[TypeAliasType]
reconcile_when_present_casilla_ids: frozenset[TypeAliasType]
externally_grounded_casilla_ids: frozenset[TypeAliasType]
tolerance: Decimal
min_coverage: Decimal
rounding_codes: frozenset[VerificationRoundingCode]

The rounding modes the folded expectations declare. Carried rather than dropped: the fold previously discarded rounding entirely, so the declaration was unreachable from the surface that consumes the policy and no reader could tell a valid declaration from a meaningless one.

discrepancy_causes: frozenset[DiscrepancyCause]

The discrepancy causes the folded expectations declare, likewise carried rather than dropped.

class VerificationPredicateOperator(*values)[source]

Bases: StrEnum

The closed registry-authored verification-predicate operator vocabulary.

ADVISORY_WHEN_POSITIVE
ADVISORY_WHEN_RATIO_GE
ALL_NONZERO
AT_MOST_ONE_POSITIVE
ANY_NONZERO
CAP_LE_WHEN_POSITIVE
CASILLA_EQUALS_IMPLIES_DIVERGES
CASILLA_EQUALS_IMPLIES_NONZERO
CASILLA_EQUALS_IMPLIES_PROFILE_FLAG
DEDUCCION_REQUIRES_ADQUISICION_BEFORE
ADVISORY_WHEN_COMPUTED_DIVERGES
EQUALS
IMPLIES_ANY_NONZERO
IMPLIES_NONZERO
PROFILE_FIELD_REQUIRED
PROFILE_FLAG_ENABLED
ROLL_FORWARD_BALANCES
class VerificationPredicateSyntax(*values)[source]

Bases: StrEnum

The syntax families accepted by the verification-predicate DSL.

CASILLA_LIST
RATIO
PROFILE_FIELD_REQUIRED
PROFILE_FLAG_ENABLED
CASILLA_LITERAL_CASILLA
CASILLA_LITERAL_PROFILE_FIELD
CASILLA_LITERAL_CASILLA_PAIR
CASILLA_TRIPLE_CUTOFF
class VerificationPredicateSpecification(operator, syntax, minimum_casilla_ids=0, maximum_casilla_ids=None)[source]

Bases: object

The grammar and casilla-arity contract of one predicate operator.

Parameters:
operator: VerificationPredicateOperator
syntax: VerificationPredicateSyntax
minimum_casilla_ids: int
maximum_casilla_ids: int | None
class ParsedVerificationPredicate(operator, arguments, casilla_ids=(), literal='', threshold='', profile_field='', applicability_filter='', cutoff='')[source]

Bases: object

A syntax-checked verification-predicate expression and its captures.

Casilla references remain raw text until registry validation resolves them against a revision or the runtime validates its hostile-string boundary.

Parameters:
operator: VerificationPredicateOperator
arguments: tuple[str, ...]
casilla_ids: tuple[str, ...]
literal: str
threshold: str
profile_field: str
applicability_filter: str
cutoff: str
verification_predicate_operator_name(expression)[source]

Return the leading DSL operator name, or None when no call begins.

Return type:

str | None

Parameters:

expression (str)

parse_verification_predicate_expression(expression)[source]

Parse one runtime-supported verification-predicate DSL expression.

None means the expression is unknown or malformed. Registry validation turns that state into an authoring-time refusal; the runtime retains its defensive fallback for anything that bypasses registry validation.

Return type:

ParsedVerificationPredicate | None

Parameters:

expression (str)

class VerificationPredicateDefinition(**data)[source]

Bases: RegistryModel

A cross-casilla invariant that must hold for VERIFICADO_COMPLETO to be granted.

Layer 2 of the hybrid verification strategy. Layer 1 handles single-casilla required gates via CasillaDefinition.required; this class handles multi-casilla structural invariants (e.g. if ingresos is non-zero then rendimiento neto must also be present).

expression uses a minimal predicate DSL:

  • advisory_when_positive(["casilla_id"]) — single-casilla positive advisory: FIRES (ADVISORY shown) iff the one named casilla value is strictly > 0. A zero or absent value holds trivially (no advisory). ADVISORY-only: no BLOCKING_RULE branch is implemented (a positive box is not itself an error — the advisory only prompts an operator review). Authored for the Modelo 100 anualidades por alimentos a favor de los hijos (casilla 0527), whose separate-escala treatment (LIRPF art. 64 for the state scale, art. 75 for the autonomic scale) is applied in the current cuota chain without the statutory mínimo-por-descendientes gating, so a payer declaring anualidades may be under-taxed; the advisory surfaces a non-blocking prompt to review the cuota pending the full separate-escala modelling, per no-silent-under-declaration. Routes through the generic single-casilla-list validation (exact arity 1) at registry build. See the advisory_when_positive branch in _evaluate_advisory_predicate_fires.

  • all_nonzero(["id1", "id2", ...]) — every listed casilla value must be non-zero (i.e. the filing invariant requires them all to be present and non-zero simultaneously).

  • any_nonzero(["id1", "id2", ...]) — at least one listed casilla value must be non-zero.

  • at_most_one_positive(["id1", "id2", ...]) — no more than one listed casilla may be strictly positive. Missing values read as zero. Authored for alternative result lanes such as Modelo 202 art. 40.3 claves 18/26, where the downstream formula uses both zero-default lanes but the official instruction permits only one positive lane.

  • cap_le_when_positive(["limited_id", "ceiling_id"]) — when the ceiling casilla is strictly positive, the limited casilla MUST NOT exceed the ceiling, enforcing AEAT cap rules like Modelo 131 C11 ≤ C10 and Modelo 130 C15 ≤ C14 (“en ningún caso podrá figurar… un importe superior a la cantidad positiva consignada”). Predicate holds when ceiling ≤ 0; the cap applies only when the operator’s gross liability is positive.

  • implies_nonzero(["antecedent_id", "consequent_id"]) — material implication with a strictly-positive antecedent test: predicate holds iff casilla_values[antecedent] <= 0 OR casilla_values[consequent] != 0. Authored for AEAT cuota-mínima invariants of the shape “cuando C01 sea positivo, C07 debe ser distinta de cero” (M131 EO cuota mínima, M130/M303 régimen simplificado analogues). The antecedent is strictly-positive rather than non-zero to mirror the regulatory phrasing; a casilla with a negative value does not trigger the implication. A missing consequent value evaluates to Decimal(0) and therefore violates the predicate when the antecedent is positive.

  • implies_any_nonzero(["antecedent_id", "c1_id", "c2_id", ...]) — the N-consequent generalisation of implies_nonzero: predicate holds iff casilla_values[antecedent] <= 0 OR at least one listed consequent is non-zero. Authored for the Modelo 303 official-Diseño contradiction where a computed total (iva.cuota-devengada-total, iva.cuota-deducible-total) is strictly positive but every constituent official numbered box (the dr303 base/cuota tranche cells the operator transcribes to the AEAT sede) is still zero — a silent under-declaration the verify gate would otherwise grant with zero findings. ADVISORY (the official numbered boxes are an operator-entered layer the calculate path does not auto-populate, so the contradiction is surfaced as a non-blocking alert rather than a refusal). The first consequent slot onward is the constituent set; a single consequent reduces to implies_nonzero.

  • profile_field_required("profile_field_name", "applicability_filter") — profile-state-aware conditional non-zero requirement. Returns True (predicate holds) when the named applicability_filter evaluates False against the TaxpayerProfile, OR when the named profile field is present and non-empty. Returns False (predicate violated) only when the applicability filter activates AND the profile field is None / empty. A sibling of implies_nonzero — the conditional non-zero requirement is the same semantic shape, but the gating signal is profile state (e.g. fiscal_residency, ue_eee_status) rather than another casilla value. First use site: M210 representante-fiscal gate (TRLIRNR Art 10).

  • profile_flag_enabled("profile_field_name") — profile-state advisory: predicate FIRES (ADVISORY shown) iff the named boolean TaxpayerProfile field is true. ADVISORY-only. Authored for the Modelo 130 Art. 109 activity-income coverage profile fact: the legal 70% threshold is an income-coverage/profile fact, not a ratio between retenciones amount and gross income casillas.

  • casilla_equals_implies_nonzero(["antecedent_casilla_id", "literal", "consequent_casilla_id"]) — categorical-conditional material implication: predicate FIRES (ADVISORY shown) iff the operator-entered raw text value of the named antecedent (TEXT) casilla equals the literal AND the named consequent (Decimal) casilla is zero. A missing or differing antecedent value holds trivially (no advisory), same convention as the numeric-antecedent operators. ADVISORY-only: no BLOCKING_RULE branch is implemented, mirroring the existing equals (BLOCKING-only) / advisory_when_ratio_ge (ADVISORY-only) asymmetry. Authored for the M210 IRNR inmobiliaria branch, the one shape implies_nonzero cannot express because its trigger is a categorical equality (tipo_renta == "inmobiliaria") rather than a numeric antecedent, guarding against a silent under-declaration.

  • deduccion_requires_adquisicion_before(["amount_casilla_id", "acquisition_date_casilla_id", "construction_date_casilla_id", "cutoff_iso"]) — eligibility-conditional advisory: FIRES (ADVISORY shown) iff the named amount (Decimal) casilla is strictly positive (a deducción is claimed) AND no pre-cutoff eligibility signal is recorded, i.e. the acquisition-date TEXT casilla does NOT hold a date strictly before cutoff_iso AND the construction-date TEXT casilla is empty. A claimed amount with a pre-cutoff acquisition date, a non-empty construction date, or a zero/absent amount holds trivially (no advisory). ADVISORY-only: no BLOCKING_RULE branch is implemented, mirroring the casilla_equals_implies_nonzero / advisory_when_ratio_ge ADVISORY-only convention. Authored for the Modelo 100 deducción por inversión en vivienda habitual, whose transitional régimen (LIRPF DT 18ª) admits only dwellings acquired before 01-01-2013 (or pre-2013 construction); a post-2013 acquirer claiming the abolished deducción would silently over-declare the deducción (under-declare tax), the no-silent-under-declaration shape neither implies_nonzero (numeric antecedent) nor casilla_equals_implies_nonzero (categorical text equality) can express because its trigger is a DATE threshold.

  • casilla_equals_implies_diverges(["antecedent_casilla_id", "literal", "casilla_a_id", "casilla_b_id"]) — categorical-conditional divergence check: predicate FIRES (ADVISORY shown) iff the operator-entered raw text value of the named antecedent (TEXT) casilla equals the literal AND the two named (Decimal) casillas differ by more than one cent. A missing or differing antecedent value, or two casillas within a cent of each other, holds trivially (no advisory). Sibling of casilla_equals_implies_nonzero (that operator’s consequent test is “== 0”; this operator’s is “casilla_a != casilla_b”). ADVISORY-only: no BLOCKING_RULE branch is implemented, mirroring the casilla_equals_implies_nonzero / advisory_when_computed_diverges ADVISORY-only convention. Authored for the M131/M100 estimación-objetiva índice corrector de exceso (b.3): Orden HAC/1347/2024 Anexo II instrucción 2.3 declares the índice corrector de exceso INCOMPATIBLE with the índices correctores especiales (a.2 autotaxi, a.4 transporte de mercancías/mudanzas) for the activities that carry both — a no-silent-under-declaration shape neither implies_nonzero (numeric antecedent) nor advisory_when_computed_diverges (no categorical gate) can express because the trigger combines a categorical epígrafe equality with a Decimal-pair divergence.

id: VerificationPredicateId

its kind and its subject.

predicate_id beside it is the authored narrative name, scoped to the edition that declares it (modelo-100-2025-...), which is exactly why it cannot serve as the identity: a successor restating the same invariant would name it differently and nothing could see the two as one predicate. This identity is the expression’s operator and the subject the authored name states once that edition scope is removed, so the same invariant carries the same identity in every edition and the family inherits along a predecessor chain. A year that belongs to the invariant rather than to the edition – a statutory cutoff such as anterior-2013 – stays in it.

Type:

The predicate’s edition-free identity

predicate_id: str
legal_refs: LegalRefs
expression: str
finding_kind: VerificationFindingKindField