cbpr-usage-rules 0.1.2__tar.gz → 0.1.3__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 (55) hide show
  1. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/CHANGELOG.md +10 -0
  2. {cbpr_usage_rules-0.1.2/src/cbpr_usage_rules.egg-info → cbpr_usage_rules-0.1.3}/PKG-INFO +2 -2
  3. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/README.md +1 -1
  4. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/__init__.py +1 -1
  5. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/registry.py +55 -6
  6. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/camt_052.py +1 -29
  7. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/camt_054.py +1 -13
  8. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pacs_002.py +1 -5
  9. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pacs_004.py +1 -27
  10. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pacs_008.py +1 -9
  11. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pacs_008_stp.py +1 -13
  12. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pacs_009.py +1 -13
  13. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pacs_009_adv.py +1 -9
  14. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pacs_009_cov.py +1 -9
  15. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/pain_001.py +1 -26
  16. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/camt_052.py +0 -14
  17. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/camt_054.py +1 -14
  18. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pacs_002.py +1 -10
  19. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pacs_004.py +1 -27
  20. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pacs_008.py +1 -13
  21. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pacs_008_stp.py +1 -13
  22. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pacs_009.py +1 -10
  23. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pacs_009_adv.py +1 -10
  24. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pacs_009_cov.py +1 -9
  25. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/pain_001.py +1 -19
  26. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3/src/cbpr_usage_rules.egg-info}/PKG-INFO +2 -2
  27. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/LICENSE +0 -0
  28. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/MANIFEST.in +0 -0
  29. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/pyproject.toml +0 -0
  30. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/setup.cfg +0 -0
  31. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/cli.py +0 -0
  32. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/engine.py +0 -0
  33. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/helpers.py +0 -0
  34. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/loader.py +0 -0
  35. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/message.py +0 -0
  36. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/models.py +0 -0
  37. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/py.typed +0 -0
  38. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/reference/__init__.py +0 -0
  39. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/reference/countries.py +0 -0
  40. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/reference/currencies.py +0 -0
  41. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/__init__.py +0 -0
  42. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2025/__init__.py +0 -0
  43. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/rules/y2026/__init__.py +0 -0
  44. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/schema.py +0 -0
  45. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/validators/__init__.py +0 -0
  46. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/validators/bic.py +0 -0
  47. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/validators/country.py +0 -0
  48. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/validators/currency.py +0 -0
  49. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/validators/iban.py +0 -0
  50. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_rules/validators/lei.py +0 -0
  51. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_usage_rules.egg-info/SOURCES.txt +0 -0
  52. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_usage_rules.egg-info/dependency_links.txt +0 -0
  53. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_usage_rules.egg-info/entry_points.txt +0 -0
  54. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_usage_rules.egg-info/requires.txt +0 -0
  55. {cbpr_usage_rules-0.1.2 → cbpr_usage_rules-0.1.3}/src/cbpr_usage_rules.egg-info/top_level.txt +0 -0
@@ -4,6 +4,16 @@ 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.1.3]
8
+
9
+ ### Changed
10
+ - Country (`VAL-CTRY`) and currency (`VAL-CCY`) validation are now single universal
11
+ rules, like IBAN/LEI. `VAL-CTRY` checks every `<Ctry>` element; `VAL-CCY` checks
12
+ every `Ccy` attribute and `<Ccy>` element, document-wide, for all message types.
13
+ This closes gaps where country was unvalidated (e.g. pacs.008 2025, the pacs.009
14
+ family) and currency was unvalidated (pacs.002 2025), and replaces the ~13
15
+ per-module `VAL-CTRY` and ~19 per-module `VAL-CCY` rules.
16
+
7
17
  ## [0.1.2]
8
18
 
9
19
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cbpr-usage-rules
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Validate ISO 20022 CBPR+ XML messages against SWIFT usage (business) rules.
5
5
  Author: Pete Houghton
6
6
  License: MIT License
@@ -141,7 +141,7 @@ Both `validate_file` and `validate_string` return a dictionary:
141
141
  "message_type": "pacs.008", # the rule set that was applied
142
142
  "detected_message_type": "pacs.008",# auto-detected from the Document namespace
143
143
  "year": 2025,
