"""Modelo 720 / 721 foreign-asset obligation-group semantic layer.
Typed abstraction over the raw Modelo 720 casilla bindings. The registry
declares the ``clave-tipo-de-bien-o-derecho`` field and a ``foreign_asset``
row source whose ``asset_class_code`` an operator otherwise reads as an opaque
one-character clave. This module lifts that clave onto the four regulatory
declaration bloques of the Reglamento General de Gestión e Inspección
(RD 1065/2007) — cuentas (art. 42 bis), valores/derechos/IIC/seguros/rentas
(art. 42 ter), inmuebles (art. 54 bis), and monedas virtuales (art. 42
quater, the Modelo 721 sibling) — and carries each bloque's grounded
declaration thresholds: the 50.000 EUR initial declaration floor and the
20.000 EUR re-declaration increase delta.
It is a surfacing layer only. It does not aggregate observations, compute a
casilla value, apply the declarability gate, or resolve a binding; the M720
aggregation and its per-obligation-block threshold gate live in
:mod:`application.aggregation._foreign_assets`. The obligation group is
the legally load-bearing axis: the 50.000 EUR floor is a per-bloque umbral
(art. 42 bis/ter/54 bis/quater), so the ``valor``/``seguro`` classes both
belong to the single art. 42 ter valores bloque rather than two independent
thresholds.
The :class:`ForeignAssetObligationGroup` enum is declared here (a closed value
set, per the core-authority architecture contract); it is the obligation-group
sibling of the per-clave :class:`~core.aggregation.ForeignAssetClass`.
"""
from __future__ import annotations
from collections.abc import Mapping
from decimal import Decimal
from enum import StrEnum
from types import MappingProxyType
from typing import Final
from pydantic import BaseModel, Field
from ._models import STRICT_FROZEN_CONFIG
from .aggregation import ForeignAssetClass
from .external_constants import MODELO_720_REPORTING_THRESHOLD_EUR
#: Modelo 720 / 721 re-declaration increase delta. Once a bloque has been
#: declared in a prior ejercicio, the obligation to re-declare it in a later
#: ejercicio arises only when the bloque's aggregate valuation has increased by
#: MORE than this amount over the last declared value (or a member was
#: extinguished/cancelled). Binding provision: RD 1065/2007 art. 42 bis.4 /
#: art. 42 ter.5 / art. 54 bis.6 (and art. 42 quater for monedas virtuales) —
#: "hubiese experimentado un incremento superior a 20.000 euros respecto del que
#: determinó la presentación de la última declaración". The legal registry entry
#: ``rd-1065-2007:art-42-quater`` records the same structure verbatim: "inicial
#: 50.000 EUR, re-declaracion si variacion > 20.000 EUR". The gate is STRICT
#: (> 20.000), mirroring the strict initial-floor gate
#: (:data:`~core.external_constants.MODELO_720_REPORTING_THRESHOLD_EUR`).
MODELO_720_REDECLARATION_INCREASE_THRESHOLD_EUR: Final[Decimal] = Decimal("20000.00")
[docs]
class ForeignAssetObligationGroup(StrEnum):
"""Regulatory declaration bloques for foreign assets and rights (RGAT).
Each member is one of the RD 1065/2007 information-obligation bloques that
the Modelo 720 (and its monedas-virtuales sibling Modelo 721) partitions
declared assets into. The 50.000 EUR declaration floor and the 20.000 EUR
re-declaration delta apply INDEPENDENTLY per bloque, so this — not the
per-clave :class:`~core.aggregation.ForeignAssetClass` — is the axis
the declaration thresholds are keyed on.
Members:
CUENTAS: Cuentas en entidades financieras situadas en el extranjero
(RD 1065/2007 art. 42 bis). Feeds :attr:`ForeignAssetClass.ACCOUNT`.
VALORES_DERECHOS_SEGUROS: Valores, derechos, IIC, seguros y rentas
depositados, gestionados u obtenidos en el extranjero
(RD 1065/2007 art. 42 ter). Feeds
:attr:`ForeignAssetClass.SECURITY`,
:attr:`ForeignAssetClass.COLLECTIVE_INVESTMENT`, and
:attr:`ForeignAssetClass.INSURANCE`, which share this single
bloque umbral.
INMUEBLES: Bienes inmuebles y derechos sobre bienes inmuebles situados
en el extranjero (RD 1065/2007 art. 54 bis). Feeds
:attr:`ForeignAssetClass.REAL_ESTATE`.
MONEDAS_VIRTUALES: Monedas virtuales situadas en el extranjero
(RD 1065/2007 art. 42 quater; declared via Modelo 721 from ejercicio
2023). Feeds :attr:`ForeignAssetClass.VIRTUAL_CURRENCY`.
"""
CUENTAS = "cuentas"
VALORES_DERECHOS_SEGUROS = "valores_derechos_seguros"
INMUEBLES = "inmuebles"
MONEDAS_VIRTUALES = "monedas_virtuales"
FOREIGN_ASSET_CLASS_OBLIGATION_GROUP: Final[Mapping[ForeignAssetClass, ForeignAssetObligationGroup]] = MappingProxyType(
{
ForeignAssetClass.ACCOUNT: ForeignAssetObligationGroup.CUENTAS,
ForeignAssetClass.SECURITY: ForeignAssetObligationGroup.VALORES_DERECHOS_SEGUROS,
ForeignAssetClass.COLLECTIVE_INVESTMENT: ForeignAssetObligationGroup.VALORES_DERECHOS_SEGUROS,
ForeignAssetClass.INSURANCE: ForeignAssetObligationGroup.VALORES_DERECHOS_SEGUROS,
ForeignAssetClass.REAL_ESTATE: ForeignAssetObligationGroup.INMUEBLES,
ForeignAssetClass.VIRTUAL_CURRENCY: ForeignAssetObligationGroup.MONEDAS_VIRTUALES,
},
)
"""Total map from each :class:`ForeignAssetClass` clave to its RGAT bloque.
Complete by construction: every :class:`ForeignAssetClass` member is a key, so
:func:`foreign_asset_obligation_group` is total and a new asset class cannot be
added without also declaring its bloque (the parity test fails otherwise). The
art. 42 ter valores bloque carries three Modelo 720 claves (``SECURITY``,
``COLLECTIVE_INVESTMENT``, ``INSURANCE``); cuentas and inmuebles carry one
Modelo 720 clave each; monedas virtuales is the Modelo 721 sibling and carries
no Modelo 720 clave.
"""
MODELO_720_FOREIGN_ASSET_CLASS_CODES: Final[Mapping[ForeignAssetClass, str]] = MappingProxyType(
{
ForeignAssetClass.ACCOUNT: "C",
ForeignAssetClass.SECURITY: "V",
ForeignAssetClass.COLLECTIVE_INVESTMENT: "I",
ForeignAssetClass.INSURANCE: "S",
ForeignAssetClass.REAL_ESTATE: "B",
},
)
"""Official Modelo 720 position-102 ``clave-tipo-de-bien-o-derecho`` map.
The bundled AEAT record design limits Modelo 720's asset-row class code to
``C``/``V``/``I``/``S``/``B``. ``I`` is participaciones en instituciones de
inversion colectiva, while real estate is ``B``. ``VIRTUAL_CURRENCY`` is
deliberately absent because RD 1065/2007 art. 42 quater is declared through the
Modelo 721 sibling, not Modelo 720.
"""
[docs]
class ForeignAssetDeclarationThreshold(BaseModel):
"""Grounded declaration thresholds for one foreign-asset obligation bloque.
A tiny closed carrier (per the core taxonomy convention) binding an
obligation :class:`ForeignAssetObligationGroup` to its two RGAT umbrales —
the 50.000 EUR initial declaration floor and the 20.000 EUR re-declaration
increase delta — plus the ``legal_refs`` provenance that establishes them.
Strict and frozen, matching the registry schema's
:data:`~core.STRICT_FROZEN_CONFIG` convention, so an unknown group or a
stray key is rejected at construction.
Both umbral gates are STRICT in the law and in the aggregation gate: a bloque
is declarable when its aggregate valuation is strictly ABOVE
``initial_declaration_floor_eur``, and re-declaration is required when the
increase over the last declared value is strictly ABOVE
``redeclaration_increase_delta_eur``.
"""
model_config = STRICT_FROZEN_CONFIG
group: ForeignAssetObligationGroup
initial_declaration_floor_eur: Decimal = Field(gt=Decimal("0"))
redeclaration_increase_delta_eur: Decimal = Field(gt=Decimal("0"))
legal_refs: tuple[str, ...] = Field(min_length=1)
_OBLIGATION_GROUP_LEGAL_REFS: Final[Mapping[ForeignAssetObligationGroup, tuple[str, ...]]] = MappingProxyType(
{
ForeignAssetObligationGroup.CUENTAS: (
"rd-1065-2007:art-42-bis",
"ley-58-2003:da-18",
"orden-hap-72-2013:art-2",
),
ForeignAssetObligationGroup.VALORES_DERECHOS_SEGUROS: (
"rd-1065-2007:art-42-ter",
"ley-58-2003:da-18",
"orden-hap-72-2013:art-2",
),
ForeignAssetObligationGroup.INMUEBLES: (
"rd-1065-2007:art-54-bis",
"ley-58-2003:da-18",
"orden-hap-72-2013:art-2",
),
ForeignAssetObligationGroup.MONEDAS_VIRTUALES: (
"rd-1065-2007:art-42-quater",
"ley-11-2021:da-10",
"orden-hfp-886-2023:art-2",
),
},
)
FOREIGN_ASSET_DECLARATION_THRESHOLDS: Final[Mapping[ForeignAssetObligationGroup, ForeignAssetDeclarationThreshold]] = (
MappingProxyType(
{
group: ForeignAssetDeclarationThreshold(
group=group,
initial_declaration_floor_eur=MODELO_720_REPORTING_THRESHOLD_EUR,
redeclaration_increase_delta_eur=MODELO_720_REDECLARATION_INCREASE_THRESHOLD_EUR,
legal_refs=refs,
)
for group, refs in _OBLIGATION_GROUP_LEGAL_REFS.items()
},
)
)
"""Grounded declaration thresholds per obligation bloque.
Complete by construction over :class:`ForeignAssetObligationGroup`. Every bloque
shares the same two umbral VALUES (50.000 EUR floor, 20.000 EUR re-declaration
delta) because RD 1065/2007 fixes them identically across art. 42 bis/ter/54
bis/quater; the ``legal_refs`` differ per bloque because each cites its own
binding article.
"""
[docs]
def foreign_asset_obligation_group(asset_class: ForeignAssetClass) -> ForeignAssetObligationGroup:
"""Return the RGAT :class:`ForeignAssetObligationGroup` bloque a foreign-asset clave belongs to.
Total over :class:`ForeignAssetClass`. Use it to surface a Modelo 720
``foreign_asset`` row's opaque ``asset_class_code`` clave as its typed
declaration bloque (e.g. ``SECURITY`` and ``INSURANCE`` both resolve to
:attr:`ForeignAssetObligationGroup.VALORES_DERECHOS_SEGUROS`).
"""
return FOREIGN_ASSET_CLASS_OBLIGATION_GROUP[asset_class]
[docs]
def foreign_asset_declaration_threshold(
group: ForeignAssetObligationGroup,
) -> ForeignAssetDeclarationThreshold:
"""Return the grounded :class:`ForeignAssetDeclarationThreshold` for an obligation bloque."""
return FOREIGN_ASSET_DECLARATION_THRESHOLDS[group]
[docs]
def foreign_asset_class_declaration_threshold(
asset_class: ForeignAssetClass,
) -> ForeignAssetDeclarationThreshold:
"""Return the :class:`ForeignAssetDeclarationThreshold` for the bloque a clave belongs to.
Convenience composition of :func:`foreign_asset_obligation_group` and
:func:`foreign_asset_declaration_threshold`; the returned threshold is the
per-bloque umbral, so two claves in the same bloque share one threshold.
"""
return foreign_asset_declaration_threshold(foreign_asset_obligation_group(asset_class))
__all__ = [
"FOREIGN_ASSET_CLASS_OBLIGATION_GROUP",
"FOREIGN_ASSET_DECLARATION_THRESHOLDS",
"MODELO_720_FOREIGN_ASSET_CLASS_CODES",
"MODELO_720_REDECLARATION_INCREASE_THRESHOLD_EUR",
"ForeignAssetDeclarationThreshold",
"ForeignAssetObligationGroup",
"foreign_asset_class_declaration_threshold",
"foreign_asset_declaration_threshold",
"foreign_asset_obligation_group",
]