pain001.faces package#
Faces: project pain.001 messages into compatible standards with loss reports (ADR-0005).
A face projects a pain.001 payment message into an external industry standard
or rail convention: UK Open Banking v4.0.1 domestic payments, Berlin Group
NextGenPSD2 v1.3.16 payment initiation, or ISO 20022 element names in
snake_case. Every occurrence of every source element is either carried into the
payload or listed in an explicit LossReport; a face never invents a
value the target requires, it refuses the document with a FaceError.
Submodules#
pain001.faces.berlin_group module#
Berlin Group NextGenPSD2 payment initiation face (ADR-0005).
Target: the JSON request bodies of the NextGenPSD2 XS2A Framework
v1.3.16 (2025-11-27; OpenAPI file under CC BY 4.0): a document with one
transaction becomes paymentInitiation_json, one with several becomes
bulkPaymentInitiation_json with paymentInitiationBulkElement_json
items. Only properties those schemas define are produced; the rest of
the source (instruction identification, purpose, charge bearer, a
single payment’s execution date, …) is left to the loss report. The
payment product in the request path is the caller’s choice and is not
part of the body.
- pain001.faces.berlin_group.project_berlin_group(twin: dict[str, Any], bindings: dict[str, Any] | None = None) tuple[dict[str, Any], set[str]][source]#
Project a pain.001 twin into a NextGenPSD2 payment initiation body.
- Parameters:
twin – The ISO JSON twin of the document.
bindings – Unused: this face takes no side inputs (the engine refuses any).
- Returns:
The request body and the set of source leaf paths it carries.
A document with no transaction or more than one payment information block, or one lacking an element the target requires, is refused with a
FaceError.
pain001.faces.cli module#
pain001 face: project a pain.001 file into a face (ADR-0005).
pain001.faces.common module#
Shared navigation for the payment faces (UK Open Banking, Berlin Group).
A face never invents a value: an element the target requires but the
source lacks is refused with a FaceError that names it.
- pain001.faces.common.account_id(reader: Reader, node: dict[str, Any], path: str, element: str) tuple[str, str] | None[source]#
("iban", value)or("bban", value)for an account element.A BBAN is read only from
Id/Othrwhose scheme code isBBAN; any otherOthridentification has no counterpart and is left to the loss report.- Parameters:
reader – The reader recording mapped leaves.
node – The node holding the account element.
path – The indexed path of
node.element – The account element tag (
DbtrAcctorCdtrAcct).
- Returns:
The identification kind and value, or
None.
- pain001.faces.common.address(reader: Reader, party: dict[str, Any], path: str, keys: dict[str, str]) dict[str, Any][source]#
Map a party’s
PstlAdrto target keys, recording what is used.- Parameters:
reader – The reader recording mapped leaves.
party – The party node holding
PstlAdr.path – The indexed path of
party.keys – Target key for each ISO element name in
ADDRESS_TAGSthe target supports, plus"AddressLine"when it supports address lines.
- Returns:
The target address object, empty when nothing maps.
- pain001.faces.common.agent_bic(reader: Reader, node: dict[str, Any], path: str, element: str) str | None[source]#
The BIC of an agent element (
BICFI, orBICbefore .04).
- pain001.faces.common.payment_information(twin: dict[str, Any], face: str) tuple[dict[str, Any], str][source]#
The document’s single
PmtInfblock and its path.- Parameters:
twin – The ISO JSON twin.
face – The face name, for the error message.
- Returns:
The payment information node and its indexed path.
- Raises:
FaceError – When the document has no payment information block, or more than one (both faces take one debtor account and one execution date per request).
- pain001.faces.common.required(value: Any, face: str, what: str) Any[source]#
Return
value, or refuse when the target needs it and it is absent.- Parameters:
value – The projected value,
Nonewhen the source lacks it.face – The face name, for the error message.
what – The source element, as a path or a short description.
- Returns:
valueunchanged.- Raises:
FaceError – When
valueisNone.
pain001.faces.engine module#
Engine for face projections from pain.001 to external specifications (ADR-0005).
The engine builds the ISO JSON twin, runs the face’s projector and writes the loss report from indexed leaf paths: every occurrence of a repeated element is its own entry, so a dropped transaction can never pass for a mapped one. Faces are outbound only.
- pain001.faces.engine.MAX_INLINE_BYTES = 10485760#
Largest document
project_face_text()accepts, in UTF-8 bytes.The same 10 MiB the REST dashboard accepts for an upload.
- pain001.faces.engine.available_faces() list[str][source]#
Return a sorted list of registered face names.
- pain001.faces.engine.detect_version(xml: str) str[source]#
Detect the pain.001 edition from XML namespace declarations.
- Parameters:
xml – The XML text of the document.
- Returns:
The detected version string, e.g.
pain.001.001.09.- Raises:
FaceError – If no supported pain.001 namespace is detected.
- pain001.faces.engine.list_supported_faces() list[dict[str, Any]][source]#
Return each face with its description and accepted binding keys.
- pain001.faces.engine.project_face(source: str | Path, face: str = 'uk_open_banking', *, version: str | None = None, bindings: dict[str, Any] | None = None) FaceResult[source]#
Project a pain.001 document into an external industry standard face.
- Parameters:
source – XML string or Path to an XML file.
face – The name of the target standard (default:
uk_open_banking).version – Optional explicit pain.001 edition (detected if omitted).
bindings – Optional private side inputs the face accepts (for example
ConsentIdfor UK Open Banking). Keep them out of version control: they are never part of the source message.
- Returns:
A
FaceResultwith payload and explicitLossReport.- Raises:
FaceError – If the face is unknown, a binding is not accepted, the twin cannot be built or the face refuses the document.
- pain001.faces.engine.project_face_text(xml: str, face: str = 'uk_open_banking', *, version: str | None = None, bindings: dict[str, Any] | None = None) FaceResult[source]#
Project a document a client sent as text, never read from a path.
project_face()reads a string that names an existing file, a convenience for the library and the command line. A surface that takes the document from someone else (the REST API, the MCP server, the language server) calls this instead: the text must be XML, at mostMAX_INLINE_BYTES, with no entity declarations or external references, and the bindings must be a JSON object.- Parameters:
xml – The pain.001 document as XML text.
face – The name of the target standard.
version – Optional explicit pain.001 edition (detected if omitted).
bindings – Optional private side inputs the face accepts.
- Returns:
A
FaceResult, asproject_face()returns it.- Raises:
FaceError – If the text is not XML, is too large, declares entities or external references, the bindings are not an object, or
project_face()refuses the document.
- pain001.faces.engine.register_face(name: str, func: Callable[[dict[str, Any], dict[str, Any] | None], tuple[dict[str, Any], set[str]]], description: str, bindings: tuple[str, ...] = ()) None[source]#
Register a new projection face.
- Parameters:
name – Unique name for the face.
func – Projection function returning
(payload, mapped_paths), where the paths are indexed leaf paths relative toDocument(seepain001.faces.paths.leaf_paths()).description – Description of the target standard.
bindings – Binding keys the face accepts; any other is refused.
pain001.faces.iso_2018 module#
ISO 2018 compatibility face: full element names in snake_case (ADR-0005).
Each XML tag becomes its ISO 20022 message element name, read from
iso_names.json, a table derived from the Registration Authority’s
Message Definition Reports (Part 2) with the reports and the read date
recorded (see scripts/build_iso_name_table.py), then written in
snake_case: GrpHdr -> group_header. The message content is
projected without the Document root, repeatable elements stay
arrays, and an amount becomes {"amount", "currency"}. A tag the
table lists as unresolved has no name to give, so that element and
everything under it stay out of the payload and appear in the loss
report.
- pain001.faces.iso_2018.name_table() dict[str, Any][source]#
The shipped tag-to-name table with its provenance.
- pain001.faces.iso_2018.project_iso_2018(twin: dict[str, Any], bindings: dict[str, Any] | None = None) tuple[dict[str, Any], set[str]][source]#
Project a pain.001 twin into ISO element names in snake_case.
- Parameters:
twin – The ISO JSON twin of the document.
bindings – Unused: this face takes no side inputs (the engine refuses any).
- Returns:
The projected message content and the set of source leaf paths it carries.
- Raises:
FaceError – When the twin holds no pain.001 message.
pain001.faces.models module#
Data models and results for face projections (ADR-0005).
- exception pain001.faces.models.FaceError[source]#
Bases:
ValueErrorRaised when a face projection fails or an unsupported face is requested.
- class pain001.faces.models.FaceResult(face: str, version: str, payload: dict[str, Any], loss_report: LossReport)[source]#
Bases:
objectThe result of projecting a pain.001 document into a target face.
- Variables:
face (str) – The name of the target standard (e.g.
uk_open_banking).version (str) – The pain.001 source edition (e.g.
pain.001.001.09).payload (dict[str, Any]) – The JSON-serializable dictionary conforming to the target standard.
loss_report (pain001.faces.models.LossReport) – Details of which elements were mapped vs omitted.
- face: str#
- loss_report: LossReport#
- payload: dict[str, Any]#
- to_dict() dict[str, Any][source]#
Return a complete dictionary containing payload and loss metadata.
- version: str#
- class pain001.faces.models.LossReport(face: str, total_source_elements: int, mapped_paths: tuple[str, ...], unmapped_paths: tuple[str, ...])[source]#
Bases:
objectDetailed report of mapped vs unmapped element paths in a projection.
- Variables:
face (str) – The name of the projection standard.
total_source_elements (int) – Total number of leaf element paths in the source.
mapped_paths (tuple[str, ...]) – Element paths whose values were carried into the projection.
unmapped_paths (tuple[str, ...]) – Element paths present in the source that have no counterpart.
- face: str#
- property is_lossless: bool#
True when every source element path was carried into the target projection.
- property loss_ratio: float#
Proportion of source elements not mapped into the target projection (0.0 to 1.0).
- mapped_paths: tuple[str, ...]#
- total_source_elements: int#
- unmapped_paths: tuple[str, ...]#
pain001.faces.paths module#
Indexed leaf paths of an ISO JSON twin, and a reader that records them.
A loss report is only honest if every occurrence of a repeated element
has its own path: PmtInf[0]/CdtTrfTxInf[1]/Amt/InstdAmt is a
different leaf from PmtInf[0]/CdtTrfTxInf[0]/Amt/InstdAmt. Paths are
relative to Document and carry an index on every array item. An
amount object ({"amt", "Ccy"}) is one leaf plus its /@Ccy.
- class pain001.faces.paths.Reader[source]#
Bases:
objectRead values from a twin and record each leaf path that was used.
- amount(node: Any, path: str) tuple[str, str] | None[source]#
The
(amount, currency)of an amount object, recorded.
- mark() frozenset[str][source]#
A snapshot of the recorded leaves, for
rollback().
- rollback(mark: frozenset[str]) None[source]#
Forget leaves recorded since
mark: their value was dropped.
- text(node: Any, path: str, *keys: str) str | None[source]#
The text leaf at
keysbelownode, recorded, orNone.- Parameters:
node – The twin node to start from.
path – The indexed path of
node.*keys – Element tags to descend through; each must be a single (non-repeatable) element.
- Returns:
The leaf’s text, or
Nonewhen any step is missing or the target is not a text leaf.
- pain001.faces.paths.is_amount(node: Any) bool[source]#
True when
nodeis a twin amount object{"amt", "Ccy"}.
pain001.faces.uk_open_banking module#
UK Open Banking domestic payment face (ADR-0005).
Target: the request body of POST /domestic-payment-consents,
OBWriteDomesticConsent4, in the Open Banking Read/Write API v4.0.1
(Open Banking Limited, MIT Open Licence). When the private bindings
supply a ConsentId the same initiation becomes the
OBWriteDomestic2 body of POST /domestic-payments.
A domestic payment carries one payment, so the face takes a document
with exactly one transaction. Scheme names come from the Open Banking
internal code set: UK.OBIE.IBAN for accounts, UK.OBIE.BICFI for
the creditor agent. Elements without a counterpart (local instrument,
purpose, charge bearer, execution date, regulatory reporting, …) are
left to the loss report rather than translated by guesswork.
- pain001.faces.uk_open_banking.project_uk_open_banking(twin: dict[str, Any], bindings: dict[str, Any] | None = None) tuple[dict[str, Any], set[str]][source]#
Project a one-transaction pain.001 twin into a UK Open Banking body.
- Parameters:
twin – The ISO JSON twin of the document.
bindings – Private side inputs:
ConsentId(makes the body anOBWriteDomestic2) andRisk(theOBRisk1object, which has no pain.001 source).
- Returns:
The request body and the set of source leaf paths it carries.
- Raises:
FaceError – When the document has more than one transaction, or lacks an element the target requires.