cadrumo.application.modelo.reconciliation module

Modelo reconciliation: compare work-unit state and computed result against evidence.

modelo_reconcile accepts a modelo work unit and either an AEAT justificante PDF or a filed declaración PDF, then produces a ModeloReconciliationReport.

For a justificante, the report records whether the work unit’s modelo, period, ejercicio, and active-profile tax id match the receipt, AND — where the revision declares reconciliation_total_casilla_ids and a persisted calculation revision exists — whether the receipt’s printed total equals the canonical computed result casilla (aeat-calculation-aggregation). A filed-amount divergence surfaces as a typed total diff carrying the reconciling expectation’s legal grounding; where the total could not be reconciled (no map, no revision, no printed total) a totals_not_reconciled advisory discloses it so an identity-only matches is never a silent false green.

For a filed declaración, the same header comparison runs, and — for the modelos enrolled in _DECLARATION_CASILLA_RECONCILE_MODELOS — every casilla the registry’s verification policy reconciles is compared, one by one, against the persisted revision’s casilla_values via application.modelo.reconcile_casilla.detect_casilla_divergences(). A divergence surfaces as a typed casilla diff (ModeloReconciliationDiffKind.CASILLA). A modelo not yet enrolled in casilla-level declaration reconcile is refused with ReconciliationDeclaracionSourceUnsupportedError rather than silently degrading to header-only comparison.

The path-based service is local-only: it never contacts AEAT and never invokes require_live_read — the computed result is read from the already-persisted CalculationRevision, never a fresh calculation. Authenticated live pulls use modelo_reconcile_bytes after storing captured justificante bytes in secure storage.

Both paths persist their outcome twice over, in ONE unit of work: a ModeloReconciliationRecord carrying the grounded diffs and the advisories into the encrypted reconciliation record store selected by the bound ModeloReconciliationPersistencePort, and a slim MODELO_RECONCILED BucketEvent carrying the verdict and the divergence count. The detail lives in the record because a bucket-event payload value is capped at 500 characters and one grounded Modelo 100 casilla diff already encodes to a median 303 — two divergences were unpersistable for 99.6% of that modelo’s casillas, and 175 overflowed on the first. reconcile list reports which fields diverged by reading the record store.

Core types: RegistrySnapshot.

class ModeloReconciliationCommand(**data)[source]

Bases: BaseModel

Strict input contract for modelo_reconcile.

source_path points to the operator-supplied evidence file and source_kind records how that file must be parsed. Justificante PDFs are supported for every modelo; declaration PDFs are supported (casilla-level) only for modelos in _DECLARATION_CASILLA_RECONCILE_MODELOS — an unenrolled modelo is refused before parsing.

Parameters:

data (Any)

work_unit_id: WorkUnitId
source_kind: ModeloReconciliationEvidenceKind
source_path: Path
actor: str
class ModeloReconciliationBytesCommand(**data)[source]

Bases: BaseModel

Strict input contract for reconciling secure-storage justificante bytes.

Used by authenticated live pulls after the captured justificante has already been persisted in secure storage. The raw bytes remain in memory; source_ref is the non-file secure-storage reference recorded in the reconciliation event.

Parameters:

data (Any)

work_unit_id: WorkUnitId
source_kind: ModeloReconciliationEvidenceKind
source_bytes: bytes
source_ref: str
actor: str
class ModeloReconciliationReport(**data)[source]

Bases: BaseModel

Outcome of modelo_reconcile.

The verdict summarises the comparison at the work-unit level. The diff list enumerates the disagreements — header-field (modelo, period, ejercicio, tax id), the filed justificante total against the computed result casilla where reconciled, and (for a declaración source on an enrolled modelo) each per-casilla divergence; empty on matches. The advisory list carries non-blocking disclosures (a total or casilla set that could not be reconciled, an identity anchor that could not be verified); advisories never flip the verdict.

Parameters:

data (Any)

work_unit_id: WorkUnitId
bucket_id: BucketId
source_kind: ModeloReconciliationEvidenceKind
source_path: str
verdict: ModeloReconciliationVerdict
diffs: tuple[ModeloReconciliationDiff, ...]
advisories: tuple[ModeloReconciliationAdvisory, ...]
reconciled_at: datetime
narrative: str
exception ReconciliationEvidenceInvalidError(message=None, *, context=None, translated_message=None)[source]

