cadrumo.application.filing._export_parity module¶
Pre-write structural-parity gate for the fichero-BOE export.
A .boe file is accepted by AEAT on its bytes, and a SHA-256 digest proves
only that the bytes on disk are the bytes that were written – never that they
carry what the modelo requires. A draft that silently lost a computed casilla
still serialises to a well-formed, correctly-digested file whose slots are
blank: a structurally-thin filing. This module holds the assertions that run
between rendering and pathlib.Path.write_bytes(), so such a file is
refused before it can exist rather than discovered by AEAT.
Three dimensions are asserted, each a hard enumerated
FilingExportError naming exactly what drifted:
Casilla presence – every casilla that is a calculation RESULT (declares a formula) or is schema-required, that the completeness manifest lists AND the official record files a slot for at this filing’s disposition, carries a real value on disk (
assert_export_mirrors_manifest()).Record/section order – the records reaching disk, emitted in their declared
order, follow the registry export-layout declaration order, with no two rendered records sharing anorder(_assert_record_order_fidelity()).Casilla numbering/segmento – every representable manifest casilla carries the
(number, segmento)the registryCasillaDefinitiondeclares (_assert_casilla_metadata_fidelity()).
Disposition is the axis running through all three¶
None of the three is a property of the layout alone: each is scoped to what
this filing’s disposition actually files. The bank-account (DID) page is the
worked case – it belongs to the layout but reaches disk only where the fichero
must carry an account, so a casilla it carries is required on such a filing and
out of scope otherwise. _did_page_suppressed() is therefore the shared
predicate behind representability and record order alike, and lives here with the
disposition-scoped concern rather than beside the renderer that also consults it.
“Must carry an account” is NOT “is a refund”, and this prose said it was until a
domiciliación filing was measured going out with no account for AEAT to debit.
The three refund codes need the page because AEAT pays into the account; U
needs it because AEAT charges the account. The Diseño settles it by labelling
position 23 Domiciliación/Devolución - IBAN – one field for both directions
– while every other field on the page is prefixed Devolución -.
Set derivation and the gate are one concern¶
boe_representable_casilla_ids(), rendered_casilla_ids(), and
required_applicable_casilla_ids() are the gate’s own set algebra, and are
public because the parity regressions and the export-completeness gate pin them
directly. They are exported here so a change to the required-set semantics
propagates from one derivation authority to every production consumer, rather
than being mirrored into a second copy that can drift out of agreement with the
gate it is supposed to describe.
See also
application.filing.exportRenders the layout and calls
assert_export_mirrors_manifest()before writing the bytes.
- boe_representable_casilla_ids(layout, *, draft, headers, prior_domiciliation_election, schema_provider)[source]¶
Return the casillas the
.boelayout files a slot for, for this disposition.A casilla is representable when the official record design carries a field for it that this draft’s disposition does not suppress.
xml_dictionarylayouts derive their casillas from the dictionary entries;fixed_widthlayouts from everyCASILLAfield plus the binding-row casilla mappings (row_field_casilla_ids), across records not suppressed for the disposition (e.g. the DID refund page on a non-refund filing).The completeness gate intersects the calculation-completeness manifest with this set: a manifest casilla absent here is a calculation-closure casilla the official filed record does not carry, so it is out of scope for the
.boeparity gate rather than a drift.That out-of-scope verdict is no longer taken on trust. The fixed-width branch delegates to
fixed_width_record_casilla_ids(), the same derivation the registry-build export-exemption gate runs over EVERY declared record; that gate refuses at build any manifest casilla this set would exempt from a demand unless the casilla declares WHY it files no slot. Absence here therefore carries a reviewed reason behind it rather than being indistinguishable from a forgotten annotation.Core types:
ModeloDraft.- Return type:
frozenset[TypeAliasType]- Parameters:
layout (ExportLayoutDefinition)
draft (ModeloDraft)
headers (Mapping[FilingProducerKey, object])
prior_domiciliation_election (PriorDomiciliationElection)
schema_provider (RegistrySchemaAccessor)
- rendered_casilla_ids(layout, *, draft, headers, prior_domiciliation_election, schema_provider)[source]¶
Return the representable casillas whose value actually reaches disk.
A representable casilla reaches disk only when the
ModeloDraftcarries a value for it; a representable casilla absent fromdraft.valuesrenders as a blank fixed-width slot (or an omitted xml element), which is the structurally-thin file the completeness gate exists to refuse. This is the rendered set the gate compares against the manifest-required-and-representable set.- Return type:
frozenset[TypeAliasType]- Parameters:
layout (ExportLayoutDefinition)
draft (ModeloDraft)
headers (Mapping[FilingProducerKey, object])
prior_domiciliation_election (PriorDomiciliationElection)
schema_provider (RegistrySchemaAccessor)
- required_applicable_casilla_ids(manifest, *, collection, representable)[source]¶
Return the casillas that must carry a value in a complete fichero-BOE export.
A casilla is required-applicable when it declares a formula (a calculation RESULT) or is schema-required, AND the official export record files a slot for it at this filing’s disposition (
representable). Optional operator-input casillas — retenciones, prior payments, deductions the taxpayer may legitimately not have — declare neither a formula nor arequiredflag, so they are excluded: a blank slot for them is a valid zero, not a structurally-thin file.This is the single derivation authority behind the completeness gate (
assert_export_mirrors_manifest()), so a change to the required-set semantics propagates to every production consumer in one place.The regression tests deliberately do NOT consume this function as their expectation: they pin it against an independent partition read straight off the
CasillaCollection. A test whose expected set comes from the subject itself can only detect that the subject was called, never that its semantics changed – and a relaxation here silently drops a casilla out of the pre-write gate, letting a structurally-thin fichero-BOE ship behind a valid SHA-256 digest. Do not “de-duplicate” the tests onto this function.- Parameters:
manifest (
CalculationCompletenessManifest) – Revision’sCalculationCompletenessManifest.collection (
CasillaCollection) –CasillaCollectionfor the modelo, supplyingformulaandrequiredfor each declared casilla.representable (
frozenset[TypeAliasType]) – Casilla IDs the official export record files a slot for at this filing’s disposition; seeboe_representable_casilla_ids().
- Return type:
frozenset[TypeAliasType]- Returns:
Frozen set of casilla IDs that must carry a real value before the fichero-BOE bytes are written.
See also
boe_representable_casilla_ids()Derives the
representableargument from the export layout and headers.assert_export_mirrors_manifest()Gate that enforces this required set before writing bytes.
- assert_export_mirrors_manifest(layout, *, draft, headers, prior_domiciliation_election, schema_provider, manifest, casilla_metadata)[source]¶
Panic if the
.boewould not mirror the manifest-required structure.The completeness gate: every casilla that is a calculation RESULT (declares a formula) or is schema-required, and that the calculation-completeness manifest lists AND the official record files (
representablefor thisModeloDraft’s disposition), MUST carry a value on disk. Such a casilla rendered blank means the calculation did not populate it – a structurally-thin file behind a valid SHA-256 digest, which this gate refuses with a hardFilingExportErrornaming every missing casilla with its official record number and segmento, so the panic is loud and explicit.Optional operator-input casillas – retenciones, prior payments, deductions the taxpayer may legitimately not have – are NOT required to carry a value: a blank slot for them is a valid zero, not a thin file (grounded in the AEAT casilla semantics; e.g. Modelo 131 casillas 02/08/09/12/14 are optional inputs), so they are excluded from the required set. A manifest casilla absent from the representable set is a calculation-closure casilla the official record does not file and is likewise out of scope. Callers pass
manifestonly when the revision declares one; a revision without a manifest is handled by the coverage-advisory path, not here.The gate applies only to the fixed-width fichero-BOE. In that format every field occupies its byte slot always, so an omitted required casilla renders a blank slot behind a valid digest – the structurally-thin file. An
xml_dictionaryexport instead omits an absent casilla as an absent optional element, which is legitimate (a filer declares only the casillas its situation requires), so completeness is not asserted for that transport.Beyond casilla presence, the gate asserts two further structural-fidelity dimensions before any bytes are written, so the
.boemirrors the official modelo-revision structure and not merely its casilla set:Record/section order: the records that reach disk, emitted in their declared
order, must follow the registry export-layout declaration order, and no two rendered records may share anorder(_assert_record_order_fidelity()).Casilla numbering/segmento: every manifest casilla the official record files a slot for must carry the same
(number, segmento)the registryCasillaDefinitiondeclares – re-grounded against the projectedCasillaRecordMetadata, not the manifest’s own copy (_assert_casilla_metadata_fidelity()).
Each dimension is a hard, enumerated
FilingExportError; a structural divergence is a failure, never a warning.- Return type:
- Parameters:
layout (ExportLayoutDefinition)
draft (ModeloDraft)
headers (Mapping[FilingProducerKey, object])
prior_domiciliation_election (PriorDomiciliationElection)
schema_provider (RegistrySchemaAccessor)
manifest (CalculationCompletenessManifest)
casilla_metadata (tuple[CasillaRecordMetadata, ...])
- assert_rate_boxes_account_for_total(partitions, *, draft)[source]¶
Panic if the draft’s rate boxes account for less than their declared total.
A rate-specific official box asserts a rate, so a ledger row recording a cuota without recording the rate charged reaches the rate-blind total layer and no box. The return keeps the money and the breakdown keeps its integrity; what it loses is the property that the parts sum to the whole, and AEAT reconciles those boxes against that total.
The refusal sits at the write door for the same reason the completeness gate does: the application never files, so the artefact leaves here for a human to submit with nothing behind it. A blank computed slot and a breakdown that does not reach its own total are the same class of defect – a return whose structure contradicts what the calculation determined – and both cost the taxpayer a correction they cannot make, where the refusal costs them a ledger repair they can.
Unlike the completeness gate this applies to every transport. That gate is fixed-width-only because its subject is a blank BYTE SLOT, which the xml transport does not write; this one’s subject is an arithmetic relation between values, which is equally false in either encoding. A box the xml transport legitimately omits reads as zero here, which is what it declares.
The condition is never the operator’s first notice of it: the calculate path raises the same shortfall as a non-blocking advisory, computed by the same
rate_box_coverage_shortfalls()over the same partitions.- Parameters:
partitions (
Sequence[RateBoxPartition]) – The revision’s derived rate-box partitions, fromrate_box_partitions. Empty for every revision declaring no rate-specific binding, which makes this a no-op there.draft (
ModeloDraft) – TheModeloDraftabout to be written.
- Raises:
FilingExportError – A partition’s boxes account for less than its total, naming each shortfall with its tier, its total casilla and its boxes.
- Return type:
- did_page_suppressed(record, *, draft, headers, prior_domiciliation_election)¶
Return whether a DID (bank-account) page record must be suppressed.
The sole DID predicate is
disposition_requires_bank_account OR Nota 3. Nota 3 means an M303 rectificativa with casilla 111 semantically present and aKEEPprior-domiciliation election. It is independent of the current disposition, while a selectedCANCEL_OR_MODIFYdisables only that added path, never a DID page required by the current disposition.This function is shared by the renderer and parity derivations so they cannot disagree about which official record reaches disk.
- Return type:
- Parameters:
record (ExportRecordDefinition)
draft (ModeloDraft)
headers (Mapping[FilingProducerKey, object])
prior_domiciliation_election (PriorDomiciliationElection)
- assert_xml_declaration_aux_declared(layout)[source]¶
Panic if an
xml_dictionaryexport cannot write its mandatoryAux.Every AEAT Modelo 100 XSD opens
DeclaracionwithAux,minOccurs=1, whoseIdiomaandVERSIONchildren are eachminOccurs=1too. No bundled dictionary declares a singleAuxrow in any revision, so the dictionary-driven writer cannot reach the block and the values are declared on the layout instead. When one is undeclared the block cannot be written at all: a partialAuxis invalid, and omitting it makes the document fail at its very first element.The refusal exists because the alternative is worse than an error. AEAT types
VERSIONastipo_String4L— four characters, permissive pattern, no enumeration — so an invented token would VALIDATE, the document would start passing every check made of it, and the gap would stop being reported while an unverified value rode to the tax authority. Refusing keeps the gap visible; the export resumes the moment the layout declares a real value, with no code change.This runs at the write door rather than inside the renderer because the property it defends is that no unfileable artefact reaches the operator, and because an operator who receives a file believes they hold a filing artefact.
- Parameters:
layout (
ExportLayoutDefinition) – The export layout about to be written.- Raises:
FilingExportError – The layout is
xml_dictionaryand cannot supply the mandatory block, naming the undeclared field.- Return type: