cbpr-usage-rules 0.3.0__tar.gz → 0.5.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.
Files changed (98) hide show
  1. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/CHANGELOG.md +20 -0
  2. {cbpr_usage_rules-0.3.0/src/cbpr_usage_rules.egg-info → cbpr_usage_rules-0.5.0}/PKG-INFO +28 -3
  3. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/README.md +27 -2
  4. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/__init__.py +19 -1
  5. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/cli.py +52 -2
  6. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/engine.py +10 -3
  7. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/camt.052.max.xml +79 -79
  8. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/camt.052.min.xml +1 -1
  9. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/camt.054.max.xml +78 -78
  10. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/camt.054.min.xml +1 -1
  11. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2026 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2025}/pacs.002.max.xml +17 -17
  12. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.002.min.xml +3 -3
  13. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.004.max.xml +49 -49
  14. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.004.min.xml +3 -3
  15. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.008.max.xml +41 -41
  16. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.008.min.xml +3 -3
  17. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.008_stp.max.xml +41 -41
  18. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.008_stp.min.xml +6 -6
  19. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2026 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2025}/pacs.009.max.xml +37 -37
  20. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2026 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2025}/pacs.009.min.xml +3 -3
  21. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.009_adv.max.xml +41 -41
  22. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2026 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2025}/pacs.009_adv.min.xml +3 -3
  23. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.009_cov.max.xml +53 -53
  24. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pacs.009_cov.min.xml +3 -3
  25. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2025/pain.001.max.xml +28 -28
  26. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2026 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2025}/pain.001.min.xml +2 -2
  27. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/camt.052.max.xml +79 -79
  28. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/camt.052.min.xml +1 -1
  29. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/camt.054.max.xml +78 -78
  30. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/camt.054.min.xml +1 -1
  31. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2025 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2026}/pacs.002.max.xml +17 -17
  32. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.002.min.xml +3 -3
  33. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.004.max.xml +45 -45
  34. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.004.min.xml +3 -3
  35. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.008.max.xml +41 -41
  36. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.008.min.xml +3 -3
  37. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.008_stp.max.xml +41 -41
  38. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.008_stp.min.xml +6 -6
  39. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2025 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2026}/pacs.009.max.xml +37 -37
  40. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2025 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2026}/pacs.009.min.xml +3 -3
  41. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.009_adv.max.xml +41 -41
  42. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2025 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2026}/pacs.009_adv.min.xml +3 -3
  43. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.009_cov.max.xml +53 -53
  44. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pacs.009_cov.min.xml +3 -3
  45. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/examples/y2026/pain.001.max.xml +27 -27
  46. {cbpr_usage_rules-0.3.0/src/cbpr_rules/examples/y2025 → cbpr_usage_rules-0.5.0/src/cbpr_rules/examples/y2026}/pain.001.min.xml +2 -2
  47. cbpr_usage_rules-0.5.0/src/cbpr_rules/idgen.py +243 -0
  48. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0/src/cbpr_usage_rules.egg-info}/PKG-INFO +28 -3
  49. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_usage_rules.egg-info/SOURCES.txt +3 -1
  50. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/tests/test_examples.py +24 -8
  51. cbpr_usage_rules-0.5.0/tests/test_idgen.py +79 -0
  52. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/LICENSE +0 -0
  53. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/MANIFEST.in +0 -0
  54. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/pyproject.toml +0 -0
  55. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/setup.cfg +0 -0
  56. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/helpers.py +0 -0
  57. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/loader.py +0 -0
  58. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/message.py +0 -0
  59. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/models.py +0 -0
  60. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/py.typed +0 -0
  61. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/reference/__init__.py +0 -0
  62. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/reference/countries.py +0 -0
  63. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/reference/currencies.py +0 -0
  64. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/registry.py +0 -0
  65. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/__init__.py +0 -0
  66. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/__init__.py +0 -0
  67. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/camt_052.py +0 -0
  68. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/camt_054.py +0 -0
  69. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pacs_002.py +0 -0
  70. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pacs_004.py +0 -0
  71. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pacs_008.py +0 -0
  72. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pacs_008_stp.py +0 -0
  73. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pacs_009.py +0 -0
  74. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pacs_009_adv.py +0 -0
  75. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pacs_009_cov.py +0 -0
  76. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2025/pain_001.py +0 -0
  77. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/__init__.py +0 -0
  78. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/camt_052.py +0 -0
  79. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/camt_054.py +0 -0
  80. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pacs_002.py +0 -0
  81. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pacs_004.py +0 -0
  82. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pacs_008.py +0 -0
  83. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pacs_008_stp.py +0 -0
  84. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pacs_009.py +0 -0
  85. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pacs_009_adv.py +0 -0
  86. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pacs_009_cov.py +0 -0
  87. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/rules/y2026/pain_001.py +0 -0
  88. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/schema.py +0 -0
  89. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/validators/__init__.py +0 -0
  90. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/validators/bic.py +0 -0
  91. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/validators/country.py +0 -0
  92. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/validators/currency.py +0 -0
  93. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/validators/iban.py +0 -0
  94. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_rules/validators/lei.py +0 -0
  95. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_usage_rules.egg-info/dependency_links.txt +0 -0
  96. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_usage_rules.egg-info/entry_points.txt +0 -0
  97. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_usage_rules.egg-info/requires.txt +0 -0
  98. {cbpr_usage_rules-0.3.0 → cbpr_usage_rules-0.5.0}/src/cbpr_usage_rules.egg-info/top_level.txt +0 -0
