Source code for aeat.domain.modelos._row_models

"""Typed CLI row models for multi-row informational modelos.

Provides strictly-validated pydantic row shapes for operator-supplied
detail rows on modelos whose filing content is a list of repeating
records rather than a set of scalar casilla values.

Supported row types:

* ``Modelo184MemberRow`` — atribución member for modelo 184
  (``--row miembro nif=X share=Y importe=Z``)
* ``Modelo232VinculadaRow`` — operación vinculada for modelo 232
  (``--row vinculada nif=X tipo_vinculacion=Y importe=Z metodo=M pais=P``)
* ``Modelo349OperadorRow`` — operador intracomunitario for modelo 349
  (``--row operador codigo_pais=DE nif_comunitario=DE123456789 razon_social=X clave_operacion=E importe=Y``)
  Used when no collectible-invoice ledger exists; maps directly to the
  Tipo-2 operador record layout (Orden HAC/174/2020 Anexo II).
* ``Modelo349RectificacionRow`` — rectificación intracomunitaria for modelo 349
  (``--row rectificacion codigo_pais=DE nif_comunitario=DE123456789 razon_social=X``
  ``clave_operacion=E ejercicio=2025 periodo=2T base_rectificada=Y base_anterior=Z``)
  Used when the operator declares Tipo-2 rectification records directly.
* ``Modelo347ContraparteRow`` — contraparte declarada for modelo 347
  (``--row contraparte nif=X nombre=Y importe_Q1=Z clave_operacion=A``)
  One row per counterparty. Annual importe threshold check (> €3,005.06)
  is performed by the CLI validator, not the model, so partial row sets
  accumulate correctly before final validation.

These models are the CLI boundary layer. They validate operator input
before being carried into ``detail_rows`` on the ``CalculationRevision``.
"""

from __future__ import annotations

import re
from collections.abc import Sequence
from decimal import Decimal
from typing import Annotated, Literal

from pydantic import BaseModel, Field, StringConstraints, field_validator

from ...core import STRICT_FROZEN_CONFIG
from ...core.errors import AeatError
from ...core.external_constants import M347_THRESHOLD_EUR as M347_THRESHOLD_EUR  # re-export
from ...core.identity import nif_iva_format_for_country

# ---------------------------------------------------------------------------
# Shared type aliases
# ---------------------------------------------------------------------------

_NifStr = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=20)]
_NameStr = Annotated[str, StringConstraints(strip_whitespace=True, max_length=200)]
_RequiredNameStr = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=200)]
_IsoCountryCode = Annotated[str, StringConstraints(strip_whitespace=True, min_length=2, max_length=2)]


# ---------------------------------------------------------------------------
# Modelo 184 - atribucion de rentas member row
#
# Legal authority: Orden HAP/2250/2015 art. 3; Ley 35/2006 arts. 86-89.
# One row per miembro (socio / comunero / partícipe) of the entity.
# ---------------------------------------------------------------------------


