pain001.twins package#

Twins: lossless representations of the same pain.001 message (ADR-0005).

A twin carries everything the XML carries and comes back as the same XML, element for element. The canonical twin is the ISO 20022 Registration Authority’s JSON convention of 2025 (pain001.twins.iso_json); the records twin (pain001.twins.records) is the library’s own flat input rows with the list of what they cannot carry. Faces, which project a message for a compatible standard with a loss report, are a later layer.

Submodules#

pain001.twins.iso_json module#

The ISO JSON twin of a pain.001 document.

The shape follows the ISO 20022 Registration Authority’s Generation of JSON Schema Draft 2020-12 for ISO 20022:2013 (v2.0, June 2025):

  • keys are the XML tag names, unchanged, under a Document root;

  • an amount with a currency attribute is {"amt": "…", "Ccy": "…"};

  • decimals, dates and times are strings exactly as the XML spells them;

  • booleans are the strings "true" and "false";

  • namespaces and xsi hints are not represented (the edition names the schema);

  • an empty element, which the XSD allows and the RA forbids, is {} so the twin stays lossless; the schema reports it.

One rule is this project’s, not the RA’s: every element the schema declares repeatable is always an array, even with one occurrence, so a twin has one shape. The decoder accepts the RA’s other form (a bare value) as well.

Decoding goes through the XSD, so a twin that does not fit the edition raises TwinError rather than producing bad XML.

pain001.twins.iso_json.SUPPORTED_PREFIX = 'pain.001.'#

The message family the twin layer covers (ADR-0005).

exception pain001.twins.iso_json.TwinError[source]#

Bases: ValueError

The twin or the edition is not usable.

pain001.twins.iso_json.from_iso_json(doc: dict[str, Any], version: str) → str[source]#

The pain.001 document of an ISO JSON twin.

Parameters:
  • doc – {"Document": {...}} in the RA convention; repeatable elements may be arrays or bare values, in any key order.

  • version – The edition to encode against.

Returns:

The XML text, declared and indented, valid against the edition.

Raises:

TwinError – If the twin has no Document root, names an element the edition does not declare, or breaks a facet.

pain001.twins.iso_json.supported(version: str) → bool[source]#

True when version is a bundled pain.001 edition.

pain001.twins.iso_json.to_iso_json(xml: str, version: str) → dict[str, Any][source]#

The ISO JSON twin of a pain.001 document.

Parameters:
  • xml – The document text.

  • version – Its edition, e.g. pain.001.001.09.

Returns:

{"Document": {...}} in the RA convention with always-array normalisation.

Raises:

TwinError – If the edition is not a bundled pain.001 edition or the document does not validate against it.

pain001.twins.records module#

The records twin: a pain.001 document as the library’s flat input rows.

The CSV pipeline renders a bundled template from flat records whose columns the input JSON Schema names (debtor_name, creditor_account_IBAN …). The records twin reads those columns back out of a document, one row per transaction, so any corpus file can be regenerated through the pipeline, and it says exactly which element paths the pipeline cannot carry: the gap.

Which column lands on which element is not written anywhere; it is derived once per edition by rendering the template with sentinel values and reading where each sentinel ends up. That keeps the mapping true to the template the pipeline actually uses.

class pain001.twins.records.RecordsTwin(rows: list[dict[str, str]], gap: list[str] = <factory>, missing_required: list[str] = <factory>)[source]#

Bases: object

The rows, what they cannot carry, and what the input schema wants.

Variables:
  • rows (list[dict[str, str]]) – One flat record per transaction of the first payment block.

  • gap (list[str]) – Element paths present in the document that no column carries, relative to Document and without occurrence indexes.

  • missing_required (list[str]) – Columns the edition’s preparer requires that the document gave no value for; with any of these the pipeline cannot regenerate the file.

gap: list[str]#
missing_required: list[str]#
rows: list[dict[str, str]]#
pain001.twins.records.column_paths(version: str) → dict[str, tuple[str, ...]][source]#