@@ -4,6 +4,26 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
5
5
  to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.5.0]
8
+
9
+ ### Added
10
+ - Deterministic **ID generation** library (`cbpr_rules.idgen`, "Counting Strings"
11
+ algorithm — no `random`/`uuid`): `generate_iban`, `generate_lei`, `generate_bic`,
12
+ `generate_uuid`/`generate_uetr`, `generate_mid`, plus `counting_string`/
13
+ `counting_sequence` and seeded helpers. Every id is structurally valid and passes
14
+ the package's own validators; same seed → same output. New CLI
15
+ `cbpr-validate --generate {iban,lei,bic,uuid,uetr,mid,text}` (with
16
+ `--seed/--country/--bank/--branch/--count`). The bundled example messages now
17
+ source their identifiers from this library (seeded per example).
18
+
19
+ ## [0.4.0]
20
+
21
+ ### Added
22
+ - List only the **enforceable** rules for a year + message type:
23
+ `list_rules(year, msgtype, enforced_only=True)` in the API and
24
+ `cbpr-validate --enforced --year … --type …` on the CLI (advisory rules omitted;
25
+ composes with `--json`).
26
+
7
27
  ## [0.3.0]
8
28
 
9
29
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cbpr-usage-rules
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Validate ISO 20022 CBPR+ XML messages against SWIFT usage (business) rules.
5
5
  Author: Pete Houghton
6
6
  License: MIT License
@@ -172,11 +172,34 @@ xml = cbpr_rules.example_message(2025, "pacs.008", "max") # variant defaults t
172
172
  cbpr_rules.example_variants(2025, "pacs.008") # ['min', 'max']
173
173
  ```
174
174
 
175
- The data is **fictitious** (synthetic BICs/IBANs/LEIs, `EXAMPLE` names) and does
175
+ The data is **fictitious** (synthetic, generated BICs/IBANs/LEIs) and does
176
176
  not reproduce any real sample — use them as starting templates, not real payments.
177
177
 
178
178
  ---
179
179
 
180
+ ## Generating identifiers
181
+
182
+ The example values above are produced by a small **deterministic** ID generator
183
+ (the "Counting Strings" algorithm — no `random`/`uuid`, same seed → same output).
184
+ Every generated id is structurally valid and passes the tool's own validators.
185
+
186
+ ```bash
187
+ cbpr-validate --generate iban --country AT --seed s1 # ISO 13616 + mod-97
188
+ cbpr-validate --generate lei --seed acme # ISO 17442 + check digits
189
+ cbpr-validate --generate bic --bank JSBP --country GB # -> JSBPGB..XXX
190
+ cbpr-validate --generate uuid --count 3 # RFC 4122 v4 (UETR)
191
+ cbpr-validate --generate mid --country GB # message identifier
192
+ ```
193
+ ```python
194
+ from cbpr_rules import generate_iban, generate_lei, generate_bic, generate_uetr
195
+ generate_iban("DE", seed="acme") # deterministic, valid German IBAN
196
+ generate_bic(country="GB", seed="acme")
197
+ ```
198
+
199
+ IBAN generation supports the 27 EU countries plus GB.
200
+
201
+ ---
202
+
180
203
  ## The result object
181
204
 
182
205
  Both `validate_file` and `validate_string` return a dictionary:
@@ -258,11 +281,13 @@ matching is by local name, so namespace prefixes never matter.
258
281
  ```python
259
282
  cbpr_rules.available(2025) # -> ['camt.052', 'pacs.008', ...]
260
283
  cbpr_rules.list_rules(2025, "pacs.008") # -> [{rule_number, name, description, severity, enforced}, ...]
284
+ cbpr_rules.list_rules(2025, "pacs.008", enforced_only=True) # only the enforceable rules
261
285
  ```
262
286
 
263
287
  ```bash
264
288
  cbpr-validate --year 2025 --list-types
