cbpr-usage-rules 0.1.0__py3-none-any.whl

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 (48) hide show
  1. cbpr_rules/__init__.py +21 -0
  2. cbpr_rules/cli.py +176 -0
  3. cbpr_rules/engine.py +100 -0
  4. cbpr_rules/helpers.py +420 -0
  5. cbpr_rules/loader.py +77 -0
  6. cbpr_rules/message.py +170 -0
  7. cbpr_rules/models.py +83 -0
  8. cbpr_rules/py.typed +0 -0
  9. cbpr_rules/reference/__init__.py +9 -0
  10. cbpr_rules/reference/countries.py +28 -0
  11. cbpr_rules/reference/currencies.py +25 -0
  12. cbpr_rules/registry.py +107 -0
  13. cbpr_rules/rules/__init__.py +1 -0
  14. cbpr_rules/rules/y2025/__init__.py +1 -0
  15. cbpr_rules/rules/y2025/camt_052.py +224 -0
  16. cbpr_rules/rules/y2025/camt_054.py +176 -0
  17. cbpr_rules/rules/y2025/pacs_002.py +212 -0
  18. cbpr_rules/rules/y2025/pacs_004.py +831 -0
  19. cbpr_rules/rules/y2025/pacs_008.py +375 -0
  20. cbpr_rules/rules/y2025/pacs_008_stp.py +367 -0
  21. cbpr_rules/rules/y2025/pacs_009.py +273 -0
  22. cbpr_rules/rules/y2025/pacs_009_adv.py +255 -0
  23. cbpr_rules/rules/y2025/pacs_009_cov.py +358 -0
  24. cbpr_rules/rules/y2025/pain_001.py +306 -0
  25. cbpr_rules/rules/y2026/__init__.py +1 -0
  26. cbpr_rules/rules/y2026/camt_052.py +191 -0
  27. cbpr_rules/rules/y2026/camt_054.py +182 -0
  28. cbpr_rules/rules/y2026/pacs_002.py +208 -0
  29. cbpr_rules/rules/y2026/pacs_004.py +491 -0
  30. cbpr_rules/rules/y2026/pacs_008.py +377 -0
  31. cbpr_rules/rules/y2026/pacs_008_stp.py +369 -0
  32. cbpr_rules/rules/y2026/pacs_009.py +260 -0
  33. cbpr_rules/rules/y2026/pacs_009_adv.py +256 -0
  34. cbpr_rules/rules/y2026/pacs_009_cov.py +324 -0
  35. cbpr_rules/rules/y2026/pain_001.py +272 -0
  36. cbpr_rules/schema.py +97 -0
  37. cbpr_rules/validators/__init__.py +16 -0
  38. cbpr_rules/validators/bic.py +21 -0
  39. cbpr_rules/validators/country.py +11 -0
  40. cbpr_rules/validators/currency.py +11 -0
  41. cbpr_rules/validators/iban.py +26 -0
  42. cbpr_rules/validators/lei.py +17 -0
  43. cbpr_usage_rules-0.1.0.dist-info/METADATA +335 -0
  44. cbpr_usage_rules-0.1.0.dist-info/RECORD +48 -0
  45. cbpr_usage_rules-0.1.0.dist-info/WHEEL +5 -0
  46. cbpr_usage_rules-0.1.0.dist-info/entry_points.txt +2 -0
  47. cbpr_usage_rules-0.1.0.dist-info/licenses/LICENSE +21 -0
  48. cbpr_usage_rules-0.1.0.dist-info/top_level.txt +1 -0
