pain001.corpus package#
The example corpus engine (ADR-0003).
This package grows over the 0.0.67 to 0.0.69 releases. Today it holds
the schema inventory and coverage yardstick, the scenario loader and
the version-aware builder with its identifier factories, the shipped
market files under data/market, the generated coverage sets under
data/coverage and, under rules, the MDR cross-element rules,
the overlay grammar, the XML-to-row projection, the validation ladder
and the vendored ISO external code sets. The market packs for the
sixteen countries follow in 0.0.68 and 0.0.69.
Subpackages#
- pain001.corpus.rules package
- Submodules
- pain001.corpus.rules.external_codes module
- pain001.corpus.rules.ladder module
- pain001.corpus.rules.mdr module
- pain001.corpus.rules.overlays module
- pain001.corpus.rules.projection module
Submodules#
pain001.corpus.api module#
The shipped corpus, from the installed package.
Everything under pain001/corpus/data ships in the wheel: the market
files with their provenance sidecars and the coverage sets with their
reports. These functions read them without a checkout, which is what
the suite’s other members (the MCP server, the LSP, the website build)
need.
- class pain001.corpus.api.CorpusFile(kind: str, version: str, path: Path, scenario_id: str | None = None, country: str | None = None, family: str | None = None, variant: str | None = None)[source]#
Bases:
objectOne shipped XML file.
- Variables:
kind (str) –
marketorcoverage.version (str) – The message type.
path (pathlib.Path) – Where the file is.
scenario_id (str | None) – The scenario, for market files.
country (str | None) – ISO 3166 alpha-2, for market files.
family (str | None) – The rail family, for market files.
variant (str | None) – The overlay id a bank variant was built with.
- country: str | None = None#
- family: str | None = None#
- kind: str#
- path: Path#
- scenario_id: str | None = None#
- twin(fmt: str = 'iso-json') Any[source]#
A twin of the file (ADR-0005).
- Parameters:
fmt –
iso-jsonfor the ISO JSON twin as a dict (the shipped.iso.jsonwhen there is one, else computed), orrecordsfor theRecordsTwin.- Returns:
The twin.
- Raises:
ValueError – For an unknown format.
- variant: str | None = None#
- version: str#
- pain001.corpus.api.coverage_report(version: str) dict[str, Any][source]#
The shipped coverage verdict of one edition.
- Parameters:
version – The message type.
- Returns:
sources, path and branch counts and percentages, completeness, missing and exempt lists.
- Return type:
The
coverage.jsoncontent- Raises:
FileNotFoundError – If the edition has no coverage set.
- pain001.corpus.api.get_file(scenario_id: str, version: str, variant: str | None = None) str[source]#
The text of one market file.
- Parameters:
scenario_id – The scenario, e.g.
gb.chaps.property-purchase.version – The message type, e.g.
pain.001.001.09.variant – An overlay id for a bank variant, else the generic file.
- Returns:
The XML text.
- Raises:
FileNotFoundError – If the scenario has no file for that edition.
- pain001.corpus.api.get_twin(scenario_id: str, version: str, fmt: str = 'iso-json', variant: str | None = None) Any[source]#
A twin of one market file (ADR-0005).
- Parameters:
scenario_id – The scenario.
version – The message type; twins cover pain.001 editions.
fmt –
iso-jsonorrecords.variant – An overlay id for a bank variant, else the generic file.
- Returns:
The ISO JSON twin as a dict, or the records twin.
- Raises:
FileNotFoundError – If the scenario has no file for that edition.
- pain001.corpus.api.list_files(kind: str | None = None) list[CorpusFile][source]#
Every shipped corpus file: market files, then coverage sets, each by path.
- Parameters:
kind –
market,coverageorNonefor both.- Returns:
The files.
- pain001.corpus.api.provenance(scenario_id: str, version: str, variant: str | None = None) dict[str, Any][source]#
The provenance sidecar of one market file, parsed.
- Parameters:
scenario_id – The scenario.
version – The message type.
variant – An overlay id for a bank variant, else the generic file.
- Returns:
sources, confidence, evidence, build report, validation ladder and SHA-256.
- Return type:
The sidecar
- Raises:
FileNotFoundError – If the scenario has no file for that edition.
pain001.corpus.builder module#
Scenario to XML, one edition at a time (ADR-0003, decision 6).
The builder works in four passes:
compose: the friendly scenario keys become a tree keyed by ISO element names (
GrpHdr,PmtInf…), the same tree for every edition.rawobjects are merged in verbatim, so anything the friendly layer does not name can still be expressed.conform: the tree is walked against the target edition’s
Inventory. An element the edition spells differently is renamed (BICandBICFI,BICOrBEIandAnyBIC); a scalar that meets a choice is wrapped (AdrTpbecameCd/Prtryin .09,ReqdExctnDtbecameDt/DtTm); a wrapped value that meets a plain element is unwrapped; a repeat the edition caps is cut to the cap; an element the edition does not declare is dropped. Every such move is listed in theBuildReport.order: children are sorted into the schema’s declared order, read from the inventory, and empty branches pruned.
serialise and validate: lxml writes the document with a fixed indentation and declaration, then the edition’s XSD must accept it, or
BuildErrorcarries the reasons.
Values are strings all the way through, so 425000.00 renders as
written and a rebuild is byte-identical.
- pain001.corpus.builder.ALIASES: dict[str, tuple[str, ...]] = {'AnyBIC': ('BICOrBEI',), 'BIC': ('BICFI',), 'BICFI': ('BIC',), 'BICOrBEI': ('AnyBIC',)}#
Elements that changed name between editions but not meaning.
- exception pain001.corpus.builder.BuildError[source]#
Bases:
ValueErrorThe rendered document did not validate; the message says why.
- class pain001.corpus.builder.BuildReport(version: str, renamed: list[tuple[str, str]] = <factory>, wrapped: list[str] = <factory>, unwrapped: list[str] = <factory>, dropped: list[str] = <factory>, truncated: list[str] = <factory>)[source]#
Bases:
objectWhat the conform pass had to do to fit the edition.
- Variables:
version (str) – The edition built.
renamed (list[tuple[str, str]]) –
(path, new_name)for each alias applied.wrapped (list[str]) – Paths where a scalar was wrapped into a choice child.
unwrapped (list[str]) – Paths where a wrapped value was flattened.
dropped (list[str]) – Paths the edition does not declare, left out.
truncated (list[str]) – Paths where a repeat was cut to the edition’s cap.
- dropped: list[str]#
- renamed: list[tuple[str, str]]#
- truncated: list[str]#
- unwrapped: list[str]#
- version: str#
- wrapped: list[str]#
- class pain001.corpus.builder.BuildResult(version: str, xml: str, report: BuildReport)[source]#
Bases:
objectA rendered edition and the report of how it was fitted.
- report: BuildReport#
- version: str#
- xml: str#
- pain001.corpus.builder.NUMBER_FIRST = frozenset({'AU', 'CA', 'GB', 'HK', 'IE', 'MY', 'SG', 'US'})#
Markets that write the house number before the street on a line.
- pain001.corpus.builder.WRAP_PREFERENCE: tuple[str, ...] = ('Cd', 'Dt', 'DtTm', 'Tp', 'Id')#
When a scalar meets a complex slot, the child that carries it.
- pain001.corpus.builder.build(scenario: Scenario | dict[str, Any], version: str, patch: dict[str, Any] | None = None) BuildResult[source]#
Render one scenario in one edition.
- Parameters:
scenario – A
Scenarioor a scenario document.version – A bundled message type the scenario lists.
patch – Scenario keys to merge in first, as an overlay’s
patchdoes for a bank variant.
- Returns:
The
BuildResultwith XML text and the fit report.- Raises:
BuildError – If the scenario cannot be composed, does not list the version, or the rendered document fails the XSD.
- pain001.corpus.builder.build_all(scenario: Scenario) list[BuildResult][source]#
Render a scenario in every edition it lists, in listed order.
- pain001.corpus.builder.deep_merge(base: dict[str, Any], patch: dict[str, Any]) dict[str, Any][source]#
A copy of
basewithpatchmerged in, nested dicts recursively.- Parameters:
base – The scenario document.
patch – Keys to set; a nested dict merges, anything else replaces;
{"*": {...}}against a list merges into every item;Noneremoves the key.
- Returns:
The merged copy; neither input is modified.
pain001.corpus.coverage_sets module#
Generate the schema coverage set of an edition (ADR-0003, WS3).
The coverage corpus proves what each XSD can express: every element
path and every choice branch appears in at least one file of the
edition’s set, and every file is schema-valid. Nothing hand-written
could keep that promise for 1,700 paths per edition, so the set is
derived from the Inventory:
every leaf gets a value derived from its facets: an enumeration’s first member, a verified sample for each of the twelve patterns the ISO schemas use, the minimum length of a text,
1.00for an amount, fixed dates,truefor a flag;the first file carries every element once, taking the first branch of every choice; each following file carries only the subtrees that still hold an unhit path or branch (plus whatever is mandatory around them), taking the least-covered branch of every choice, until the
coverage()report is complete;every file must pass the edition’s XSD before it is kept.
Later files are therefore small, and an edition needs a handful of files: one per recipe plus one per extra choice branch.
The MDR cross-element rules (pain001.corpus.rules.mdr) shape
the set too, so every file is a payment the report would accept:
blocks allowed on PmtInf or on the transaction but not both are
steered like choice branches; the cheque path has its own recipes
(delivered to the creditor agent, or not), transfers never carry a
cheque instruction; intermediary agents and charges accounts pull in
their prerequisites; a mandate’s amendment flag follows its details.
- pain001.corpus.coverage_sets.ANY_CONTENT = {'Data': 'x'}#
The element written into an
xs:anyslot.
- exception pain001.corpus.coverage_sets.CoverageBuildError[source]#
Bases:
ValueErrorA generated file did not validate, or a facet has no sample.
- class pain001.corpus.coverage_sets.CoverageFileInfo(name: str, recipe: str, description: str, focus: tuple[str, ...], adds_paths: int, adds_branches: int)[source]#
Bases:
objectWhat one file of a set is for, so its name means something.
- Variables:
name (str) – The file name,
NN-<recipe>-<focus>.xml.recipe (str) – The recipe the file follows.
description (str) – One sentence a reader can pick the file by.
focus (tuple[str, ...]) – The blocks (children of PmtInf, of the transaction, or of GrpHdr) the file’s new coverage mostly falls under.
adds_paths (int) – Declared paths this file is the first to use.
adds_branches (int) – Choice branches this file is the first to take.
- adds_branches: int#
- adds_paths: int#
- description: str#
- focus: tuple[str, ...]#
- name: str#
- recipe: str#
- class pain001.corpus.coverage_sets.CoverageSet(version: str, files: tuple[str, ...], report: CoverageReport, manifest: tuple[CoverageFileInfo, ...] = ())[source]#
Bases:
objectA generated set, what each file is for, and the report.
- files: tuple[str, ...]#
- manifest: tuple[CoverageFileInfo, ...] = ()#
- report: CoverageReport#
- version: str#
- pain001.corpus.coverage_sets.EXCLUSIVE: dict[str, tuple[tuple[str, str], ...]] = {'pain.001': (('PmtTpInf', 'CdtTrfTxInf/PmtTpInf'), ('ChrgBr', 'CdtTrfTxInf/ChrgBr'), ('UltmtDbtr', 'CdtTrfTxInf/UltmtDbtr'), ('InstrForDbtrAgt', 'CdtTrfTxInf/InstrForDbtrAgt')), 'pain.008': (('PmtTpInf', 'DrctDbtTxInf/PmtTpInf'), ('ChrgBr', 'DrctDbtTxInf/ChrgBr'), ('UltmtCdtr', 'DrctDbtTxInf/UltmtCdtr'), ('CdtrSchmeId', 'DrctDbtTxInf/DrctDbtTx/CdtrSchmeId'))}#
Blocks allowed on PmtInf or on the transaction, never both.
- pain001.corpus.coverage_sets.PATTERN_SAMPLES: dict[str, str] = {'[0-9]{1,15}': '1', '[0-9]{2}': '01', '[A-Z0-9]{18,18}[0-9]{2,2}': '000000K1PEBE35ILCW94', '[A-Z0-9]{18}[0-9]{2}[A-Z0-9]{0,32}': '000000K1PEBE35ILCW94', '[A-Z0-9]{4,4}[A-Z]{2,2}[A-Z0-9]{2,2}([A-Z0-9]{3,3}){0,1}': 'BANKDEFFXXX', '[A-Z]{2,2}': 'DE', '[A-Z]{2,2}[0-9]{2,2}[a-zA-Z0-9]{1,30}': 'DE89370400440532013000', '[A-Z]{3,3}': 'EUR', '[A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}': 'BANKDEFFXXX', '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}': 'c10f2393-6d2a-456b-8d41-d976a10e55ea', '[a-zA-Z0-9]{4}': 'ABCD', '\\+[0-9]{1,3}-[0-9()+\\-]{1,30}': '+49-3012345678'}#
A verified sample for every pattern the bundled schemas declare.
- pain001.corpus.coverage_sets.PREREQUISITES: dict[str, str] = {'ChrgsAcctAgt': 'ChrgsAcct', 'IntrmyAgt1Acct': 'IntrmyAgt1', 'IntrmyAgt2': 'IntrmyAgt1', 'IntrmyAgt2Acct': 'IntrmyAgt2', 'IntrmyAgt3': 'IntrmyAgt2', 'IntrmyAgt3Acct': 'IntrmyAgt3'}#
An element that, when emitted, needs its sibling emitted too.
- pain001.corpus.coverage_sets.RECIPE_LABELS: dict[str, str] = {'cheque-no-agent': 'cheque with no creditor agent (PmtMtd CHK)', 'cheque-to-agent': 'cheque delivered to the creditor agent (PmtMtd CHK)', 'collection': 'direct debit collection (PmtMtd DD)', 'transfer': 'credit transfer (PmtMtd TRF)'}#
What each recipe shapes, in words a reader of the file name needs.
- class pain001.corpus.coverage_sets.Recipe(name: str, pins: dict[str, str], forbid: frozenset[str] = frozenset({}), require: frozenset[str] = frozenset({}))[source]#
Bases:
objectHow one file of a set is shaped to satisfy the MDR rules.
Paths are relative to
PmtInf(CdtTrfTxInf/ChqInstr).- Variables:
name (str) – Short label.
pins (dict[str, str]) – Values that override the facet sample at a path.
forbid (frozenset[str]) – Paths never emitted.
require (frozenset[str]) – Paths always emitted, even in a sparse file.
- forbid: frozenset[str] = frozenset({})#
- name: str#
- pins: dict[str, str]#
- require: frozenset[str] = frozenset({})#
- pain001.corpus.coverage_sets.build_coverage_set(version: str, max_files: int = 16) CoverageSet[source]#
Generate the coverage set of a bundled edition.
- Parameters:
version – The message type.
max_files – Safety cap on the number of files.
- Returns:
The
CoverageSet; its report is complete unless the cap was hit.- Raises:
CoverageBuildError – If a generated file fails the XSD.
- pain001.corpus.coverage_sets.describe_file(index: int, recipe: Recipe, new_paths: int, new_branches: int, focus: tuple[str, ...]) CoverageFileInfo[source]#
Name and describe the
index-th file of a set.- Parameters:
index – One-based position in the set.
recipe – The recipe the file follows.
new_paths – Paths the file is the first to use.
new_branches – Branches the file is the first to take.
focus – The blocks the new coverage mostly falls under.
- Returns:
The
CoverageFileInfo.
- pain001.corpus.coverage_sets.sample_value(entry: ElementEntry) str[source]#
A schema-valid value for a leaf, from its facets.
- Parameters:
entry – A
simpleorattributeinventory entry.- Returns:
The value.
- Raises:
CoverageBuildError – If a pattern has no sample in the table.
pain001.corpus.identifiers module#
Synthetic identifiers that pass every structural check (ADR-0003, 5).
Corpus files need account numbers, BICs and LEIs that validators
accept without pointing at anyone’s real account. Every factory here
is deterministic in its seed, so a scenario renders the same bytes
on every build, and produces values that satisfy the published
structure: IBAN mod-97 with the country’s BBAN shape and its internal
check digits (Belgian mod 97, French RIB key, Italian CIN, Spanish
control digits, Czech mod 11, Swedish Luhn over clearing and account, Dutch
eleven-proof), the
SWIFT BIC pattern in its test-and-training form (0 as the eighth
character), ISO 17442 LEIs under an unassigned LOU prefix, ABA routing
numbers with their check digit, and RFC 4122 v4-shaped UETRs.
Real public routing codes (sort codes, BLZ, ABI/CAB, bank codes) are fine to use and the scenarios do; the account parts are synthetic.
- pain001.corpus.identifiers.COUNTRIES: tuple[str, ...] = ('GB', 'FR', 'NL', 'DE', 'IT', 'BE', 'ES', 'US', 'CZ', 'CH', 'SE', 'LU', 'HK', 'SG', 'MY', 'QA', 'AE')#
The sixteen markets of the plan; “SW” covers Switzerland and Sweden.
- pain001.corpus.identifiers.LEI_PREFIX = '0000'#
An LOU prefix no operator has been assigned, so a corpus LEI can never collide with a registered one.
- pain001.corpus.identifiers.NON_IBAN: frozenset[str] = frozenset({'HK', 'MY', 'SG', 'US'})#
Markets whose accounts are not expressed as IBANs.
- pain001.corpus.identifiers.aba_check_digit(first_eight: str) str[source]#
The ninth digit of an ABA routing number.
- pain001.corpus.identifiers.aba_is_valid(routing: str) bool[source]#
True when
routingis nine digits with the right check digit.
- pain001.corpus.identifiers.account_for(country: str, seed: int) dict[str, str][source]#
A domestic account identification for a non-IBAN market.
- Parameters:
country –
US,HK,SGorMY.seed – Any integer.
- Returns:
{"routing": ..., "number": ...}with the market’s routing identifier (ABA number, HK bank+branch, SG bank+branch, MY bank code) and a synthetic account number.- Raises:
ValueError – If the country is an IBAN market.
- pain001.corpus.identifiers.belgian_check(body: str) str[source]#
Belgian account check: body mod 97, with 0 written as 97.
- pain001.corpus.identifiers.bic_is_test(bic: str) bool[source]#
True when the BIC carries the test-and-training marker.
- pain001.corpus.identifiers.czech_mod11_ok(number: str) bool[source]#
Czech prefix/account numbers weight 6,3,7,9,10,5,8,4,2,1 mod 11.
- pain001.corpus.identifiers.czech_number(rng: _Stream, length: int) str[source]#
A number of
lengthdigits that passes the Czech mod-11 check.
- pain001.corpus.identifiers.dutch_eleven_proof(rng: _Stream) str[source]#
A ten-digit Dutch account number that passes the eleven-proof.
- pain001.corpus.identifiers.french_rib_key(bank: str, branch: str, account: str) str[source]#
The RIB key: 97 minus (89·bank + 15·branch + 3·account) mod 97.
- pain001.corpus.identifiers.iban_check_digits(country: str, bban: str) str[source]#
The two check digits that make
country + ?? + bbanvalid.
- pain001.corpus.identifiers.iban_for(country: str, seed: int) str[source]#
A valid, synthetic IBAN in the country’s BBAN shape.
- Parameters:
country – One of the IBAN markets in
COUNTRIES.seed – Any integer; the same seed gives the same IBAN.
- Returns:
The IBAN.
- Raises:
ValueError – If the country has no IBAN shape here.
- pain001.corpus.identifiers.iban_is_valid(iban: str) bool[source]#
True when the IBAN has its country’s length and mod-97 remainder 1.
- pain001.corpus.identifiers.italian_cin(abi: str, cab: str, account: str) str[source]#
The CIN control letter over ABI + CAB + account.
- pain001.corpus.identifiers.lei_check_digits(body: str) str[source]#
ISO 17442 check digits (MOD 97-10 over the 18-character body).
- pain001.corpus.identifiers.lei_is_valid(lei: str) bool[source]#
True when
leiis twenty characters and passes MOD 97-10.
- pain001.corpus.identifiers.luhn_check_digit(body: str) str[source]#
The Luhn (mod 10) check digit that completes
body.
- pain001.corpus.identifiers.make_aba(seed: int) str[source]#
A nine-digit ABA routing number with a valid check digit.
- pain001.corpus.identifiers.make_bic(country: str, seed: int, branch: bool = False) str[source]#
A BIC in the SWIFT test-and-training form.
The eighth character is
0, which SWIFT reserves for test BICs, so the value matches the ISO pattern and can never be a live institution’s code.- Parameters:
country – ISO 3166 alpha-2 code for characters five and six.
seed – Any integer.
branch – Append a three-character branch code.
- Returns:
An eight- or eleven-character BIC.
- pain001.corpus.identifiers.make_iban(country: str, bban: str) str[source]#
Assemble a valid IBAN from a country and its BBAN.
- Parameters:
country – ISO 3166 alpha-2 code.
bban – The basic bank account number, already shaped.
- Returns:
The IBAN.
- Raises:
ValueError – If the BBAN length does not match the country.
- pain001.corpus.identifiers.make_lei(seed: int) str[source]#
A structurally valid LEI under
LEI_PREFIX.- Parameters:
seed – Any integer.
- Returns:
A twenty-character LEI whose check digits verify.
- pain001.corpus.identifiers.make_uetr(seed: int) str[source]#
A deterministic RFC 4122 version-4-shaped UUID for
PmtId/UETR.- Parameters:
seed – Any integer.
- Returns:
The canonical lower-case hyphenated form.
- pain001.corpus.identifiers.mod97(text: str) int[source]#
ISO 7064 MOD 97-10 remainder of a digit-and-letter string.
pain001.corpus.inventory module#
What an XSD can express, and how much of it a set of files uses.
An Inventory lists every element path an ISO 20022 schema
declares, every attribute, every xs:any slot, and every
xs:choice group with its branches, with the leaf facets (length,
pattern, enumeration, digits, bounds) a file must respect. It is the
yardstick ADR-0003 defines coverage against: every element path and
every choice branch hit by at least one file of a version’s coverage
set, with a named exemption list.
coverage() measures a set of XML files against an inventory and
returns a CoverageReport; scripts/corpus_coverage.py turns
that into the make corpus-coverage gate.
Paths are slash-joined local names from the root, /Document/...;
attributes are path/@name; an xs:any slot is path/*. A
choice is identified by the path of the element that holds it, and a
branch by the local names of the elements that start it.
- class pain001.corpus.inventory.ChoiceEntry(path: str, branches: tuple[tuple[str, ...], ...])[source]#
Bases:
objectAn
xs:choicegroup and the branches a file can take.- Variables:
path (str) – Path of the element whose content holds the choice.
branches (tuple[tuple[str, ...], ...]) – One tuple per branch, holding the local names of the elements that branch starts with (
("*",)forxs:any).
- branches: tuple[tuple[str, ...], ...]#
- path: str#
- class pain001.corpus.inventory.CoverageReport(message_type: str, sources: tuple[str, ...], hit_paths: tuple[str, ...], missing_paths: tuple[str, ...], hit_branches: tuple[str, ...], missing_branches: tuple[str, ...], exempt: tuple[str, ...], unknown: tuple[str, ...])[source]#
Bases:
objectHow much of an inventory a set of files exercises.
- Variables:
message_type (str) – The inventory’s message type.
sources (tuple[str, ...]) – The files measured, as given.
hit_paths (tuple[str, ...]) – Declared paths at least one file uses.
missing_paths (tuple[str, ...]) – Declared paths no file uses, exemptions removed.
hit_branches (tuple[str, ...]) – Choice branch ids at least one file takes.
missing_branches (tuple[str, ...]) – Branch ids no file takes, exemptions removed.
exempt (tuple[str, ...]) – Paths and branch ids excluded from the gate, as given.
unknown (tuple[str, ...]) – Paths the files use that the inventory does not declare.
- property branch_percent: float#
Hit branches over declared branches, exemptions excluded.
- property complete: bool#
True when nothing non-exempt is missing.
- exempt: tuple[str, ...]#
- hit_branches: tuple[str, ...]#
- hit_paths: tuple[str, ...]#
- message_type: str#
- missing_branches: tuple[str, ...]#
- missing_paths: tuple[str, ...]#
- property path_percent: float#
Hit paths over declared paths, exemptions excluded.
- sources: tuple[str, ...]#
- unknown: tuple[str, ...]#
- class pain001.corpus.inventory.ElementEntry(path: str, kind: str, type_name: str | None = None, min_occurs: int = 1, max_occurs: int | None = 1, primitive: str | None = None, facets: dict[str, ~typing.Any] = <factory>)[source]#
Bases:
objectOne declared slot in a schema: element, attribute or
xs:any.- Variables:
path (str) – Slash-joined local names from
/Document.kind (str) –
complex,simple,attributeorany.type_name (str | None) – The schema type’s local name, if it has one.
min_occurs (int) – Minimum occurrences at this position.
max_occurs (int | None) – Maximum occurrences,
Nonefor unbounded.primitive (str | None) – The XSD primitive a simple value reduces to.
facets (dict[str, Any]) – Normalised facets:
enumeration(list),patterns(list of regex strings) and the length, digit and bound facets keyed by their snake_case name.
- facets: dict[str, Any]#
- kind: str#
- max_occurs: int | None = 1#
- min_occurs: int = 1#
- path: str#
- primitive: str | None = None#
- type_name: str | None = None#
- class pain001.corpus.inventory.Inventory(message_type: str, namespace: str, elements: tuple[ElementEntry, ...], choices: tuple[ChoiceEntry, ...])[source]#
Bases:
objectEverything one schema can express.
- Variables:
message_type (str) – The message type the schema defines.
namespace (str) – The schema’s target namespace.
elements (tuple[pain001.corpus.inventory.ElementEntry, ...]) – Every element, attribute and
xs:anyslot.choices (tuple[pain001.corpus.inventory.ChoiceEntry, ...]) – Every
xs:choicegroup.
- choices: tuple[ChoiceEntry, ...]#
- elements: tuple[ElementEntry, ...]#
- classmethod from_dict(data: dict[str, Any]) Inventory[source]#
Rebuild an inventory from
to_dict()output.- Parameters:
data – The dict form of an inventory.
- Returns:
The equivalent
Inventory.
- message_type: str#
- namespace: str#
- to_dict() dict[str, Any][source]#
A JSON-ready dict;
from_dict()reverses it.
- pain001.corpus.inventory.MAX_DEPTH = 64#
Descent stops when a complex type appears again on its own path. ISO 20022 payment schemas are not recursive, so the cap is a guard, not a tuning knob.
- pain001.corpus.inventory.build_inventory(xsd_path: str | Path, message_type: str) Inventory[source]#
Inventory one schema file.
- Parameters:
xsd_path – The XSD to walk; its root element must be
Document.message_type – The message type to label the inventory with.
- Returns:
The schema’s
Inventory.
- pain001.corpus.inventory.coverage(inventory: Inventory, sources: Iterable[str | Path], exemptions: Iterable[str] = ()) CoverageReport[source]#
Measure files against an inventory.
- Parameters:
inventory – The yardstick.
sources – XML files or XML texts; their paths are unioned.
exemptions – Paths or branch ids (
path -> A+B) to leave out of the missing lists. Unknown names are ignored.
- Returns:
The
CoverageReport.
- pain001.corpus.inventory.inventory_for(message_type: str) Inventory[source]#
The inventory of a bundled message type’s XSD, cached.
- Parameters:
message_type – A registered message type, e.g.
pain.001.001.09.- Returns:
The bundled schema’s
Inventory. A message type that is not bundled propagates the registry’sKeyError.
pain001.corpus.registry module#
Scenario documents: what a payment is, validated and discovered.
A scenario is a YAML document conforming to
pain001/corpus/schema/corpus.schema.json. It names the payment’s
parties, accounts, agents, rail choices and transactions in friendly
keys; pain001.corpus.builder turns it into each ISO edition the
scenario lists. Scenarios live under scenarios/ at the repository
root, the source of truth that is not shipped; the built files are.
- pain001.corpus.registry.SCENARIOS_DIR = PosixPath('/home/runner/work/pain001/pain001/scenarios')#
The source-of-truth directory in a checkout (absent from the wheel).
- class pain001.corpus.registry.Scenario(id: str, family: str, country: str, versions: tuple[str, ...], data: dict[str, Any], source: Path | None = None)[source]#
Bases:
objectOne validated scenario.
- Variables:
id (str) – The scenario id,
country.rail.name.family (str) – The rail family, e.g.
priority-payment.country (str) – ISO 3166 alpha-2 market.
versions (tuple[str, ...]) – The message types the scenario renders to.
data (dict[str, Any]) – The whole validated document.
source (pathlib.Path | None) – Where it was loaded from, if a file.
- country: str#
- data: dict[str, Any]#
- family: str#
- id: str#
- property message: str#
pain.001orpain.008, from the first version.
- property overlays: tuple[str, ...] | None#
Overlay ids to apply (L3), or
Noneto apply byapplies_to.
- property profiles: tuple[str, ...]#
Scheme and rail profile names the built files must pass (L2).
- property seed: int#
seedif given, else a hash of the id.- Type:
The identifier seed
- source: Path | None = None#
- versions: tuple[str, ...]#
- exception pain001.corpus.registry.ScenarioError[source]#
Bases:
ValueErrorA scenario document is malformed; the message names where.
- pain001.corpus.registry.load_scenario(path: str | Path) Scenario[source]#
Load and validate one YAML scenario file.
- Parameters:
path – The
.yamlfile.- Returns:
The
Scenario.- Raises:
ScenarioError – If the YAML does not parse or fails the schema.
- pain001.corpus.registry.load_scenarios(root: str | Path = PosixPath('/home/runner/work/pain001/pain001/scenarios')) list[Scenario][source]#
Load every
*.yamlunderroot, sorted by id.The
overlayssubtree holds overlay files, not scenarios, and is skipped.- Parameters:
root – The scenarios directory.
- Returns:
The scenarios; empty when the directory does not exist.
- Raises:
ScenarioError – If any file is invalid, or two share an id.
- pain001.corpus.registry.scenario_from(document: Any, source: Path | None = None) Scenario[source]#
Validate a document and wrap it.
- Parameters:
document – The parsed scenario.
source – The file it came from, for messages.
- Returns:
The
Scenario.- Raises:
ScenarioError – If the document fails the schema, or lists editions of more than one message family.
- pain001.corpus.registry.validate_document(document: Any, source: str = '<scenario>') None[source]#
Check a document against the scenario schema.
- Parameters:
document – The parsed YAML/JSON.
source – A label for error messages, usually the file path.
- Raises:
ScenarioError – With the JSON pointer and reason of the first problem, and the count of others.