pain001.corpus.rules package#

Rule sources the corpus builder and validators draw on.

Submodules#

pain001.corpus.rules.external_codes module#

The ISO 20022 external code sets, vendored.

ISO publishes the external code sets quarterly at iso20022.org, no login needed, as XLSX, XSD and a JSON Schema document whose definitions hold one enum per code set. That JSON is vendored verbatim under pain001/corpus/data/external_codes/<edition>.json; the edition is the file’s stem. Codes are version-independent: the same list serves pain.001.001.03 and .13 alike.

scripts/refresh_external_codes.py swaps in a newer edition and refuses when a code the bundled data uses has been withdrawn.

A few sets (the bank transaction domain, family and sub-family codes, card transaction categories) are declared without an enum because ISO publishes their codes in a separate structured list; they are listed by sets_without_codes() and codes() raises for them.

pain001.corpus.rules.external_codes.code_sets() → tuple[str, ...][source]#

Every set name the edition declares, sorted.

pain001.corpus.rules.external_codes.codes(set_name: str) → tuple[str, ...][source]#

The codes of one set, in publication order.

Parameters:

set_name – An ISO set name such as ExternalPurpose1Code.

Returns:

The codes.

Raises:

KeyError – If the set is unknown or carries no codes.

pain001.corpus.rules.external_codes.describe(set_name: str, code: str) → str | None[source]#

The published one-line meaning of a code, if the edition has one.

Parameters:
  • set_name – An ISO set name.

  • code – A code in that set.

Returns:

The description text, or None when the edition gives none.

pain001.corpus.rules.external_codes.edition() → str[source]#

The vendored edition’s name, e.g. 2Q2026_v3.

pain001.corpus.rules.external_codes.edition_file(data_dir: Path = PosixPath('/home/runner/work/pain001/pain001/pain001/corpus/data/external_codes')) → Path[source]#

The vendored edition, the one *.json in data_dir.

Parameters:

data_dir – Directory holding the vendored JSON.

Returns:

The path of the edition file.

Raises:

FileNotFoundError – If there is not exactly one edition file.

pain001.corpus.rules.external_codes.is_valid(set_name: str, code: str) → bool[source]#

True when code is in set_name.

Parameters:
  • set_name – An ISO set name.

  • code – The candidate code, exact case.

Returns:

Whether the code is published in that set.

pain001.corpus.rules.external_codes.load_definitions(path: Path) → dict[str, dict[str, Any]][source]#

The definitions block of any edition file.

Parameters:

path – An external code set JSON Schema document.

Returns:

Its definitions.

pain001.corpus.rules.external_codes.sets_without_codes() → tuple[str, ...][source]#

Sets declared without an enum, sorted.

pain001.corpus.rules.ladder module#

The validation ladder for one built corpus file (ADR-0003, L0 to L3).

  • L0 the edition’s XSD (the builder already refuses a failure);

  • L1 the MDR cross-element rules;

  • L2 the scheme and rail profiles the scenario lists, run on the file’s row projection;

  • L3 the overlays that apply to the scenario or its family.

run_ladder() returns a JSON-ready record the build writes into the file’s provenance sidecar and the gate re-checks; ladder_passes() says whether any rung produced an error-severity finding.

pain001.corpus.rules.ladder.applicable_overlays(scenario: Scenario, pool: list[Overlay], variant: str | None = None, version: str | None = None) → list[Overlay][source]#

The overlays that judge one file.

Rule-only overlays that target the scenario or its family judge every file of the scenario. An overlay with a patch judges only the variant built with that patch.

Parameters:
  • scenario – The scenario.

  • pool – Every loaded overlay.

  • variant – The overlay id the file was built with, or None for the generic file.

  • version – The edition of the file; overlays that do not cover it are left out.

Returns:

The overlays to evaluate, in pool order.

pain001.corpus.rules.ladder.ladder_passes(record: dict[str, Any]) → bool[source]#

True when no rung of a run_ladder() record has an error.

pain001.corpus.rules.ladder.run_ladder(scenario: Scenario, version: str, xml: str, overlays: list[Overlay] | None = None, variant: str | None = None) → dict[str, Any][source]#

Run L0 to L3 on one built file.

Parameters:
  • scenario – The scenario the file was built from.

  • version – The edition.

  • xml – The built document.

  • overlays – The overlay pool; defaults to scenarios/overlays.

  • variant – The overlay id the file was built with, if any.

Returns:

{"xsd": ..., "mdr": ..., "profiles": {...}, "overlays": {...}} with error and warning counts and the first messages of each.

pain001.corpus.rules.mdr module#

The ISO 20022 MDR cross-element rules for pain.001 and pain.008.

The Message Definition Report (Payments Initiation, maintenance 2020-2021, part 2) names the constraints an XSD cannot express: a block allowed at the payment-information level or the transaction level but not both, the cheque path’s dependencies, intermediary agents in order, charges accounts with their agent. This module carries every constraint the report defines for the two initiation messages, quoted from the report, and checks a document against them.

