Skip to main content

ISO 20022 structured-address remediation and payment-rejection prevention for the November 2026 cliff.

Project description

structured-address-fix: ISO 20022 Structured-Address Remediation

PyPI Version Python Versions License Tests Quality OpenSSF Scorecard Documentation

A vendor-neutral core library that detects, scores, and remediates non-compliant postal addresses in ISO 20022 payment messages — ahead of the 14 November 2026 cliff, when fully unstructured addresses are rejected across SWIFT CBPR+, HVPS+, T2 RTGS, CHAPS, Fedwire, and Lynx. Point it at a pacs.008 or pain.001 document, get back an explainable list of findings, and turn an AdrLine-only address into a structured one with reversible, auditable patches.

The cliff, in one line. From 14 November 2026 an ISO 20022 postal address that is AdrLine-only (unstructured) is non-compliant and liable to be rejected by the major cross-border and high-value schemes. A hybrid address (town + country plus at most two residual AdrLine lines) is the minimum bar; structured is the target. This library exists to move addresses across that line before it bites.

Contents

Overview

structured-address-fix is the shared core of a small ISO 20022 address toolchain. It reads the postal addresses carried by the parties of a payment message (debtor, creditor, agents, ultimate parties), classifies each one as structured, hybrid, or unstructured per CBPR+ UG2026, scores the compliance risk against a chosen policy, and — where it can — proposes a remediation that promotes the address toward the structured target.

Two properties are non-negotiable:

The public surface is the structured_address_fix.services facade; the domain models are re-exported from the package root for convenience.

The ISO 20022 Suite

structured-address-fix is the postal-address remediation core of the sebastienrousseau ISO 20022 suite. It supersedes and generalises the bespoke pacs008.standards.address module, and it is the shared engine that a thin Model Context Protocol server wraps for AI agents — so the classification and remediation logic lives in exactly one place and every surface behaves identically.

Package Role
structured-address-fix The core library (this package): domain model, policies, adapters, services facade, plugin SDK
structured-address-fix-mcp Thin Model Context Protocol server wrapping the services facade for AI agents
pacs008 FI-to-FI credit-transfer library; delegates its address linting + Nov-2026 readiness here
flowchart LR
    A["MCP / REST / LSP wrapper"] -->|delegates to| B["structured_address_fix.services"]
    B -->|classify + score + remediate| C["ISO 20022 pacs.008 / pain.001 XML"]

Install

structured-address-fix runs on macOS, Linux, and Windows and requires Python 3.12+ and pip. Its only runtime dependencies are pydantic, defusedxml, and xmlschema.

python -m pip install structured-address-fix
Using an isolated virtual environment (recommended)
python -m venv venv
source venv/bin/activate        # macOS/Linux
venv\Scripts\activate           # Windows
python -m pip install -U structured-address-fix

Quick Start

For the guided tour, see docs/quickstart.md.

Classify an address

The domain model is re-exported from the package root and works standalone. CanonicalAddress models the ISO 20022 PostalAddress27 type and classifies itself per CBPR+ UG2026:

from structured_address_fix import CanonicalAddress

# Unstructured: AdrLine only, no town / country -> rejected from the cliff.
unstructured = CanonicalAddress(
    address_lines=("221B Baker Street", "London NW1 6XE", "United Kingdom"),
)
print(unstructured.classification)   # AddressClassification.UNSTRUCTURED

# Structured: town + country + structured detail, no residual AdrLine.
structured = CanonicalAddress(
    building_number="221B",
    street_name="Baker Street",
    post_code="NW1 6XE",
    town_name="London",
    country="GB",
)
print(structured.classification)     # AddressClassification.STRUCTURED

# Serialise back to ISO 20022 element names for the wire.
structured.model_dump(by_alias=True, exclude_none=True)
# {'BldgNb': '221B', 'StrtNm': 'Baker Street', 'PstCd': 'NW1 6XE',
#  'TwnNm': 'London', 'Ctry': 'GB', ...}

Assess and remediate a message

The top-level entry point is the structured_address_fix.services facade: assess returns a read-only ValidationReport; remediate adds the proposed fixes (and, optionally, the patched XML):

from structured_address_fix.services import assess, remediate

pacs008_xml = ...  # an ISO 20022 pacs.008 document, as text

