"""Resolve printed country evidence to a party's territorial scope, or to nothing.
The IVA treatment of a business invoice turns on WHERE each party is established,
so a reading pipeline that recovers amounts but not places cannot answer the
question the operation actually poses. The only country signal a read document
carries is printed: the two-letter prefix on a NIF-IVA, or the country stated in a
party's address. This module turns that printed evidence into the closed
:class:`IvaTerritorialScope` the classifier consumes -- or into ``None``, which is
the answer far more often than it looks.
**A code establishes a territory only through the closed vocabulary.** Every rung
on this axis is a deterministic lookup against bounded registry data, and the
country rung is no exception: a code the catalogues do not carry is an unmatched
token that fires nothing. It was once a shape check instead, so any well-formed
pair resolved to the registry-projected third-country token -- and third country
on the issued side is export treatment, zero-rated, so ``XX``, ``ZZ`` and ``QQ``
exempted an operation silently from a value with no referent. The ISO
user-assigned ranges denote nothing by construction; shape-validity is not
reference. An unmatched token is reported rather than dropped, through
:func:`stated_country_code_status`, which separates the operator's typo from
this codebase's own catalogue gap.
**Absence is modelled as ``None``, never as an enum member.** A missing scope is
not a kind of scope, and adding an ``UNKNOWN`` member would put a value into a
closed set that every consumer's rule table would then have to special-case. The
optional type says the same thing where the type checker can enforce it.
**A wrong establishment is worse than an absent one, and it is worse in a
specific direction.** Resolving an unrecognised or unstated country to the
domestic scope silently converts an intra-community or reverse-charge operation
into a domestic one -- a value that is plausible at every boundary it crosses and
wrong only against the counterparty's own declaration. That is the
restrictive-default shape this codebase has been bitten by before, so the rule
here is strict: this module NEVER returns a Spanish scope.
**Why "ES" resolves to ``None`` rather than to the mainland.** This is the
substantive limit, not a caution. ``ES`` names the Member State, and Spain
contains three different IVA territories: the peninsula and Balearics inside the
territorio de aplicación del impuesto, the Canary Islands under IGIC, and Ceuta
and Melilla under IPSI -- the latter two outside LIVA entirely (Ley 37/1992 art.
3.Dos). A country code cannot distinguish them, so resolving ``ES`` to
the registry-projected mainland token would place every Canarian and Ceutan
party inside a territory their operations are not subject to, which is exactly
the silent domestic capture above. Discriminating them needs sub-national printed
evidence -- the address province or postal code -- which this module does not
receive and does not guess at.
So the honest reading of a Spanish prefix is "Spain, territory undetermined", and
the honest return for it is ``None``.
**Why a country NAME needs its own rung.** A code is printed only where a
NIF-IVA is printed. Everywhere else the country appears in an address block as a
name, in the document's own language -- "Alemania", "Deutschland", "Germany".
Asking a reading stage for the code would be asking it to translate, and
translation is inference. So the name is transcribed verbatim and matched
against a bounded vocabulary held as registry data, which is deterministic
lookup; the code that yields is then handed to the same country resolver, so
nothing about what a country ESTABLISHES is decided twice.
**Why a STATED code needs one too, and why it is not just a wider length check.**
A machine-readable invoice does not print a name; it states a code, and the
syntaxes disagree about which code system. UBL states the alpha-2 form, Facturae
the alpha-3 form -- ``ESP`` where everything here is keyed ``ES``. Handed
straight to :func:`territorial_scope_for_country` that fails the shape test and
returns ``None``, which is indistinguishable from a document stating no country
at all: the evidence is present and parsed and establishes nothing. So the
correspondence is a column in the same registry vocabulary the names are matched
against, and :func:`country_code_for_stated_country_code` is the lookup against
it -- a bounded table, never a rule turning three letters into two.
See Also:
:class:`IvaTerritorialScope`
The closed target this resolves into.
:class:`EUMemberState`
The Member State catalogue the EU branch is derived from.
"""
from __future__ import annotations
from collections.abc import Mapping
from enum import StrEnum
from typing import TYPE_CHECKING, Final, NamedTuple, TypeGuard
from ...core.identity.nif_iva import normalise_nif_iva
from ...core.parsing.codes import normalise_iso_3166_alpha2_jurisdiction
from ..calculations.registry.eu_member_state_catalogue import resolve_eu_member_state_catalogue
from ..calculations.registry.nif_iva_catalogue import resolve_nif_iva_catalogue
from . import country_vocabulary as _country_vocabulary
from .classification import (
IvaTerritorialScope,
require_iva_territorial_scope,
territorial_scope_alias,
)
if TYPE_CHECKING:
from ..calculations.registry.authority import PinnedAuthorityOperation
__all__ = [
"SPAIN_COUNTRY_CODE",
"StatedCountryCodeStatus",
"country_code_for_printed_country_name",
"country_code_for_printed_tax_identifier",
"country_code_for_stated_country_code",
"record_country_code_status",
"stated_country_code_status",
"territorial_scope_for_country",
"territorial_scope_for_spanish_postal_code",
]
_ALPHA2_LENGTH: Final[int] = 2
_ALPHA3_LENGTH: Final[int] = 3
_ASCII_UPPERCASE: Final[str] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
SPAIN_COUNTRY_CODE: Final[str] = "ES"
"""The one code this module deliberately refuses to resolve.
Named rather than inlined because two things depend on agreeing about it: the
resolver's refusal, and the gate that proves the refusal holds. A literal spelled
twice is the drift this codebase keeps closing.
"""
def _is_object_mapping(value: object) -> TypeGuard[Mapping[object, object]]:
"""Narrow one runtime component to an object-keyed mapping before validation."""
return isinstance(value, Mapping)
def _eu_member_codes(*, operation: PinnedAuthorityOperation) -> frozenset[str]:
"""Return non-Spanish EU codes projected from fact 0131."""
return frozenset(
str(member).upper()
for member in resolve_eu_member_state_catalogue(authority=operation).all_states
if str(member).upper() != SPAIN_COUNTRY_CODE
)
_USER_ASSIGNED_ALPHA2: Final[frozenset[str]] = frozenset(
{"AA", "ZZ"}
| {f"Q{letter}" for letter in "MNOPQRSTUVWXYZ"}
| {f"X{letter}" for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"},
)
"""The alpha-2 pairs ISO 3166-1 reserves for private use: ``AA``, ``QM``-``QZ``,
``XA``-``XZ`` and ``ZZ``.
Written as ranges rather than a list because that is what the standard reserves,
and a hand-expanded copy would be a second statement of one rule. These pairs
denote no country BY CONSTRUCTION -- not "no country yet", the way an unallocated
code does, but "reserved so that no country will ever be allocated here". That is
what makes them the sharp end of the axis: a document stating one has stated a
string, and the honest report is a typo rather than a data gap.
``XI`` falls inside the reserved ``XA``-``XZ`` block and is nevertheless a member
of :class:`EUMemberState` for goods under the Protocol, which is exactly why
membership is asked BEFORE this set is consulted. A reader that checked the
ranges first would drop Northern Ireland out of the intra-community branch as a
placeholder.
"""
_USER_ASSIGNED_ALPHA3: Final[frozenset[str]] = frozenset(
{f"AA{letter}" for letter in _ASCII_UPPERCASE}
| {f"Q{first}{second}" for first in "MNOPQRSTUVWXYZ" for second in _ASCII_UPPERCASE}
| {f"X{first}{second}" for first in _ASCII_UPPERCASE for second in _ASCII_UPPERCASE}
| {f"ZZ{letter}" for letter in _ASCII_UPPERCASE},
)
"""The alpha-3 codes ISO 3166-1 reserves for private use: ``AAA``-``AAZ``,
``QMA``-``QZZ``, ``XAA``-``XZZ`` and ``ZZA``-``ZZZ``.
The alpha-3 counterpart of :data:`_USER_ASSIGNED_ALPHA2`, and it exists because
the record axis admits both spellings while only one of them was being judged.
``ZZ`` reported as the placeholder it is and ``ZZZ`` -- the same placeholder,
one letter longer -- reported as a jurisdiction our vocabulary might simply be
missing, which is the OPPOSITE operator instruction: go and enrol it. Enrolling
a reserved code in the country vocabulary would be ungrounded registry data.
That asymmetry lands on the format it can least afford. Facturae states the
country in alpha-3 and is the Spanish national format, so it is the spelling most
of this corpus arrives in; the alpha-2 half of the axis was gated and this half
was not.
Written as ranges for the same reason its sibling is: that is what the standard
reserves, and a hand-expanded list would be a second statement of one rule.
"""
[docs]
class StatedCountryCodeStatus(StrEnum):
"""What a well-formed alpha-2 code IS, once the closed vocabulary is asked.
The axis exists because the two ways a code can fail to name a country are
different problems with different owners, and collapsing them would leave an
operator unable to tell which they are looking at. It says nothing about
what a country ESTABLISHES -- :func:`territorial_scope_for_country` stays
the single authority on that -- and nothing about codes that are not
well-formed alpha-2 pairs at all, which get no status.
"""
CATALOGUED = "catalogued"
"""A bounded catalogue in this codebase names the code.
The one status a rung can fire from. Spain is catalogued and still
establishes no scope, which is a different refusal entirely: ``ES`` names
the Member State while the IVA territory inside it stays undetermined.
"""
UNASSIGNED = "unassigned"
"""The code is in an ISO user-assigned range, so it denotes nothing.
The operator's typo signal. ``XX``, ``ZZ`` and ``QQ`` are the forms a
placeholder, a truncated field or a slipped keystroke actually takes, and
no catalogue anywhere will ever resolve them.
"""
UNCATALOGUED = "uncatalogued"
"""The code may name a real jurisdiction the bundled vocabulary omits.
A data gap somebody can close by adding the country, rather than a document
defect. Reported as the weaker claim it is: the ranges above are the only
codes this codebase can say denote nothing, so anything outside them that
the vocabulary does not carry is reported as unknown to US rather than
asserted to be meaningless. It fires no rung until the catalogue carries it.
"""
class _CarveOut(NamedTuple):
"""One territory whose treatment does not follow from its code alone.
Exactly one of the three fields carries the disposition, which the loader
enforces: a row that named two would be a territory the law treats two ways.
Attributes:
assimilated_to: The alpha-2 code this territory is treated AS, or
``None``. A pointer rather than a scope, because LIVA art. 3.Tres
fixes what a territory is assimilated to and never what that parent
establishes -- the Isle of Man followed the United Kingdom out of the
Community without the article changing a word.
scope: The scope the territory establishes directly, or ``None``. Used
for the exclusions, where art. 3.Dos.1 puts the territory outside the
interior del país and art. 3.Dos.3 then makes anything outside it a
third territory.
establishes_nothing: Whether the territory is recognised and its scope
deliberately left to other evidence. The Spanish case: the code is
known, and which of the three Spanish territories a party sits in is
settled by the postal rung that owns the sub-national evidence.
"""
assimilated_to: str | None
scope: IvaTerritorialScope | None
establishes_nothing: bool
def _territory_carve_outs(
*,
operation: PinnedAuthorityOperation,
) -> dict[str, _CarveOut]:
"""Return every territory whose IVA treatment its country code does not give.
Read from the published runtime authority, where the compiler has already
verified the legal grounding and disposition invariants.
Returns:
Each alpha-2 code mapped to its disposition.
Raises:
IvaCatalogueError: When a published scope cannot be adapted into the
operational closed set.
"""
from ..calculations.registry.runtime_catalogues import TerritoryCarveOut
loaded = operation.runtime_catalogue("territory_carve_outs")
if not _is_object_mapping(loaded):
from .errors import IvaCatalogueError
raise IvaCatalogueError("indexed authority territory component has an invalid shape")
records: list[tuple[str, TerritoryCarveOut]] = []
for code, record in loaded.items():
if not isinstance(code, str) or not isinstance(record, TerritoryCarveOut):
from .errors import IvaCatalogueError
raise IvaCatalogueError("indexed authority territory component has an invalid shape")
records.append((code, record))
return {
code: _CarveOut(
assimilated_to=record.assimilated_to,
scope=require_iva_territorial_scope(record.scope, operation=operation)
if record.scope is not None
else None,
establishes_nothing=record.establishes_nothing,
)
for code, record in records
}
def _catalogued_country_codes(
*,
operation: PinnedAuthorityOperation,
) -> frozenset[str]:
"""Return every alpha-2 code a bounded catalogue in this codebase names.
The union of the catalogues that already exist, and deliberately not a new
one: the printed-name vocabulary carries the countries a document's address
block names, :class:`EUMemberState` carries the Member States the
intra-community branch turns on, and the carve-out table carries the
territories LIVA art. 3 treats differently from the country they sit in or
beside. No two of them nest -- Northern Ireland is a Member State for goods
with no address-block name, and Monaco is a carve-out that is neither -- so
asking any one alone would silently drop a real jurisdiction out of the axis.
Spain is added explicitly rather than left to fall out of either, because
the resolver's refusal for ``ES`` must stay a deliberate territorial
refusal and never become an unmatched-token one; the two are reported to the
operator as different things.
Returns:
The catalogued codes.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read or breaks
its one-name-one-country invariant.
"""
return (
frozenset(_country_vocabulary.country_codes_by_printed_name(operation=operation).values())
| _eu_member_codes(operation=operation)
| {SPAIN_COUNTRY_CODE}
| frozenset(_territory_carve_outs(operation=operation))
)
[docs]
def stated_country_code_status(
stated_code: str | None,
*,
operation: PinnedAuthorityOperation,
) -> StatedCountryCodeStatus | None:
"""Return what a stated alpha-2 code is, or ``None`` when it is not one.
The diagnostic half of the country axis. :func:`territorial_scope_for_country`
answers "what does this establish" and returns ``None`` for every code
outside the vocabulary; this answers "and why", so an unmatched token reaches
the operator as a string they can correct rather than as a blank.
Args:
stated_code: The code as printed or as a structured record states it, or
``None``. Surrounding whitespace and letter case are normalised, the
same way every other reader of this axis normalises them.
operation: The pinned authority operation supplying the country vocabulary.
Returns:
The status, or ``None`` when nothing that could be an alpha-2 code was
stated. A status is a statement ABOUT a code, so an absent field, a
blank one and an address line that landed in the country slot get none:
reporting those as unassigned countries would spend the operator's
attention naming a string nobody claimed was a country.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read.
"""
if stated_code is None:
return None
candidate = stated_code.strip().upper()
if len(candidate) != _ALPHA2_LENGTH or not candidate.isalpha():
return None
if candidate in _catalogued_country_codes(operation=operation):
return StatedCountryCodeStatus.CATALOGUED
if candidate in _USER_ASSIGNED_ALPHA2:
return StatedCountryCodeStatus.UNASSIGNED
return StatedCountryCodeStatus.UNCATALOGUED
def _normalise_country_code_for_scope(country_code: str | None) -> str | None:
"""Return a well-formed, catalogue-ready country code or ``None``."""
if country_code is None:
return None
candidate = country_code.strip().upper()
if len(candidate) != _ALPHA2_LENGTH or not candidate.isalpha():
return None
return normalise_iso_3166_alpha2_jurisdiction(candidate)
def _scope_for_carve_out(
carve_out: _CarveOut,
*,
operation: PinnedAuthorityOperation,
) -> IvaTerritorialScope | None:
"""Resolve one carve-out's direct scope, refusal, or assimilation parent."""
if carve_out.establishes_nothing:
return None
if carve_out.scope is not None:
return carve_out.scope
# Assimilation is resolved by RE-ASKING the country resolver for the parent
# rather than reading a scope off the row. The article fixes what a territory
# is treated as and never what that parent establishes, so following the
# pointer keeps the answer true as the parent's own status changes.
return territorial_scope_for_country(carve_out.assimilated_to, operation=operation)
def _scope_for_catalogued_country(
country_code: str,
*,
operation: PinnedAuthorityOperation,
) -> IvaTerritorialScope | None:
"""Return the scope for a known code, applying statutory carve-outs first."""
carve_out = _territory_carve_outs(operation=operation).get(country_code)
if carve_out is not None:
return _scope_for_carve_out(carve_out, operation=operation)
if country_code == SPAIN_COUNTRY_CODE:
return None
if country_code in _eu_member_codes(operation=operation):
return territorial_scope_alias("eu_member", operation=operation)
return territorial_scope_alias("third_country", operation=operation)
[docs]
def territorial_scope_for_country(
country_code: str | None,
*,
operation: PinnedAuthorityOperation,
) -> IvaTerritorialScope | None:
"""Return the territorial scope a country code establishes, via the closed vocabulary.
**The rung is a match against a bounded vocabulary, never a shape check.** A
code this codebase's catalogues do not carry is an UNMATCHED TOKEN: it fires
nothing, and the caller walks on or exhausts to an unknown scope. That is
the substantive rule of this function, and it is stated here rather than at
any one caller because every caller inherits it -- a resolver that answered
on shape would hand the same wrong establishment to each of them.
**Why shape is not enough, in the direction that costs money.** Any
well-formed pair once resolved to the registry-projected third-country
token, so ``XX``, ``ZZ`` and ``QQ``
settled a party outside the EU -- and on the issued side third country is
export treatment, zero-rated. An issuer typo, a placeholder or a truncated
field therefore exempted an operation silently, from evidence the ladder
treated as decisive. The ISO user-assigned ranges denote nothing by
construction, so deriving a tax treatment from one derives it from a value
with no referent: shape-validity is not reference, which is the same lesson
a checksum-valid tax identifier of the wrong entity teaches one axis over.
**Decisiveness does not depend on the outcome.** A code outside the
vocabulary fires no rung whichever way the resulting treatment would fall.
Making evidence count only when its consequence is unfavourable would invert
the fact-to-derivation direction -- the caller would need the tax
consequence before deciding whether the evidence exists -- and would leave a
wrong third-country answer standing wherever the outcome happened to be the
over-payment one, which nothing else in this codebase watches.
Args:
country_code: An ISO 3166-1 alpha-2 code as printed or as a structured
record states it -- a NIF-IVA prefix or an address country.
Surrounding whitespace and letter case are normalised here, because
a document prints what it prints; anything that is not then a
well-formed alpha-2 code is treated as ABSENT rather than coerced or
refused.
Absent rather than refused is the deliberate half. The core
jurisdiction validator raises on malformed input, which is right for
a configured value an operator supplied and wrong for a transcription
of whatever a supplier chose to print: unreadable evidence is a
normal outcome of reading, not an error condition. The shape is
therefore checked before that validator is consulted, so the
validator stays the single authority on what a well-formed code is
without an exception being used for ordinary control flow.
operation: The pinned authority operation supplying the country vocabulary.
Returns:
the registry-projected EU-member token for a Member State other than
Spain, the registry-projected third-country token for a CATALOGUED
country outside that catalogue, or ``None`` when the code is absent,
malformed, Spanish, or names no country the vocabulary carries.
``None`` is returned for Spain BY DESIGN, not as a gap: the code names
the State while the IVA territory inside it stays undetermined, and the
three Spanish territories are treated differently by law. A caller
needing the Spanish scope must resolve it from sub-national evidence.
``None`` for an unmatched token is not silence either. A caller that
must tell an operator WHY nothing resolved asks
:func:`stated_country_code_status`, which separates a code that denotes
nothing from one this codebase simply does not carry yet.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read or breaks
its one-name-one-country invariant. Propagated rather than softened:
a corrupt bundled table is a defect, not an unestablished party.
"""
normalised = _normalise_country_code_for_scope(country_code)
if normalised is None or normalised not in _catalogued_country_codes(operation=operation):
return None
return _scope_for_catalogued_country(normalised, operation=operation)
[docs]
def country_code_for_printed_tax_identifier(
printed_identifier: str | None,
*,
operation: PinnedAuthorityOperation,
) -> str | None:
"""Return the alpha-2 code a printed tax IDENTIFIER names, or ``None``.
The first rung of the establishment ladder. An intra-community IVA number
leads with its Member State's IVA prefix, so a document printing
``DE811234567`` states a country in the one place a domestic invoice's
address block often does not.
**The prefix is matched against the closed IVA prefix vocabulary, and the
body against that State's published VIES structure.** The prefix alone is
not evidence: two leading letters occur in plenty of strings a reader might
put in an identifier field, and ``FRANCISCO`` would otherwise place a party
in France. Requiring the whole number to match the shape its own prefix
claims makes the rung answer only where a real IVA number was printed.
**A Spanish identifier contributes nothing here, by design.** ``ES`` is
absent from the prefix vocabulary because Spanish identifiers are checksum
identifiers rather than structural ones -- and, more deeply, because
registration is not establishment: the non-resident ``N`` leader, the
``L``/``M`` identifiers and the ``X``/``Y``/``Z`` series all belong to
parties registered in Spain and established elsewhere, and establishment for
IVA is the sede de actividad económica (Ley 37/1992 arts. 69-70). So a
Spanish-prefixed number is handed back to the ladder, where the printed
address country -- a statement about WHERE the party is, not about where it
is registered -- is what may name Spain and open the postal rung.
Args:
printed_identifier: The identifier as transcribed, or ``None``. Spacing
and separator punctuation are normalised away, because an issuer
prints ``BE 0123.456.789`` as readily as ``BE0123456789``.
operation: The pinned authority operation supplying the IVA prefix vocabulary.
Returns:
The ISO 3166-1 alpha-2 code -- ``GR`` for a Greek ``EL`` number, since
the IVA prefix and the ISO code diverge there and every catalogue
downstream is ISO-keyed -- or ``None`` when no IVA number was printed,
the prefix names no Member State, or the body does not match the shape
its prefix claims.
"""
if printed_identifier is None:
return None
normalised = normalise_nif_iva(printed_identifier)
if len(normalised) < _ALPHA2_LENGTH:
return None
candidate = normalised[:_ALPHA2_LENGTH]
catalogue = resolve_nif_iva_catalogue(authority=operation)
prefix = catalogue.prefix_for_country(candidate)
if prefix is None:
return None
spec = catalogue.format_for_country(prefix)
if spec is None or not spec.pattern.match(normalised):
return None
return catalogue.iso_country_for_prefix(prefix)
_POSTAL_PREFIX_LENGTH: Final[int] = 2
_POSTAL_CODE_LENGTH: Final[int] = 5
def _excluded_territories_by_prefix(
*,
operation: PinnedAuthorityOperation,
) -> dict[str, IvaTerritorialScope]:
"""Return the postal prefixes that name a territory OUTSIDE the TAI.
Read from the published runtime authority because a territorial boundary is
regulated data rather than an implementation default.
Only the EXCLUDED territories are enumerated, so a prefix absent from the
table is inside the TAI. Enumerating the rest would be a second copy of the
Spanish province list, maintained for no purpose.
Returns:
Each excluded prefix mapped to its scope.
Raises:
IvaCatalogueError: When a published scope cannot be adapted into the
operational closed set.
"""
from ..calculations.registry.runtime_catalogues import SpanishPostalTerritory
loaded = operation.runtime_catalogue("spanish_postal_territories")
if not _is_object_mapping(loaded):
from .errors import IvaCatalogueError
raise IvaCatalogueError("indexed authority postal-territory component has an invalid shape")
records: list[tuple[str, SpanishPostalTerritory]] = []
for prefix, record in loaded.items():
if not isinstance(prefix, str) or not isinstance(record, SpanishPostalTerritory):
from .errors import IvaCatalogueError
raise IvaCatalogueError("indexed authority postal-territory component has an invalid shape")
records.append((prefix, record))
return {prefix: require_iva_territorial_scope(record.scope, operation=operation) for prefix, record in records}
[docs]
def territorial_scope_for_spanish_postal_code(
postal_code: str | None,
*,
operation: PinnedAuthorityOperation,
) -> IvaTerritorialScope | None:
"""Return the Spanish IVA territory a printed postal code establishes.
The sub-national half of the establishment question, and the reason the
country half returns nothing for Spain. The first two digits of a Spanish
postal code are the province code, so this is a lookup against the excluded
territories rather than an inference about them.
Args:
postal_code: The postal code as printed. Surrounding whitespace is
normalised; anything that is not five digits is treated as absent
rather than coerced, because a document prints what it prints and
unreadable evidence is a normal outcome of reading.
operation: The pinned authority operation supplying the territorial registry.
Returns:
the registry-projected Canarias or Ceuta/Melilla token for a prefix the
registry excludes, and the registry-projected mainland token for a well-formed code
outside those, or ``None`` when no code was readable.
``None`` rather than the mainland for an absent or malformed code, and
that asymmetry is the whole safety property. The peninsula is the
majority population, so a default would be invisible in testing and
would silently place Canarian and Ceutan parties inside a territory
their operations are not subject to -- the restrictive-provision-as-
default failure, one level below the country axis that already refuses
it.
Raises:
IvaCatalogueError: When the bundled territory registry cannot be read.
"""
if postal_code is None:
return None
candidate = postal_code.strip()
if len(candidate) != _POSTAL_CODE_LENGTH or not candidate.isdigit():
return None
excluded = _excluded_territories_by_prefix(operation=operation)
return excluded.get(
candidate[:_POSTAL_PREFIX_LENGTH],
territorial_scope_alias("mainland", operation=operation),
)
[docs]
def country_code_for_stated_country_code(
stated_code: str | None,
*,
operation: PinnedAuthorityOperation,
) -> str | None:
"""Return the alpha-2 code a STRUCTURED record's country element states.
The structured counterpart of :func:`country_code_for_printed_country_name`,
and it exists for the mirror-image reason. A printed document states a
country as a name; a machine-readable one states it as a code -- and the
syntaxes disagree about which code system. UBL states the alpha-2 form
(EN16931 BT-40 / BT-55); Facturae states the alpha-3 form, ``ESP`` where
every country surface in this codebase is keyed ``ES``.
**The failure this closes is silent, which is why it is a function rather
than a widened length check.** An alpha-3 code handed to
:func:`territorial_scope_for_country` fails its shape test and returns
``None``, indistinguishable from a document that stated no country -- so a
Facturae invoice whose ``CountryCode`` element is present, read and parsed
establishes nothing, and the postal rung gated on country evidence naming
Spain stays shut for the entire Spanish national format.
**The alpha-3 leg is a lookup against registry data, never a transformation.**
The correspondence is a column in the bundled country vocabulary beside the
alpha-2 code it names, so a code outside that bounded table yields ``None``
exactly as an unlisted printed name does. There is no rule turning three
letters into two.
Args:
stated_code: The country code as the record states it, in either code
system, or ``None``. Surrounding whitespace and letter case are
normalised, because the value is machine-produced but the case
convention is the issuer's.
operation: The pinned authority operation supplying the country vocabulary.
Returns:
The upper-case ISO 3166-1 alpha-2 code, or ``None`` when the element is
absent, malformed, or states a code in either system that the closed
vocabulary does not carry.
**Both legs are equally bounded**, and that symmetry is the point rather
than an implementation detail. A stated alpha-2 code is matched against
the same closed vocabulary a printed one is, so a machine-readable
document establishes exactly what the identical string printed in an
address block establishes -- neither more nor less. A looser alpha-2 leg
would let a UBL ``CountryCode`` of ``XX`` settle a party outside the EU
while the printed form settled nothing, and the structured reader is
precisely the surface that puts machine-stated strings in front of the
rung.
``None`` NEVER degrades to a country and above all never to Spain, for
the reason every other rung refuses to: the peninsula is the majority
population, so a domestic default would be invisible in testing while
placing foreign parties inside the territorio de aplicación del impuesto.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read or breaks
its one-code-one-country invariant. Propagated rather than softened:
a corrupt bundled table is a defect, not an unestablished party.
"""
if stated_code is None:
return None
candidate = stated_code.strip().upper()
if not candidate.isalpha():
return None
if len(candidate) == _ALPHA2_LENGTH:
# Already the target system, so this leg normalises and then asks the
# same closed vocabulary the printed legs ask. The core jurisdiction
# normaliser is a SHAPE authority rather than a membership one, so it
# cannot be the last word here: on its own it passes `XX` through, and a
# code with no referent then settles a territory downstream. Membership
# is asked separately so the two questions stay separable and neither
# authority is asked for the other's answer.
normalised = normalise_iso_3166_alpha2_jurisdiction(candidate)
if normalised is None or normalised not in _catalogued_country_codes(operation=operation):
return None
return normalised
if len(candidate) == _ALPHA3_LENGTH:
return _country_vocabulary.country_codes_by_alpha3(operation=operation).get(candidate)
return None
[docs]
def record_country_code_status(
stated_code: str | None,
*,
operation: PinnedAuthorityOperation,
) -> StatedCountryCodeStatus | None:
"""Return what a country code a RECORD states is, in either ISO spelling.
The structured sibling of :func:`stated_country_code_status`, and here for
the reason that one exists at all: an unmatched token has to reach the caller
as a string it can act on rather than as a blank. What differs is which
spellings are admitted. That function is handed values read off a printed
page, where the country slot may hold anything the reader found, so it
answers only about alpha-2 and declines everything else -- correctly, because
calling an address line a bad country code would spend an operator's
attention naming a string nobody claimed was a country.
A machine-readable record's country ELEMENT is not that. The schema has
already said the token is a country code, and the formats disagree only about
which spelling they state it in: EN16931 states alpha-2 (BT-40 / BT-55) and
Facturae -- the Spanish national format, so the majority of this corpus --
states alpha-3. So this admits both, exactly as
:func:`country_code_for_stated_country_code` already does for the resolution
question, and the two stay in step by construction because this asks that
function first.
Four answers, in the order the evidence narrows:
* a token the bundled correspondence places -- ``ES``, ``ESP``, ``DEU`` --
is ``CATALOGUED``;
* an alpha-2 token keeps whatever :func:`stated_country_code_status` calls
it, so the ISO user-assigned pairs stay the issuer's error and a real
jurisdiction the vocabulary omits stays this codebase's own gap;
* an alpha-3 token in the ISO user-assigned ranges is ``UNASSIGNED``, on
exactly the terms its alpha-2 sibling is. Judged from
:data:`_USER_ASSIGNED_ALPHA3` rather than from length, because the
catch-all below would otherwise report ``ZZZ`` as a gap in our data while
``ZZ`` -- the same placeholder -- reports as the issuer's;
* any other three-letter alphabetic token is ``UNCATALOGUED``. This is the
answer the alpha-2 authority structurally cannot give: a three-letter
string is outside its domain entirely.
**The two failure kinds must stay apart, because they are opposite
instructions.** A consumer sparing a declared relief on a catalogue gap and
refusing it on an ISO-unassigned code depends on this distinction:
collapsing them either rejects a legitimate export over a row nobody has
written, or honours a relief claimed on a code with no referent. The same
split decides whether the operator is sent to re-read the document or to
enrol a country -- and enrolling a reserved code would be ungrounded
registry data.
**Any three-letter token in a country element is read as a country claim**,
including one that is plainly something else -- ``EUR`` from a currency
field, ``TOT`` from a totals row. That is deliberate rather than an
oversight: the schema said this element holds a country code, so a token
that is not one is a document defect worth naming, and the alternative is
the silence this function exists to remove. It is the one place this axis is
less cautious than its printed-value sibling, and it can afford to be
because the element is typed.
Args:
stated_code: The token the record's country element carries, or ``None``.
Surrounding whitespace and letter case are normalised, the way every
reader of this axis normalises them.
operation: The pinned authority operation supplying the country vocabulary.
Returns:
The status, or ``None`` when the element stated nothing -- absent or
blank -- or stated something of no country-code shape at all. Absence is
not a kind of failure.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read.
"""
if stated_code is None:
return None
candidate = stated_code.strip().upper()
if not candidate:
return None
if country_code_for_stated_country_code(candidate, operation=operation) is not None:
return StatedCountryCodeStatus.CATALOGUED
alpha2_status = stated_country_code_status(candidate, operation=operation)
if alpha2_status is not None:
return alpha2_status
if candidate in _USER_ASSIGNED_ALPHA3:
# Asked BEFORE the catch-all below, or the reserved ranges fall through
# it and are reported as a gap in OUR vocabulary. That is the opposite
# instruction: it sends the operator to enrol a code ISO reserved so
# that no country will ever be allocated to it.
return StatedCountryCodeStatus.UNASSIGNED
if len(candidate) == _ALPHA3_LENGTH and candidate.isalpha():
return StatedCountryCodeStatus.UNCATALOGUED
return None
[docs]
def country_code_for_printed_country_name(
printed_name: str | None,
*,
operation: PinnedAuthorityOperation,
) -> str | None:
"""Return the alpha-2 code a printed country NAME establishes, or ``None``.
The second rung of the establishment ladder, and the reason it exists: a
country prints as a name in the document's own language -- "Alemania",
"Deutschland", "Germany" -- so asking a reader for ``DE`` would be a
translation, and translation is inference. Transcribing the name verbatim and
matching it against this closed vocabulary is a deterministic lookup instead,
the same shape as matching a transcribed regime mención against the closed
statutory vocabulary.
Matching is EXACT after normalisation, not containment. A country name is a
field value rather than a phrase inside prose, and containment over country
names is actively wrong: ``"Niger"`` is contained in ``"Nigeria"`` and
``"Guinea"`` in ``"Papua New Guinea"``, so a containment match would place a
party in the wrong tax territory from a correctly printed name. A near miss
is not a match here.
Args:
printed_name: The country name transcribed from the document, or
``None``. Case, surrounding and repeated whitespace, and combining
accents are normalised away; nothing else is.
operation: The pinned authority operation supplying the country vocabulary.
Returns:
The upper-case ISO 3166-1 alpha-2 code, or ``None`` when the name is
absent, blank, or not in the vocabulary.
``None`` NEVER degrades to a country and above all never to Spain. The
peninsula is the majority population, so a domestic default would be
invisible in testing while silently placing foreign parties inside the
territorio de aplicación del impuesto. An unrecognised name is handed
back to the ladder, which exhausts to an unknown scope and asks the
operator.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read.
"""
if printed_name is None:
return None
candidate = _country_vocabulary.normalise_printed_country_name(printed_name)
if not candidate:
return None
return _country_vocabulary.country_codes_by_printed_name(operation=operation).get(candidate)