Bases: CadrumoError

Raised when the supplied external evidence cannot be parsed.

Raised for malformed justificantes. The CLI surfaces it as a refusal with the canonical recovery hint; downstream consumers branch on it without string-matching the message.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • translated_message (str | None)

Return type:

None

code
exception ReconciliationDeclaracionSourceUnsupportedError(message=None, *, context=None, translated_message=None)[source]

Bases: CadrumoError

Raised when a declaración reconcile targets a modelo not yet enrolled.

Casilla-level declaración reconciliation is enrolled one modelo at a time in _DECLARATION_CASILLA_RECONCILE_MODELOS; a modelo outside that set refuses cleanly rather than silently degrading to a header-only compare.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • translated_message (str | None)

Return type:

None

code
modelo_reconcile(command, *, operation)[source]

Reconcile a modelo work unit against a justificante or declaración PDF file.

Local-only: never contacts AEAT and never invokes require_live_read.

For a justificante, compares the neutral observation returned by the bound ReconciliationEvidenceParserPort. The receipt totals ARE reconciled against the persisted revision’s computed result where the revision declares reconciliation_total_casilla_ids.

For a declaración, consumes the parser port’s structural observation and — for modelos enrolled in _DECLARATION_CASILLA_RECONCILE_MODELOS — compares every registry-reconciled casilla against the persisted revision’s casilla_values, surfacing each divergence as a typed casilla diff. A modelo outside that set raises ReconciliationDeclaracionSourceUnsupportedError.

Emits MODELO_RECONCILED into the bucket-event-history catalogue. The verdict is included in the event payload so downstream auditors can replay the reconciliation timeline without re-parsing the evidence.

Return type:

ModeloReconciliationReport

Returns:

A ModeloReconciliationReport.

Parameters:
modelo_reconcile_bytes(command, *, operation)[source]

Reconcile secure-storage evidence bytes without materialising a plaintext file.

Declaración reconciliation is not offered on this bytes path, but not because a filed declaración’s bytes cannot exist here: the filed-history sweep (application.live) already captures filed declaración observations, complete with per-casilla values and their own artefact bytes, into secure storage. What this command still lacks is a way to reconcile THOSE bytes: it accepts only justificante-shaped evidence a caller uploads. A pulled declaración never needs uploading in the first place — its per-casilla values are already reconciled against the taxpayer’s own local calculation by application.modelo.pulled_filing_reconcile.pulled_filing_divergence_findings(), which reads both sides out of the same bucket the sweep already populated. Use modelo_reconcile() with a local declaración PDF file for casilla-level reconcile of a declaración held only on disk.

Return type:

ModeloReconciliationReport

Returns:

The ModeloReconciliationReport comparing the parsed justificante metadata to the work unit and active profile.

Parameters:
reconcile_parsed_justificante(*, work_unit, source_kind, source_ref, actor, justificante, operation)[source]

Reconcile parsed justificante evidence with the selected work unit.

Return type:

ModeloReconciliationReport

Parameters:
reconcile_parsed_declaracion(*, work_unit, source_kind, source_ref, actor, declaracion, operation)[source]

Reconcile parsed declaration evidence with the selected work unit.

Return type:

ModeloReconciliationReport

Parameters:
reconcile_receipt_totals(*, work_unit, justificante, revision, operation)[source]

Reconcile the receipt total against the canonical computed result casilla.

Resolves the registry snapshot for work_unit, reads the reconciliation_total_casilla_ids map its verification expectations declare (the same map calculation_result_summary consumes), and compares the receipt’s printed total against revision.casilla_values[target_casilla] at the expectation’s declared tolerance. revision is the persisted revision the caller asserts was presented; None means no such revision exists. A divergence is a typed total diff carrying the reconciling expectation’s legal_refs / source_refs. Every branch that cannot perform the comparison returns a totals_not_reconciled advisory instead of silently passing.

Parameters:
Return type:

tuple[list[ModeloReconciliationDiff], list[ModeloReconciliationAdvisory]]

Returns:

The typed diffs and advisories the comparison produced.