aeat.application.aggregation._retencion_observations_repository module¶
Encrypted persistence for per-perceptor retención records (Modelo 180/193).
The DEDICATED store the retenciones-summary family reads to count perceptors
DISTINCTLY. Modelo 180 casilla decl.total-perceptores (“Número total de
perceptores … Número de registros de tipo 2”, AEAT Diseño de Registro) is the
count of distinct perceptor NIFs on the annual declaration, NOT the sum of the
quarterly Modelo 115 aggregate counts. The validated distinct-count primitive
aggregate_retenciones_180 already exists; what it lacked was a persisted,
calc-mesh-readable per-perceptor source so the calculate path could compute the
distinct count instead of falling back to the wrong quarterly sum. This module is
that source: it persists each RetencionObservation (perceptor NIF +
scheme + taxable base + retención) keyed by (modelo, filing_year, period) plus
the per-perceptor identity, so the pull and calculate surfaces read ONE store.
Sensitivity is SensitivityClass
FINANCIAL — perceptor NIFs are identity-bearing financial data, stored
encrypted at rest through a
SecureBoundRepository that writes
Envelope records. The plaintext NIF
lives only inside the encrypted payload; the object key carries the sha256 of
the NIF (the iva-wallet-decision key convention), never the cleartext value
(sensitive-financial-data-secure-storage-only).
The namespace, schema version, object-key grammar, and custody disposition are
declared by
adapters.persistence.storage.RETENCION_OBSERVATIONS_NAMESPACE.
ADR 2026-06-24-retenciones-perceptor-count-adr. Producers (the pull/aggregate
entrypoints) write here through one shared helper; the P02 calc-mesh resolver
reads here and calls the distinct-count primitive.
- retencion_observation_key(modelo, filing_year, period, perceptor_nif, scheme)[source]¶
Opaque per-perceptor object key — the NIF is hashed, never cleartext.
Secure-object payloads are encrypted, but object keys are storage metadata, so the perceptor NIF is sha256-hashed (the iva-wallet-decision key convention). Distinct (perceptor NIF, scheme) pairs persist as distinct rows so a perceptor paid under more than one scheme is preserved while the distinct-NIF count stays correct.
- class RetencionObservationRepository(*, bucket_id=None, objects=None, settings=None)[source]¶
Bases:
SecureBoundRepository[_RetencionObservationEnvelopePayload]Encrypted repository for per-perceptor
RetencionObservationpayloads.The
SecureBoundRepositorybase wraps each payload in aEnvelopeunderadapters.persistence.storage.RETENCION_OBSERVATIONS_NAMESPACEand enforces the namespace’s FINANCIALSensitivityClass.See also
adapters.persistence.storage.RETENCION_OBSERVATIONS_NAMESPACESecure-object namespace and hashed object-key contract.
retencion_observation_key()Deterministic key builder that keeps the plaintext NIF out of storage metadata.
persist_retencion_observations()Shared producer write path for pull and calculate parity.
- Parameters:
bucket_id (str | None)
objects (SecureObjectRepository | None)
settings (Settings | None)
-
sensitivity:
ClassVar[SensitivityClass]¶
- payload_type¶
alias of
_RetencionObservationEnvelopePayload
- extract_identifier(payload)[source]¶
Return the natural id for
payload(used as the SQL object key).Subclasses MUST override. The base implementation raises
NotImplementedError.- Return type:
- Parameters:
payload (_RetencionObservationEnvelopePayload)
- save_observation(*, modelo, filing_year, period, observation, source_kind, captured_at=None, source_metadata=None)[source]¶
Persist one per-perceptor retención row keyed by (modelo, filing_year, period, NIF, scheme).
- replace_observations(*, modelo, filing_year, period, observations, source_kind, captured_at=None, source_metadata=None)[source]¶
Replace the FULL per-perceptor set for one (modelo, filing_year, period).
SET-REPLACE, not additive upsert: clears any prior rows for the exact key-tuple, then writes the supplied set. A re-pull where the operator DROPPED a perceptor must not leave the stale row behind — otherwise the next calculate’s distinct count is inflated by a perceptor no longer declared (a silent over-count, the inverse of the bug RET-1 fixes). An empty
observationsclears the window (the operator declared none); the P02 resolver raises a no-silentAggregationValidationErrorwhen a declaring revision then reads an empty store, before a zero perceptor count can be filed.
- load_observations(modelo, period)[source]¶
Return every persisted per-perceptor observation for one (modelo, filing_year, period).
The calc-mesh perceptor-count resolver (P02) folds these through the validated distinct-count primitive. An empty tuple means no per-perceptor records were persisted for the window — the resolver MUST fail loudly rather than materialising a zero count.
- Return type:
- Returns:
Persisted
RetencionObservationrecords for the requested window.- Parameters:
- persist_retencion_observations(*, modelo, filing_year, period, observations, source_kind='aggregate_pull')[source]¶
The ONE shared write path every per-perceptor producer calls.
Factoring the persist behind a single application helper makes store completeness STRUCTURAL rather than per-entrypoint discipline a future producer could forget (an unwritten producer -> an incomplete store -> the exact pull≠calculate divergence RET-1 fixes). Writes to the active bucket’s encrypted store with SET-REPLACE semantics so pull and calculate read one source. aggregate_per_modelo stays pure — persistence is the entrypoint’s job, not the aggregator’s (aeat-architecture-boundaries).