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:
StrEnumThe comparison a profile predicate performs.
- EQUALS¶
- NOT_EQUALS¶
- class WorkbookFormulaCoverage(*values)[source]¶
Bases:
StrEnumHow 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:
StrEnumWhether 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:
StrEnumThe 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:
RegistryModelDeclare 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:
RegistryModelDeclare 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:
RegistryModelDeclare 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:
StrEnumClosed 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 – andnone(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.roundingwas a barestr, sorounding = "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:
StrEnumClosed vocabulary of causes a verification discrepancy may be attributed to.
- EXTRACTION_UNRELIABLE¶
- UNMODELLED_RULE¶
- ROUNDING¶
- CORRECTNESS_DIVERGENCE¶
- class VerificationExpectationDefinition(**data)[source]¶
Bases:
RegistryModelDeclare 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
ingresartotal 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:
objectFolded 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_idsare the coverage-gated reconciliation targets: a filing that fails to reconcile them belowmin_coverageis NEEDS_REVIEW.reconcile_when_present_casilla_idsare 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_idsis the third, orthogonal axis: of the casillas a filing reconciles (computed_casilla_idsorreconcile_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[VerificationExpectationId, ...])
reconciliation_total_casilla_ids (Mapping[Annotated[SettlementDirection, BeforeValidator(func=~cadrumo.domain.calculations.registry.schema_base.coerce_enum_member.<locals>._coerce, json_schema_input_type=PydanticUndefined)], CasillaId])
tolerance (Decimal)
min_coverage (Decimal)
rounding_codes (frozenset[VerificationRoundingCode])
discrepancy_causes (frozenset[DiscrepancyCause])
-
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.
-
rounding_codes:
frozenset[VerificationRoundingCode]¶ The rounding modes the folded expectations declare. Carried rather than dropped: the fold previously discarded
roundingentirely, 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:
StrEnumThe 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:
StrEnumThe 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:
objectThe grammar and casilla-arity contract of one predicate operator.
- Parameters:
operator (VerificationPredicateOperator)
syntax (VerificationPredicateSyntax)
minimum_casilla_ids (int)
maximum_casilla_ids (int | None)
-
operator:
VerificationPredicateOperator¶
-
syntax:
VerificationPredicateSyntax¶
- class ParsedVerificationPredicate(operator, arguments, casilla_ids=(), literal='', threshold='', profile_field='', applicability_filter='', cutoff='')[source]¶
Bases:
objectA 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¶
- verification_predicate_operator_name(expression)[source]¶
Return the leading DSL operator name, or
Nonewhen no call begins.
- parse_verification_predicate_expression(expression)[source]¶
Parse one runtime-supported verification-predicate DSL expression.
Nonemeans 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:
- Parameters:
expression (str)
- class VerificationPredicateDefinition(**data)[source]¶
Bases:
RegistryModelA 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).expressionuses 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: noBLOCKING_RULEbranch 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 theadvisory_when_positivebranch 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 iffcasilla_values[antecedent] <= 0ORcasilla_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 toDecimal(0)and therefore violates the predicate when the antecedent is positive.implies_any_nonzero(["antecedent_id", "c1_id", "c2_id", ...])— the N-consequent generalisation ofimplies_nonzero: predicate holds iffcasilla_values[antecedent] <= 0OR 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 toimplies_nonzero.profile_field_required("profile_field_name", "applicability_filter")— profile-state-aware conditional non-zero requirement. ReturnsTrue(predicate holds) when the namedapplicability_filterevaluatesFalseagainst the TaxpayerProfile, OR when the named profile field is present and non-empty. ReturnsFalse(predicate violated) only when the applicability filter activates AND the profile field isNone/ empty. A sibling ofimplies_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: noBLOCKING_RULEbranch is implemented, mirroring the existingequals(BLOCKING-only) /advisory_when_ratio_ge(ADVISORY-only) asymmetry. Authored for the M210 IRNR inmobiliaria branch, the one shapeimplies_nonzerocannot 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 beforecutoff_isoAND 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: noBLOCKING_RULEbranch is implemented, mirroring thecasilla_equals_implies_nonzero/advisory_when_ratio_geADVISORY-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 neitherimplies_nonzero(numeric antecedent) norcasilla_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 ofcasilla_equals_implies_nonzero(that operator’s consequent test is “== 0”; this operator’s is “casilla_a != casilla_b”). ADVISORY-only: noBLOCKING_RULEbranch is implemented, mirroring thecasilla_equals_implies_nonzero/advisory_when_computed_divergesADVISORY-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 neitherimplies_nonzero(numeric antecedent) noradvisory_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_idbeside 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 asanterior-2013– stays in it.- Type:
The predicate’s edition-free identity
- predicate_id: str¶
- legal_refs: LegalRefs¶
- expression: str¶
- finding_kind: VerificationFindingKindField¶