265
- cbpr-validate --year 2025 --type pacs.008 --list
289
+ cbpr-validate --year 2025 --type pacs.008 --list # all rules
290
+ cbpr-validate --year 2025 --type pacs.008 --enforced # enforceable rules only
266
291
  ```
267
292
 
268
293
  ### Algorithmic field validation
@@ -117,11 +117,34 @@ xml = cbpr_rules.example_message(2025, "pacs.008", "max") # variant defaults t
117
117
  cbpr_rules.example_variants(2025, "pacs.008") # ['min', 'max']
118
118
  ```
119
119
 
120
- The data is **fictitious** (synthetic BICs/IBANs/LEIs, `EXAMPLE` names) and does
120
+ The data is **fictitious** (synthetic, generated BICs/IBANs/LEIs) and does
121
121
  not reproduce any real sample — use them as starting templates, not real payments.
122
122
 
123
123
  ---
124
124
 
125
+ ## Generating identifiers
126
+
127
+ The example values above are produced by a small **deterministic** ID generator
128
+ (the "Counting Strings" algorithm — no `random`/`uuid`, same seed → same output).
129
+ Every generated id is structurally valid and passes the tool's own validators.
130
+
131
+ ```bash
132
+ cbpr-validate --generate iban --country AT --seed s1 # ISO 13616 + mod-97
133
+ cbpr-validate --generate lei --seed acme # ISO 17442 + check digits
134
+ cbpr-validate --generate bic --bank JSBP --country GB # -> JSBPGB..XXX
135
+ cbpr-validate --generate uuid --count 3 # RFC 4122 v4 (UETR)
136
+ cbpr-validate --generate mid --country GB # message identifier
137
+ ```
138
+ ```python
139
+ from cbpr_rules import generate_iban, generate_lei, generate_bic, generate_uetr
140
+ generate_iban("DE", seed="acme") # deterministic, valid German IBAN
141
+ generate_bic(country="GB", seed="acme")
142
+ ```
143
+
144
+ IBAN generation supports the 27 EU countries plus GB.
145
+
146
+ ---
147
+
125
148
  ## The result object
126
149
 
127
150
  Both `validate_file` and `validate_string` return a dictionary:
@@ -203,11 +226,13 @@ matching is by local name, so namespace prefixes never matter.
203
226
  ```python
204
227
  cbpr_rules.available(2025) # -> ['camt.052', 'pacs.008', ...]
205
228
  cbpr_rules.list_rules(2025, "pacs.008") # -> [{rule_number, name, description, severity, enforced}, ...]
229
+ cbpr_rules.list_rules(2025, "pacs.008", enforced_only=True) # only the enforceable rules
206
230
  ```
207
231
 
208
232
  ```bash
209
233
  cbpr-validate --year 2025 --list-types
210
- cbpr-validate --year 2025 --type pacs.008 --list
234
+ cbpr-validate --year 2025 --type pacs.008 --list # all rules
235
+ cbpr-validate --year 2025 --type pacs.008 --enforced # enforceable rules only
211
236
  ```
212
237
 
213
238
  ### Algorithmic field validation
