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,375 @@
|
|
|
1
|
+
"""CBPR+ SR2025 usage rules for pacs.008.001.08 (FIToFICustomerCreditTransfer).
|
|
2
|
+
|
|
3
|
+
Reference module: this is the template the other (year, message type) modules
|
|
4
|
+
follow. Each rule is registered explicitly with its source rule number, name and
|
|
5
|
+
description, and implemented either with a shared combinator from ``helpers`` or
|
|
6
|
+
a bespoke ``fn(msg, report)`` for cross-field / cross-schema logic.
|
|
7
|
+
|
|
8
|
+
Rule numbers and text are taken from the published usage guideline's Rules sheet;
|
|
9
|
+
XML paths are the short ISO 20022 tags from its Full_View / XML Path column.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from ...registry import advisory, rule
|
|
14
|
+
from ...validators import is_valid_bic, is_valid_currency
|
|
15
|
+
from ...helpers import (
|
|
16
|
+
address_hybrid,
|
|
17
|
+
address_lines_max_length,
|
|
18
|
+
bic_presence_exclusive,
|
|
19
|
+
business_msg_id_carries_group_id,
|
|
20
|
+
charges_required_when_amounts_differ,
|
|
21
|
+
code_in,
|
|
22
|
+
each_value_valid,
|
|
23
|
+
header_msg_def_id_matches,
|
|
24
|
+
mutually_exclusive,
|
|
25
|
+
no_postal_address_duplication,
|
|
26
|
+
not_matching_pattern,
|
|
27
|
+
presence_together,
|
|
28
|
+
required_when_absent,
|
|
29
|
+
requires_if_present,
|
|
30
|
+
same_value,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
MT = "pacs.008"
|
|
34
|
+
YEAR = 2025
|
|
35
|
+
ROOT = "/Document/FIToFICstmrCdtTrf"
|
|
36
|
+
TX = ROOT + "/CdtTrfTxInf"
|
|
37
|
+
|
|
38
|
+
# Repeated rule descriptions (identical across the locations they apply to).
|
|
39
|
+
D_AGENT_NAME_ADR = "Name and Address must always be present together."
|
|
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."
|
|
52
|
+
)
|
|
53
|
+
D_GRACE_UNSTRUCT = (
|
|
54
|
+
"If Postal Address is present and if no other element than Address Line is "
|
|
55
|
+
"present then every occurrence of Address Line must not exceed 35 characters."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def reg(number: str, name: str, description: str, check) -> None:
|
|
60
|
+
"""Register a combinator-built check as a rule."""
|
|
61
|
+
rule(MT, YEAR, number, name, description)(check)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _agent_block(prefix: str, fin_inst_path: str, n_name, n_struct, n_hybrid, n_unstruct) -> None:
|
|
65
|
+
"""The four rules that recur for each agent: Name+Address + grace period."""
|
|
66
|
+
pstl = fin_inst_path + "/PstlAdr"
|
|
67
|
+
reg(n_name, "CBPR_Agent_Name_Postal_Address_FormalRule", D_AGENT_NAME_ADR,
|
|
68
|
+
presence_together(fin_inst_path, "Nm", "PstlAdr"))
|
|
69
|
+
reg(n_struct, "CBPR_GracePeriod_Structured_FormalRule", D_GRACE_STRUCT,
|
|
70
|
+
required_when_absent(pstl, "AdrLine", ["TwnNm", "Ctry"]))
|
|
71
|
+
reg(n_hybrid, "CBPR_GracePeriod_Hybrid_FormalRule", D_GRACE_HYBRID,
|
|
72
|
+
address_hybrid(pstl))
|
|
73
|
+
reg(n_unstruct, "CBPR_GracePeriod_Unstructured_FormalRule", D_GRACE_UNSTRUCT,
|
|
74
|
+
address_lines_max_length(pstl, 35))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _party_block(party_path: str, n_name_adr: str, n_struct=None, n_hybrid=None, n_unstruct=None) -> None:
|
|
78
|
+
reg(n_name_adr, "CBPR_Party_Name_Postal_Address_FormalRule", D_PARTY_NAME_ADR,
|
|
79
|
+
requires_if_present(party_path, "PstlAdr", "Nm"))
|
|
80
|
+
if n_struct:
|
|
81
|
+
pstl = party_path + "/PstlAdr"
|
|
82
|
+
reg(n_struct, "CBPR_GracePeriod_Structured_FormalRule", D_GRACE_STRUCT,
|
|
83
|
+
required_when_absent(pstl, "AdrLine", ["TwnNm", "Ctry"]))
|
|
84
|
+
reg(n_hybrid, "CBPR_GracePeriod_Hybrid_FormalRule", D_GRACE_HYBRID,
|
|
85
|
+
address_hybrid(pstl))
|
|
86
|
+
reg(n_unstruct, "CBPR_GracePeriod_Unstructured_FormalRule", D_GRACE_UNSTRUCT,
|
|
87
|
+
address_lines_max_length(pstl, 35))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Bespoke cross-field / cross-schema rules
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
def _values_match(msg, report, path_a, path_b, label):
|
|
95
|
+
a_nodes = msg.find(path_a)
|
|
96
|
+
if not a_nodes:
|
|
97
|
+
return
|
|
98
|
+
b_vals = {msg.text_of(n) for n in msg.find(path_b)}
|
|
99
|
+
if not b_vals:
|
|
100
|
+
return
|
|
101
|
+
a_vals = {msg.text_of(n) for n in a_nodes}
|
|
102
|
+
if a_vals != b_vals:
|
|
103
|
+
a_show = ", ".join(sorted(a_vals)) or "(empty)"
|
|
104
|
+
b_show = ", ".join(sorted(b_vals)) or "(empty)"
|
|
105
|
+
report(a_nodes[0], detail=f"{label}: {a_show} != {b_show}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@rule(MT, YEAR, "R1", "CBPR_Priority_Instruction_Priority_FormalRule",
|
|
109
|
+
'If "Priority" is used in the BAH for pacs messages, the value should be '
|
|
110
|
+
'identical to the one in the Payment Type Information/InstructionPriority if present.')
|
|
111
|
+
def _r1(msg, report):
|
|
112
|
+
if msg.present("/AppHdr/Prty") and msg.present(TX + "/PmtTpInf/InstrPrty"):
|
|
113
|
+
_values_match(msg, report, "/AppHdr/Prty", TX + "/PmtTpInf/InstrPrty",
|
|
114
|
+
"BAH Priority must equal InstructionPriority")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
_BIC_PAIRS = [
|
|
118
|
+
("/AppHdr/Fr/FIId/FinInstnId/BICFI", TX + "/InstgAgt/FinInstnId/BICFI", "From vs Instructing Agent"),
|
|
119
|
+
("/AppHdr/To/FIId/FinInstnId/BICFI", TX + "/InstdAgt/FinInstnId/BICFI", "To vs Instructed Agent"),
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@rule(MT, YEAR, "R2", "CBPR_From_To_Instructing_Instructed_Agent_BIC_1_FormalRule",
|
|
124
|
+
'BAH "From"/"To" BIC must match Instructing/Instructed Agent BIC, except '
|
|
125
|
+
"where BAH CopyDuplicate = COPY or CODU.")
|
|
126
|
+
def _r2(msg, report):
|
|
127
|
+
if any(v in {"COPY", "CODU"} for v in msg.values("/AppHdr/CpyDplct")):
|
|
128
|
+
return
|
|
129
|
+
for a, b, label in _BIC_PAIRS:
|
|
130
|
+
_values_match(msg, report, a, b, label)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@rule(MT, YEAR, "R3", "CBPR_From_To_Instructing_Instructed_Agent_BIC_2_FormalRule",
|
|
134
|
+
'BAH "From"/"To" BIC must match Instructing/Instructed Agent BIC if '
|
|
135
|
+
"CopyDuplicate is absent.")
|
|
136
|
+
def _r3(msg, report):
|
|
137
|
+
if not msg.absent("/AppHdr/CpyDplct"):
|
|
138
|
+
return
|
|
139
|
+
for a, b, label in _BIC_PAIRS:
|
|
140
|
+
_values_match(msg, report, a, b, label)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# R12: agent name/address on each ChargesInformation/Agent
|
|
144
|
+
reg("R12", "CBPR_Agent_Name_Postal_Address_FormalRule", D_AGENT_NAME_ADR,
|
|
145
|
+
presence_together(TX + "/ChrgsInf/Agt/FinInstnId", "Nm", "PstlAdr"))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@rule(MT, YEAR, "R13", "CBPR_Instruction_for_Creditor_Agent1_FormalRule",
|
|
149
|
+
'The code "HOLD" is not allowed if the code "CHQB" is present.')
|
|
150
|
+
def _r13(msg, report):
|
|
151
|
+
for tx in msg.each(TX):
|
|
152
|
+
codes = set(msg.values("InstrForCdtrAgt/Cd", tx))
|
|
153
|
+
if "CHQB" in codes and "HOLD" in codes:
|
|
154
|
+
report(tx, detail="HOLD not allowed when CHQB present")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@rule(MT, YEAR, "R14", "CBPR_Instruction_for_Creditor_Agent2_FormalRule",
|
|
158
|
+
'The code "TELB" is not allowed if the code "PHOB" is present.')
|
|
159
|
+
def _r14(msg, report):
|
|
160
|
+
for tx in msg.each(TX):
|
|
161
|
+
codes = set(msg.values("InstrForCdtrAgt/Cd", tx))
|
|
162
|
+
if "PHOB" in codes and "TELB" in codes:
|
|
163
|
+
report(tx, detail="TELB not allowed when PHOB present")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Reimbursement agents (Group Header / Settlement Information)
|
|
167
|
+
_agent_block("InstgRmbrsmntAgt", ROOT + "/GrpHdr/SttlmInf/InstgRmbrsmntAgt/FinInstnId",
|
|
168
|
+
"R20", "R21", "R22", "R24")
|
|
169
|
+
_agent_block("InstdRmbrsmntAgt", ROOT + "/GrpHdr/SttlmInf/InstdRmbrsmntAgt/FinInstnId",
|
|
170
|
+
"R25", "R26", "R27", "R28")
|
|
171
|
+
_agent_block("ThrdRmbrsmntAgt", ROOT + "/GrpHdr/SttlmInf/ThrdRmbrsmntAgt/FinInstnId",
|
|
172
|
+
"R29", "R30", "R31", "R32")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
reg("R33", "CBPR_Related_Remit_Info_Remit_Info_Mutually_Exclusive_FormalRule",
|
|
176
|
+
"In the interbank space, Related Remittance Information and Remittance "
|
|
177
|
+
"Information are mutually exclusive and all may be absent.",
|
|
178
|
+
mutually_exclusive(TX, ["RltdRmtInf", "RmtInf"]))
|
|
179
|
+
|
|
180
|
+
reg("R34", "CBPR_Remittance_Mutually_Exclusive_FormalRule",
|
|
181
|
+
"Either Structured or Unstructured Remittance can be present.",
|
|
182
|
+
mutually_exclusive(TX, ["RmtInf/Ustrd", "RmtInf/Strd"]))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@rule(MT, YEAR, "R35", "CBPR_CRED_FormalRule",
|
|
186
|
+
"Charge information is mandatory if CRED is present - if no charges are "
|
|
187
|
+
'taken, Zero must be used in "Amount" (any agent in the payment chain).')
|
|
188
|
+
def _r35(msg, report):
|
|
189
|
+
for tx in msg.each(TX):
|
|
190
|
+
cb = msg.values("ChrgBr", tx)
|
|
191
|
+
if cb and all(v == "CRED" for v in cb) and msg.absent("ChrgsInf", tx):
|
|
192
|
+
report(tx, detail="ChargesInformation required when ChargeBearer is CRED")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@rule(MT, YEAR, "R36", "CBPR_Instruction_For_Creditor_Presence_Code_FormalRule",
|
|
196
|
+
"Each code can only be used once for element Instruction For Creditor Agent.")
|
|
197
|
+
def _r36(msg, report):
|
|
198
|
+
for tx in msg.each(TX):
|
|
199
|
+
codes = msg.values("InstrForCdtrAgt/Cd", tx)
|
|
200
|
+
if len(codes) != len(set(codes)):
|
|
201
|
+
report(tx, detail="duplicate InstructionForCreditorAgent code")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@rule(MT, YEAR, "R37", "CBPR_DEBT_FormalRule",
|
|
205
|
+
'If "Charge Bearer/DEBT" is present, then only one occurrence of '
|
|
206
|
+
'"Charge Information" is allowed.')
|
|
207
|
+
def _r37(msg, report):
|
|
208
|
+
for tx in msg.each(TX):
|
|
209
|
+
if "DEBT" in msg.values("ChrgBr", tx) and len(msg.find("ChrgsInf", tx)) > 1:
|
|
210
|
+
report(tx, detail="only one ChargesInformation allowed when ChargeBearer is DEBT")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
reg("R38", "CBPR_Instruction_Identification_FormalRule",
|
|
214
|
+
"This field must not start or end with a slash '/' and must not contain two "
|
|
215
|
+
"consecutive slashes '//'.",
|
|
216
|
+
not_matching_pattern(TX + "/PmtId/InstrId", r"(/.*)|(.*/)|(.*//.*)"))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@rule(MT, YEAR, "R41", "CBPR_Interbank_Settlement_Currency_FormalRule",
|
|
220
|
+
"The codes XAU, XAG, XPD and XPT are not allowed, as these codes are only "
|
|
221
|
+
"used for commodities.")
|
|
222
|
+
def _r41(msg, report):
|
|
223
|
+
for el, ccy in msg.attr_nodes(TX + "/IntrBkSttlmAmt", "Ccy"):
|
|
224
|
+
if ccy in {"XAU", "XAG", "XPD", "XPT"}:
|
|
225
|
+
report(el, detail=f"commodity currency '{ccy}' not allowed")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# Charges agent + transaction-chain agents (each: name/address + grace period)
|
|
229
|
+
_agent_block("ChrgsInfAgt", TX + "/ChrgsInf/Agt/FinInstnId", "R12b", "R46", "R47", "R48")
|
|
230
|
+
_agent_block("PrvsInstgAgt1", TX + "/PrvsInstgAgt1/FinInstnId", "R49", "R50", "R51", "R52")
|
|
231
|
+
_agent_block("PrvsInstgAgt2", TX + "/PrvsInstgAgt2/FinInstnId", "R53", "R54", "R55", "R56")
|
|
232
|
+
_agent_block("PrvsInstgAgt3", TX + "/PrvsInstgAgt3/FinInstnId", "R57", "R58", "R59", "R60")
|
|
233
|
+
_agent_block("IntrmyAgt1", TX + "/IntrmyAgt1/FinInstnId", "R61", "R62", "R63", "R64")
|
|
234
|
+
_agent_block("IntrmyAgt2", TX + "/IntrmyAgt2/FinInstnId", "R65", "R66", "R67", "R68")
|
|
235
|
+
_agent_block("IntrmyAgt3", TX + "/IntrmyAgt3/FinInstnId", "R69", "R70", "R71", "R72")
|
|
236
|
+
_agent_block("DbtrAgt", TX + "/DbtrAgt/FinInstnId", "R87", "R88", "R89", "R90")
|
|
237
|
+
_agent_block("CdtrAgt", TX + "/CdtrAgt/FinInstnId", "R91", "R92", "R93", "R94")
|
|
238
|
+
|
|
239
|
+
# Parties
|
|
240
|
+
_party_block(TX + "/UltmtDbtr", "R76")
|
|
241
|
+
_party_block(TX + "/InitgPty", "R77")
|
|
242
|
+
_party_block(TX + "/Dbtr", "R82", "R84", "R85", "R86")
|
|
243
|
+
_party_block(TX + "/Cdtr", "R99", "R101", "R102", "R103")
|
|
244
|
+
_party_block(TX + "/UltmtCdtr", "R106")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _party_any_bic(number: str, party: str) -> None:
|
|
248
|
+
reg(number, "CBPR_Party_Name_Any_BIC_FormalRule", D_PARTY_ANY_BIC,
|
|
249
|
+
required_when_absent(party, "Id/OrgId/AnyBIC", ["Nm"]))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
_party_any_bic("R81", TX + "/Dbtr")
|
|
253
|
+
_party_any_bic("R95", TX + "/Cdtr")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _bic_presence(number: str, party: str) -> None:
|
|
257
|
+
desc = ("If AnyBIC is present, then Name and Postal Address are not allowed "
|
|
258
|
+
"(other elements remain optional).")
|
|
259
|
+
reg(number, "CBPR_Party_BIC_Presence_TextualRule", desc, bic_presence_exclusive(party))
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
_bic_presence("R83", TX + "/Dbtr")
|
|
263
|
+
_bic_presence("R100", TX + "/Cdtr")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# Mechanizable textual rules + algorithmic field validation
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
reg("R8", "CBPR_Business_Service_Usage_TextualRule",
|
|
270
|
+
'The value "swift.cbprplus.03" must be used.',
|
|
271
|
+
code_in("/AppHdr/BizSvc", ["swift.cbprplus.03"]))
|
|
272
|
+
|
|
273
|
+
# Promoted from advisory: mechanizable header / address / charges rules.
|
|
274
|
+
reg("R5", "CBPR_Business_Message_Identifier_TextualRule",
|
|
275
|
+
"The Business Message Identifier is the unique identifier of the Business "
|
|
276
|
+
"Message instance. It must contain the Message Identification element from "
|
|
277
|
+
"the Group Header of the underlying message, where available.",
|
|
278
|
+
business_msg_id_carries_group_id())
|
|
279
|
+
|
|
280
|
+
reg("R6", "CBPR_Message_Definition_Identifier_TextualRule",
|
|
281
|
+
"The Message Definition Identifier of the Business Message instance must be "
|
|
282
|
+
"formatted exactly as it appears in the namespace of the Business Message.",
|
|
283
|
+
header_msg_def_id_matches())
|
|
284
|
+
|
|
285
|
+
reg("R23", "CBPR_Duplication_Postal_Address_TextualRule",
|
|
286
|
+
"Data present in structured elements within the Postal Address must not, "
|
|
287
|
+
"under any circumstances, be repeated in AddressLine.",
|
|
288
|
+
no_postal_address_duplication())
|
|
289
|
+
|
|
290
|
+
# CBPR_DEBT_Rule_1: same-currency Instructed/Interbank amounts - charges are
|
|
291
|
+
# mandatory when the two amounts differ (prepaid charges). Conservative: only
|
|
292
|
+
# fires when both amounts are present in the same currency and differ.
|
|
293
|
+
reg("R42", "CBPR_DEBT_Rule_1_TextualRule",
|
|
294
|
+
"If Instructed amount and Interbank Settlement amount are expressed in the "
|
|
295
|
+
"same currency and differ, charge information is mandatory.",
|
|
296
|
+
charges_required_when_amounts_differ(TX, "InstdAmt", "IntrBkSttlmAmt", "ChrgsInf"))
|
|
297
|
+
|
|
298
|
+
reg("R44", "CBPR_DEBT_Rule_1_TextualRule",
|
|
299
|
+
"If Instructed amount and Interbank Settlement amount are expressed in the "
|
|
300
|
+
"same currency and differ, Charge Information is mandatory.",
|
|
301
|
+
charges_required_when_amounts_differ(TX, "InstdAmt", "IntrBkSttlmAmt", "ChrgsInf"))
|
|
302
|
+
|
|
303
|
+
# Specific validations required by the brief (algorithmic), applied to the
|
|
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
|
+
reg("VAL-BIC", "CBPR_Valid_Agent_BIC",
|
|
314
|
+
"Instructing/Instructed Agent BICFI must be a structurally valid BIC.",
|
|
315
|
+
each_value_valid(TX + "/InstgAgt/FinInstnId/BICFI", is_valid_bic, "BIC"))
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
# Advisory textual rules (not mechanically enforceable - surfaced as guidance)
|
|
320
|
+
# ---------------------------------------------------------------------------
|
|
321
|
+
_ADVISORY = {
|
|
322
|
+
"R4": ("CBPR_Character_Set_Usage_TextualRule",
|
|
323
|
+
"For further description on the usage of the field, please refer to the CBPR Plus UHB."),
|
|
324
|
+
"R7": ("CBPR_Business_Service_TextualRule",
|
|
325
|
+
"Business Service may be used by SWIFT to support differentiated processing."),
|
|
326
|
+
"R9": ("CBPR_Market_Practice_TextualRule",
|
|
327
|
+
"Market Practice may be used by SWIFT on SWIFT-administered services."),
|
|
328
|
+
"R10": ("CBPR_Related_Business_Application_Header_TextualRule",
|
|
329
|
+
"If used, the Related BAH must transport the exact same information as in the BAH of the related message."),
|
|
330
|
+
"R11": ("CBPR_Related_BAH_Business_Service_TextualRule",
|
|
331
|
+
"If related BAH is present, it should transport the element Business Service."),
|
|
332
|
+
"R15": ("CBPR_Agent_National_only_TextualRule",
|
|
333
|
+
"When all agents are in the same country, the clearing code only may be used."),
|
|
334
|
+
"R16": ("CBPR_Agent_Option_1_TextualRule",
|
|
335
|
+
"BICFI, complemented optionally with a LEI (preferred option)."),
|
|
336
|
+
"R17": ("CBPR_Agent_Option_2_TextualRule",
|
|
337
|
+
"(Clearing Code OR LEI) AND (Name AND postal address with minimum Town Name and Country)."),
|
|
338
|
+
"R18": ("CBPR_Agent_Option_3_TextualRule",
|
|
339
|
+
"Name AND postal address with minimum Town Name and Country."),
|
|
340
|
+
"R19": ("CBPR_Agent_Point_To_Point_On_SWIFT_TextualRule",
|
|
341
|
+
"If the transaction is exchanged on the SWIFT network, then BIC is mandatory."),
|
|
342
|
+
"R39": ("CBPR_Local_Instrument_Guideline", "The preferred option is coded information."),
|
|
343
|
+
"R40": ("CBPR_Category_Purpose_Guideline", "The preferred option is coded information."),
|
|
344
|
+
"R43": ("CBPR_DEBT_Rule_2_TextualRule",
|
|
345
|
+
"Different-currency Instructed/Interbank amounts: if ChargeBearer/DEBT, charge information rules apply."),
|
|
346
|
+
"R45": ("CBPR_SHAR_TextualRule",
|
|
347
|
+
"If deduct is taken then Charge Information is mandatory."),
|
|
348
|
+
"R73": ("CBPR_UltimateDebtor_Option_3_Jurisdictions_only_TextualRule",
|
|
349
|
+
"Jurisdictional transactions: Name and/or Identification."),
|
|
350
|
+
"R74": ("CBPR_Ultimate_Debtor_Option_1_TextualRule",
|
|
351
|
+
"Name AND structured/hybrid postal address with minimum Town Name and Country."),
|
|
352
|
+
"R75": ("CBPR_Ultimate_Debtor_Option_2_TextualRule",
|
|
353
|
+
"Name AND structured/hybrid postal address with minimum Town Name and Country."),
|
|
354
|
+
"R78": ("CBPR_Debtor_Option_3_Jurisdictions_only_TextualRule",
|
|
355
|
+
"Jurisdictional transactions: Debtor Name with Account or Identification."),
|
|
356
|
+
"R79": ("CBPR_Debtor_Option_2_TextualRule",
|
|
357
|
+
"Name AND postal address (Unstructured / Structured / Hybrid with Town Name and Country)."),
|
|
358
|
+
"R80": ("CBPR_Debtor_Option_1_TextualRule",
|
|
359
|
+
"Organisation Identification/AnyBIC AND (Account Number OR Organisation Identification/Other)."),
|
|
360
|
+
"R96": ("CBPR_Creditor_Option_3_Jurisdictions_only_TextualRule",
|
|
361
|
+
"Jurisdictional transactions: Creditor Name with Account or Identification."),
|
|
362
|
+
"R97": ("CBPR_Creditor_Option_1_TextualRule",
|
|
363
|
+
"Organisation Identification/AnyBIC AND (Account Number OR Organisation Identification/Other)."),
|
|
364
|
+
"R98": ("CBPR_Creditor_Option_2_TextualRule",
|
|
365
|
+
"Name AND postal address (Unstructured / Structured / Hybrid with Town Name and Country)."),
|
|
366
|
+
"R104": ("CBPR_UltimateCreditor_Option_2_Jurisdictions_only_TextualRule",
|
|
367
|
+
"Jurisdictional transactions: Name and/or Identification."),
|
|
368
|
+
"R105": ("CBPR_Ultimate_Creditor_Option_1_TextualRule",
|
|
369
|
+
"Name AND structured/hybrid postal address with minimum Town Name and Country."),
|
|
370
|
+
"R107": ("CBPR_Purpose_Guideline", "The preferred option is coded information."),
|
|
371
|
+
"R108": ("CBPR_Remittance_Rules_TextualRule",
|
|
372
|
+
"Use of Structured Remittance must be bilaterally or multilaterally agreed."),
|
|
373
|
+
}
|
|
374
|
+
for _num, (_name, _desc) in _ADVISORY.items():
|
|
375
|
+
advisory(MT, YEAR, _num, _name, _desc)
|