144
- "rules_evaluated": 86,
144
+ "rules_evaluated": 87,
145
145
  "violations": [
146
146
  {
147
147
  "rule_number": "pacs.008:R41", # unique within the message type
@@ -86,7 +86,7 @@ Both `validate_file` and `validate_string` return a dictionary:
86
86
  "message_type": "pacs.008", # the rule set that was applied
87
87
  "detected_message_type": "pacs.008",# auto-detected from the Document namespace
88
88
  "year": 2025,
89
- "rules_evaluated": 86,
89
+ "rules_evaluated": 87,
90
90
  "violations": [
91
91
  {
92
92
  "rule_number": "pacs.008:R41", # unique within the message type
@@ -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.1.2"
7
+ __version__ = "0.1.3"
8
8
 
9
9
  from .models import Rule, Severity, Violation
10
10
  from .engine import validate_file, validate_string, list_rules, available
@@ -11,7 +11,7 @@ import pkgutil
11
11
  from typing import Callable, Dict, List, Tuple
12
12
 
13
13
  from .models import Rule, Severity, Violation
14
- from .validators import is_valid_iban, is_valid_lei
14
+ from .validators import is_valid_country, is_valid_currency, is_valid_iban, is_valid_lei
15
15
 
16
16
  # (year, message_type) -> list[Rule]
17
17
  _REGISTRY: Dict[Tuple[int, str], List[Rule]] = {}
@@ -99,24 +99,32 @@ def _discover(year: int) -> None:
99
99
 
100
100
 
101
101
  # Cross-cutting datatype validations applied to every message type. In ISO 20022
102
- # these elements are self-validating datatypes wherever they appear, so a single
102
+ # these are self-validating datatypes wherever they appear, so a single
103
103
  # document-wide scan covers all message types (and all party positions - Debtor,
104
104
  # Creditor, Agent, Intermediary) correctly:
105
105
  # <IBAN> is IBAN2007Identifier (ISO 7064 mod-97);
106
- # <LEI> is LEIIdentifier (ISO 17442, 18 alphanumerics + 2 ISO 7064 check digits).
106
+ # <LEI> is LEIIdentifier (ISO 17442, 18 alphanumerics + 2 ISO 7064 check digits);
107
+ # <Ctry> is CountryCode (ISO 3166-1 alpha-2).
107
108
  # (number, element, validator, name, description)
108
- _UNIVERSAL = [
109
+ _UNIVERSAL_ELEMENT = [
109
110
  ("VAL-IBAN", "IBAN", is_valid_iban, "CBPR_Valid_IBAN",
110
111
  "Every IBAN must be structurally valid and pass the ISO 7064 mod-97 check."),
111
112
  ("VAL-LEI", "LEI", is_valid_lei, "CBPR_Valid_LEI",
112
113
  "Every LEI must be a structurally valid ISO 17442 identifier with correct check digits."),
114
+ ("VAL-CTRY", "Ctry", is_valid_country, "CBPR_Valid_Country",
115
+ "Every Country must be a valid ISO 3166-1 alpha-2 country code."),
113
116
  ]
114
117
 
118
+ # Currency is carried as the ``Ccy`` XML attribute on amount elements
119
+ # (ActiveCurrencyAndAmount etc.) and occasionally as a ``<Ccy>`` element (account
120
+ # currency). Both must be a valid ISO 4217 code.
121
+ _CCY_DESC = "Every currency must be a valid ISO 4217 currency code."
122
+
115
123
 
116
124
  def _universal_rules(msgtype: str) -> List[Rule]:
117
- """Build the cross-cutting datatype rules (IBAN, LEI) for a message type."""
125
+ """Build the cross-cutting datatype rules (IBAN, LEI, country, currency)."""
118
126
  rules: List[Rule] = []
119
- for number, element, validator, name, description in _UNIVERSAL:
127
+ for number, element, validator, name, description in _UNIVERSAL_ELEMENT:
120
128
  rule_id = f"{msgtype}:{number}"
121
129
 
122
130
  def check(msg, _el=element, _val=validator, _id=rule_id, _name=name, _desc=description):
@@ -138,9 +146,50 @@ def _universal_rules(msgtype: str) -> List[Rule]:
138
146
  return out
139
147
 
140
148
  rules.append(Rule(rule_id, name, description, Severity.VIOLATION, check))
149
+
150
+ rules.append(_currency_rule(msgtype))
141
151
  return rules
142
152
 
143
153
 
154
+ def _currency_rule(msgtype: str) -> Rule:
155
+ rule_id = f"{msgtype}:VAL-CCY"
156
+
157
+ def check(msg) -> List[Violation]:
158
+ out: List[Violation] = []
159
+
160
+ def flag(el, code):
161
+ out.append(
162
+ Violation(
163
+ rule_number=rule_id,
164
+ name="CBPR_Valid_Currency",
165
+ description=_CCY_DESC,
166
+ detail=f"invalid currency '{code}'",
167
+ found=msg.snippet_of(el),
168
+ xpath=msg.xpath_of(el),
169
+ line=msg.line_of(el),
170
+ )
171
+ )
172
+
173
+ # Every ``Ccy`` attribute, anywhere in the message.
174
+ for root in (msg.bah, msg.document):
175
+ if root is None:
176
+ continue
177
+ for el in root.iter():
178
+ if not isinstance(el.tag, str):
179
+ continue
180
+ code = el.get("Ccy")
181
+ if code and not is_valid_currency(code):
182
+ flag(el, code)
183
+ # Standalone ``<Ccy>`` elements (e.g. account currency).
184
+ for el in msg.iter_local("Ccy"):
185
+ code = msg.text_of(el)
186
+ if code and not is_valid_currency(code):
187
+ flag(el, code)
188
+ return out
189
+
190
+ return Rule(rule_id, "CBPR_Valid_Currency", _CCY_DESC, Severity.VIOLATION, check)
191
+
192
+
144
193
  def load_rules(year: int, msgtype: str) -> List[Rule]:
145
194
  _discover(int(year))
146
195
  rules = list(_REGISTRY.get(_key(year, msgtype), []))
@@ -11,7 +11,7 @@ rules are surfaced as advisory guidance. Algorithmic field validations
11
11
  from __future__ import annotations
12
12
 
13
13
  from ...registry import advisory, rule
14
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
14
+ from ...validators import is_valid_bic
15
15
  from ...helpers import (
16
16
  amount_equals_sum,
17
17
  business_msg_id_carries_group_id,
@@ -117,34 +117,6 @@ reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
117
117
  _val_bic)
118
118
 
119
119
 
120
- def _val_ctry(msg, report):
121
- for node in msg.find(RPT + "/Acct/Svcr/FinInstnId/PstlAdr/Ctry"):
122
- val = msg.text_of(node)
123
- if val and not is_valid_country(val):
124
- report(node, detail=f"invalid country '{val}'")
125
- for node in msg.find(RPT + "/Acct/Ownr/PstlAdr/Ctry"):
126
- val = msg.text_of(node)
127
- if val and not is_valid_country(val):
128
- report(node, detail=f"invalid country '{val}'")
129
-
130
-
131
- reg("VAL-CTRY", "CBPR_Valid_Country",
132
- "Every PostalAddress Country must be a valid ISO 3166 alpha-2 code.",
133
- _val_ctry)
134
-
135
-
136
- def _val_ccy(msg, report):
137
- for p in (RPT + "/Bal/Amt", RPT + "/Ntry/Amt"):
138
- for el, ccy in msg.attr_nodes(p, "Ccy"):
139
- if ccy and not is_valid_currency(ccy):
140
- report(el, detail=f"invalid currency '{ccy}'")
141
-
142
-
143
- reg("VAL-CCY", "CBPR_Valid_Amount_Currency",
144
- "Balance and Entry amount currency must be a valid ISO 4217 code.",
145
- _val_ccy)
146
-
147
-
148
120
  # ---------------------------------------------------------------------------
149
121
  # Advisory textual rules (not mechanically enforceable - surfaced as guidance)
150
122
  # ---------------------------------------------------------------------------
@@ -8,7 +8,7 @@ mechanizable textual rules are enforced, the rest are surfaced as advisories.
8
8
  from __future__ import annotations
9
9
 
10
10
  from ...registry import advisory, rule
11
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
11
+ from ...validators import is_valid_bic
12
12
  from ...helpers import (
13
13
  business_msg_id_carries_group_id,
14
14
  code_in,
@@ -76,22 +76,10 @@ reg("R4", "CBPR_Message_Definition_Identifier_TextualRule",
76
76
  # ---------------------------------------------------------------------------
77
77
  # Algorithmic field validations (brief), only for fields present in camt.054
78
78
  # ---------------------------------------------------------------------------
79
- reg("VAL-CCY", "CBPR_Valid_Entry_Currency",
80
- "Entry Amount currency must be a valid ISO 4217 code.",
81
- lambda msg, report: [
82
- report(el, detail=f"invalid currency '{ccy}'")
83
- for el, ccy in msg.attr_nodes(ENTRY + "/Amt", "Ccy")
84
- if ccy and not is_valid_currency(ccy)
85
- ])
86
-
87
79
  reg("VAL-BIC", "CBPR_Valid_Account_Servicer_BIC",
88
80
  "Account Servicer BICFI must be a structurally valid BIC.",
89
81
  each_value_valid(NTFCTN + "/Acct/Svcr/FinInstnId/BICFI", is_valid_bic, "BIC"))
90
82
 
91
- reg("VAL-CTRY", "CBPR_Valid_Account_Owner_Country",
92
- "Account Owner postal address Country must be a valid ISO 3166 code.",
93
- each_value_valid(NTFCTN + "/Acct/Ownr/PstlAdr/Ctry", is_valid_country, "country"))
94
-
95
83
 
96
84
  # ---------------------------------------------------------------------------
97
85
  # Advisory textual / guideline rules (not mechanically enforceable)
@@ -12,7 +12,7 @@ Paths are the short ISO 20022 tags from the guideline's XML Path column.
12
12
  from __future__ import annotations
13
13
 
14
14
  from ...registry import advisory, rule
15
- from ...validators import is_valid_bic, is_valid_country
15
+ from ...validators import is_valid_bic
16
16
  from ...helpers import (
17
17
  address_hybrid,
18
18
  address_lines_max_length,
@@ -172,10 +172,6 @@ reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
172
172
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
173
173
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
174
174
 
175
- reg("VAL-CTRY", "CBPR_Valid_Originator_Country",
176
- "Originator Postal Address Country must be a valid ISO 3166 country code.",
177
- each_value_valid(ORGTR + "/PstlAdr/Ctry", is_valid_country, "country"))
178
-
179
175
 
180
176
  # ---------------------------------------------------------------------------
181
177
  # Advisory textual rules (not mechanically enforceable - surfaced as guidance)
@@ -12,7 +12,7 @@ from __future__ import annotations
12
12
  import re
13
13
 
14
14
  from ...registry import advisory, rule
15
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
15
+ from ...validators import is_valid_bic
16
16
  from ...helpers import (
17
17
  address_hybrid,
18
18
  address_lines_max_length,
@@ -691,23 +691,6 @@ def _run_all(*checks):
691
691
  return check
692
692
 
693
693
 
694
- def _valid_ccy(path):
695
- def check(msg, report):
696
- for el, ccy in msg.attr_nodes(path, "Ccy"):
697
- if ccy and not is_valid_currency(ccy):
698
- report(el, detail=f"invalid currency '{ccy}'")
699
- return check
700
-
701
-
702
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
703
- "Settlement amount currencies must be valid ISO 4217 codes.",
704
- _run_all(
705
- _valid_ccy(TX + "/OrgnlIntrBkSttlmAmt"),
706
- _valid_ccy(TX + "/RtrdIntrBkSttlmAmt"),
707
- _valid_ccy(TX + "/RtrdInstdAmt"),
708
- _valid_ccy(OTR + "/IntrBkSttlmAmt"),
709
- ))
710
-
711
694
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
712
695
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
713
696
  _run_all(
@@ -715,15 +698,6 @@ reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
715
698
  each_value_valid(TX + "/InstdAgt/FinInstnId/BICFI", is_valid_bic, "BIC"),
716
699
  ))
717
700
 
718
- reg("VAL-CTRY", "CBPR_Valid_Country",
719
- "Every Country code must be a valid ISO 3166 country code.",
720
- _run_all(
721
- each_value_valid(RC + "/Dbtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
722
- each_value_valid(RC + "/Cdtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
723
- each_value_valid(OTR + "/Dbtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
724
- each_value_valid(OTR + "/Cdtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
725
- ))
726
-
727
701
  # ---------------------------------------------------------------------------
728
702
  # Advisory textual / guideline rules (not mechanically enforceable)
729
703
  # ---------------------------------------------------------------------------
@@ -11,7 +11,7 @@ XML paths are the short ISO 20022 tags from its Full_View / XML Path column.
11
11
  from __future__ import annotations
12
12
 
13
13
  from ...registry import advisory, rule
14
- from ...validators import is_valid_bic, is_valid_currency
14
+ from ...validators import is_valid_bic
15
15
  from ...helpers import (
16
16
  address_hybrid,
17
17
  address_lines_max_length,
@@ -302,14 +302,6 @@ reg("R44", "CBPR_DEBT_Rule_1_TextualRule",
302
302
 
303
303
  # Specific validations required by the brief (algorithmic), applied to the
304
304
  # fields where these data types appear in pacs.008.
305
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
306
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
307
- lambda msg, report: [
308
- report(el, detail=f"invalid currency '{ccy}'")
309
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
310
- if ccy and not is_valid_currency(ccy)
311
- ])
312
-
313
305
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
314
306
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
315
307
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
@@ -13,7 +13,7 @@ from __future__ import annotations
13
13
  import re as _re
14
14
 
15
15
  from ...registry import advisory, rule
16
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
16
+ from ...validators import is_valid_bic
17
17
  from ...helpers import (
18
18
  address_hybrid,
19
19
  address_lines_max_length,
@@ -251,22 +251,10 @@ reg("R8", "CBPR_Business_Service_Usage_TextualRule",
251
251
  code_in("/AppHdr/BizSvc", ["swift.cbprplus.stp.03"]))
252
252
 
253
253
 
254
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
255
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
256
- lambda msg, report: [
257
- report(el, detail=f"invalid currency '{ccy}'")
258
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
259
- if ccy and not is_valid_currency(ccy)
260
- ])
261
-
262
254
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
263
255
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
264
256
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
265
257
 
266
- reg("VAL-CTRY", "CBPR_Valid_Country",
267
- "Every Postal Address Country must be a valid ISO 3166 country code.",
268
- each_value_valid(TX + "/Dbtr/PstlAdr/Ctry", is_valid_country, "country"))
269
-
270
258
 
271
259
  # ---------------------------------------------------------------------------
272
260
  # Promoted from advisory: mechanizable textual rules now enforced.
@@ -11,7 +11,7 @@ Name/PostalAddress follow the *agent* (present-together) rule via FinInstnId.
11
11
  from __future__ import annotations
12
12
 
13
13
  from ...registry import advisory, rule
14
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
14
+ from ...validators import is_valid_bic
15
15
  from ...helpers import (
16
16
  address_hybrid,
17
17
  address_lines_max_length,
@@ -199,22 +199,10 @@ reg("R28", "CBPR_Duplication_Postal_Address_TextualRule",
199
199
  no_postal_address_duplication())
200
200
 
201
201
  # Algorithmic validations required by the brief, for fields present in pacs.009.
202
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
203
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
204
- lambda msg, report: [
205
- report(el, detail=f"invalid currency '{ccy}'")
206
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
207
- if ccy and not is_valid_currency(ccy)
208
- ])
209
-
210
202
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
211
203
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
212
204
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
213
205
 
214
- reg("VAL-CTRY", "CBPR_Valid_Postal_Country",
215
- "Every Postal Address Country must be a valid ISO 3166 country code.",
216
- each_value_valid(TX + "/CdtrAgt/FinInstnId/PstlAdr/Ctry", is_valid_country, "country"))
217
-
218
206
 
219
207
  # ---------------------------------------------------------------------------
220
208
  # Advisory textual rules (not mechanically enforceable - surfaced as guidance)
@@ -12,7 +12,7 @@ Name/PostalAddress + grace-period rules (not the customer party rules).
12
12
  from __future__ import annotations
13
13
 
14
14
  from ...registry import advisory, rule
15
- from ...validators import is_valid_bic, is_valid_currency
15
+ from ...validators import is_valid_bic
16
16
  from ...helpers import (
17
17
  address_hybrid,
18
18
  address_lines_max_length,
@@ -198,14 +198,6 @@ reg("R20", "CBPR_Duplication_Postal_Address_TextualRule",
198
198
  # ---------------------------------------------------------------------------
199
199
  # Specific algorithmic validations required by the brief (fields present here).
200
200
  # ---------------------------------------------------------------------------
201
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
202
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
203
- lambda msg, report: [
204
- report(el, detail=f"invalid currency '{ccy}'")
205
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
206
- if ccy and not is_valid_currency(ccy)
207
- ])
208
-
209
201
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
210
202
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
211
203
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
@@ -10,7 +10,7 @@ are the short ISO 20022 tags from its Full_View / XML Path column.
10
10
  from __future__ import annotations
11
11
 
12
12
  from ...registry import advisory, rule
13
- from ...validators import is_valid_bic, is_valid_currency
13
+ from ...validators import is_valid_bic
14
14
  from ...helpers import (
15
15
  address_hybrid,
16
16
  address_lines_max_length,
@@ -235,14 +235,6 @@ reg("R8", "CBPR_Business_Service_Usage_TextualRule",
235
235
  code_in("/AppHdr/BizSvc", ["swift.cbprplus.cov.03"]))
236
236
 
237
237
  # Algorithmic validations (brief), for fields present in pacs.009 COV.
238
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
239
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
240
- lambda msg, report: [
241
- report(el, detail=f"invalid currency '{ccy}'")
242
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
243
- if ccy and not is_valid_currency(ccy)
244
- ])
245
-
246
238
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
247
239
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
248
240
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
@@ -9,7 +9,7 @@ module (``pacs_008``): combinator-built rules go through ``reg``/``_agent_block`
9
9
  from __future__ import annotations
10
10
 
11
11
  from ...registry import advisory, rule
12
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
12
+ from ...validators import is_valid_bic
13
13
  from ...helpers import (
14
14
  address_hybrid,
15
15
  address_lines_max_length,
@@ -141,14 +141,6 @@ reg("R69", "CBPR_Structured_Remittance_Information_TextualRule",
141
141
  # ---------------------------------------------------------------------------
142
142
  # Algorithmic field validation (brief), only for fields present in pain.001.
143
143
  # ---------------------------------------------------------------------------
144
- reg("VAL-CCY", "CBPR_Valid_Instructed_Amount_Currency",
145
- "Instructed Amount currency must be a valid ISO 4217 code.",
146
- lambda msg, report: [
147
- report(el, detail=f"invalid currency '{ccy}'")
148
- for el, ccy in msg.attr_nodes(TX + "/Amt/InstdAmt", "Ccy")
149
- if ccy and not is_valid_currency(ccy)
150
- ])
151
-
152
144
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
153
145
  "Every Financial Institution BICFI must be a structurally valid BIC.",
154
146
  lambda msg, report: [
@@ -164,23 +156,6 @@ reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
164
156
  if msg.text_of(node) and not is_valid_bic(msg.text_of(node))
165
157
  ])
166
158
 
167
- reg("VAL-CTRY", "CBPR_Valid_Country",
168
- "Every Country must be a valid ISO 3166-1 alpha-2 code.",
169
- lambda msg, report: [
170
- report(node, detail=f"invalid country '{msg.text_of(node)}'")
171
- for path in (
172
- PMTINF + "/Dbtr/PstlAdr/Ctry",
173
- PMTINF + "/DbtrAgt/FinInstnId/PstlAdr/Ctry",
174
- TX + "/Cdtr/PstlAdr/Ctry",
175
- TX + "/CdtrAgt/FinInstnId/PstlAdr/Ctry",
176
- TX + "/IntrmyAgt1/FinInstnId/PstlAdr/Ctry",
177
- TX + "/IntrmyAgt2/FinInstnId/PstlAdr/Ctry",
178
- TX + "/IntrmyAgt3/FinInstnId/PstlAdr/Ctry",
179
- )
180
- for node in msg.find(path)
181
- if msg.text_of(node) and not is_valid_country(msg.text_of(node))
182
- ])
183
-
184
159
 
185
160
  # ---------------------------------------------------------------------------
186
161
  # Advisory textual rules (not mechanically enforceable - surfaced as guidance)
@@ -11,8 +11,6 @@ from __future__ import annotations
11
11
  from ...registry import advisory, rule
12
12
  from ...validators import (
13
13
  is_valid_bic,
14
- is_valid_country,
15
- is_valid_currency,
16
14
  )
17
15
  from ...helpers import (
18
16
  amount_equals_sum,
@@ -89,22 +87,10 @@ reg("R13", "CBPR_Original_Instruction_Identification_FormalRule",
89
87
  # ---------------------------------------------------------------------------
90
88
  # Algorithmic field validations (brief), for fields present in camt.052
91
89
  # ---------------------------------------------------------------------------
92
- reg("VAL-CCY", "CBPR_Valid_Balance_Currency",
93
- "Balance Amount currency must be a valid ISO 4217 code.",
94
- lambda msg, report: [
95
- report(el, detail=f"invalid currency '{ccy}'")
96
- for el, ccy in msg.attr_nodes(RPT + "/Bal/Amt", "Ccy")
97
- if ccy and not is_valid_currency(ccy)
98
- ])
99
-
100
90
  reg("VAL-BIC", "CBPR_Valid_Account_Servicer_BIC",
101
91
  "Account Servicer BICFI must be a structurally valid BIC.",
102
92
  each_value_valid(RPT + "/Acct/Svcr/FinInstnId/BICFI", is_valid_bic, "BIC"))
103
93
 
104
- reg("VAL-CTRY", "CBPR_Valid_Account_Owner_Country",
105
- "Account Owner Postal Address Country must be a valid ISO 3166 code.",
106
- each_value_valid(RPT + "/Acct/Ownr/PstlAdr/Ctry", is_valid_country, "Country"))
107
-
108
94
 
109
95
  # ---------------------------------------------------------------------------
110
96
  # Mechanizable textual rules promoted from advisory to enforced.
@@ -11,7 +11,7 @@ rules are surfaced via ``advisory``.
11
11
  from __future__ import annotations
12
12
 
13
13
  from ...registry import advisory, rule
14
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
14
+ from ...validators import is_valid_bic
15
15
  from ...helpers import (
16
16
  business_msg_id_carries_group_id,
17
17
  each_value_valid,
@@ -97,23 +97,10 @@ reg("R13", "CBPR_Original_Instruction_Identification_FormalRule",
97
97
  # Algorithmic field validation (brief-required VAL-* checks), applied to the
98
98
  # fields where these data types appear in camt.054.
99
99
  # ---------------------------------------------------------------------------
100
- reg("VAL-CCY", "CBPR_Valid_Entry_Amount_Currency",
101
- "Entry Amount currency must be a valid ISO 4217 code.",
102
- lambda msg, report: [
103
- report(el, detail=f"invalid currency '{ccy}'")
104
- for el, ccy in msg.attr_nodes(ENTRY + "/Amt", "Ccy")
105
- if ccy and not is_valid_currency(ccy)
106
- ])
107
-
108
100
  reg("VAL-BIC", "CBPR_Valid_Account_Servicer_BIC",
109
101
  "Account Servicer BICFI must be a structurally valid BIC.",
110
102
  each_value_valid(NTFCTN + "/Acct/Svcr/FinInstnId/BICFI", is_valid_bic, "BIC"))
111
103
 
112
- reg("VAL-CTRY", "CBPR_Valid_Account_Servicer_Country",
113
- "Account Servicer postal address Country must be a valid ISO 3166 code.",
114
- each_value_valid(NTFCTN + "/Acct/Svcr/FinInstnId/PstlAdr/Ctry",
115
- is_valid_country, "country"))
116
-
117
104
 
118
105
  # ---------------------------------------------------------------------------
119
106
  # Mechanizable textual rules promoted from advisory to enforced. Both are
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
  import re as _re
11
11
 
12
12
  from ...registry import advisory, rule
13
- from ...validators import is_valid_bic, is_valid_currency
13
+ from ...validators import is_valid_bic
14
14
  from ...helpers import (
15
15
  business_msg_id_carries_group_id,
16
16
  header_msg_def_id_matches,
@@ -141,15 +141,6 @@ reg("R20", "CBPR_Party_Name_Any_BIC_FormalRule",
141
141
  # ---------------------------------------------------------------------------
142
142
  # Algorithmic field validation (project brief) - only fields present here.
143
143
  # ---------------------------------------------------------------------------
144
- reg("VAL-CCY", "CBPR_Valid_Original_Interbank_Settlement_Currency",
145
- "Original Interbank Settlement Amount currency must be a valid ISO 4217 code.",
146
- lambda msg, report: [
147
- report(el, detail=f"invalid currency '{ccy}'")
148
- for el, ccy in msg.attr_nodes(TX + "/OrgnlTxRef/IntrBkSttlmAmt", "Ccy")
149
- if ccy and not is_valid_currency(ccy)
150
- ])
151
-
152
-
153
144
  @rule(MT, YEAR, "VAL-BIC", "CBPR_Valid_Agent_BIC",
154
145
  "Every Instructing/Instructed Agent BICFI must be a structurally valid BIC.")
155
146
  def _val_bic(msg, report):
@@ -10,7 +10,7 @@ Structure mirrors the reference module ``y2025/pacs_008.py``.
10
10
  from __future__ import annotations
11
11
 
12
12
  from ...registry import advisory, rule
13
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
13
+ from ...validators import is_valid_bic
14
14
  from ...helpers import (
15
15
  bic_presence_exclusive,
16
16
  business_msg_id_carries_group_id,
@@ -258,23 +258,6 @@ def _run_all(*checks):
258
258
  return check
259
259
 
260
260
 
261
- def _valid_ccy(path: str):
262
- def check(msg, report):
263
- for el, ccy in msg.attr_nodes(path, "Ccy"):
264
- if ccy and not is_valid_currency(ccy):
265
- report(el, detail=f"invalid currency '{ccy}'")
266
- return check
267
-
268
-
269
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
270
- "Settlement amount currencies must be valid ISO 4217 codes.",
271
- _run_all(
272
- _valid_ccy(TX + "/OrgnlIntrBkSttlmAmt"),
273
- _valid_ccy(TX + "/RtrdIntrBkSttlmAmt"),
274
- _valid_ccy(TX + "/RtrdInstdAmt"),
275
- _valid_ccy(OTR + "/IntrBkSttlmAmt"),
276
- ))
277
-
278
261
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
279
262
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
280
263
  _run_all(
@@ -282,15 +265,6 @@ reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
282
265
  each_value_valid(TX + "/InstdAgt/FinInstnId/BICFI", is_valid_bic, "BIC"),
283
266
  ))
284
267
 
285
- reg("VAL-CTRY", "CBPR_Valid_Country",
286
- "Every Country code must be a valid ISO 3166 country code.",
287
- _run_all(
288
- each_value_valid(RC + "/Dbtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
289
- each_value_valid(RC + "/Cdtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
290
- each_value_valid(OTR + "/Dbtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
291
- each_value_valid(OTR + "/Cdtr/Pty/PstlAdr/Ctry", is_valid_country, "country"),
292
- ))
293
-
294
268
 
295
269
  # ---------------------------------------------------------------------------
296
270
  # Mechanizable textual rules + advisory textual rules.
@@ -9,7 +9,7 @@ registered as advisories.
9
9
  from __future__ import annotations
10
10
 
11
11
  from ...registry import advisory, rule
12
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
12
+ from ...validators import is_valid_bic
13
13
  from ...helpers import (
14
14
  bic_presence_exclusive,
15
15
  business_msg_id_carries_group_id,
@@ -307,22 +307,10 @@ reg("R63", "CBPR_Structured_RemittanceInformation_TextualRule",
307
307
  # ---------------------------------------------------------------------------
308
308
  # Algorithmic field validation required by the project brief
309
309
  # ---------------------------------------------------------------------------
310
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
311
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
312
- lambda msg, report: [
313
- report(el, detail=f"invalid currency '{ccy}'")
314
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
315
- if ccy and not is_valid_currency(ccy)
316
- ])
317
-
318
310
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
319
311
  "Every BICFI in the message must be a structurally valid BIC.",
320
312
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
321
313
 
322
- reg("VAL-CTRY", "CBPR_Valid_Country",
323
- "Every Country code must be a valid ISO 3166 code.",
324
- each_value_valid(TX + "/Dbtr/PstlAdr/Ctry", is_valid_country, "Country"))
325
-
326
314
 
327
315
  # ---------------------------------------------------------------------------
328
316
  # Advisory textual rules (not mechanically enforceable - surfaced as guidance)
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
  import re
12
12
 
13
13
  from ...registry import advisory, rule
14
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
14
+ from ...validators import is_valid_bic
15
15
  from ...helpers import (
16
16
  bic_presence_exclusive,
17
17
  business_msg_id_carries_group_id,
@@ -309,22 +309,10 @@ def _r20(msg, report):
309
309
  # ---------------------------------------------------------------------------
310
310
  # Algorithmic field validation (VAL-*) - only for fields present in this message
311
311
  # ---------------------------------------------------------------------------
312
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
313
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
314
- lambda msg, report: [
315
- report(el, detail=f"invalid currency '{ccy}'")
316
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
317
- if ccy and not is_valid_currency(ccy)
318
- ])
319
-
320
312
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
321
313
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
322
314
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
323
315
 
324
- reg("VAL-CTRY", "CBPR_Valid_Country",
325
- "Every PostalAddress Country must be a valid ISO 3166 alpha-2 code.",
326
- each_value_valid(TX + "/Dbtr/PstlAdr/Ctry", is_valid_country, "country"))
327
-
328
316
 
329
317
  # ---------------------------------------------------------------------------
330
318
  # Advisory textual rules (not mechanically enforceable - surfaced as guidance)
@@ -9,7 +9,7 @@ otherwise surfaced as advisories.
9
9
  from __future__ import annotations
10
10
 
11
11
  from ...registry import advisory, rule
12
- from ...validators import is_valid_bic, is_valid_currency
12
+ from ...validators import is_valid_bic
13
13
  from ...helpers import (
14
14
  business_msg_id_carries_group_id,
15
15
  header_msg_def_id_matches,
@@ -161,15 +161,6 @@ for _num, _path in _AGENT_NAME_ADR.items():
161
161
  # ---------------------------------------------------------------------------
162
162
  # Algorithmic field validation (brief), only for fields present in pacs.009.
163
163
  # ---------------------------------------------------------------------------
164
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
165
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
166
- lambda msg, report: [
167
- report(el, detail=f"invalid currency '{ccy}'")
168
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
169
- if ccy and not is_valid_currency(ccy)
170
- ])
171
-
172
-
173
164
  @rule(MT, YEAR, "VAL-BIC", "CBPR_Valid_Agent_BIC",
174
165
  "Every Agent BICFI in the message must be a structurally valid BIC.")
175
166
  def _val_bic(msg, report):
@@ -9,7 +9,7 @@ otherwise surfaced as advisories.
9
9
  from __future__ import annotations
10
10
 
11
11
  from ...registry import advisory, rule
12
- from ...validators import is_valid_bic, is_valid_currency
12
+ from ...validators import is_valid_bic
13
13
  from ...helpers import (
14
14
  not_matching_pattern,
15
15
  presence_together,
@@ -167,15 +167,6 @@ for _num, _path in _AGENT_NAME_ADR.items():
167
167
  # ---------------------------------------------------------------------------
168
168
  # Algorithmic field validation (brief), only for fields present in pacs.009 ADV.
169
169
  # ---------------------------------------------------------------------------
170
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
171
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
172
- lambda msg, report: [
173
- report(el, detail=f"invalid currency '{ccy}'")
174
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
175
- if ccy and not is_valid_currency(ccy)
176
- ])
177
-
178
-
179
170
  @rule(MT, YEAR, "VAL-BIC", "CBPR_Valid_Agent_BIC",
180
171
  "Every Agent BICFI in the message must be a structurally valid BIC.")
181
172
  def _val_bic(msg, report):
@@ -10,7 +10,7 @@ are the short ISO 20022 tags from its Full_View / XML Path column.
10
10
  from __future__ import annotations
11
11
 
12
12
  from ...registry import advisory, rule
13
- from ...validators import is_valid_bic, is_valid_currency
13
+ from ...validators import is_valid_bic
14
14
  from ...helpers import (
15
15
  bic_presence_exclusive,
16
16
  business_msg_id_carries_group_id,
@@ -210,14 +210,6 @@ _party_name_adr("R59", UND + "/UltmtCdtr")
210
210
  # ---------------------------------------------------------------------------
211
211
  # Algorithmic field validation (brief), for fields present in pacs.009 COV.
212
212
  # ---------------------------------------------------------------------------
213
- reg("VAL-CCY", "CBPR_Valid_Settlement_Currency",
214
- "Interbank Settlement Amount currency must be a valid ISO 4217 code.",
215
- lambda msg, report: [
216
- report(el, detail=f"invalid currency '{ccy}'")
217
- for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy")
218
- if ccy and not is_valid_currency(ccy)
219
- ])
220
-
221
213
  reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
222
214
  "Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
223
215
  each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
@@ -9,7 +9,7 @@ small helpers, and cross-field / cross-schema logic is written as bespoke
9
9
  from __future__ import annotations
10
10
 
11
11
  from ...registry import advisory, rule
12
- from ...validators import is_valid_bic, is_valid_country, is_valid_currency
12
+ from ...validators import is_valid_bic
13
13
  from ...helpers import (
14
14
  business_msg_id_carries_group_id,
15
15
  header_msg_def_id_matches,
@@ -182,14 +182,6 @@ _CTRY_PATHS = [
182
182
  ]
183
183
 
184
184
 
185
- @rule(MT, YEAR, "VAL-CCY", "CBPR_Valid_Instructed_Amount_Currency",
186
- "Instructed Amount currency must be a valid ISO 4217 code.")
187
- def _val_ccy(msg, report):
188
- for el, ccy in msg.attr_nodes(TX + "/Amt/InstdAmt", "Ccy"):
189
- if ccy and not is_valid_currency(ccy):
190
- report(el, detail=f"invalid currency '{ccy}'")
191
-
192
-
193
185
  @rule(MT, YEAR, "VAL-BIC", "CBPR_Valid_Agent_BIC",
194
186
  "Every Financial Institution BICFI must be a structurally valid BIC.")
195
187
  def _val_bic(msg, report):
@@ -200,16 +192,6 @@ def _val_bic(msg, report):
200
192
  report(node, detail=f"invalid BIC: '{val}'")
201
193
 
202
194
 
203
- @rule(MT, YEAR, "VAL-CTRY", "CBPR_Valid_Country",
204
- "Every Country code in a Postal Address must be a valid ISO 3166 code.")
205
- def _val_ctry(msg, report):
206
- for path in _CTRY_PATHS:
207
- for node in msg.find(path):
208
- val = msg.text_of(node)
209
- if val and not is_valid_country(val):
210
- report(node, detail=f"invalid country: '{val}'")
211
-
212
-
213
195
  # ---------------------------------------------------------------------------
214
196
  # Promoted from advisory: mechanizable header / address / remittance rules.
215
197
  # Each combinator is conservative: it skips when its inputs are absent or
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cbpr-usage-rules
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Validate ISO 20022 CBPR+ XML messages against SWIFT usage (business) rules.
5
5
  Author: Pete Houghton
6
6
  License: MIT License
@@ -141,7 +141,7 @@ Both `validate_file` and `validate_string` return a dictionary:
141
141
  "message_type": "pacs.008", # the rule set that was applied
142
142
  "detected_message_type": "pacs.008",# auto-detected from the Document namespace
143
143
  "year": 2025,
144
- "rules_evaluated": 86,
144
+ "rules_evaluated": 87,
145
145
  "violations": [
146
146
  {
147
147
  "rule_number": "pacs.008:R41", # unique within the message type