cbpr-usage-rules 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cbpr_usage_rules-0.1.0/CHANGELOG.md +28 -0
- cbpr_usage_rules-0.1.0/LICENSE +21 -0
- cbpr_usage_rules-0.1.0/MANIFEST.in +4 -0
- cbpr_usage_rules-0.1.0/PKG-INFO +335 -0
- cbpr_usage_rules-0.1.0/README.md +280 -0
- cbpr_usage_rules-0.1.0/pyproject.toml +56 -0
- cbpr_usage_rules-0.1.0/setup.cfg +4 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/__init__.py +21 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/cli.py +176 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/engine.py +100 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/helpers.py +420 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/loader.py +77 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/message.py +170 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/models.py +83 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/py.typed +0 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/reference/__init__.py +9 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/reference/countries.py +28 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/reference/currencies.py +25 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/registry.py +107 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/__init__.py +1 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/__init__.py +1 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/camt_052.py +224 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/camt_054.py +176 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pacs_002.py +212 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pacs_004.py +831 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pacs_008.py +375 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pacs_008_stp.py +367 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pacs_009.py +273 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pacs_009_adv.py +255 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pacs_009_cov.py +358 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2025/pain_001.py +306 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/__init__.py +1 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/camt_052.py +191 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/camt_054.py +182 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pacs_002.py +208 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pacs_004.py +491 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pacs_008.py +377 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pacs_008_stp.py +369 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pacs_009.py +260 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pacs_009_adv.py +256 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pacs_009_cov.py +324 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/rules/y2026/pain_001.py +272 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/schema.py +97 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/validators/__init__.py +16 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/validators/bic.py +21 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/validators/country.py +11 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/validators/currency.py +11 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/validators/iban.py +26 -0
- cbpr_usage_rules-0.1.0/src/cbpr_rules/validators/lei.py +17 -0
- cbpr_usage_rules-0.1.0/src/cbpr_usage_rules.egg-info/PKG-INFO +335 -0
- cbpr_usage_rules-0.1.0/src/cbpr_usage_rules.egg-info/SOURCES.txt +53 -0
- cbpr_usage_rules-0.1.0/src/cbpr_usage_rules.egg-info/dependency_links.txt +1 -0
- cbpr_usage_rules-0.1.0/src/cbpr_usage_rules.egg-info/entry_points.txt +2 -0
- cbpr_usage_rules-0.1.0/src/cbpr_usage_rules.egg-info/requires.txt +7 -0
- cbpr_usage_rules-0.1.0/src/cbpr_usage_rules.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
|
|
5
|
+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Initial package: validation engine, wrapper-tolerant loader, public API
|
|
11
|
+
(`validate_file`, `validate_string`, `list_rules`, `available`), and the
|
|
12
|
+
`cbpr-validate` CLI (text + JSON output).
|
|
13
|
+
- Algorithmic validators: IBAN, LEI, BIC, ISO 3166-1 country, ISO 4217 currency.
|
|
14
|
+
- CBPR+ SR2025 / SR2026 usage rules per message type.
|
|
15
|
+
- Violations report `detail` (why this instance failed) and `found` (the
|
|
16
|
+
offending XML); the CLI wraps output and summarises advisory rules unless
|
|
17
|
+
`--advisory` is passed.
|
|
18
|
+
- Promoted mechanizable textual rules from advisory to enforced: header↔message
|
|
19
|
+
consistency (`MsgDefIdr`, `BizMsgIdr`), Postal Address / AddressLine
|
|
20
|
+
duplication, `AnyBIC` presence exclusivity, Structured Remittance length, and
|
|
21
|
+
conditional charge / amount-sum checks. Contextual rules (referring to a
|
|
22
|
+
related/underlying message, network, jurisdiction, or recommendation) remain
|
|
23
|
+
advisory.
|
|
24
|
+
- Optional XSD schema validation: pass `xsd=` (a path or list) to
|
|
25
|
+
`validate_file`/`validate_string`, or `--xsd PATH` (repeatable) on the CLI. XSDs
|
|
26
|
+
are auto-matched to the Document or AppHdr by `targetNamespace` and reported in a
|
|
27
|
+
separate `xsd` result block / CLI section. XSD files are not bundled. The CLI
|
|
28
|
+
exit code is non-zero on usage-rule OR schema failure.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Peter Houghton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cbpr-usage-rules
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Validate ISO 20022 CBPR+ XML messages against SWIFT usage (business) rules.
|
|
5
|
+
Author: Pete Houghton
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2021 Peter Houghton
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/phoughton/iso20022-cbpr-ur
|
|
29
|
+
Project-URL: Repository, https://github.com/phoughton/iso20022-cbpr-ur
|
|
30
|
+
Project-URL: Issues, https://github.com/phoughton/iso20022-cbpr-ur/issues
|
|
31
|
+
Keywords: iso20022,cbpr,swift,payments,validation,pacs,pain,camt
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
43
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
44
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
45
|
+
Requires-Python: >=3.9
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
License-File: LICENSE
|
|
48
|
+
Requires-Dist: lxml>=4.9
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
51
|
+
Requires-Dist: openpyxl>=3.1; extra == "dev"
|
|
52
|
+
Requires-Dist: build; extra == "dev"
|
|
53
|
+
Requires-Dist: twine; extra == "dev"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# cbpr-usage-rules
|
|
57
|
+
|
|
58
|
+
Validate **ISO 20022 CBPR+** XML messages (the SWIFT cross-border payments
|
|
59
|
+
profile) against the **Usage / Business Rules** that apply on top of the XSD
|
|
60
|
+
schemas. These are the rules that the schema alone cannot express: cross-field
|
|
61
|
+
constraints, cross-schema checks between the Business Application Header (BAH)
|
|
62
|
+
and the message Document, conditional presence, code-list restrictions, and
|
|
63
|
+
field-format algorithms (IBAN, LEI, BIC, country, currency).
|
|
64
|
+
|
|
65
|
+
Rules are versioned per year (currently **2025** and **2026**) and organised by
|
|
66
|
+
message type (pacs.008, pacs.009 and its COV/ADV variants, pacs.002, pacs.004,
|
|
67
|
+
pain.001, camt.052, camt.054, and the STP variants).
|
|
68
|
+
|
|
69
|
+
> **This package is AI generated.** The rule logic was derived, with review,
|
|
70
|
+
> from the published CBPR+ usage-guideline spreadsheets. Treat it as an aid, not
|
|
71
|
+
> as a substitute for the official SWIFT specifications. Verify against the
|
|
72
|
+
> source material before relying on results in production.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install cbpr-usage-rules
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Requires Python 3.9+ and depends only on `lxml`.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Quick start
|
|
87
|
+
|
|
88
|
+
### As a library
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import cbpr_rules
|
|
92
|
+
|
|
93
|
+
result = cbpr_rules.validate_file("payment.xml", year=2025)
|
|
94
|
+
|
|
95
|
+
if result["valid"]:
|
|
96
|
+
print("Compliant")
|
|
97
|
+
else:
|
|
98
|
+
for v in result["violations"]:
|
|
99
|
+
print(f"{v['rule_number']} line {v['line']} {v['xpath']}")
|
|
100
|
+
print(f" {v['description']}")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`validate_string` is the same but takes XML text:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
result = cbpr_rules.validate_string(xml_text, year=2026)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### From the command line
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Human-readable (default). Exit code is non-zero if there are violations.
|
|
113
|
+
cbpr-validate payment.xml --year 2025
|
|
114
|
+
|
|
115
|
+
# JSON output
|
|
116
|
+
cbpr-validate payment.xml --year 2025 --json
|
|
117
|
+
|
|
118
|
+
# Read from stdin
|
|
119
|
+
cat payment.xml | cbpr-validate --year 2026
|
|
120
|
+
|
|
121
|
+
# Also list the advisory (non-enforced) rules in full
|
|
122
|
+
cbpr-validate payment.xml --year 2025 --advisory
|
|
123
|
+
|
|
124
|
+
# Additionally schema-validate against an XSD (repeatable, results shown separately)
|
|
125
|
+
cbpr-validate payment.xml --year 2025 --xsd pacs.008.001.08.xsd
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Each reported violation shows the rule, **why** this instance failed (`Problem:`),
|
|
129
|
+
the **offending XML** from your file (`Found:`), and its line + xpath (`At:`).
|
|
130
|
+
Advisory rules are summarised as a count by default; pass `--advisory` to list them.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## The result object
|
|
135
|
+
|
|
136
|
+
Both `validate_file` and `validate_string` return a dictionary:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
{
|
|
140
|
+
"valid": False, # True only if there are no VIOLATION-severity findings
|
|
141
|
+
"message_type": "pacs.008", # the rule set that was applied
|
|
142
|
+
"detected_message_type": "pacs.008",# auto-detected from the Document namespace
|
|
143
|
+
"year": 2025,
|
|
144
|
+
"rules_evaluated": 84,
|
|
145
|
+
"violations": [
|
|
146
|
+
{
|
|
147
|
+
"rule_number": "pacs.008:R41", # unique within the message type
|
|
148
|
+
"name": "CBPR_Interbank_Settlement_Currency_FormalRule",
|
|
149
|
+
"description": "The codes XAU, XAG, XPD and XPT are not allowed ...",
|
|
150
|
+
"detail": "commodity currency 'XAU' not allowed", # why this instance was flagged
|
|
151
|
+
"found": "<IntrBkSttlmAmt Ccy=\"XAU\">1000.00</IntrBkSttlmAmt>", # the offending XML
|
|
152
|
+
"xpath": "/RequestPayload/Document/FIToFICstmrCdtTrf/CdtTrfTxInf/IntrBkSttlmAmt",
|
|
153
|
+
"line": 39, # 1-based line in the source XML
|
|
154
|
+
"severity": "violation"
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"advisory": [ # textual guidance that cannot be mechanically enforced
|
|
158
|
+
{"rule_number": "pacs.008:R4", "name": "...", "description": "..."}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Every violation carries the four required pieces of information: the **xpath**,
|
|
164
|
+
the **line number**, a **description**, and a **unique rule number**.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Mid-level usage
|
|
169
|
+
|
|
170
|
+
### Choosing the year
|
|
171
|
+
|
|
172
|
+
Pass `year=2025` or `year=2026`. Rule loading is per-year and lazy — only the
|
|
173
|
+
requested year's modules are imported. You can validate against more than one
|
|
174
|
+
year in the same process simply by calling with different `year` values.
|
|
175
|
+
|
|
176
|
+
### Message type detection and variants
|
|
177
|
+
|
|
178
|
+
The message type is auto-detected from the `<Document>` namespace, so you
|
|
179
|
+
normally don't pass it. Business variants that share a base namespace — **STP**
|
|
180
|
+
(pacs.008/pacs.009), **COV** and **ADV** (pacs.009) — cannot be told apart from
|
|
181
|
+
the XML alone, so select them explicitly when you want their stricter rule sets:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
cbpr_rules.validate_file("cover.xml", year=2025, msgtype="pacs.009_cov")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
cbpr-validate cover.xml --year 2025 --type pacs.009_cov
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Wrapper tags
|
|
192
|
+
|
|
193
|
+
Messages arrive inside different envelopes (`<RequestPayload>`, `<DataPDU>`,
|
|
194
|
+
SWIFT SAA wrappers, and so on). The validator locates the `AppHdr` and
|
|
195
|
+
`Document` elements wherever they sit in the tree and ignores the surrounding
|
|
196
|
+
wrapper, so the same input validates identically regardless of envelope. Element
|
|
197
|
+
matching is by local name, so namespace prefixes never matter.
|
|
198
|
+
|
|
199
|
+
### Severity
|
|
200
|
+
|
|
201
|
+
- `violation` — a usage rule is broken; this makes `valid` false.
|
|
202
|
+
- `info` — advisory guidance surfaced for awareness; it never fails validation
|
|
203
|
+
and appears in the separate `advisory` list (textual rules that cannot be
|
|
204
|
+
mechanically checked).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## In-depth usage
|
|
209
|
+
|
|
210
|
+
### Discovering the rule set
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
cbpr_rules.available(2025) # -> ['camt.052', 'pacs.008', ...]
|
|
214
|
+
cbpr_rules.list_rules(2025, "pacs.008") # -> [{rule_number, name, description, severity, enforced}, ...]
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
cbpr-validate --year 2025 --list-types
|
|
219
|
+
cbpr-validate --year 2025 --type pacs.008 --list
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Algorithmic field validation
|
|
223
|
+
|
|
224
|
+
Beyond the published rules, the following formats are validated with their
|
|
225
|
+
standard algorithms wherever the corresponding fields appear:
|
|
226
|
+
|
|
227
|
+
| Field | Standard | Check |
|
|
228
|
+
|----------|---------------------|-----------------------------------------|
|
|
229
|
+
| IBAN | ISO 13616 | structure + mod-97 check digits |
|
|
230
|
+
| LEI | ISO 17442 | structure + ISO 7064 mod-97-10 check |
|
|
231
|
+
| BIC | ISO 9362 | 8/11-char structure + valid country |
|
|
232
|
+
| Country | ISO 3166-1 alpha-2 | membership |
|
|
233
|
+
| Currency | ISO 4217 | membership |
|
|
234
|
+
|
|
235
|
+
The reference code lists are vendored, so validation never makes a network call.
|
|
236
|
+
|
|
237
|
+
Some of these check-digit algorithms and reference code lists were derived from
|
|
238
|
+
Wikipedia (e.g. [IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number),
|
|
239
|
+
[LEI](https://en.wikipedia.org/wiki/Legal_Entity_Identifier), and the
|
|
240
|
+
[ISO 3166 country codes](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes)).
|
|
241
|
+
Verify against the authoritative ISO/registry sources before relying on them in
|
|
242
|
+
production.
|
|
243
|
+
|
|
244
|
+
### Optional XSD schema validation
|
|
245
|
+
|
|
246
|
+
You can additionally validate a message against one or more **XSD schemas** as a
|
|
247
|
+
*separate* second result set. XSD files are **not** bundled with the package —
|
|
248
|
+
supply your own path(s):
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
result = cbpr_rules.validate_file("payment.xml", year=2025, xsd="pacs.008.001.08.xsd")
|
|
252
|
+
# or several: xsd=["head.001.001.02.xsd", "pacs.008.001.08.xsd"]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
cbpr-validate payment.xml --year 2025 --xsd pacs.008.001.08.xsd
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Each XSD is auto-matched by its `targetNamespace`: a message schema validates the
|
|
260
|
+
`Document`, a head.001 schema validates the `AppHdr`. When (and only when) an XSD
|
|
261
|
+
is supplied, the result gains a separate top-level `xsd` block, and the CLI prints
|
|
262
|
+
a distinct **XSD SCHEMA VALIDATION** section:
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
"xsd": {
|
|
266
|
+
"checked": True,
|
|
267
|
+
"schema_valid": False, # all supplied schemas passed?
|
|
268
|
+
"schemas": [
|
|
269
|
+
{
|
|
270
|
+
"file": "pacs.008.001.08.xsd",
|
|
271
|
+
"target_namespace": "urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08",
|
|
272
|
+
"validated_element": "Document", # or "AppHdr" / "root"
|
|
273
|
+
"valid": False,
|
|
274
|
+
"errors": [{"message": "...", "line": 82, "xpath": "/Document/.../CdtrAgt"}]
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Usage-rule results and schema results stay separate: the top-level `valid` reflects
|
|
281
|
+
only the usage rules, while schema validity is `xsd.schema_valid`. The **CLI exit
|
|
282
|
+
code is non-zero if either** the usage rules or the schema fail. With no `--xsd`,
|
|
283
|
+
nothing about XSD appears in the output.
|
|
284
|
+
|
|
285
|
+
### Handling errors
|
|
286
|
+
|
|
287
|
+
`validate_file` / `validate_string` raise `cbpr_rules.engine.ValidationError`
|
|
288
|
+
for unparseable XML or when the message type cannot be determined and was not
|
|
289
|
+
supplied. The CLI turns these into a message on stderr and exit code 2.
|
|
290
|
+
|
|
291
|
+
### CLI exit codes
|
|
292
|
+
|
|
293
|
+
| Code | Meaning |
|
|
294
|
+
|------|----------------------------------|
|
|
295
|
+
| 0 | Valid (no violations) |
|
|
296
|
+
| 1 | Invalid (one or more violations) |
|
|
297
|
+
| 2 | Usage error / could not validate |
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## How the rules are organised
|
|
302
|
+
|
|
303
|
+
Each `(year, message type)` has a hand-authored Python module under
|
|
304
|
+
`cbpr_rules/rules/y<year>/`. Rules are built from a small library of reusable
|
|
305
|
+
combinators (presence, conditional presence, mutual exclusion, value matching,
|
|
306
|
+
length, code lists, address grace-period rules) plus bespoke functions for
|
|
307
|
+
cross-field and cross-schema logic. The XSD files in the source material are
|
|
308
|
+
informational only and are not reimplemented here.
|
|
309
|
+
|
|
310
|
+
### Enforced vs advisory
|
|
311
|
+
|
|
312
|
+
Every published rule is registered. A rule is **enforced** (can produce a
|
|
313
|
+
violation) when it can be checked deterministically from the message alone —
|
|
314
|
+
this includes the formal pseudo-code rules and the mechanizable textual rules,
|
|
315
|
+
such as:
|
|
316
|
+
|
|
317
|
+
- header ↔ message consistency (`MsgDefIdr` matches the Document definition;
|
|
318
|
+
`BizMsgIdr` carries the GroupHeader `MsgId`);
|
|
319
|
+
- no structured Postal Address value duplicated in an `AddressLine`;
|
|
320
|
+
- `AnyBIC` present ⇒ `Name`/`PostalAddress` not allowed;
|
|
321
|
+
- Structured Remittance ≤ 9,000 characters;
|
|
322
|
+
- charge rules where instructed and settlement amounts share a currency and
|
|
323
|
+
differ, and amount-total-equals-sum checks.
|
|
324
|
+
|
|
325
|
+
A rule stays **advisory** (`severity: info`, surfaced as guidance, never failing
|
|
326
|
+
validation) when it cannot be verified from the message in isolation — for
|
|
327
|
+
example anything that refers to a *related or underlying message* not present
|
|
328
|
+
(`Original_*`, related-BAH, COV/ADV UETR/E2E), depends on the SWIFT network or a
|
|
329
|
+
jurisdiction, or expresses a recommendation / bilaterally-agreed practice.
|
|
330
|
+
Checks are deliberately **conservative**: each skips when its inputs are absent
|
|
331
|
+
or ambiguous, so a compliant message is never failed spuriously.
|
|
332
|
+
|
|
333
|
+
## Licence
|
|
334
|
+
|
|
335
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# cbpr-usage-rules
|
|
2
|
+
|
|
3
|
+
Validate **ISO 20022 CBPR+** XML messages (the SWIFT cross-border payments
|
|
4
|
+
profile) against the **Usage / Business Rules** that apply on top of the XSD
|
|
5
|
+
schemas. These are the rules that the schema alone cannot express: cross-field
|
|
6
|
+
constraints, cross-schema checks between the Business Application Header (BAH)
|
|
7
|
+
and the message Document, conditional presence, code-list restrictions, and
|
|
8
|
+
field-format algorithms (IBAN, LEI, BIC, country, currency).
|
|
9
|
+
|
|
10
|
+
Rules are versioned per year (currently **2025** and **2026**) and organised by
|
|
11
|
+
message type (pacs.008, pacs.009 and its COV/ADV variants, pacs.002, pacs.004,
|
|
12
|
+
pain.001, camt.052, camt.054, and the STP variants).
|
|
13
|
+
|
|
14
|
+
> **This package is AI generated.** The rule logic was derived, with review,
|
|
15
|
+
> from the published CBPR+ usage-guideline spreadsheets. Treat it as an aid, not
|
|
16
|
+
> as a substitute for the official SWIFT specifications. Verify against the
|
|
17
|
+
> source material before relying on results in production.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install cbpr-usage-rules
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python 3.9+ and depends only on `lxml`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
### As a library
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import cbpr_rules
|
|
37
|
+
|
|
38
|
+
result = cbpr_rules.validate_file("payment.xml", year=2025)
|
|
39
|
+
|
|
40
|
+
if result["valid"]:
|
|
41
|
+
print("Compliant")
|
|
42
|
+
else:
|
|
43
|
+
for v in result["violations"]:
|
|
44
|
+
print(f"{v['rule_number']} line {v['line']} {v['xpath']}")
|
|
45
|
+
print(f" {v['description']}")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`validate_string` is the same but takes XML text:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
result = cbpr_rules.validate_string(xml_text, year=2026)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### From the command line
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Human-readable (default). Exit code is non-zero if there are violations.
|
|
58
|
+
cbpr-validate payment.xml --year 2025
|
|
59
|
+
|
|
60
|
+
# JSON output
|
|
61
|
+
cbpr-validate payment.xml --year 2025 --json
|
|
62
|
+
|
|
63
|
+
# Read from stdin
|
|
64
|
+
cat payment.xml | cbpr-validate --year 2026
|
|
65
|
+
|
|
66
|
+
# Also list the advisory (non-enforced) rules in full
|
|
67
|
+
cbpr-validate payment.xml --year 2025 --advisory
|
|
68
|
+
|
|
69
|
+
# Additionally schema-validate against an XSD (repeatable, results shown separately)
|
|
70
|
+
cbpr-validate payment.xml --year 2025 --xsd pacs.008.001.08.xsd
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Each reported violation shows the rule, **why** this instance failed (`Problem:`),
|
|
74
|
+
the **offending XML** from your file (`Found:`), and its line + xpath (`At:`).
|
|
75
|
+
Advisory rules are summarised as a count by default; pass `--advisory` to list them.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## The result object
|
|
80
|
+
|
|
81
|
+
Both `validate_file` and `validate_string` return a dictionary:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
{
|
|
85
|
+
"valid": False, # True only if there are no VIOLATION-severity findings
|
|
86
|
+
"message_type": "pacs.008", # the rule set that was applied
|
|
87
|
+
"detected_message_type": "pacs.008",# auto-detected from the Document namespace
|
|
88
|
+
"year": 2025,
|
|
89
|
+
"rules_evaluated": 84,
|
|
90
|
+
"violations": [
|
|
91
|
+
{
|
|
92
|
+
"rule_number": "pacs.008:R41", # unique within the message type
|
|
93
|
+
"name": "CBPR_Interbank_Settlement_Currency_FormalRule",
|
|
94
|
+
"description": "The codes XAU, XAG, XPD and XPT are not allowed ...",
|
|
95
|
+
"detail": "commodity currency 'XAU' not allowed", # why this instance was flagged
|
|
96
|
+
"found": "<IntrBkSttlmAmt Ccy=\"XAU\">1000.00</IntrBkSttlmAmt>", # the offending XML
|
|
97
|
+
"xpath": "/RequestPayload/Document/FIToFICstmrCdtTrf/CdtTrfTxInf/IntrBkSttlmAmt",
|
|
98
|
+
"line": 39, # 1-based line in the source XML
|
|
99
|
+
"severity": "violation"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"advisory": [ # textual guidance that cannot be mechanically enforced
|
|
103
|
+
{"rule_number": "pacs.008:R4", "name": "...", "description": "..."}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Every violation carries the four required pieces of information: the **xpath**,
|
|
109
|
+
the **line number**, a **description**, and a **unique rule number**.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Mid-level usage
|
|
114
|
+
|
|
115
|
+
### Choosing the year
|
|
116
|
+
|
|
117
|
+
Pass `year=2025` or `year=2026`. Rule loading is per-year and lazy — only the
|
|
118
|
+
requested year's modules are imported. You can validate against more than one
|
|
119
|
+
year in the same process simply by calling with different `year` values.
|
|
120
|
+
|
|
121
|
+
### Message type detection and variants
|
|
122
|
+
|
|
123
|
+
The message type is auto-detected from the `<Document>` namespace, so you
|
|
124
|
+
normally don't pass it. Business variants that share a base namespace — **STP**
|
|
125
|
+
(pacs.008/pacs.009), **COV** and **ADV** (pacs.009) — cannot be told apart from
|
|
126
|
+
the XML alone, so select them explicitly when you want their stricter rule sets:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
cbpr_rules.validate_file("cover.xml", year=2025, msgtype="pacs.009_cov")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cbpr-validate cover.xml --year 2025 --type pacs.009_cov
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Wrapper tags
|
|
137
|
+
|
|
138
|
+
Messages arrive inside different envelopes (`<RequestPayload>`, `<DataPDU>`,
|
|
139
|
+
SWIFT SAA wrappers, and so on). The validator locates the `AppHdr` and
|
|
140
|
+
`Document` elements wherever they sit in the tree and ignores the surrounding
|
|
141
|
+
wrapper, so the same input validates identically regardless of envelope. Element
|
|
142
|
+
matching is by local name, so namespace prefixes never matter.
|
|
143
|
+
|
|
144
|
+
### Severity
|
|
145
|
+
|
|
146
|
+
- `violation` — a usage rule is broken; this makes `valid` false.
|
|
147
|
+
- `info` — advisory guidance surfaced for awareness; it never fails validation
|
|
148
|
+
and appears in the separate `advisory` list (textual rules that cannot be
|
|
149
|
+
mechanically checked).
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## In-depth usage
|
|
154
|
+
|
|
155
|
+
### Discovering the rule set
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
cbpr_rules.available(2025) # -> ['camt.052', 'pacs.008', ...]
|
|
159
|
+
cbpr_rules.list_rules(2025, "pacs.008") # -> [{rule_number, name, description, severity, enforced}, ...]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
cbpr-validate --year 2025 --list-types
|
|
164
|
+
cbpr-validate --year 2025 --type pacs.008 --list
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Algorithmic field validation
|
|
168
|
+
|
|
169
|
+
Beyond the published rules, the following formats are validated with their
|
|
170
|
+
standard algorithms wherever the corresponding fields appear:
|
|
171
|
+
|
|
172
|
+
| Field | Standard | Check |
|
|
173
|
+
|----------|---------------------|-----------------------------------------|
|
|
174
|
+
| IBAN | ISO 13616 | structure + mod-97 check digits |
|
|
175
|
+
| LEI | ISO 17442 | structure + ISO 7064 mod-97-10 check |
|
|
176
|
+
| BIC | ISO 9362 | 8/11-char structure + valid country |
|
|
177
|
+
| Country | ISO 3166-1 alpha-2 | membership |
|
|
178
|
+
| Currency | ISO 4217 | membership |
|
|
179
|
+
|
|
180
|
+
The reference code lists are vendored, so validation never makes a network call.
|
|
181
|
+
|
|
182
|
+
Some of these check-digit algorithms and reference code lists were derived from
|
|
183
|
+
Wikipedia (e.g. [IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number),
|
|
184
|
+
[LEI](https://en.wikipedia.org/wiki/Legal_Entity_Identifier), and the
|
|
185
|
+
[ISO 3166 country codes](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes)).
|
|
186
|
+
Verify against the authoritative ISO/registry sources before relying on them in
|
|
187
|
+
production.
|
|
188
|
+
|
|
189
|
+
### Optional XSD schema validation
|
|
190
|
+
|
|
191
|
+
You can additionally validate a message against one or more **XSD schemas** as a
|
|
192
|
+
*separate* second result set. XSD files are **not** bundled with the package —
|
|
193
|
+
supply your own path(s):
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
result = cbpr_rules.validate_file("payment.xml", year=2025, xsd="pacs.008.001.08.xsd")
|
|
197
|
+
# or several: xsd=["head.001.001.02.xsd", "pacs.008.001.08.xsd"]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cbpr-validate payment.xml --year 2025 --xsd pacs.008.001.08.xsd
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Each XSD is auto-matched by its `targetNamespace`: a message schema validates the
|
|
205
|
+
`Document`, a head.001 schema validates the `AppHdr`. When (and only when) an XSD
|
|
206
|
+
is supplied, the result gains a separate top-level `xsd` block, and the CLI prints
|
|
207
|
+
a distinct **XSD SCHEMA VALIDATION** section:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
"xsd": {
|
|
211
|
+
"checked": True,
|
|
212
|
+
"schema_valid": False, # all supplied schemas passed?
|
|
213
|
+
"schemas": [
|
|
214
|
+
{
|
|
215
|
+
"file": "pacs.008.001.08.xsd",
|
|
216
|
+
"target_namespace": "urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08",
|
|
217
|
+
"validated_element": "Document", # or "AppHdr" / "root"
|
|
218
|
+
"valid": False,
|
|
219
|
+
"errors": [{"message": "...", "line": 82, "xpath": "/Document/.../CdtrAgt"}]
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Usage-rule results and schema results stay separate: the top-level `valid` reflects
|
|
226
|
+
only the usage rules, while schema validity is `xsd.schema_valid`. The **CLI exit
|
|
227
|
+
code is non-zero if either** the usage rules or the schema fail. With no `--xsd`,
|
|
228
|
+
nothing about XSD appears in the output.
|
|
229
|
+
|
|
230
|
+
### Handling errors
|
|
231
|
+
|
|
232
|
+
`validate_file` / `validate_string` raise `cbpr_rules.engine.ValidationError`
|
|
233
|
+
for unparseable XML or when the message type cannot be determined and was not
|
|
234
|
+
supplied. The CLI turns these into a message on stderr and exit code 2.
|
|
235
|
+
|
|
236
|
+
### CLI exit codes
|
|
237
|
+
|
|
238
|
+
| Code | Meaning |
|
|
239
|
+
|------|----------------------------------|
|
|
240
|
+
| 0 | Valid (no violations) |
|
|
241
|
+
| 1 | Invalid (one or more violations) |
|
|
242
|
+
| 2 | Usage error / could not validate |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## How the rules are organised
|
|
247
|
+
|
|
248
|
+
Each `(year, message type)` has a hand-authored Python module under
|
|
249
|
+
`cbpr_rules/rules/y<year>/`. Rules are built from a small library of reusable
|
|
250
|
+
combinators (presence, conditional presence, mutual exclusion, value matching,
|
|
251
|
+
length, code lists, address grace-period rules) plus bespoke functions for
|
|
252
|
+
cross-field and cross-schema logic. The XSD files in the source material are
|
|
253
|
+
informational only and are not reimplemented here.
|
|
254
|
+
|
|
255
|
+
### Enforced vs advisory
|
|
256
|
+
|
|
257
|
+
Every published rule is registered. A rule is **enforced** (can produce a
|
|
258
|
+
violation) when it can be checked deterministically from the message alone —
|
|
259
|
+
this includes the formal pseudo-code rules and the mechanizable textual rules,
|
|
260
|
+
such as:
|
|
261
|
+
|
|
262
|
+
- header ↔ message consistency (`MsgDefIdr` matches the Document definition;
|
|
263
|
+
`BizMsgIdr` carries the GroupHeader `MsgId`);
|
|
264
|
+
- no structured Postal Address value duplicated in an `AddressLine`;
|
|
265
|
+
- `AnyBIC` present ⇒ `Name`/`PostalAddress` not allowed;
|
|
266
|
+
- Structured Remittance ≤ 9,000 characters;
|
|
267
|
+
- charge rules where instructed and settlement amounts share a currency and
|
|
268
|
+
differ, and amount-total-equals-sum checks.
|
|
269
|
+
|
|
270
|
+
A rule stays **advisory** (`severity: info`, surfaced as guidance, never failing
|
|
271
|
+
validation) when it cannot be verified from the message in isolation — for
|
|
272
|
+
example anything that refers to a *related or underlying message* not present
|
|
273
|
+
(`Original_*`, related-BAH, COV/ADV UETR/E2E), depends on the SWIFT network or a
|
|
274
|
+
jurisdiction, or expresses a recommendation / bilaterally-agreed practice.
|
|
275
|
+
Checks are deliberately **conservative**: each skips when its inputs are absent
|
|
276
|
+
or ambiguous, so a compliant message is never failed spuriously.
|
|
277
|
+
|
|
278
|
+
## Licence
|
|
279
|
+
|
|
280
|
+
MIT — see [LICENSE](LICENSE).
|