# 1. Score every party's address against a policy.
report = assess(pacs008_xml, policy="cbpr-2026")
print(report.is_compliant)                       # False
for finding in report.findings:
    print(finding.code, finding.party_role, finding.message)
# SAF001 Dbtr  Address is AdrLine-only; rejected from 2026-11-14

# 2. Propose reversible, explainable patches for what can be fixed.
result = remediate(pacs008_xml, policy="cbpr-2026")
for suggestion in result.suggestions:
    print(suggestion.before.classification, "->",
          suggestion.after.classification)       # unstructured -> hybrid
    for op in suggestion.operations:
        print(op.op, op.path, op.reason_code, op.confidence)
    # Anything no heuristic could resolve is reported, never hidden:
    for residual in suggestion.residual_findings:
        print("residual:", residual.code)

The built-in policies, XML adapters, per-country heuristics, and the assess / remediate facade land in the v0.0.x series; see ROADMAP.md. The domain model and error taxonomy shipped in v0.0.2 and are stable.

Architecture

The library is layered so that each concern is testable in isolation and the public surface stays small. Dependencies point downward only.

Layer Module Responsibility
Domain structured_address_fix.domain Pure Pydantic v2 entities and their invariants, with zero I/O: CanonicalAddress, AddressedParty, RiskFinding, PatchOperation, the result envelopes, and the wire-facing enums.
Policies structured_address_fix.policies Rulebooks that inspect a CanonicalAddress and raise RiskFindings: the four built-ins (see below), each citing a rulebook clause.
Adapters structured_address_fix.adapters defusedxml parsing of pacs.008 / pain.001, party discovery, and the per-country AdrLine → structured heuristics that populate the canonical model with recorded confidence + source tokens.
Services structured_address_fix.services The assess / remediate facade — the single public entry point wrappers (MCP / REST / LSP) call.
Plugins structured_address_fix.plugins The premium rule-pack SDK: entry-point discovery of third-party PolicyPacks, gated by entitlement + integrity checks.

Policies

A policy is a rulebook: it names the finding codes that apply and the severity each carries. Four are built in and open-source:

Policy id Scope Use it when
cbpr-2026 SWIFT CBPR+ UG2026 cross-border requirements — the Nov-2026 cliff rules. The default. You send or receive cross-border SWIFT payments
sepa SEPA credit-transfer address expectations You operate in the euro retail-payment area
hvps-plus HVPS+ high-value / RTGS market-practice (T2, CHAPS, Fedwire, Lynx) You settle over a high-value system
generic-structured Scheme-agnostic structured-vs-unstructured hygiene You want the baseline structural checks with no scheme assumptions

The default policy is cbpr-2026, overridable per call or via the SAF_DEFAULT_POLICY environment variable. Finding codes (SAF001SAF008) are a stable public API: a code's meaning is fixed once released. See docs/policies.md for the full rule-to-code mapping and docs/error-taxonomy.md for the exception codes.

Premium rule packs

The four built-in policies cover the open, published scheme rules. Some requirements — proprietary market-practice profiles, correspondent-specific overlays, jurisdiction-specific address validation — are better shipped as premium rule packs: separately installable Python distributions that register additional policies through an entry point.

A pack is discovered automatically once installed, contributes its own policy ids and finding codes (with their own prefixes, so they never collide with the core SAF codes), and is gated at load time by the entitlement + integrity checks in the error taxonomy (PackNotLicensedError, PackIntegrityError). The core never bundles a pack; you opt in by installing one.

from structured_address_fix.services import assess

# A premium pack's policy id, resolved through the plugin registry.
report = assess(message_xml, policy="acme-correspondent-2026")

Writing a rule pack

Publishing a rule pack means shipping a PolicyPack and advertising it under the structured_address_fix.policies entry-point group:

# pyproject.toml of your pack distribution
[project.entry-points."structured_address_fix.policies"]
acme-correspondent-2026 = "acme_saf_pack:PACK"
# acme_saf_pack/__init__.py
from structured_address_fix.domain import (
    FindingCode, RiskFinding, Severity, CanonicalAddress,
)

def check(address: CanonicalAddress, *, location: str) -> list[RiskFinding]:
    """Raise the pack's findings against a single address."""
    findings: list[RiskFinding] = []
    if address.post_code is None:
        findings.append(RiskFinding(
            code=FindingCode.MISSING_TOWN,  # or a pack-specific code
            severity=Severity.ERROR,
            message="Correspondent profile requires a post code.",
            policy_id="acme-correspondent-2026",
            location=location,
        ))
    return findings