Which element paths each input column renders to, per edition.

Derived by rendering the template with a sentinel per column and reading the sentinels back. Columns the template never renders map to nothing, and that is the input side of the gap.

Parameters:

version – A bundled pain.001 edition.

Returns:

Column to paths (relative to Document, no indexes). A column can land on more than one element.

pain001.twins.records.input_columns(version: str) → tuple[list[str], list[str]][source]#

The pipeline’s columns and the input schema’s required columns.

The universe is the input schema’s properties plus the header of the bundled template.csv: the older preparers require address and reference columns the schema does not list, and the sample carries them.

pain001.twins.records.preparer_required(version: str) → tuple[str, ...][source]#

The columns the edition’s preparer refuses to render without.

Learned by asking the preparer to render an empty row and reading the names it lists, so the records twin judges against the gate the pipeline really applies rather than the input schema’s list. A group of alternatives (an IBAN or an account number) is one entry, its names joined by |.

pain001.twins.records.to_records(xml: str, version: str) → RecordsTwin[source]#

The records twin of a pain.001 document.

Parameters:
  • xml – The document.

  • version – Its edition.

Returns:

rows, gap and missing required columns.

Return type:

The RecordsTwin

pain001.twins.schema module#

JSON Schema 2020-12 for the ISO JSON twin of a pain.001 edition.

Generated from the schema inventory by the rules of the ISO 20022 Registration Authority’s Generation of JSON Schema Draft 2020-12 for ISO 20022:2013 (v2.0, June 2025):

  • the root requires Document, which requires the message block;

  • a component is an object with additionalProperties: false, required for its mandatory children and minProperties: 1 when it has none;

  • a choice is an object with exactly one property;

  • a repeatable element is anyOf a single item and a bounded array, so the RA’s bare-value form and this project’s always-array twin both validate;

  • an amount with a currency attribute is {"amt", "Ccy"};

  • every simple value is a string: enumerations become enum, text lengths minLength/maxLength, XSD patterns are anchored, decimals get a pattern built from their total and fraction digits and their lower bound, dates and times get the RA’s patterns, booleans allow true, false, 1 and 0;

  • the supplementary-data envelope is open.

Definitions are keyed by ISO type name under $defs (the RA uses $anchor; the content is the same). No tool generates this from an XSD; the inventory already carries every facet the rules consume.

pain001.twins.schema.SCHEMA_DIR = PosixPath('/home/runner/work/pain001/pain001/pain001/schemas/iso-json')#

Where the committed schemas live.

pain001.twins.schema.decimal_pattern(facets: dict[str, Any]) → str[source]#

The RA-style pattern for a decimal from its facets.

At most total_digits digits in all, at most fraction_digits of them after the point; leading and trailing zeros count, as the RA document says; a sign is allowed only when no lower bound of zero forbids it; 5. and .5 are accepted like the RA’s algebra.

Parameters:

facets – total_digits, fraction_digits and optional min_inclusive.

Returns:

An anchored ECMA regular expression.

pain001.twins.schema.generate_schema(version: str) → dict[str, Any][source]#

Generate the twin schema of an edition from its inventory.

Parameters:

version – A bundled pain.001 edition.

Returns:

The JSON Schema document; a non-pain.001 edition is refused with TwinError by the shared check.

pain001.twins.schema.iso_json_schema(version: str) → dict[str, Any][source]#

The committed twin schema of an edition, or the generated one.

Parameters:

version – A bundled pain.001 edition.

Returns:

The JSON Schema document.

pain001.twins.schema.schema_text(version: str) → str[source]#

The generated schema as the text the repository commits.

pain001.twins.schema.validate_iso_json(doc: Any, version: str) → list[str][source]#

Validate a twin against its edition’s schema.

Parameters:
  • doc – The twin.

  • version – The edition.

Returns:

Findings as <json path>: <message>, deepest first within each failure; empty when valid.