The guidance-level SupplementaryDataRule is listed for completeness but not enforced, since the report itself phrases it as “should”.

class pain001.corpus.rules.mdr.MdrFinding(rule_id: str, path: str, message: str)[source]#

Bases: object

One violated constraint at one place.

Variables:
  • rule_id (str) – The MDR constraint name.

  • path (str) – The element path the finding sits on.

  • message (str) – What went wrong, in the report’s terms.

message: str#
path: str#
rule_id: str#
class pain001.corpus.rules.mdr.MdrRule(rule_id: str, messages: tuple[str, ...], definition: str, enforced: bool = True)[source]#

Bases: object

One MDR constraint.

Variables:
  • rule_id (str) – The constraint name in the report.

  • messages (tuple[str, ...]) – The message families it applies to.

  • definition (str) – The report’s wording.

  • enforced (bool) – False for guidance the report phrases as “should”.

definition: str#
enforced: bool = True#
messages: tuple[str, ...]#
rule_id: str#
pain001.corpus.rules.mdr.evaluate_mdr(xml: str) → list[MdrFinding][source]#

Check a document against every enforced MDR constraint.

Parameters:

xml – A pain.001 or pain.008 document.

Returns:

The findings, in rule then document order; empty when the document satisfies every constraint.

Raises:

ValueError – If the document is neither message family.

pain001.corpus.rules.mdr.rules_for(message: str) → tuple[MdrRule, ...][source]#

The enforced rules of a message family (pain.001 or pain.008).

pain001.corpus.rules.overlays module#

Bank and scheme overlays: the shared assertion grammar.

An overlay is a small rule set a bank or scheme layers on top of the XSD: “the charge bearer must be SLEV for EUR”, “the town is required”, “no proprietary service level”. iso20022-bank-profile-mcp and the readiness suite each carried an evaluator for the same three verbs; this module is the one both can import, reads their files unchanged, and adds the verbs the corpus needs.

Rule shape (a bank-profile custom_rules entry is exactly this):

rule_id: sepa-eur-slev
description: For EUR payments the charge bearer must be SLEV.
locator: ChrgBr
assertion: if:Ccy=EUR:equals:SLEV
error_code: SEPA_CHRGBR_NOT_SLEV      # optional
severity: error                       # optional, default error
Locators

A local name (ChrgBr) matches every element with that name; a slash path (PmtInf/ChrgBr, Cdtr/PstlAdr/TwnNm) matches every element whose path ends with it. required asks for at least one match; every other verb is checked on each match.

Verbs

required, forbidden, equals:<v>, one_of:[a,b,c] (or one_of:a|b|c), max_length:<n>, matches:<regex>, charset:<name> (iso20022, ascii, latin1) and if:<elem>=<v>:<verb...>, whose tail is any verb above; the original if:<elem>=<v>:equals:<v2> reads the same, and if:<elem>:<verb...> (no =) applies the tail when the element is present at all.

Patches

An overlay may carry a patch: scenario keys deep-merged into the scenario before building, so a bank’s fixed choices (a service level code, a charge bearer) produce their own file variant beside the generic one, judged by the overlay’s rules.

class pain001.corpus.rules.overlays.Finding(rule_id: str, code: str, severity: str, locator: str, path: str, assertion: str, message: str, value: str | None = None)[source]#

Bases: object

One violated rule at one element.

Variables:
  • rule_id (str) – The rule.

  • code (str) – Its error code.

  • severity (str) – error or warning.

  • locator (str) – The rule’s locator.

  • path (str) – The element path checked, or the locator when absent.

  • assertion (str) – The rule’s assertion.

  • message (str) – The rule’s description.

  • value (str | None) – The offending text, if any.

assertion: str#
code: str#
locator: str#
message: str#
path: str#
rule_id: str#
severity: str#
value: str | None = None#
pain001.corpus.rules.overlays.OVERLAYS_DIR = PosixPath('/home/runner/work/pain001/pain001/scenarios/overlays')#

The source-of-truth directory in a checkout (absent from the wheel).

class pain001.corpus.rules.overlays.Overlay(overlay_id: str, title: str, applies_to: tuple[str, ...] = (), source: dict[str, ~typing.Any] = <factory>, rules: tuple[~pain001.corpus.rules.overlays.Rule, ...] = (), path: ~pathlib.Path | None = None, patch: dict[str, ~typing.Any] = <factory>, patches: dict[str, dict[str, ~typing.Any]] = <factory>, versions: tuple[str, ...] = ())[source]#

Bases: object

A named rule set with its citation.

Variables:
  • overlay_id (str) – Stable id, e.g. gb.boe.chaps-purpose.

  • title (str) – Human title.

  • applies_to (tuple[str, ...]) – Scenario ids or families it targets; empty means all.

  • source (dict[str, Any]) – Citation of the document it derives from.

  • rules (tuple[pain001.corpus.rules.overlays.Rule, ...]) – The rules.

  • path (pathlib.Path | None) – Where it was loaded from, if a file.

  • patch (dict[str, Any]) – Scenario keys merged in before building a variant.

  • patches (dict[str, dict[str, Any]]) – Per-scenario additions to patch, keyed by scenario id.

  • versions (tuple[str, ...]) – Message types the overlay covers; empty means every edition. A guideline written for pain.001.001.03 does not judge a .09 file, whose elements are spelled differently.