PACK = ...  # a PolicyPack binding the id "acme-correspondent-2026" to check()

The full contract — the PolicyPack shape, custom finding-code prefixes, entitlement, and integrity signing — is in docs/writing-a-rule-pack.md.

When not to use structured-address-fix

  • Your addresses are already fully structured. If every message you send already carries TwnNm + Ctry + structured detail and no AdrLine, there is nothing to remediate; a one-off assess to confirm compliance is all you need.
  • You need to generate whole payment messages. This library remediates the postal-address portion of an existing message; it does not build a pacs.008 or pain.001 from scratch. Use the message-family libraries for that (e.g. pacs008, pain001).
  • You want an agent tool, not a library import. Use the thin structured-address-fix-mcp server, which wraps this facade for MCP clients.
  • You need authoritative address verification against a postal database. The AdrLine → structured heuristics are deterministic, explainable best-effort splits with recorded confidence; they do not consult a national address file. Low-confidence or unresolvable fragments are reported as residual findings for a human to resolve, never silently guessed.
  • You need a schema validator. For pure XSD validation of a whole document, use xmlschema directly; this library focuses on address compliance, not full-message schema conformance.

Development

structured-address-fix uses Poetry and mise.

git clone https://github.com/sebastienrousseau/structured-address-fix.git && cd structured-address-fix
mise install
poetry install
poetry shell

A Makefile orchestrates the quality gates (kept in lockstep with CI):

make check        # lint + type-check + test (REQUIRED before commit)
make test         # pytest at 100% line + branch coverage
make lint         # ruff + black --check
make type-check   # mypy --strict
make security     # bandit
make examples     # run each examples/*.py
make pip-compile  # regenerate hash-pinned requirements/*.txt from *.in

Security

Untrusted ISO 20022 XML is parsed with defusedxml, never the standard-library parser, so XXE / billion-laughs / external-DTD attacks are structurally prevented. The canonical model is frozen and extra="forbid", and every error is a typed StructuredAddressError with a stable code and safe context, so a wrapping transport can serialise {"error": ...} payloads rather than leaking tracebacks. Premium rule packs are opt-in and gated by entitlement + integrity checks. Reporting practice, supported versions, the NIST SSDF practice mapping, and the accepted OpenSSF Scorecard findings are documented in SECURITY.md. Vulnerabilities go via GitHub Private Vulnerability Reporting, not public issues.

Documentation

License

Licensed under the Apache License, Version 2.0. Any contribution submitted for inclusion shall be licensed as above, without additional terms.

Contributing

Contributions are welcome — see the contributing instructions. Thanks to all contributors.

Acknowledgements

Built on Pydantic, defusedxml, and xmlschema, and grounded in the ISO 20022 CBPR+ / HVPS+ structured-address requirements.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

structured_address_fix-0.0.2.tar.gz (50.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

structured_address_fix-0.0.2-py3-none-any.whl (79.5 kB view details)

Uploaded Python 3

File details

Details for the file structured_address_fix-0.0.2.tar.gz.

File metadata

  • Download URL: structured_address_fix-0.0.2.tar.gz
  • Upload date:
  • Size: 50.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for structured_address_fix-0.0.2.tar.gz
Algorithm Hash digest
SHA256 fe3258f99fa33358f36c8d43e287cede9be26fc8b232a26788da1b632512a922
MD5 ac39e58150aea107a3179c4d9f07b163
BLAKE2b-256 a51f64ab7e894e87b32e438e041bda99ab4e86f0637f59a3937b2b7556346a1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for structured_address_fix-0.0.2.tar.gz:

Publisher: release.yml on sebastienrousseau/structured-address-fix

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file structured_address_fix-0.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for structured_address_fix-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 f6eb8ca5dccfe20f65630e882f51e4461690931d9f16740e0619bc27fbb23370
MD5 cd579b7d556198345c319582db559f47
BLAKE2b-256 b985e84850a1f1f4a24719fa66eff839bdd83da60752f373d3481eafa3ea6aad

See more details on using hashes here.

Provenance

The following attestation bundles were made for structured_address_fix-0.0.2-py3-none-any.whl:

Publisher: release.yml on sebastienrousseau/structured-address-fix

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page