@@ -4,7 +4,7 @@ This package is AI generated. See the README for details and caveats.
4
4
  """
5
5
  from __future__ import annotations
6
6
 
7
- __version__ = "0.3.0"
7
+ __version__ = "0.5.0"
8
8
 
9
9
  from .models import Rule, Severity, Violation
10
10
  from .engine import (
@@ -15,6 +15,16 @@ from .engine import (
15
15
  example_message,
16
16
  example_variants,
17
17
  )
18
+ from .idgen import (
19
+ generate_iban,
20
+ generate_lei,
21
+ generate_bic,
22
+ generate_uuid,
23
+ generate_uetr,
24
+ generate_mid,
25
+ counting_string,
26
+ counting_sequence,
27
+ )
18
28
 
19
29
  __all__ = [
20
30
  "__version__",
@@ -27,4 +37,12 @@ __all__ = [
27
37
  "available",
28
38
  "example_message",
29
39
  "example_variants",
40
+ "generate_iban",
41
+ "generate_lei",
42
+ "generate_bic",
43
+ "generate_uuid",
44
+ "generate_uetr",
45
+ "generate_mid",
46
+ "counting_string",
47
+ "counting_sequence",
30
48
  ]
@@ -9,6 +9,7 @@ import textwrap
9
9
  from typing import List, Optional
10
10
 
11
11
  from . import __version__
12
+ from . import idgen
12
13
  from .engine import (
13
14
  ValidationError,
14
15
  available,
@@ -19,6 +20,29 @@ from .engine import (
19
20
  )
20
21
 
21
22
 
23
+ def _generate(args) -> List[str]:
24
+ """Produce the ``--generate`` output lines (one id per --count)."""
25
+ kind = args.generate
26
+ out: List[str] = []
27
+ for i in range(max(args.count, 1)):
28
+ seed = f"{args.seed}:{i}" if args.count > 1 else args.seed
29
+ if kind == "iban":
30
+ if not args.country:
31
+ raise ValueError("--generate iban requires --country (e.g. --country AT)")
32
+ out.append(idgen.generate_iban(args.country, seed))
33
+ elif kind == "lei":
34
+ out.append(idgen.generate_lei(seed))
35
+ elif kind == "bic":
36
+ out.append(idgen.generate_bic(args.bank, args.country or "GB", args.branch, seed))
37
+ elif kind in ("uuid", "uetr"):
38
+ out.append(idgen.generate_uuid(idgen.deterministic_int(seed or str(i), 0, 2**63)))
39
+ elif kind == "mid":
40
+ out.append(idgen.generate_mid("CBPR", args.country or "GB", i + 1))
41
+ elif kind == "text":
42
+ out.append(idgen.deterministic_string(seed or "text", 16))
43
+ return out
44
+
45
+
22
46
  def _build_parser() -> argparse.ArgumentParser:
23
47
  p = argparse.ArgumentParser(
24
48
  prog="cbpr-validate",
@@ -72,11 +96,28 @@ def _build_parser() -> argparse.ArgumentParser:
72
96
  help="Root element tag wrapping the AppHdr+Document in --example output "
73
97
  "(default: Envelope).",
74
98
  )
99
+ p.add_argument(
100
+ "--generate",
101
+ choices=("iban", "lei", "bic", "uuid", "uetr", "mid", "text"),
102
+ help="Generate a deterministic, structurally-valid identifier and print it "
103
+ "(use --seed/--country/--bank/--branch/--count to control output).",
104
+ )
105
+ p.add_argument("--seed", default="", help="Seed for --generate (same seed -> same output).")
106
+ p.add_argument("--country", help="Country code for --generate iban/bic/mid (e.g. AT, GB).")
107
+ p.add_argument("--bank", help="Bank code for --generate bic (4 letters, padded with X).")
108
+ p.add_argument("--branch", default="XXX", help="Branch code for --generate bic (default XXX).")
109
+ p.add_argument("--count", type=int, default=1, help="How many ids to generate (default 1).")
75
110
  p.add_argument(
76
111
  "--list",
77
112
  action="store_true",
78
113
  help="List the rules for --year/--type instead of validating.",
79
114
  )
115
+ p.add_argument(
116
+ "--enforced",
117
+ action="store_true",
118
+ help="List only the enforceable (mechanically-checked) rules for --year/--type; "
119
+ "advisory rules are omitted. Implies --list.",
120
+ )
80
121
  p.add_argument(
81
122
  "--list-types", action="store_true", help="List message types available for --year."
82
123
  )
@@ -205,6 +246,15 @@ def main(argv: Optional[List[str]] = None) -> int:
205
246
  print(json.dumps(types) if args.json else "\n".join(types))
206
247
  return 0
207
248
 
249
+ if args.generate:
250
+ try:
251
+ for line in _generate(args):
252
+ print(line)
253
+ except (ValueError, ValidationError) as exc:
254
+ print(f"error: {exc}", file=sys.stderr)
255
+ return 2
256
+ return 0
257
+
208
258
  if args.example:
209
259
  if args.year is None or args.msgtype is None:
210
260
  print("error: --example requires --year and --type", file=sys.stderr)
@@ -216,11 +266,11 @@ def main(argv: Optional[List[str]] = None) -> int:
216
266
  return 2
217
267
  return 0
218
268
 
219
- if args.list:
269
+ if args.list or args.enforced:
220
270
  if args.year is None or args.msgtype is None:
221
271
  print("error: --year and --type are required for --list", file=sys.stderr)
222
272
  return 2
223
- rules = list_rules(args.year, args.msgtype)
273
+ rules = list_rules(args.year, args.msgtype, enforced_only=args.enforced)
224
274
  if args.json:
225
275
  print(json.dumps(rules, indent=2))
226
276
  else:
@@ -95,9 +95,16 @@ def validate_string(xml: str, year: int, msgtype: Optional[str] = None, xsd=None
95
95
  return _validate_tree(tree, year, msgtype, xsd)
96
96
 
97
97
 
98
- def list_rules(year: int, msgtype: str) -> List[dict]:
99
- """Return metadata for every rule registered for a (year, message type)."""
100
- return [r.to_dict() for r in load_rules(year, msgtype)]
98
+ def list_rules(year: int, msgtype: str, enforced_only: bool = False) -> List[dict]:
99
+ """Return metadata for the rules registered for a (year, message type).
100
+
101
+ With ``enforced_only=True`` only the enforceable (mechanically-checked) rules
102
+ are returned, omitting advisory/catalogue-only ones.
103
+ """
104
+ rules = [r.to_dict() for r in load_rules(year, msgtype)]
105
+ if enforced_only:
106
+ rules = [r for r in rules if r["enforced"]]
107
+ return rules
101
108
 
102
109
 
103
110
  def available(year: int) -> List[str]: