aeat.application.aggregation._percepciones_observations_repository module¶
Encrypted persistence for per-perceptor-clave percepciones records (Modelo 190).
The DEDICATED store the percepciones-count resolver reads to count percepciones
DISTINCTLY. Modelo 190 casilla decl.total-percepciones (“Número total de
percepciones … Número de registros de tipo 2”, AEAT Diseño de Registros) is the
count of DISTINCT (perceptor NIF, clave, subclave) type-2 records on the annual
declaration — a perceptor paid under two claves files two percepciones — NOT the
distinct-NIF perceptor count (that is RET-1’s perceptor_count for Modelo
180/193) and NOT the sum of the quarterly Modelo 111 perceptor counts. The
validated distinct-count primitive (the percepcion_count withholding fact)
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
clave-bearing WithholdingObservation keyed by (modelo, filing_year,
period) plus the per-perceptor-clave identity, so the pull and calculate
surfaces read ONE store (one-aggregation-path-pull-equals-calculate).
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.WITHHOLDING_OBSERVATIONS_NAMESPACE.
ADR 2026-06-25-modelo-190-percepciones-count-adr. Producers (the
pull/aggregate entrypoints) write here through one shared helper; the P03
calc-mesh resolver reads here and calls the distinct-count primitive. This is the
percepciones counterpart of _retencion_observations_repository (the
perceptores store); the two stores are intentionally distinct — different distinct
keys (NIF+clave+subclave vs NIF), different models, different modelos.
This module was renamed from _withholding_observations_repository.py per the
import-centralization ADR ruling 4: the “withholding” stem collided with the
project’s Spanish-stem naming convention (retencion already names the
sibling Modelo 180/193 store), so this module — and the repository symbols it
owns locally — follow the percepciones stem instead. The
WithholdingObservation domain
type it wraps is an unrelated, widely shared registry taxonomy type and is
out of scope for this rename.
- percepcion_observation_key(modelo, filing_year, period, perceptor_tax_id, clave, subclave)[source]¶
Opaque per-perceptor-clave 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, clave, subclave) triples persist as distinct rows so a perceptor paid under more than one clave is preserved as the distinct percepciones the Diseño counts (registros de tipo 2).
- class PercepcionObservationRepository(*, bucket_id=None, objects=None, settings=None)[source]¶
Bases:
SecureBoundRepository[_PercepcionObservationEnvelopePayload]Encrypted repository for per-perceptor-clave
WithholdingObservationpayloads.The
SecureBoundRepositorybase wraps each payload in aEnvelopeunderadapters.persistence.storage.WITHHOLDING_OBSERVATIONS_NAMESPACEand enforces the namespace’s FINANCIALSensitivityClass.See also
adapters.persistence.storage.WITHHOLDING_OBSERVATIONS_NAMESPACESecure-object namespace and hashed object-key contract.
percepcion_observation_key()Deterministic key builder that hashes the NIF and preserves clave/subclave identity.
persist_percepcion_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
_PercepcionObservationEnvelopePayload
- 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 (_PercepcionObservationEnvelopePayload)
- save_observation(*, modelo, filing_year, period, observation, source_kind, captured_at=None, source_metadata=None)[source]¶
Persist one per-perceptor-clave row keyed by (modelo, year, period, NIF, clave, subclave).
- replace_observations(*, modelo, filing_year, period, observations, source_kind, captured_at=None, source_metadata=None)[source]¶
Replace the FULL per-perceptor-clave 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 percepción must not leave the stale row behind — otherwise the next calculate’s distinct count is inflated by a percepción no longer declared (a silent over-count). An empty
observationsclears the window (the operator declared none); the P03 resolver surfaces a no-silent advisory when it then reads empty.
- load_observations(modelo, period)[source]¶
Return persisted per-perceptor-clave
WithholdingObservationrecords.The result covers one
(modelo, filing_year, period)window.The calc-mesh percepciones-count resolver (P03) folds these through the validated distinct-count primitive. An empty tuple means no per-perceptor records were persisted for the window — the resolver MUST surface a no-silent advisory rather than materialising a silent zero.
- Return type:
- Parameters:
- persist_percepcion_observations(*, modelo, filing_year, period, observations, source_kind='aggregate_pull')[source]¶
The ONE shared write path every per-perceptor-clave 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 #28 fixes). Writes to the active bucket’s encrypted store with SET-REPLACE semantics so pull and calculate read one source.