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.
- cbpr_rules/__init__.py +21 -0
- cbpr_rules/cli.py +176 -0
- cbpr_rules/engine.py +100 -0
- cbpr_rules/helpers.py +420 -0
- cbpr_rules/loader.py +77 -0
- cbpr_rules/message.py +170 -0
- cbpr_rules/models.py +83 -0
- cbpr_rules/py.typed +0 -0
- cbpr_rules/reference/__init__.py +9 -0
- cbpr_rules/reference/countries.py +28 -0
- cbpr_rules/reference/currencies.py +25 -0
- cbpr_rules/registry.py +107 -0
- cbpr_rules/rules/__init__.py +1 -0
- cbpr_rules/rules/y2025/__init__.py +1 -0
- cbpr_rules/rules/y2025/camt_052.py +224 -0
- cbpr_rules/rules/y2025/camt_054.py +176 -0
- cbpr_rules/rules/y2025/pacs_002.py +212 -0
- cbpr_rules/rules/y2025/pacs_004.py +831 -0
- cbpr_rules/rules/y2025/pacs_008.py +375 -0
- cbpr_rules/rules/y2025/pacs_008_stp.py +367 -0
- cbpr_rules/rules/y2025/pacs_009.py +273 -0
- cbpr_rules/rules/y2025/pacs_009_adv.py +255 -0
- cbpr_rules/rules/y2025/pacs_009_cov.py +358 -0
- cbpr_rules/rules/y2025/pain_001.py +306 -0
- cbpr_rules/rules/y2026/__init__.py +1 -0
- cbpr_rules/rules/y2026/camt_052.py +191 -0
- cbpr_rules/rules/y2026/camt_054.py +182 -0
- cbpr_rules/rules/y2026/pacs_002.py +208 -0
- cbpr_rules/rules/y2026/pacs_004.py +491 -0
- cbpr_rules/rules/y2026/pacs_008.py +377 -0
- cbpr_rules/rules/y2026/pacs_008_stp.py +369 -0
- cbpr_rules/rules/y2026/pacs_009.py +260 -0
- cbpr_rules/rules/y2026/pacs_009_adv.py +256 -0
- cbpr_rules/rules/y2026/pacs_009_cov.py +324 -0
- cbpr_rules/rules/y2026/pain_001.py +272 -0
- cbpr_rules/schema.py +97 -0
- cbpr_rules/validators/__init__.py +16 -0
- cbpr_rules/validators/bic.py +21 -0
- cbpr_rules/validators/country.py +11 -0
- cbpr_rules/validators/currency.py +11 -0
- cbpr_rules/validators/iban.py +26 -0
- cbpr_rules/validators/lei.py +17 -0
- cbpr_usage_rules-0.1.0.dist-info/METADATA +335 -0
- cbpr_usage_rules-0.1.0.dist-info/RECORD +48 -0
- cbpr_usage_rules-0.1.0.dist-info/WHEEL +5 -0
- cbpr_usage_rules-0.1.0.dist-info/entry_points.txt +2 -0
- cbpr_usage_rules-0.1.0.dist-info/licenses/LICENSE +21 -0
- cbpr_usage_rules-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""CBPR+ SR2025 usage rules for pacs.008.001.08 STP (FIToFICustomerCreditTransfer).
|
|
2
|
+
|
|
3
|
+
Mirrors the reference module ``pacs_008``: each Rules-sheet R-index is registered
|
|
4
|
+
with its real number, name token and description, implemented either with a shared
|
|
5
|
+
combinator from ``helpers`` or a bespoke ``fn(msg, report)`` for cross-field logic.
|
|
6
|
+
Textual rules that are mechanizable are enforced; the rest are advisory.
|
|
7
|
+
|
|
8
|
+
Rule numbers and text are taken from the published STP usage guideline's Rules
|
|
9
|
+
sheet; XML paths are the short ISO 20022 tags from its translated XML Path column.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re as _re
|
|
14
|
+
|
|
15
|
+
from ...registry import advisory, rule
|
|
16
|
+
from ...validators import is_valid_bic, is_valid_country, is_valid_currency
|
|
17
|
+
from ...helpers import (
|
|
18
|
+
address_hybrid,
|
|
19
|
+
address_lines_max_length,
|
|
20
|
+
bic_presence_exclusive,
|
|
21
|
+
business_msg_id_carries_group_id,
|
|
22
|
+
charges_required_when_amounts_differ,
|
|
23
|
+
code_in,
|
|
24
|
+
each_value_valid,
|
|
25
|
+
header_msg_def_id_matches,
|
|
26
|
+
mutually_exclusive,
|
|
27
|
+
no_postal_address_duplication,
|
|
28
|
+
not_matching_pattern,
|
|
29
|
+
presence_together,
|
|
30
|
+
required_when_absent,
|
|
31
|
+
requires_if_present,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
MT = "pacs.008_stp"
|
|
35
|
+
YEAR = 2025
|
|
36
|
+
ROOT = "/Document/FIToFICstmrCdtTrf"
|
|
37
|
+
TX = ROOT + "/CdtTrfTxInf"
|
|
38
|
+
|
|
39
|
+
# Repeated rule descriptions (identical across the locations they apply to).
|
|
40
|
+
D_PARTY_NAME_ADR = "If Postal Address is present then Name is mandatory."
|
|
41
|
+
D_PARTY_ANY_BIC = (
|
|
42
|
+
"If AnyBIC is absent then Name is mandatory and it is recommended to also "
|
|
43
|
+
"provide the Postal Address."
|
|
44
|
+
)
|
|
45
|
+
D_GRACE_STRUCT = (
|
|
46
|
+
"If Postal Address is used, and if Address Line is absent, then Town Name "
|
|
47
|
+
"and Country must be present."
|
|
48
|
+
)
|
|
49
|
+
D_GRACE_HYBRID = (
|
|
50
|
+
"If Address Line is present and any other Postal Address element(s) are "
|
|
51
|
+
"present, then Town Name and Country are mandatory in Postal Address and a "
|
|
52
|
+
"maximum of two occurrences of Address Line are allowed."
|
|
53
|
+
)
|
|
54
|
+
D_GRACE_UNSTRUCT = (
|
|
55
|
+
"If Postal Address is present and if no other element than AddressLine is "
|
|
56
|
+
"present then every occurrence of Address line must no exceed 35 characters."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reg(number: str, name: str, description: str, check) -> None:
|
|
61
|
+
"""Register a combinator-built check as a rule."""
|
|
62
|
+
rule(MT, YEAR, number, name, description)(check)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _party_block(party_path: str, n_name_adr: str, name_token: str,
|
|
66
|
+
n_struct=None, n_hybrid=None, n_unstruct=None) -> None:
|
|
67
|
+
reg(n_name_adr, name_token, D_PARTY_NAME_ADR,
|
|
68
|
+
requires_if_present(party_path, "PstlAdr", "Nm"))
|
|
69
|
+
if n_struct:
|
|
70
|
+
pstl = party_path + "/PstlAdr"
|
|
71
|
+
reg(n_struct, "CBPR_GracePeriod_Structured_FormalRule", D_GRACE_STRUCT,
|
|
72
|
+
required_when_absent(pstl, "AdrLine", ["TwnNm", "Ctry"]))
|
|
73
|
+
reg(n_hybrid, "CBPR_GracePeriod_Hybrid_FormalRule", D_GRACE_HYBRID,
|
|
74
|
+
address_hybrid(pstl))
|
|
75
|
+
reg(n_unstruct, "CBPR_GracePeriod_Unstructured_FormalRule", D_GRACE_UNSTRUCT,
|
|
76
|
+
address_lines_max_length(pstl, 35))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# Bespoke cross-field / cross-schema rules
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
def _values_match(msg, report, path_a, path_b, label):
|
|
84
|
+
a_nodes = msg.find(path_a)
|
|
85
|
+
if not a_nodes:
|
|
86
|
+
return
|
|
87
|
+
b_vals = {msg.text_of(n) for n in msg.find(path_b)}
|
|
88
|
+
if not b_vals:
|
|
89
|
+
return
|
|
90
|
+
a_vals = {msg.text_of(n) for n in a_nodes}
|
|
91
|
+
if a_vals != b_vals:
|
|
92
|
+
report(a_nodes[0], detail=label)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@rule(MT, YEAR, "R1", "CBPR_Priority_Instruction_Priority_FormalRule",
|
|
96
|
+
'If "Priority" is used in the BAH for pacs messages, the value should be '
|
|
97
|
+
'identical to the one in the "Payment Type Information/InstructionPriority" if present.')
|
|
98
|
+
def _r1(msg, report):
|
|
99
|
+
if msg.present("/AppHdr/Prty") and msg.present(TX + "/PmtTpInf/InstrPrty"):
|
|
100
|
+
_values_match(msg, report, "/AppHdr/Prty", TX + "/PmtTpInf/InstrPrty",
|
|
101
|
+
"BAH Priority must equal InstructionPriority")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_BIC_PAIRS = [
|
|
105
|
+
("/AppHdr/Fr/FIId/FinInstnId/BICFI", TX + "/InstgAgt/FinInstnId/BICFI", "From vs Instructing Agent"),
|
|
106
|
+
("/AppHdr/To/FIId/FinInstnId/BICFI", TX + "/InstdAgt/FinInstnId/BICFI", "To vs Instructed Agent"),
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@rule(MT, YEAR, "R2", "CBPR_From_To_Instructing_Instructed_Agent_BIC_1_FormalRule",
|
|
111
|
+
'BAH "From" BIC must match "Instructing Agent" BIC, except where BAH '
|
|
112
|
+
'CopyDuplicate = COPY or = CODU BAH "To" BIC must match "Instructed Agent" '
|
|
113
|
+
"BIC, except where BAH CopyDuplicate = COPY or = CODU")
|
|
114
|
+
def _r2(msg, report):
|
|
115
|
+
if any(v in {"COPY", "CODU"} for v in msg.values("/AppHdr/CpyDplct")):
|
|
116
|
+
return
|
|
117
|
+
for a, b, label in _BIC_PAIRS:
|
|
118
|
+
_values_match(msg, report, a, b, label)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@rule(MT, YEAR, "R3", "CBPR_From_To_Instructing_Instructed_Agent_BIC_2_FormalRule",
|
|
122
|
+
'BAH "From" BIC must match "Instructing Agent" BIC if CopyDuplicate is '
|
|
123
|
+
'absent. BAH "To" BIC must match "Instructed Agent" BIC if CopyDuplicate is absent.')
|
|
124
|
+
def _r3(msg, report):
|
|
125
|
+
if not msg.absent("/AppHdr/CpyDplct"):
|
|
126
|
+
return
|
|
127
|
+
for a, b, label in _BIC_PAIRS:
|
|
128
|
+
_values_match(msg, report, a, b, label)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
reg("R12", "CBPR_Related_Remit_Info_Remit_Info_Mutually_Exclusive_FormalRule",
|
|
132
|
+
"In the interbank space, Related Remittance Information and Remittance "
|
|
133
|
+
"Information are mutually exclusive and all may be absent.",
|
|
134
|
+
mutually_exclusive(TX, ["RltdRmtInf", "RmtInf"]))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@rule(MT, YEAR, "R13", "CBPR_CRED_FormalRule",
|
|
138
|
+
"Charge information is mandatory if CRED is present – if no charges are "
|
|
139
|
+
'taken, Zero must be used in "Amount" (any agent in the payment chain).')
|
|
140
|
+
def _r13(msg, report):
|
|
141
|
+
for tx in msg.each(TX):
|
|
142
|
+
cb = msg.values("ChrgBr", tx)
|
|
143
|
+
if cb and all(v == "CRED" for v in cb) and msg.absent("ChrgsInf", tx):
|
|
144
|
+
report(tx, detail="ChargesInformation required when ChargeBearer is CRED")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# R14-R18: domestic "country couple" rules. When both Debtor Agent and Creditor
|
|
148
|
+
# Agent BICs fall in the listed countries (positions 5-6 of the BIC), the Debtor
|
|
149
|
+
# and Creditor must both carry a Name and an Account/IBAN.
|
|
150
|
+
def _country_couple(number: str, name_token: str, desc: str, countries):
|
|
151
|
+
rx = _re.compile(r".{4}(" + "|".join(countries) + r").*")
|
|
152
|
+
|
|
153
|
+
def check(msg, report):
|
|
154
|
+
for tx in msg.each(TX):
|
|
155
|
+
dbt_agt = msg.values("DbtrAgt/FinInstnId/BICFI", tx)
|
|
156
|
+
cdt_agt = msg.values("CdtrAgt/FinInstnId/BICFI", tx)
|
|
157
|
+
if not dbt_agt or not cdt_agt:
|
|
158
|
+
return
|
|
159
|
+
if not all(rx.fullmatch(v) for v in dbt_agt):
|
|
160
|
+
continue
|
|
161
|
+
if not all(rx.fullmatch(v) for v in cdt_agt):
|
|
162
|
+
continue
|
|
163
|
+
ok = (msg.present("Dbtr/Nm", tx) and msg.present("Cdtr/Nm", tx)
|
|
164
|
+
and msg.present("DbtrAcct/Id/IBAN", tx)
|
|
165
|
+
and msg.present("CdtrAcct/Id/IBAN", tx))
|
|
166
|
+
if not ok:
|
|
167
|
+
report(tx, detail="Debtor/Creditor Name and Account/IBAN required for domestic country couple")
|
|
168
|
+
|
|
169
|
+
rule(MT, YEAR, number, name_token, desc)(check)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
_CC_DESC = (
|
|
173
|
+
"Transactions exchanged within these country couples are considered as "
|
|
174
|
+
"domestic ones. IF Creditor Agent and Debtor Agent BICs are part of "
|
|
175
|
+
"following countries: {cc} Then: Debtor and Creditor must be identified "
|
|
176
|
+
"using a Name and the Account/IBAN."
|
|
177
|
+
)
|
|
178
|
+
_country_couple("R14", "CBPR_Debtor_Creditor_ES/AD_FormalRule",
|
|
179
|
+
_CC_DESC.format(cc="ES, AD"), ["ES", "AD"])
|
|
180
|
+
_country_couple("R15", "CBPR_Debtor_Creditor_FR/MC_FormalRule",
|
|
181
|
+
_CC_DESC.format(cc="FR, MC"), ["FR", "MC"])
|
|
182
|
+
_country_couple("R16", "CBPR_Debtor_Creditor_IT/SM_FormalRule",
|
|
183
|
+
_CC_DESC.format(cc="IT, SM"), ["IT", "SM"])
|
|
184
|
+
_country_couple("R17", "CBPR_Debtor_Creditor_IT/VA_FormalRule",
|
|
185
|
+
_CC_DESC.format(cc="IT, VA"), ["IT", "VA"])
|
|
186
|
+
|
|
187
|
+
_IBAN_COUNTRIES = [
|
|
188
|
+
"AT", "BE", "BG", "BV", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", "FR",
|
|
189
|
+
"GB", "GF", "GI", "GP", "GR", "HR", "HU", "IE", "IS", "IT", "LI", "LT",
|
|
190
|
+
"LU", "LV", "MQ", "MT", "NL", "NO", "PL", "PM", "PT", "RE", "RO", "SE",
|
|
191
|
+
"SI", "SJ", "SK",
|
|
192
|
+
]
|
|
193
|
+
_country_couple(
|
|
194
|
+
"R18", "CBPR_Debtor_Creditor_IBAN_FormalRule",
|
|
195
|
+
"IF Creditor Agent and Debtor Agent BICs are part of following countries: "
|
|
196
|
+
"AT, BE, BG, BV, CY, CZ, DE, DK, EE, ES, FI, FR, GB, GF, GI, GP, GR, HR, "
|
|
197
|
+
"HU, IE, IS, IT, LI, LT, LU, LV, MQ (FR), MT, NL, NO, PL, PM (FR), PT, RE "
|
|
198
|
+
"(FR), RO, SE, SI, SJ, SK Then: Debtor and Creditor must be identified "
|
|
199
|
+
"using a Name and the Account/IBAN.",
|
|
200
|
+
_IBAN_COUNTRIES)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@rule(MT, YEAR, "R19", "CBPR_DEBT_FormalRule",
|
|
204
|
+
'If "Charge Bearer/DEBT" is present, then only one occurrence of '
|
|
205
|
+
'"Charge Information" is allowed.')
|
|
206
|
+
def _r19(msg, report):
|
|
207
|
+
for tx in msg.each(TX):
|
|
208
|
+
if "DEBT" in msg.values("ChrgBr", tx) and len(msg.find("ChrgsInf", tx)) > 1:
|
|
209
|
+
report(tx, detail="only one ChargesInformation allowed when ChargeBearer is DEBT")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
reg("R20", "CBPR_Instruction_Identification_FormalRule",
|
|
213
|
+
"This field must not start or end with a slash '/' and must not contain two "
|
|
214
|
+
"consecutive slashes '//'.",
|
|
215
|
+
not_matching_pattern(TX + "/PmtId/InstrId", r"(/.*)|(.*/)|(.*//.*)"))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@rule(MT, YEAR, "R21", "CBPR_Interbank_Settlement_Currency_FormalRule",
|
|
219
|
+
"The codes XAU, XAG, XPD and XPT are not allowed, as these are codes are "
|
|
220
|
+
"only used for commodities.")
|
|
221
|
+
def _r21(msg, report):
|
|
222
|
+
for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy"):
|
|
223
|
+
if ccy in {"XAU", "XAG", "XPD", "XPT"}:
|
|
224
|
+
report(el, detail=f"commodity currency '{ccy}' not allowed")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# Parties (name/postal + grace period blocks where applicable)
|
|
228
|
+
_party_block(TX + "/UltmtDbtr", "R28", "CBPR_Party_Name_Postal_Address_FormalRule")
|
|
229
|
+
_party_block(TX + "/InitgPty", "R30", "CBPR_Party_Name_Postal_Address_FormalRule")
|
|
230
|
+
_party_block(TX + "/Dbtr", "R34", "CBPR_Party_Name_Postal_Address_FormalRule",
|
|
231
|
+
"R37", "R38", "R39")
|
|
232
|
+
_party_block(TX + "/Cdtr", "R40", "CBPR_Party_Name_Postal_Address_FormalRule",
|
|
233
|
+
"R46", "R47", "R48")
|
|
234
|
+
_party_block(TX + "/UltmtCdtr", "R51", "CBPR_Name_Postal_Address_FormalRule")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _party_any_bic(number: str, party: str) -> None:
|
|
238
|
+
reg(number, "CBPR_Party_Name_Any_BIC_FormalRule", D_PARTY_ANY_BIC,
|
|
239
|
+
required_when_absent(party, "Id/OrgId/AnyBIC", ["Nm"]))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
_party_any_bic("R35", TX + "/Dbtr")
|
|
243
|
+
_party_any_bic("R44", TX + "/Cdtr")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# Mechanizable textual rules + algorithmic field validation
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
reg("R8", "CBPR_Business_Service_Usage_TextualRule",
|
|
250
|
+
'The value "swift.cbprplus.stp.03" must be used.',
|
|
251
|
+
code_in("/AppHdr/BizSvc", ["swift.cbprplus.stp.03"]))
|
|
252
|
+
|
|
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
|
+
reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
|
|
263
|
+
"Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
|
|
264
|
+
each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
|
|
265
|
+
|
|
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
|
+
|
|
271
|
+
# ---------------------------------------------------------------------------
|
|
272
|
+
# Promoted from advisory: mechanizable textual rules now enforced.
|
|
273
|
+
# ---------------------------------------------------------------------------
|
|
274
|
+
reg("R5", "CBPR_Business_Message_Identifier_TextualRule",
|
|
275
|
+
"The Business Message Identifier is the unique identifier of the Business Message instance "
|
|
276
|
+
"that is being transported with this header, as defined by the sending application or system. "
|
|
277
|
+
"Must contain the Message Identification element from the Group Header of the underlying "
|
|
278
|
+
"message, where available.",
|
|
279
|
+
business_msg_id_carries_group_id())
|
|
280
|
+
|
|
281
|
+
reg("R6", "CBPR_Message_Definition_Identifier_TextualRule",
|
|
282
|
+
"The Message Definition Identifier of the Business Message instance that is being transported "
|
|
283
|
+
"with this header. In general, it must be formatted exactly as it appears in the namespace of "
|
|
284
|
+
"the Business Message instance.",
|
|
285
|
+
header_msg_def_id_matches())
|
|
286
|
+
|
|
287
|
+
reg("R22", "CBPR_DEBT_Rule_1_TextualRule",
|
|
288
|
+
"If Instructed amount and Interbank Settlement Amount are expressed in the same currency: "
|
|
289
|
+
"If Charge Bearer/DEBT is used then Charge Information is only mandatory in case of prepaid "
|
|
290
|
+
"charges (that is if Interbank Settlement Amount is higher than Instructed Amount) and in "
|
|
291
|
+
"that case zero amount is not allowed.",
|
|
292
|
+
charges_required_when_amounts_differ(TX, "InstdAmt", "IntrBkSttlmAmt", "ChrgsInf"))
|
|
293
|
+
|
|
294
|
+
reg("R29", "CBPR_Duplication_Postal_Address_TextualRule",
|
|
295
|
+
"Data present in structured elements within the Postal Address must not, under any circumstances "
|
|
296
|
+
"be repeated in AddressLine.",
|
|
297
|
+
no_postal_address_duplication())
|
|
298
|
+
|
|
299
|
+
reg("R36", "CBPR_Debtor_BIC_Presence_TextualRule",
|
|
300
|
+
"If Any BIC is present, then (Name and Postal Address) is NOT allowed (other elements remain "
|
|
301
|
+
"optional) - However, in case of conflicting information, AnyBIC will always take precedence.",
|
|
302
|
+
bic_presence_exclusive(TX + "/Dbtr"))
|
|
303
|
+
|
|
304
|
+
reg("R45", "CBPR_Creditor_BIC_Presence_TextualRule",
|
|
305
|
+
"If Any BIC is present, then (Name and Postal Address) is NOT allowed (other elements remain "
|
|
306
|
+
"optional) - However, in case of conflicting information, AnyBIC will always take precedence.",
|
|
307
|
+
bic_presence_exclusive(TX + "/Cdtr"))
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ---------------------------------------------------------------------------
|
|
311
|
+
# Advisory textual rules (not mechanically enforceable - surfaced as guidance)
|
|
312
|
+
# ---------------------------------------------------------------------------
|
|
313
|
+
_ADVISORY = {
|
|
314
|
+
"R4": ("CBPR_Character_Set_Usage_TextualRule",
|
|
315
|
+
"For further description on the usage of the field, pls refer to the CBPR Plus UHB."),
|
|
316
|
+
"R7": ("CBPR_Business_Service_TextualRule",
|
|
317
|
+
"This field may be used by SWIFT to support differentiated processing on SWIFT-administered "
|
|
318
|
+
"services such as FINplus."),
|
|
319
|
+
"R9": ("CBPR_Market_Practice_TextualRule",
|
|
320
|
+
"This field may be used by SWIFT on SWIFT-administered services."),
|
|
321
|
+
"R10": ("CBPR_Related_Business_Application_Header_TextualRule",
|
|
322
|
+
"If used, the Related BAH must transport the exact same information as in the BAH of the related message."),
|
|
323
|
+
"R11": ("CBPR_RelatedBAHBusinessService_TextualRule",
|
|
324
|
+
"If related BAH is present, it should transport the element Business Service."),
|
|
325
|
+
"R23": ("CBPR_DEBT_Rule_2_TextualRule",
|
|
326
|
+
"If Instructed amount and Interbank Settlement Amount are not expressed in the same currency: "
|
|
327
|
+
"If Charge Bearer/DEBT is used then Charge Information is only mandatory in case of prepaid "
|
|
328
|
+
"charges (that is if Interbank Settlement Amount is higher than Instructed Amount WHEN "
|
|
329
|
+
"converted in the same currency) and in that case zero amount is not allowed."),
|
|
330
|
+
"R24": ("CBPR_SHAR_TextualRule",
|
|
331
|
+
"If deduct taken then charge information is mandatory. It is optional for initiator (not taking deduct)."),
|
|
332
|
+
"R25": ("CBPR_Ultimate_Debtor_Option_1_TextualRule",
|
|
333
|
+
"Name AND [(Structured Postal Address) OR (Hybrid Postal Address) with minimum Town Name & "
|
|
334
|
+
"Country - it is recommended to add Post code when available)"),
|
|
335
|
+
"R26": ("CBPR_Ultimate_Debtor_Option_2_TextualRule",
|
|
336
|
+
"Name AND [(Structured Postal Address) OR (Hybrid Postal Address) with minimum Town Name & "
|
|
337
|
+
"Country - it is recommended to add Post code when available] AND (Identification: Private or Organisation)"),
|
|
338
|
+
"R27": ("CBPR_UltimateDebtor_Option_3_Jurisdictions_only_TextualRule",
|
|
339
|
+
"For Jurisdictional transactions, Name and/or Identification (Private or Organisation). "
|
|
340
|
+
"The jurisdictional rules apply only when all agents in the payment chain underly the same jurisdiction."),
|
|
341
|
+
"R31": ("CBPR_Debtor_Option_1_TextualRule",
|
|
342
|
+
"Organisation Identification/AnyBIC AND (Account Number OR Organisation Identification/Other)"),
|
|
343
|
+
"R32": ("CBPR_Debtor_Option_2_TextualRule",
|
|
344
|
+
"Name AND (Unstructured OR [Structured Address with minimum Town Name & Country (+ recommended "
|
|
345
|
+
"to add Postal code when available)]) AND (Account Number OR Identification: Private or Organisation)"),
|
|
346
|
+
"R33": ("CBPR_Debtor_Option_3_Jurisdictions_only_TextualRule",
|
|
347
|
+
"For Jurisdictional transactions, Debtor/Name is mandatory with either Debtor Account OR Debtor "
|
|
348
|
+
"Identification. The jurisdictional rules apply only when all agents in the payment chain underly "
|
|
349
|
+
"the same jurisdiction."),
|
|
350
|
+
"R41": ("CBPR_Creditor_Option_1_TextualRule",
|
|
351
|
+
"Organisation Identification/AnyBIC AND (Account Number OR Organisation Identification/Other)"),
|
|
352
|
+
"R42": ("CBPR_Creditor_Option_2_TextualRule",
|
|
353
|
+
"Name AND (Unstructured OR [Structured Address with minimum Town Name & Country (+ recommended "
|
|
354
|
+
"to add Postal code when available)]) AND (Account Number OR Identification: Private or Organisation)"),
|
|
355
|
+
"R43": ("CBPR_Creditor_Option_3_Jurisdictions_only_TextualRule",
|
|
356
|
+
"For Jurisdictional transactions, Creditor/Name is mandatory with either Creditor Account OR "
|
|
357
|
+
"Creditor Identification. The jurisdictional rules apply only when all agents in the payment "
|
|
358
|
+
"chain underly the same jurisdiction."),
|
|
359
|
+
"R49": ("CBPR_Ultimate_Creditor_Option_1_TextualRule",
|
|
360
|
+
"Name AND [(Structured Postal Address) OR (Hybrid Postal Address) with minimum Town Name & "
|
|
361
|
+
"Country - it is recommended to add Post code when available)]. Other elements are optional."),
|
|
362
|
+
"R50": ("CBPR_UltimateCreditor_Option_2_Jurisdictions_only_TextualRule",
|
|
363
|
+
"For Jurisdictional transactions, Name and/or Identification (Private or Organisation). "
|
|
364
|
+
"The jurisdictional rules apply only when all agents in the payment chain underly the same jurisdiction."),
|
|
365
|
+
}
|
|
366
|
+
for _num, (_name, _desc) in _ADVISORY.items():
|
|
367
|
+
advisory(MT, YEAR, _num, _name, _desc)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""CBPR+ SR2025 usage rules for pacs.009.001.08 (FinancialInstitutionCreditTransfer).
|
|
2
|
+
|
|
3
|
+
Authored against the published usage guideline's Rules sheet. Every R-index is
|
|
4
|
+
registered with its real rule number, name and description; formal rules use the
|
|
5
|
+
shared combinators from ``helpers`` where the shape matches, and bespoke
|
|
6
|
+
``fn(msg, report)`` functions for cross-field / cross-schema logic.
|
|
7
|
+
|
|
8
|
+
Note: in pacs.009 the Debtor and Creditor are financial institutions, so their
|
|
9
|
+
Name/PostalAddress follow the *agent* (present-together) rule via FinInstnId.
|
|
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
|
+
address_hybrid,
|
|
17
|
+
address_lines_max_length,
|
|
18
|
+
business_msg_id_carries_group_id,
|
|
19
|
+
code_in,
|
|
20
|
+
each_value_valid,
|
|
21
|
+
header_msg_def_id_matches,
|
|
22
|
+
no_postal_address_duplication,
|
|
23
|
+
not_matching_pattern,
|
|
24
|
+
presence_together,
|
|
25
|
+
required_when_absent,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
MT = "pacs.009"
|
|
29
|
+
YEAR = 2025
|
|
30
|
+
ROOT = "/Document/FICdtTrf"
|
|
31
|
+
TX = ROOT + "/CdtTrfTxInf"
|
|
32
|
+
|
|
33
|
+
# Repeated rule descriptions (identical across the locations they apply to).
|
|
34
|
+
D_AGENT_NAME_ADR = "Name and Address must always be present together."
|
|
35
|
+
D_GRACE_STRUCT = (
|
|
36
|
+
"If Postal Address is used, and if Address Line is absent, then Town Name "
|
|
37
|
+
"and Country must be present."
|
|
38
|
+
)
|
|
39
|
+
D_GRACE_HYBRID = (
|
|
40
|
+
"If Address Line is present and any other Postal Address element(s) are "
|
|
41
|
+
"present, then Town Name and Country are mandatory in Postal Address and a "
|
|
42
|
+
"maximum of two occurrences of Address Line are allowed."
|
|
43
|
+
)
|
|
44
|
+
D_GRACE_UNSTRUCT = (
|
|
45
|
+
"If Postal Address is present and if no other element than Address Line is "
|
|
46
|
+
"present then every occurrence of Address Line must not exceed 35 characters."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def reg(number: str, name: str, description: str, check) -> None:
|
|
51
|
+
"""Register a combinator-built check as a rule."""
|
|
52
|
+
rule(MT, YEAR, number, name, description)(check)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _agent_block(fin_inst_path: str, n_name, n_struct, n_hybrid, n_unstruct) -> None:
|
|
56
|
+
"""The four rules that recur for each agent: Name+Address + grace period."""
|
|
57
|
+
pstl = fin_inst_path + "/PstlAdr"
|
|
58
|
+
reg(n_name, "CBPR_Agent_Name_Postal_Address_FormalRule", D_AGENT_NAME_ADR,
|
|
59
|
+
presence_together(fin_inst_path, "Nm", "PstlAdr"))
|
|
60
|
+
reg(n_struct, "CBPR_GracePeriod_Structured_FormalRule", D_GRACE_STRUCT,
|
|
61
|
+
required_when_absent(pstl, "AdrLine", ["TwnNm", "Ctry"]))
|
|
62
|
+
reg(n_hybrid, "CBPR_GracePeriod_Hybrid_FormalRule", D_GRACE_HYBRID,
|
|
63
|
+
address_hybrid(pstl))
|
|
64
|
+
reg(n_unstruct, "CBPR_GracePeriod_Unstructured_FormalRule", D_GRACE_UNSTRUCT,
|
|
65
|
+
address_lines_max_length(pstl, 35))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Bespoke cross-field / cross-schema rules
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def _values_match(msg, report, path_a, path_b, label):
|
|
73
|
+
a_nodes = msg.find(path_a)
|
|
74
|
+
if not a_nodes:
|
|
75
|
+
return
|
|
76
|
+
b_vals = {msg.text_of(n) for n in msg.find(path_b)}
|
|
77
|
+
if not b_vals:
|
|
78
|
+
return
|
|
79
|
+
a_vals = {msg.text_of(n) for n in a_nodes}
|
|
80
|
+
if a_vals != b_vals:
|
|
81
|
+
report(a_nodes[0], detail=label)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@rule(MT, YEAR, "R1", "CBPR_Priority_Instruction_Priority_FormalRule",
|
|
85
|
+
'If "Priority" is used in the BAH for pacs messages, the value should be '
|
|
86
|
+
'identical to the one in the "Payment Type Information/InstructionPriority" '
|
|
87
|
+
"if present.")
|
|
88
|
+
def _r1(msg, report):
|
|
89
|
+
if msg.present("/AppHdr/Prty") and msg.present(TX + "/PmtTpInf/InstrPrty"):
|
|
90
|
+
_values_match(msg, report, "/AppHdr/Prty", TX + "/PmtTpInf/InstrPrty",
|
|
91
|
+
"BAH Priority must equal InstructionPriority")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_BIC_PAIRS = [
|
|
95
|
+
("/AppHdr/Fr/FIId/FinInstnId/BICFI", TX + "/InstgAgt/FinInstnId/BICFI", "From vs Instructing Agent"),
|
|
96
|
+
("/AppHdr/To/FIId/FinInstnId/BICFI", TX + "/InstdAgt/FinInstnId/BICFI", "To vs Instructed Agent"),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@rule(MT, YEAR, "R2", "CBPR_From_To_Instructing_Instructed_Agent_BIC_1_FormalRule",
|
|
101
|
+
'BAH "From" BIC must match "Instructing Agent" BIC, except where BAH '
|
|
102
|
+
'CopyDuplicate = COPY or = CODU. BAH "To" BIC must match "Instructed Agent" '
|
|
103
|
+
"BIC, except where BAH CopyDuplicate = COPY or = CODU.")
|
|
104
|
+
def _r2(msg, report):
|
|
105
|
+
if any(v in {"COPY", "CODU"} for v in msg.values("/AppHdr/CpyDplct")):
|
|
106
|
+
return
|
|
107
|
+
for a, b, label in _BIC_PAIRS:
|
|
108
|
+
_values_match(msg, report, a, b, label)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@rule(MT, YEAR, "R3", "CBPR_From_To_Instructing_Instructed_Agent_BIC_2_FormalRule",
|
|
112
|
+
'BAH "From" BIC must match "Instructing Agent" BIC if CopyDuplicate is '
|
|
113
|
+
'absent. BAH "To" BIC must match "Instructed Agent" BIC if CopyDuplicate is '
|
|
114
|
+
"absent.")
|
|
115
|
+
def _r3(msg, report):
|
|
116
|
+
if not msg.absent("/AppHdr/CpyDplct"):
|
|
117
|
+
return
|
|
118
|
+
for a, b, label in _BIC_PAIRS:
|
|
119
|
+
_values_match(msg, report, a, b, label)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@rule(MT, YEAR, "R12", "CBPR_Instruction_For_Creditor_Presence_Code_FormalRule",
|
|
123
|
+
'Each code can only be used once for element "Instruction For Creditor Agent".')
|
|
124
|
+
def _r12(msg, report):
|
|
125
|
+
for tx in msg.each(TX):
|
|
126
|
+
codes = msg.values("InstrForCdtrAgt/Cd", tx)
|
|
127
|
+
if len(codes) != len(set(codes)):
|
|
128
|
+
report(tx, detail="duplicate InstructionForCreditorAgent code")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
reg("R13", "CBPR_Instruction_Identification_FormalRule",
|
|
132
|
+
"This field must not start or end with a slash '/' and must not contain two "
|
|
133
|
+
"consecutive slashes '//'.",
|
|
134
|
+
not_matching_pattern(TX + "/PmtId/InstrId", r"(/.*)|(.*/)|(.*//.*)"))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@rule(MT, YEAR, "R15", "CBPR_End_To_End_Identification_FormalRule",
|
|
138
|
+
"For the E2E identification, the below restrictions apply to the first 16 "
|
|
139
|
+
"characters: - The first one and the 16th one cannot be '/' and - The "
|
|
140
|
+
"string of 16 characters cannot contain '//'.")
|
|
141
|
+
def _r15(msg, report):
|
|
142
|
+
import re as _re
|
|
143
|
+
pats = [_re.compile(r"/.*"), _re.compile(r".{15}/.*"), _re.compile(r".{0,14}//.*")]
|
|
144
|
+
for node in msg.find(TX + "/PmtId/EndToEndId"):
|
|
145
|
+
val = msg.text_of(node)
|
|
146
|
+
if val and any(p.fullmatch(val) for p in pats):
|
|
147
|
+
report(node, detail="EndToEndId violates first-16-character slash restrictions")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@rule(MT, YEAR, "R20", "CBPR_Interbank_Settlement_Currency_FormalRule",
|
|
151
|
+
"The codes XAU, XAG, XPD and XPT are not allowed, as these are codes are "
|
|
152
|
+
"only used for commodities.")
|
|
153
|
+
def _r20(msg, report):
|
|
154
|
+
for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy"):
|
|
155
|
+
if ccy in {"XAU", "XAG", "XPD", "XPT"}:
|
|
156
|
+
report(el, detail=f"commodity currency '{ccy}' not allowed")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Transaction-chain agents (each: name/address + grace period).
|
|
160
|
+
# In pacs.009 Debtor and Creditor are financial institutions (FinInstnId), so
|
|
161
|
+
# they follow the agent present-together rule.
|
|
162
|
+
_agent_block(TX + "/PrvsInstgAgt1/FinInstnId", "R25", "R26", "R27", "R29")
|
|
163
|
+
_agent_block(TX + "/PrvsInstgAgt2/FinInstnId", "R30", "R31", "R32", "R33")
|
|
164
|
+
_agent_block(TX + "/PrvsInstgAgt3/FinInstnId", "R34", "R35", "R36", "R37")
|
|
165
|
+
_agent_block(TX + "/IntrmyAgt1/FinInstnId", "R38", "R39", "R40", "R41")
|
|
166
|
+
_agent_block(TX + "/IntrmyAgt2/FinInstnId", "R42", "R43", "R44", "R45")
|
|
167
|
+
_agent_block(TX + "/IntrmyAgt3/FinInstnId", "R46", "R47", "R48", "R49")
|
|
168
|
+
_agent_block(TX + "/Dbtr/FinInstnId", "R50", "R51", "R52", "R53")
|
|
169
|
+
_agent_block(TX + "/DbtrAgt/FinInstnId", "R54", "R55", "R56", "R57")
|
|
170
|
+
_agent_block(TX + "/CdtrAgt/FinInstnId", "R58", "R59", "R60", "R61")
|
|
171
|
+
_agent_block(TX + "/Cdtr/FinInstnId", "R62", "R63", "R64", "R65")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Mechanizable textual rules + algorithmic field validation
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
reg("R8", "CBPR_Business_Service_Usage_TextualRule",
|
|
178
|
+
'The value "swift.cbprplus.03" must be used.',
|
|
179
|
+
code_in("/AppHdr/BizSvc", ["swift.cbprplus.03"]))
|
|
180
|
+
|
|
181
|
+
# Cross-schema BAH vs Document identity checks (promoted from advisory; each
|
|
182
|
+
# combinator is conservative and skips when its inputs are absent).
|
|
183
|
+
reg("R5", "CBPR_Business_Message_Identifier_TextualRule",
|
|
184
|
+
"The Business Message Identifier is the unique identifier of the Business Message "
|
|
185
|
+
"instance that is being transported with this header, as defined by the sending "
|
|
186
|
+
"application or system. Must contain the Message Identification element from the "
|
|
187
|
+
"Group Header of the underlying message, where available.",
|
|
188
|
+
business_msg_id_carries_group_id())
|
|
189
|
+
|
|
190
|
+
reg("R6", "CBPR_Message_Definition_Identifier_TextualRule",
|
|
191
|
+
"The Message Definition Identifier of the Business Message instance that is being "
|
|
192
|
+
"transported with this header. In general, it must be formatted exactly as it "
|
|
193
|
+
"appears in the namespace of the Business Message instance.",
|
|
194
|
+
header_msg_def_id_matches())
|
|
195
|
+
|
|
196
|
+
reg("R28", "CBPR_Duplication_Postal_Address_TextualRule",
|
|
197
|
+
"Data present in structured elements within the Postal Address must not, under "
|
|
198
|
+
"any circumstances be repeated in AddressLine.",
|
|
199
|
+
no_postal_address_duplication())
|
|
200
|
+
|
|
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
|
+
reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
|
|
211
|
+
"Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
|
|
212
|
+
each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
|
|
213
|
+
|
|
214
|
+
reg("VAL-LEI", "CBPR_Valid_Agent_LEI",
|
|
215
|
+
"Every Financial Institution LEI must be a structurally valid LEI.",
|
|
216
|
+
each_value_valid(TX + "/DbtrAgt/FinInstnId/LEI", is_valid_lei, "LEI"))
|
|
217
|
+
|
|
218
|
+
reg("VAL-CTRY", "CBPR_Valid_Postal_Country",
|
|
219
|
+
"Every Postal Address Country must be a valid ISO 3166 country code.",
|
|
220
|
+
each_value_valid(TX + "/CdtrAgt/FinInstnId/PstlAdr/Ctry", is_valid_country, "country"))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
# Advisory textual rules (not mechanically enforceable - surfaced as guidance)
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
_ADVISORY = {
|
|
227
|
+
"R4": ("CBPR_Character_Set_Usage_TextualRule",
|
|
228
|
+
"For further description on the usage of the field, pls refer to the CBPR Plus UHB."),
|
|
229
|
+
"R7": ("CBPR_Business_Service_TextualRule",
|
|
230
|
+
"This field may be used by SWIFT to support differentiated processing on "
|
|
231
|
+
"SWIFT-administered services such as FINplus."),
|
|
232
|
+
"R9": ("CBPR_Market_Practice_TextualRule",
|
|
233
|
+
"This field may be used by SWIFT on SWIFT-administered services. For a description "
|
|
234
|
+
"of reserved values, please refer to the Service Description for your service."),
|
|
235
|
+
"R10": ("CBPR_Related_Business_Application_Header_TextualRule",
|
|
236
|
+
"If used, the Related BAH must transport the exact same information as in the BAH "
|
|
237
|
+
"of the related message."),
|
|
238
|
+
"R11": ("CBPR_Related_BAH_Business_Service_TextualRule",
|
|
239
|
+
"If related BAH is present, it should transport the element Business Service."),
|
|
240
|
+
"R14": ("CBPR_E2E_CORE_TextualRule",
|
|
241
|
+
"In the pacs.009 CORE, the E2E identification is provided by the Debtor (Agent)."),
|
|
242
|
+
"R16": ("CBPR_E2E_CORE_ADV_TextualRule",
|
|
243
|
+
"If pacs.009CORE is used to cover pacs.009ADV, the E2E identification should "
|
|
244
|
+
"transport the instruction identification of the underlying pacs.009 ADV."),
|
|
245
|
+
"R17": ("CBPR_UETR_TextualRule",
|
|
246
|
+
"If the pacs.009 is used to settle a pacs.009 Advice, the UETR should transport "
|
|
247
|
+
"the UETR of the underlying pacs.009 Advice."),
|
|
248
|
+
"R18": ("CBPR_Local_Instrument_Guideline",
|
|
249
|
+
"The preferred option is coded information."),
|
|
250
|
+
"R19": ("CBPR_Category_Purpose_Guideline",
|
|
251
|
+
"The preferred option is coded information."),
|
|
252
|
+
"R21": ("CBPR_Agent_National_only_TextualRule",
|
|
253
|
+
"Whenever Debtor Agent, Creditor Agent and all agents in between are located "
|
|
254
|
+
"within the same country, the clearing code only may be used."),
|
|
255
|
+
"R22": ("CBPR_Agent_Option_1_TextualRule",
|
|
256
|
+
"BICFI, complemented optionally with a LEI (preferred option)."),
|
|
257
|
+
"R23": ("CBPR_Agent_Option_2_TextualRule",
|
|
258
|
+
"(Clearing Code OR LEI) AND (Name AND (Unstructured postal address OR "
|
|
259
|
+
"[Structured postal address with minimum Town Name and Country] OR [Hybrid "
|
|
260
|
+
"postal address with minimum Town Name and Country])."),
|
|
261
|
+
"R24": ("CBPR_Agent_Option_3_TextualRule",
|
|
262
|
+
"Name AND (Unstructured OR [Structured postal address with minimum Town Name and "
|
|
263
|
+
"Country] OR [Hybrid postal address with minimum Town Name and Country])."),
|
|
264
|
+
"R66": ("CBPR_Instruction_Information_TextualRule",
|
|
265
|
+
"If the pacs.009 is used to settle a pacs.009 Advice, the last available "
|
|
266
|
+
"occurrence (of the element Instruction For Creditor Agent/Instruction "
|
|
267
|
+
"Information) preceded by /UDLC/ must be used to capture the /UDLC/ (Underlying "
|
|
268
|
+
"Creditor) provided in the pacs.009 Advice."),
|
|
269
|
+
"R67": ("CBPR_Purpose_Guideline",
|
|
270
|
+
"The preferred option is coded information."),
|
|
271
|
+
}
|
|
272
|
+
for _num, (_name, _desc) in _ADVISORY.items():
|
|
273
|
+
advisory(MT, YEAR, _num, _name, _desc)
|