cbpr_rules/models.py ADDED
@@ -0,0 +1,83 @@
1
+ """Core data models: Severity, Violation, Rule."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import Callable, List, Optional, TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING: # avoid import cycle at runtime
9
+ from .message import ParsedMessage
10
+
11
+
12
+ class Severity(str, Enum):
13
+ """How serious a finding is.
14
+
15
+ VIOLATION - a usage rule is broken; the message is not compliant.
16
+ INFO - advisory guidance surfaced for awareness, not a hard breach.
17
+ """
18
+
19
+ VIOLATION = "violation"
20
+ INFO = "info"
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class Violation:
25
+ """A single rule finding against a message."""
26
+
27
+ rule_number: str
28
+ name: str
29
+ description: str
30
+ xpath: str = ""
31
+ line: Optional[int] = None
32
+ severity: Severity = Severity.VIOLATION
33
+ # Why it was flagged (rule-specific) and the offending XML at that location.
34
+ detail: str = ""
35
+ found: str = ""
36
+
37
+ def to_dict(self) -> dict:
38
+ return {
39
+ "rule_number": self.rule_number,
40
+ "name": self.name,
41
+ "description": self.description,
42
+ "detail": self.detail,
43
+ "found": self.found,
44
+ "xpath": self.xpath,
45
+ "line": self.line,
46
+ "severity": self.severity.value,
47
+ }
48
+
49
+
50
+ # A rule check receives the parsed message and a ``report`` callback and emits
51
+ # findings by calling ``report(element, detail=None)``.
52
+ CheckFn = Callable[["ParsedMessage", Callable], None]
53
+
54
+
55
+ @dataclass
56
+ class Rule:
57
+ """A single usage rule for one (year, message type)."""
58
+
59
+ rule_number: str
60
+ name: str
61
+ description: str
62
+ severity: Severity = Severity.VIOLATION
63
+ # When None the rule is advisory/catalog-only: it cannot be mechanically
64
+ # checked, so it is surfaced as guidance rather than evaluated.
65
+ check: Optional[Callable[["ParsedMessage"], List[Violation]]] = None
66
+
67
+ @property
68
+ def enforced(self) -> bool:
69
+ return self.check is not None
70
+
71
+ def run(self, msg: "ParsedMessage") -> List[Violation]:
72
+ if self.check is None:
73
+ return []
74
+ return self.check(msg)
75
+
76
+ def to_dict(self) -> dict:
77
+ return {
78
+ "rule_number": self.rule_number,
79
+ "name": self.name,
80
+ "description": self.description,
81
+ "severity": self.severity.value,
82
+ "enforced": self.enforced,
83
+ }
cbpr_rules/py.typed ADDED
File without changes
@@ -0,0 +1,9 @@
1
+ """Vendored ISO reference data (country and currency code lists).
2
+
3
+ Shipped as package data so validation never requires a network call. Sourced
4
+ from ISO 3166-1 alpha-2 and ISO 4217.
5
+ """
6
+ from .countries import ISO3166_ALPHA2
7
+ from .currencies import ISO4217
8
+
9
+ __all__ = ["ISO3166_ALPHA2", "ISO4217"]
@@ -0,0 +1,28 @@
1
+ """ISO 3166-1 alpha-2 country codes (officially assigned)."""
2
+ from __future__ import annotations
3
+
4
+ ISO3166_ALPHA2 = frozenset(
5
+ {
6
+ "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT",
7
+ "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI",
8
+ "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY",
9
+ "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN",
10
+ "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM",
11
+ "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK",
12
+ "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL",
13
+ "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM",
14
+ "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR",
15
+ "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN",
16
+ "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS",
17
+ "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK",
18
+ "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW",
19
+ "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP",
20
+ "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM",
21
+ "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW",
22
+ "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM",
23
+ "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF",
24
+ "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW",
25
+ "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI",
26
+ "VN", "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW",
27
+ }
28
+ )
@@ -0,0 +1,25 @@
1
+ """ISO 4217 active currency codes."""
2
+ from __future__ import annotations
3
+
4
+ ISO4217 = frozenset(
5
+ {
6
+ "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN",
7
+ "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV",
8
+ "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHE", "CHF",
9
+ "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE",
10
+ "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD",
11
+ "FKP", "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD",
12
+ "HNL", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "IRR", "ISK", "JMD",
13
+ "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD",
14
+ "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA",
15
+ "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MXV",
16
+ "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB",
17
+ "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB",
18
+ "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SOS",
19
+ "SRD", "SSP", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND",
20
+ "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "USN", "UYI",
21
+ "UYU", "UYW", "UZS", "VED", "VES", "VND", "VUV", "WST", "XAF", "XAG",
22
+ "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF",
23
+ "XPT", "XSU", "XTS", "XUA", "XXX", "YER", "ZAR", "ZMW", "ZWG", "ZWL",
24
+ }
25
+ )
cbpr_rules/registry.py ADDED
@@ -0,0 +1,107 @@
1
+ """Rule registry and the ``@rule`` / ``advisory`` authoring helpers.
2
+
3
+ Rule modules live under ``cbpr_rules.rules.y<year>.<msgtype>`` and register their
4
+ rules at import time via the decorators below. ``load_rules`` imports the right
5
+ module on demand and returns the rules for a (year, message type).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import importlib
10
+ import pkgutil
11
+ from typing import Callable, Dict, List, Tuple
12
+
13
+ from .models import Rule, Severity, Violation
14
+
15
+ # (year, message_type) -> list[Rule]
16
+ _REGISTRY: Dict[Tuple[int, str], List[Rule]] = {}
17
+ _LOADED: set = set()
18
+ # module name -> import error message, for modules that failed to load
19
+ IMPORT_ERRORS: Dict[str, str] = {}
20
+
21
+
22
+ def _key(year: int, msgtype: str) -> Tuple[int, str]:
23
+ return (int(year), msgtype)
24
+
25
+
26
+ def register(year: int, msgtype: str, rule: Rule) -> None:
27
+ _REGISTRY.setdefault(_key(year, msgtype), []).append(rule)
28
+
29
+
30
+ def rule(
31
+ msgtype: str,
32
+ year: int,
33
+ number: str,
34
+ name: str,
35
+ description: str,
36
+ severity: Severity = Severity.VIOLATION,
37
+ ) -> Callable:
38
+ """Decorate a ``fn(msg, report)`` checker and register it.
39
+
40
+ The wrapped function receives the ParsedMessage and a ``report`` callback.
41
+ Call ``report(element, detail=None)`` for each finding; the element supplies
42
+ the xpath and line number, and the rule supplies number/name/description.
43
+ """
44
+ rule_id = f"{msgtype}:{number}"
45
+
46
+ def decorator(fn: Callable) -> Callable:
47
+ def check(msg) -> List[Violation]:
48
+ findings: List[Violation] = []
49
+
50
+ def report(element=None, detail=None, severity_override=None):
51
+ findings.append(
52
+ Violation(
53
+ rule_number=rule_id,
54
+ name=name,
55
+ description=description,
56
+ detail=detail or "",
57
+ found=msg.snippet_of(element) if element is not None else "",
58
+ xpath=msg.xpath_of(element) if element is not None else "",
59
+ line=msg.line_of(element) if element is not None else None,
60
+ severity=severity_override or severity,
61
+ )
62
+ )
63
+
64
+ fn(msg, report)
65
+ return findings
66
+
67
+ register(year, msgtype, Rule(rule_id, name, description, severity, check))
68
+ return fn
69
+
70
+ return decorator
71
+
72
+
73
+ def advisory(msgtype: str, year: int, number: str, name: str, description: str) -> None:
74
+ """Register an advisory (non-mechanizable) rule, surfaced as guidance only."""
75
+ rule_id = f"{msgtype}:{number}"
76
+ register(year, msgtype, Rule(rule_id, name, description, Severity.INFO, check=None))
77
+
78
+
79
+ def _discover(year: int) -> None:
80
+ """Import every rule module for a year so its rules register."""
81
+ if year in _LOADED:
82
+ return
83
+ pkg_name = f"{__package__}.rules.y{year}"
84
+ try:
85
+ pkg = importlib.import_module(pkg_name)
86
+ except ModuleNotFoundError:
87
+ _LOADED.add(year)
88
+ return
89
+ for mod in pkgutil.iter_modules(pkg.__path__):
90
+ if mod.name.startswith("_"):
91
+ continue
92
+ full = f"{pkg_name}.{mod.name}"
93
+ try:
94
+ importlib.import_module(full)
95
+ except Exception as exc: # a broken rule module must not break the rest
96
+ IMPORT_ERRORS[full] = f"{type(exc).__name__}: {exc}"
97
+ _LOADED.add(year)
98
+
99
+
100
+ def load_rules(year: int, msgtype: str) -> List[Rule]:
101
+ _discover(int(year))
102
+ return list(_REGISTRY.get(_key(year, msgtype), []))
103
+
104
+
105
+ def available_message_types(year: int) -> List[str]:
106
+ _discover(int(year))
107
+ return sorted({mt for (yr, mt) in _REGISTRY if yr == int(year)})
@@ -0,0 +1 @@
1
+ """Hand-authored usage rules, organised as ``y<year>.<message_type>`` modules."""
@@ -0,0 +1 @@
1
+ """CBPR+ SR2025 usage rules."""
@@ -0,0 +1,224 @@
1
+ """CBPR+ SR2025 usage rules for camt.052.001.08 (BankToCustomerAccountReport).
2
+
3
+ Rule numbers, names and descriptions are taken from the published usage
4
+ guideline's Rules sheet; XML paths are the short ISO 20022 tags from its
5
+ Full_View / XML Path column. Formal rules are implemented with shared
6
+ combinators from ``helpers`` (or bespoke ``fn(msg, report)`` where the shape is
7
+ cross-schema); mechanizable textual rules are enforced; the remaining textual
8
+ rules are surfaced as advisory guidance. Algorithmic field validations
9
+ (VAL-*) are added for the data types that occur in this message.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from ...registry import advisory, rule
14
+ from ...validators import is_valid_bic, is_valid_country, is_valid_currency, is_valid_lei
15
+ from ...helpers import (
16
+ amount_equals_sum,
17
+ business_msg_id_carries_group_id,
18
+ each_value_valid,
19
+ header_msg_def_id_matches,
20
+ not_matching_pattern,
21
+ requires_if_present,
22
+ same_value,
23
+ )
24
+
25
+ MT = "camt.052"
26
+ YEAR = 2025
27
+ ROOT = "/Document/BkToCstmrAcctRpt"
28
+ RPT = ROOT + "/Rpt"
29
+
30
+
31
+ def reg(number: str, name: str, description: str, check) -> None:
32
+ """Register a combinator-built check as a rule."""
33
+ rule(MT, YEAR, number, name, description)(check)
34
+
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Formal rules
38
+ # ---------------------------------------------------------------------------
39
+
40
+ reg("R1", "CBPR_Copy_Duplicate_FormalRule",
41
+ "If Copy Duplicate indicator is used in the Business Application Header, it "
42
+ "must be identical to the Copy Duplicate indicator in the business document "
43
+ "(if the latter is present).",
44
+ same_value("/AppHdr/CpyDplct", RPT + "/CpyDplctInd"))
45
+
46
+ reg("R12", "CBPR_Party_Name_Postal_Address_FormalRule",
47
+ "If Postal Address is present then Name is mandatory.",
48
+ requires_if_present(RPT + "/Acct/Ownr", "PstlAdr", "Nm"))
49
+
50
+ reg("R17", "CBPR_Original_Instruction_Identification_FormalRule",
51
+ "This field must not start or end with a slash '/' and must not contain two "
52
+ "consecutive slashes '//'.",
53
+ not_matching_pattern(RPT + "/Ntry/NtryDtls/TxDtls/Refs/InstrId",
54
+ r"(/.*)|(.*/)|(.*//.*)"))
55
+
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # Mechanizable textual rule (enforced)
59
+ # ---------------------------------------------------------------------------
60
+
61
+ reg("R6", "CBPR_Business_Service_Usage_TextualRule",
62
+ 'The value "swift.cbprplus.03" must be used.',
63
+ lambda msg, report: [
64
+ report(node, detail=f"BizSvc must be 'swift.cbprplus.03', found '{val}'")
65
+ for node in msg.find("/AppHdr/BizSvc")
66
+ for val in [msg.text_of(node)]
67
+ if val and val != "swift.cbprplus.03"
68
+ ])
69
+
70
+ # Header consistency between the Business Application Header and the Document.
71
+ reg("R3", "CBPR_Business_Message_Identifier_TextualRule",
72
+ "The Business Message Identifier is the unique identifier of the Business Message "
73
+ "instance that is being transported with this header, as defined by the sending "
74
+ "application or system.",
75
+ business_msg_id_carries_group_id())
76
+
77
+ reg("R4", "CBPR_Message_Definition_Identifier_TextualRule",
78
+ "The Message Definition Identifier of the Business Message instance must be formatted "
79
+ "exactly as it appears in the namespace of the Business Message instance.",
80
+ header_msg_def_id_matches())
81
+
82
+ # Interest: total must equal the sum of the per-record amounts (Entry level).
83
+ _INTRST_TTL = RPT + "/Ntry/Intrst/TtlIntrstAndTaxAmt"
84
+ _INTRST_RCRD = RPT + "/Ntry/Intrst/Rcrd/Amt"
85
+
86
+ reg("R16", "CBPR_Interest_TextualRule",
87
+ "Total Charges And Tax Amount must equal the sum of the individual record amounts.",
88
+ amount_equals_sum(_INTRST_TTL, _INTRST_RCRD))
89
+
90
+ reg("R20", "CBPR_Interest_TextualRule",
91
+ "Total Charges And Tax Amount must equal the sum of the individual record amounts.",
92
+ amount_equals_sum(_INTRST_TTL, _INTRST_RCRD))
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Algorithmic field validation (VAL-*), only for data types present here.
97
+ # ---------------------------------------------------------------------------
98
+
99
+ # Every FinInstnId/BICFI anywhere in the report (Account Servicer + related agents)
100
+ _BIC_PATHS = (
101
+ RPT + "/Acct/Svcr/FinInstnId/BICFI",
102
+ RPT + "/Ntry/Chrgs/Rcrd/Agt/FinInstnId/BICFI",
103
+ RPT + "/Ntry/NtryDtls/TxDtls/Chrgs/Rcrd/Agt/FinInstnId/BICFI",
104
+ )
105
+
106
+
107
+ def _val_bic(msg, report):
108
+ for p in _BIC_PATHS:
109
+ for node in msg.find(p):
110
+ val = msg.text_of(node)
111
+ if val and not is_valid_bic(val):
112
+ report(node, detail=f"invalid BIC: '{val}'")
113
+
114
+
115
+ reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
116
+ "Every FinInstitution BICFI must be a structurally valid ISO 9362 BIC.",
117
+ _val_bic)
118
+
119
+
120
+ reg("VAL-LEI", "CBPR_Valid_LEI",
121
+ "Account Owner / Account Servicer LEI must be a structurally valid ISO 17442 LEI.",
122
+ each_value_valid(RPT + "/Acct/Svcr/FinInstnId/LEI", is_valid_lei, "LEI"))
123
+
124
+
125
+ def _val_ctry(msg, report):
126
+ for node in msg.find(RPT + "/Acct/Svcr/FinInstnId/PstlAdr/Ctry"):
127
+ val = msg.text_of(node)
128
+ if val and not is_valid_country(val):
129
+ report(node, detail=f"invalid country '{val}'")
130
+ for node in msg.find(RPT + "/Acct/Ownr/PstlAdr/Ctry"):
131
+ val = msg.text_of(node)
132
+ if val and not is_valid_country(val):
133
+ report(node, detail=f"invalid country '{val}'")
134
+
135
+
136
+ reg("VAL-CTRY", "CBPR_Valid_Country",
137
+ "Every PostalAddress Country must be a valid ISO 3166 alpha-2 code.",
138
+ _val_ctry)
139
+
140
+
141
+ def _val_ccy(msg, report):
142
+ for p in (RPT + "/Bal/Amt", RPT + "/Ntry/Amt"):
143
+ for el, ccy in msg.attr_nodes(p, "Ccy"):
144
+ if ccy and not is_valid_currency(ccy):
145
+ report(el, detail=f"invalid currency '{ccy}'")
146
+
147
+
148
+ reg("VAL-CCY", "CBPR_Valid_Amount_Currency",
149
+ "Balance and Entry amount currency must be a valid ISO 4217 code.",
150
+ _val_ccy)
151
+
152
+
153
+ # ---------------------------------------------------------------------------
154
+ # Advisory textual rules (not mechanically enforceable - surfaced as guidance)
155
+ # ---------------------------------------------------------------------------
156
+ _ADVISORY = {
157
+ "R2": ("CBPR_Character_Set_Usage_TextualRule",
158
+ "For further description on the usage of the field, pls refer to the CBPR Plus UHB."),
159
+ "R5": ("CBPR_Business_Service_TextualRule",
160
+ "This field may be used by SWIFT to support differentiated processing on "
161
+ "SWIFT-administered services such as FINplus."),
162
+ "R7": ("CBPR_Market_Practice_TextualRule",
163
+ "This field may be used by SWIFT on SWIFT-administered services. A user-specific value "
164
+ "may be used, but please contact your Service Administrator."),
165
+ "R8": ("CBPR_Related_Business_Application_Header_TextualRule",
166
+ "If used, the Related BAH must transport the exact same information as in the BAH of "
167
+ "the related message."),
168
+ "R9": ("CBPR_Related_BAH_Business_Service_TextualRule",
169
+ "If related BAH is present, it should transport the element Business Service."),
170
+ "R10": ("CBPR_Electronic_Sequence_Number_TextualRule",
171
+ "For intra-day report: sequential number of the report, assigned by the account "
172
+ "servicer, increased incrementally by 1 for each report sent electronically."),
173
+ "R11": ("CBPR_Copy_Duplicate_Indicator_TextualRule",
174
+ "If applicable, for Copy or Duplicate, the electronic sequence and legal sequence "
175
+ "must be the same as the original report."),
176
+ "R13": ("CBPR_Intraday_Balance_Recommendation_TextualRule",
177
+ "Every camt.052 message which includes Entry items should include Intraday Booked "
178
+ "(ITBD) and Intraday Available (ITAV) balances."),
179
+ "R14": ("CBPR_Domain_Proprietary_Recommendation_TextualRule",
180
+ "BankTransactionCode/Domain/Code is the preferred option and should be used when "
181
+ "possible."),
182
+ "R15": ("CBPR_Charges_TextualRule",
183
+ "Total Charges And Tax Amount must equal the sum of the individual record amounts."),
184
+ "R18": ("CBPR_UETR_TextualRule",
185
+ "If the underlying transaction contains/owns a UETR then it should be reported in the "
186
+ "camt.052 message."),
187
+ "R19": ("CBPR_Charges_TextualRule",
188
+ "Total Charges And Tax Amount must equal the sum of the individual record amounts."),
189
+ "R21": ("CBPR_Initiating_Party_TextualRule",
190
+ "Party initiating the payment to an agent. In the payment context this can be the "
191
+ "debtor, the creditor, or a party that initiates the payment on their behalf."),
192
+ "R22": ("CBPR_Debtor_TextualRule",
193
+ "For outward payments, report if different from account owner. For inward payments, "
194
+ "report where available. When ReversalIndicator is TRUE, the Creditor and Debtor must "
195
+ "be the same as the Creditor and Debtor of the original entry."),
196
+ "R23": ("CBPR_Debtor_Account_TextualRule",
197
+ "For inward payment, report where available. Conditional on the country regulatory "
198
+ "requirement. If IBAN is available populate the IBAN tag, else populate Other."),
199
+ "R24": ("CBPR_Ultimate_Debtor_TextualRule",
200
+ "When ReversalIndicator is TRUE, the Ultimate Creditor and Ultimate Debtor must be the "
201
+ "same as the Ultimate Creditor and Ultimate Debtor of the original entry."),
202
+ "R25": ("CBPR_Creditor_TextualRule",
203
+ "For outward payment, report where available. When ReversalIndicator is TRUE, the "
204
+ "Creditor and Debtor must be the same as the Creditor and Debtor of the original entry."),
205
+ "R26": ("CBPR_Creditor_Account_TextualRule",
206
+ "For outward payment, report where available. If IBAN is available populate the IBAN "
207
+ "tag, else populate Other."),
208
+ "R27": ("CBPR_Ultimate_Creditor_TextualRule",
209
+ "Ultimate party to which an amount of money is due. When ReversalIndicator is TRUE, "
210
+ "the Ultimate Creditor and Ultimate Debtor must be the same as in the original entry."),
211
+ "R28": ("CBPR_Debtor_Agent_TextualRule",
212
+ "One of the following must be provided - BIC or Clearing System Member or Name. When "
213
+ "ReversalIndicator is TRUE, the Creditor Agent and Debtor Agent must be the same as "
214
+ "the Creditor Agent and Debtor Agent of the original entry."),
215
+ "R29": ("CBPR_Creditor_Agent_TextualRule",
216
+ "When ReversalIndicator is TRUE, the Creditor Agent and Debtor Agent must be the same "
217
+ "as the Creditor Agent and Debtor Agent of the original entry."),
218
+ "R30": ("CBPR_Remittance_Rules_TextualRule",
219
+ "Use of Structured Remittance must be bilaterally or multilaterally agreed. Structured "
220
+ "Remittance can be repeated, however the total business data for all occurrences "
221
+ "(excluding tags) must not exceed 9,000 characters."),
222
+ }
223
+ for _num, (_name, _desc) in _ADVISORY.items():
224
+ advisory(MT, YEAR, _num, _name, _desc)