[docs] class Modelo184MemberRow(BaseModel): """One atribución member row for Modelo 184. Fields mirror the per-record Tipo-2 layout declared in the M184 ``bindings/0001-bindings.toml`` atribucion_member source block. Parity assertions: * ``nif`` → ``member_tax_id`` (binding: modelo-184-member-row-nif) * ``nombre`` → ``member_legal_name`` (binding: modelo-184-member-row-name) * ``porcentaje`` → ``share_percentage`` (binding: modelo-184-member-row-share) * ``importe`` → ``base_imponible_assigned`` (binding: modelo-184-member-row-base-assigned) * ``pais`` → ``country_code`` (ES default) """ model_config = STRICT_FROZEN_CONFIG row_type: Literal["miembro"] = "miembro" nif: _NifStr nombre: _NameStr = Field(default="") pais: _IsoCountryCode = Field(default="ES") porcentaje: Decimal = Field(description="Share percentage in the entity [0, 100]") importe: Decimal = Field(description="Attributed income/base imponible in EUR") @field_validator("pais") @classmethod def _pais_uppercase_alpha(cls, value: str) -> str: if value != value.upper() or not value.replace(" ", "").isalpha(): raise ValueError("pais must be an uppercase two-letter ISO 3166-1 country code (e.g. ES, DE, FR)") return value @field_validator("porcentaje") @classmethod def _porcentaje_within_bounds(cls, value: Decimal) -> Decimal: if value < Decimal("0") or value > Decimal("100"): raise ValueError(f"porcentaje must be within [0, 100]; got {value}") return value @field_validator("nif") @classmethod def _nif_not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("nif cannot be blank") return value.upper()
# --------------------------------------------------------------------------- # Modelo 232 - operacion vinculada row # # Legal authority: Orden HFP/816/2017 art. 3; Ley 27/2014 art. 18; # RD 634/2015 art. 13 (LIS transfer pricing). # One row per related-party transaction group. # --------------------------------------------------------------------------- # Closed catalogue of válid tipo_vinculacion codes per M232 form. _M232_TIPO_VINCULACION = Literal[ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", ] # Closed catalogue of valid tipo_operacion codes per M232 form. _M232_TIPO_OPERACION = Literal[ "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", ] # Closed catalogue of transfer-pricing method codes per M232 / LIS art. 18. _M232_METODO = Literal["CUP", "RPM", "CPM", "PS", "TNMM", ""]
[docs] class Modelo232VinculadaRow(BaseModel): """One operación vinculada row for Modelo 232. Fields mirror the related_party_operation binding source declared in ``232/revisions/2018-y-siguientes/bindings/0218…0223-*.toml``. Parity assertions: * ``nif`` → ``counterparty_tax_id`` (binding: modelo-232-related-party-row-nif) * ``nombre`` → ``counterparty_legal_name`` (binding: modelo-232-related-party-row-name) * ``pais`` → ``country_code`` (binding: modelo-232-related-party-row-country) * ``tipo_operacion`` → ``operation_kind_code`` (binding: modelo-232-related-party-row-operation-kind) * ``metodo`` → ``transfer_pricing_method_code`` (binding: modelo-232-related-party-row-tpr-method) * ``importe`` → ``amount`` (binding: modelo-232-related-party-row-amount) """ model_config = STRICT_FROZEN_CONFIG row_type: Literal["vinculada"] = "vinculada" nif: _NifStr nombre: _NameStr = Field(default="") pais: _IsoCountryCode = Field(default="ES") tipo_vinculacion: str = Field(default="1", min_length=1, max_length=2) tipo_operacion: str = Field(default="01", min_length=1, max_length=2) metodo: str = Field(default="", max_length=6) importe: Decimal @field_validator("pais") @classmethod def _pais_uppercase_alpha(cls, value: str) -> str: if value != value.upper() or not value.replace(" ", "").isalpha(): raise ValueError("pais must be an uppercase two-letter ISO 3166-1 country code (e.g. ES, DE, FR)") return value @field_validator("nif") @classmethod def _nif_not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("nif cannot be blank") return value.upper() @field_validator("metodo") @classmethod def _metodo_uppercase(cls, value: str) -> str: return value.upper()
# --------------------------------------------------------------------------- # Modelo 349 - operador intracomunitario row (manual-entry path) # # Legal authority: Orden HAC/174/2020 (Anexo II — Tipo 2 operador record); # Orden EHA/769/2010 art. 3; Ley 58/2003 art. 93; Ley 37/1992 arts. 66-70 # (operaciones intracomunitarias). # One row per counterparty + clave_operacion combination. # NIF-IVA format validation enforces country-specific patterns from # Council Directive 2006/112/EC Annex XI (the VIES registry format rules). # --------------------------------------------------------------------------- # Country-specific NIF-IVA format patterns for Modelo 349. Every current EU # Member State (plus post-Brexit Northern Ireland ``XI``) routes through the # canonical :data:`aeat.core.identity.NIF_IVA_FORMATS` authority so the # structural pattern lives in exactly one place (per the # binding-source-kind-single-taxonomy discipline: a per-family collection is # derived from the core table, never hand-maintained as a parallel literal # set). ``GB`` is the sole deliberate exception: post-Brexit UK is not an EU # Member State, so the general IVA/invoice counterparty boundary # (:mod:`aeat.domain.invoices`) correctly carries no GB structural pattern and # falls back to its generic non-EU shape check. Modelo 349's Brexit-transition # filing rules (:func:`validate_m349_country_prefix_context`) still permit a # historical ``GB`` prefix for pre-2021 rectifications and the 2021 1M/1T # transition period, so the exact GB VAT structural shape (9 or 12 digits, or # the ``GD``/``HA`` government/health-authority forms) is retained here only, # scoped to Modelo 349's own transition-period need. _M349_GB_NIF_PATTERN: re.Pattern[str] = re.compile(r"^GB(\d{9}|\d{12}|GD\d{3}|HA\d{3})$") # Valid clave de operación codes per Orden HAC/174/2020 Anexo II. _M349_CLAVE_OPERACION = Literal["E", "M", "H", "A", "T", "S", "I", "R", "D", "C"] _M349_RECTIFICACION_PERIODO = Literal[ "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "1M", "1T", "2T", "3T", "4T", ] _M349_NI_PREFIX = "XI" _M349_GB_PREFIX = "GB" _M349_SERVICE_CLAVES = frozenset({"S", "I"}) _M349_2021_FIRST_PERIODS = frozenset({"01", "1M", "1T"})
[docs] class Modelo349CountryPrefixContextError(AeatError, ValueError): """A Modelo 349 country prefix is invalid for the filing context.""" def __init__( self, *, country_code: str, clave_operacion: str, filing_year: int, period: str, reason: str, ) -> None: self.country_code = country_code self.clave_operacion = clave_operacion self.filing_year = filing_year self.period = period self.reason = reason super().__init__( translated_message="errors.refused.modelo_349_country_prefix_context", context={ "country_code": country_code, "clave_operacion": clave_operacion, "filing_year": filing_year, "period": period, "reason": reason, }, )
[docs] class Modelo349OperadorRow(BaseModel): """One operador intracomunitario row for Modelo 349 (manual-entry path). Fields mirror the Tipo-2 operador record layout declared in ``349/revisions/2020-y-siguientes/bindings/0007-bindings.toml``. This row is used when the collectible-invoice ledger is absent and the operator declares intracom counterparties directly via the CLI. Parity assertions: * ``codigo_pais`` -> ``op.codigo-pais`` (record positions 76-77) * ``nif_comunitario`` -> ``op.nif-comunitario`` (record positions 78-92) * ``razon_social`` -> ``op.apellidos-razon-social`` (record positions 93-132) * ``clave_operacion`` -> ``op.clave-operacion`` (record position 133) * ``importe`` -> ``op.base-imponible`` (record positions 134-146) """ model_config = STRICT_FROZEN_CONFIG row_type: Literal["operador"] = "operador" codigo_pais: _IsoCountryCode nif_comunitario: _NifStr razon_social: _RequiredNameStr clave_operacion: _M349_CLAVE_OPERACION importe: Decimal = Field(description="Base imponible o importe de la operacion en EUR") @field_validator("codigo_pais") @classmethod def _codigo_pais_uppercase_alpha(cls, value: str) -> str: if value != value.upper() or not value.replace(" ", "").isalpha(): raise ValueError("codigo_pais must be an uppercase two-letter ISO 3166-1 country code (e.g. DE, FR, IT)") return value @field_validator("nif_comunitario") @classmethod def _nif_comunitario_not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("nif_comunitario cannot be blank") return value.upper() @field_validator("importe") @classmethod def _importe_non_negative(cls, value: Decimal) -> Decimal: if value < Decimal("0"): raise ValueError(f"importe must be non-negative per Orden HAC/174/2020 Anexo II constraint; got {value}") return value
[docs] class Modelo349RectificacionRow(BaseModel): """One rectificación row for Modelo 349 (manual-entry path). Fields mirror the Tipo-2 rectificación record layout declared in ``349/revisions/2020-y-siguientes/bindings/0007-bindings.toml``. Parity assertions: * ``codigo_pais`` -> ``op.codigo-pais`` (record positions 76-77) * ``nif_comunitario`` -> ``op.nif-comunitario`` (record positions 78-92) * ``razon_social`` -> ``op.apellidos-razon-social`` (record positions 93-132) * ``clave_operacion`` -> ``op.clave-operacion`` (record position 133) * ``ejercicio`` -> ``rect.ejercicio-rectificado`` (record positions 147-150) * ``periodo`` -> ``rect.periodo-rectificado`` (record positions 151-152) * ``base_rectificada`` -> ``rect.base-rectificada`` (record positions 153-165) * ``base_anterior`` -> ``rect.base-anterior`` (record positions 166-178) """ model_config = STRICT_FROZEN_CONFIG row_type: Literal["rectificacion"] = "rectificacion" codigo_pais: _IsoCountryCode nif_comunitario: _NifStr razon_social: _RequiredNameStr clave_operacion: _M349_CLAVE_OPERACION ejercicio: Annotated[str, StringConstraints(strip_whitespace=True, min_length=4, max_length=4)] periodo: _M349_RECTIFICACION_PERIODO base_rectificada: Decimal = Field(description="Base imponible o importe rectificado en EUR") base_anterior: Decimal = Field(description="Base imponible declarada anteriormente en EUR") @field_validator("codigo_pais") @classmethod def _codigo_pais_uppercase_alpha(cls, value: str) -> str: if value != value.upper() or not value.replace(" ", "").isalpha(): raise ValueError("codigo_pais must be an uppercase two-letter ISO 3166-1 country code (e.g. DE, FR, IT)") return value @field_validator("nif_comunitario") @classmethod def _nif_comunitario_not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("nif_comunitario cannot be blank") return value.upper() @field_validator("periodo", mode="before") @classmethod def _periodo_uppercase(cls, value: object) -> object: if isinstance(value, str): return value.strip().upper() return value @field_validator("ejercicio") @classmethod def _ejercicio_four_digit_year(cls, value: str) -> str: if not value.isdigit(): raise ValueError("ejercicio must be a four-digit year") return value @field_validator("base_rectificada", "base_anterior") @classmethod def _bases_non_negative(cls, value: Decimal) -> Decimal: if value < Decimal("0"): raise ValueError("rectification bases must be non-negative per Orden HAC/174/2020 Anexo II constraint") return value
[docs] def validate_m349_nif_format(nif: str, pais: str) -> bool: """Return True when ``nif`` matches the expected NIF-IVA format for ``pais``. Every current EU Member State (plus ``XI``) resolves its structural pattern from the canonical :func:`aeat.core.identity.nif_iva_format_for_country` authority. ``GB`` is validated against Modelo 349's own Brexit-transition pattern (see :data:`_M349_GB_NIF_PATTERN`), since post-Brexit UK carries no entry in the general EU NIF-IVA authority. Unsupported country prefixes fail closed. The NIF string must include the same two-letter country prefix. """ normalized_pais = pais.upper() normalized_nif = nif.upper() if not normalized_nif.startswith(normalized_pais): return False if normalized_pais == "GB": return bool(_M349_GB_NIF_PATTERN.match(normalized_nif)) spec = nif_iva_format_for_country(normalized_pais) if spec is None: return False return bool(spec.pattern.match(normalized_nif))
[docs] def validate_m349_country_prefix_context( *, country_code: str, clave_operacion: str, filing_year: int, period: str, is_rectification: bool = False, rectified_year: int | None = None, rectified_period: str | None = None, ) -> None: """Validate post-Brexit ``GB`` / ``XI`` rules for Modelo 349. AEAT's Brexit IVA instructions keep ``XI`` for Northern Ireland goods operations after 2021 and exclude ``S`` / ``I`` service keys from ``XI``. Ordinary ``GB`` rows are not valid for post-transition periods, except for the limited 2021 first-period and pre-2021 rectification cases named by the official instructions. """ country = country_code.strip().upper() clave = clave_operacion.strip().upper() period_code = _normalise_m349_period(period) rectified_period_code = _normalise_m349_period(rectified_period) if rectified_period is not None else None if country == _M349_NI_PREFIX: if clave in _M349_SERVICE_CLAVES: _raise_m349_country_context_error( country_code=country, clave_operacion=clave, filing_year=filing_year, period=period_code, reason="Northern Ireland prefix XI is not accepted for service keys S or I", ) if is_rectification and rectified_year is not None and rectified_year < 2021: _raise_m349_country_context_error( country_code=country, clave_operacion=clave, filing_year=filing_year, period=period_code, reason="pre-2021 rectifications use GB, not XI", ) if not is_rectification and filing_year < 2021: _raise_m349_country_context_error( country_code=country, clave_operacion=clave, filing_year=filing_year, period=period_code, reason="XI applies only from 2021 onward", ) return if country != _M349_GB_PREFIX: return if is_rectification: if rectified_year is not None and rectified_year < 2021: return if ( rectified_year == 2021 and rectified_period_code in _M349_2021_FIRST_PERIODS and clave not in _M349_SERVICE_CLAVES ): return _raise_m349_country_context_error( country_code=country, clave_operacion=clave, filing_year=filing_year, period=period_code, reason="GB is limited to pre-2021 rectifications and the 2021 1M/1T transition case", ) if filing_year < 2021: return if filing_year == 2021 and period_code in _M349_2021_FIRST_PERIODS and clave not in _M349_SERVICE_CLAVES: return _raise_m349_country_context_error( country_code=country, clave_operacion=clave, filing_year=filing_year, period=period_code, reason="ordinary post-transition Modelo 349 rows use XI for Northern Ireland goods and exclude GB", )
def _normalise_m349_period(period: str | None) -> str: if period is None: return "" token = str(period).strip().upper() if len(token) == 1 and token.isdigit(): return f"0{token}" return token def _raise_m349_country_context_error( *, country_code: str, clave_operacion: str, filing_year: int, period: str, reason: str, ) -> None: raise Modelo349CountryPrefixContextError( country_code=country_code, clave_operacion=clave_operacion, filing_year=filing_year, period=period, reason=reason, )
[docs] def m349_nif_number_for_export(nif: str, pais: str) -> str: """Return the BOE NIF subfield without the separate country-code prefix. Modelo 349 operator records split the VAT identifier into ``codigo_pais`` and ``nif_comunitario`` fields. The CLI accepts and validates the full prefixed VAT identifier for operator ergonomics, but the fixed-width export must write only the number part into positions 78-92. """ normalized_pais = pais.upper() normalized_nif = nif.upper() if not validate_m349_nif_format(normalized_nif, normalized_pais): raise ValueError( f"nif_comunitario {nif} does not match the expected NIF-IVA format for country {pais}", ) return normalized_nif[len(normalized_pais) :]
# --------------------------------------------------------------------------- # Modelo 347 - contraparte declarada row # # Legal authority: Orden EHA/3012/2008 art. 1; RD 1065/2007 arts. 31-35 # (reglamento de gestión e inspección tributaria, obligación de informar # sobre operaciones con terceros); Ley 58/2003 art. 93. # Threshold: total annual importe > €3,005.06 per counterparty (RD # 1065/2007 art. 33.1). The threshold check is performed at the CLI # validator level, not here, so that partial row accumulation works. # --------------------------------------------------------------------------- # Valid clave de operacion codes per M347 form / Orden EHA/3012/2008. _M347_CLAVE_OPERACION = Literal["A", "B", "C", "D", "E", "F", "G", "H", "I"]
[docs] class Modelo347ContraparteRow(BaseModel): """One contraparte declarada row for Modelo 347. Fields mirror the per-counterparty Tipo-2 record layout declared in ``347/revisions/2008-y-siguientes``. One row per counterparty. The annual total importe (sum of Q1-Q4) must exceed €3,005.06 per RD 1065/2007 art. 33.1. Parity assertions: * ``nif`` → ``contraparte.nif`` (counterparty tax id) * ``nombre`` → ``contraparte.nombre`` (legal name) * ``importe_Q1/Q2/Q3/Q4`` → quarterly importe slots * ``clave_operacion`` → operation type code * ``pais_codigo`` → ``contraparte.pais`` (ISO 3166-1; None = domestic) """ model_config = STRICT_FROZEN_CONFIG row_type: Literal["contraparte"] = "contraparte" nif: _NifStr nombre: _NameStr = Field(default="") importe_Q1: Decimal = Field(default=Decimal("0")) importe_Q2: Decimal = Field(default=Decimal("0")) importe_Q3: Decimal = Field(default=Decimal("0")) importe_Q4: Decimal = Field(default=Decimal("0")) clave_operacion: _M347_CLAVE_OPERACION = "A" pais_codigo: _IsoCountryCode | None = None @field_validator("nif") @classmethod def _nif_not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("nif cannot be blank") return value.upper() @field_validator("pais_codigo") @classmethod def _pais_codigo_uppercase_alpha(cls, value: str | None) -> str | None: if value is None: return value v = value.strip().upper() if not v.isalpha() or len(v) != 2: raise ValueError("pais_codigo must be an uppercase two-letter ISO 3166-1 country code or None for domestic") return v @property def importe_total(self) -> Decimal: """Sum of quarterly importes — used for M347 threshold check.""" return self.importe_Q1 + self.importe_Q2 + self.importe_Q3 + self.importe_Q4
# --------------------------------------------------------------------------- # Discriminated union — single type accepted by the CLI --row argument # --------------------------------------------------------------------------- ModeloDetailRow = ( Modelo184MemberRow | Modelo232VinculadaRow | Modelo349OperadorRow | Modelo349RectificacionRow | Modelo347ContraparteRow ) # --------------------------------------------------------------------------- # Statutory cross-row / threshold validations (domain-owned) # ---------------------------------------------------------------------------
[docs] class Modelo347ThresholdError(AeatError, ValueError): """A Modelo 347 contraparte row falls at or below the declarability threshold.""" def __init__(self, *, nif: str, total: Decimal) -> None: self.nif = nif self.total = total super().__init__( f"M347 contraparte (nif={nif!r}): importe total {total} does not exceed the " f"{M347_THRESHOLD_EUR} threshold required by RD 1065/2007 art. 33.1", )
[docs] class Modelo184ShareSumError(AeatError, ValueError): """Modelo 184 member share percentages do not sum to exactly 100%.""" def __init__(self, *, total: Decimal, count: int) -> None: self.total = total self.count = count super().__init__( f"M184 miembro rows: share percentages must sum to exactly 100%; got {total} across {count} rows", )
[docs] def validate_m347_threshold(rows: Sequence[Modelo347ContraparteRow]) -> None: """Enforce the Modelo 347 per-counterparty declarability threshold. RD 1065/2007 art. 33.1: only counterparties whose annual operations exceed EUR 3,005.06 are declarable. The threshold applies to the SUM of every operation with the same person (same NIF), aggregated across all contraparte rows — not to each row in isolation. A counterparty's operations may be split across several rows (e.g. entregas and adquisiciones), so a per-row check would wrongly reject a counterparty whose individual rows are each at/below the threshold while their annual aggregate exceeds it (a missed declaration), and would never apply the "same person" threshold the regulation defines. Raises: Modelo347ThresholdError: for the first counterparty (in NIF first-appearance order) whose AGGREGATED annual total is at or below the threshold. """ totals_by_nif: dict[str, Decimal] = {} for row in rows: totals_by_nif[row.nif] = totals_by_nif.get(row.nif, Decimal("0")) + row.importe_total for nif, total in totals_by_nif.items(): if total <= M347_THRESHOLD_EUR: raise Modelo347ThresholdError(nif=nif, total=total)
[docs] def validate_m184_member_share_sum(rows: Sequence[Modelo184MemberRow]) -> None: """Enforce that Modelo 184 member share percentages sum to exactly 100%. Only checked when at least one miembro row is present (partial sets are skipped). Raises: Modelo184ShareSumError: when the share percentages do not total exactly 100. """ if not rows: return total = sum((row.porcentaje for row in rows), Decimal("0")) if total != Decimal("100"): raise Modelo184ShareSumError(total=total, count=len(rows))
__all__ = [ "M347_THRESHOLD_EUR", "Modelo184MemberRow", "Modelo184ShareSumError", "Modelo232VinculadaRow", "Modelo347ContraparteRow", "Modelo347ThresholdError", "Modelo349CountryPrefixContextError", "Modelo349OperadorRow", "Modelo349RectificacionRow", "ModeloDetailRow", "m349_nif_number_for_export", "validate_m184_member_share_sum", "validate_m347_threshold", "validate_m349_country_prefix_context", "validate_m349_nif_format", ]