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
Nonewhen 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
*.jsonindata_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
codeis inset_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.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
patchjudges 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
Nonefor 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:
objectOne 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:
objectOne 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.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.requiredasks for at least one match; every other verb is checked on each match.- Verbs
required,forbidden,equals:<v>,one_of:[a,b,c](orone_of:a|b|c),max_length:<n>,matches:<regex>,charset:<name>(iso20022,ascii,latin1) andif:<elem>=<v>:<verb...>, whose tail is any verb above; the originalif:<elem>=<v>:equals:<v2>reads the same, andif:<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:
objectOne violated rule at one element.
- Variables:
rule_id (str) – The rule.
code (str) – Its error code.
severity (str) –
errororwarning.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:
objectA 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;
Noneskips 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:
patchplus itspatchesentry.
- patches: dict[str, dict[str, Any]]#
- path: Path | None = None#
- source: dict[str, Any]#
- title: str#
- versions: tuple[str, ...] = ()#
- exception pain001.corpus.rules.overlays.OverlayError[source]#
Bases:
ValueErrorAn 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:
objectOne 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) –
errororwarning.
- 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.
- 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
Overlayfrom 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
Rulefrom a mapping, checking the assertion.- Parameters:
raw – The mapping (bank-profile
custom_rulesentries 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.