applies(scenario_id: str, family: str | None = None, version: str | None = None) → bool[source]#

True when the overlay targets the scenario or its family.

Parameters:
  • scenario_id – The scenario.

  • family – Its rail family.

  • version – The edition being judged; None skips the check.

Returns:

Whether the overlay applies.

applies_to: tuple[str, ...] = ()#
property has_patch: bool#

True when the overlay builds variants.

overlay_id: str#
patch: dict[str, Any]#
patch_for(scenario_id: str) → dict[str, Any][source]#

The patch for one scenario: patch plus its patches entry.

patches: dict[str, dict[str, Any]]#
path: Path | None = None#
rules: tuple[Rule, ...] = ()#
source: dict[str, Any]#
title: str#
versions: tuple[str, ...] = ()#
exception pain001.corpus.rules.overlays.OverlayError[source]#

Bases: ValueError

An overlay file or rule is malformed; the message says where.

class pain001.corpus.rules.overlays.Rule(rule_id: str, description: str, locator: str, assertion: str, error_code: str = '', severity: str = 'error')[source]#

Bases: object

One assertion.

Variables:
  • rule_id (str) – Stable id, unique within the overlay.

  • description (str) – What the rule means, for findings.

  • locator (str) – A local name or a slash path suffix.

  • assertion (str) – The verb and its argument.

  • error_code (str) – Machine code for findings; defaults to the id.

  • severity (str) – error or warning.

assertion: str#
property code: str#

The error code, falling back to the rule id.

description: str#
error_code: str = ''#
locator: str#
rule_id: str#
severity: str = 'error'#
pain001.corpus.rules.overlays.assertion_is_known(assertion: str) → bool[source]#

True when the assertion starts with a verb this module evaluates.

pain001.corpus.rules.overlays.evaluate(overlay: Overlay, xml: str) → list[Finding][source]#

Check an overlay against one document; see evaluate_rules().

pain001.corpus.rules.overlays.evaluate_rules(rules: Any, xml: str) → list[Finding][source]#

Check rules against one document.

Parameters:
  • rules – Rules to apply (an Overlay’s or any iterable).

  • xml – The document text.

Returns:

One Finding per violated rule and element, in rule then document order.

pain001.corpus.rules.overlays.load_overlay(path: str | Path) → Overlay[source]#

Load one YAML or JSON overlay file.

Parameters:

path – The file.

Returns:

The overlay.

Raises:

OverlayError – If the file does not parse or is malformed.

pain001.corpus.rules.overlays.load_overlays(root: str | Path = PosixPath('/home/runner/work/pain001/pain001/scenarios/overlays')) → list[Overlay][source]#

Load every overlay under root, sorted by id.

Parameters:

root – The overlays directory.

Returns:

The overlays; empty when the directory does not exist.

pain001.corpus.rules.overlays.overlay_from(document: dict[str, Any], path: Path | None = None) → Overlay[source]#

Build an Overlay from a parsed file.

Both the corpus shape (overlay_id, rules) and the bank-profile shape (profile_id, custom_rules) are accepted.

Parameters:
  • document – The parsed YAML or JSON.

  • path – Where it came from, for messages.

Returns:

The overlay.

Raises:

OverlayError – If the id is missing or a rule is malformed.

pain001.corpus.rules.overlays.parse_rule(raw: dict[str, Any], where: str = '<rule>') → Rule[source]#

Build a Rule from a mapping, checking the assertion.

Parameters:
  • raw – The mapping (bank-profile custom_rules entries qualify).

  • where – A label for error messages.

Returns:

The rule.

Raises:

OverlayError – If a field is missing or the verb is unknown.

pain001.corpus.rules.projection module#

Project a pain.001 or pain.008 document onto payment rows.

The scheme profiles in pain001.validation.schemes and the rail profiles in pain001.validation.rails judge rows: the flat, CSV-shaped records the loaders produce. A corpus file is XML. This module reads one document back into that row shape, one row per transaction, so a built file can be judged by the same profiles a CSV would be, with the payment-information level merged into each row and the transaction level winning where both carry a value.

Keys follow the CSV column names the validators already read (payment_currency, creditor_account_IBAN …) and add the ones the rail profiles need (creditor_agent_member_id, purpose_code, uetr, creditor_town …). Values are strings; absent elements are absent keys.

pain001.corpus.rules.projection.rows_from_xml(xml: str) → list[dict[str, str]][source]#

One row per transaction, payment-information values merged in.

Parameters:

xml – A pain.001 or pain.008 document.

Returns:

The rows, in document order.

Raises:

ValueError – If the document is neither message family.