counterparty 0.1.6__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 (64) hide show
  1. counterparty/__init__.py +29 -0
  2. counterparty/extraction/__init__.py +0 -0
  3. counterparty/extraction/clean.py +124 -0
  4. counterparty/extraction/extract_payer_payee.py +160 -0
  5. counterparty/extraction/infer_counterparty.py +17 -0
  6. counterparty/key_engine/__init__.py +0 -0
  7. counterparty/key_engine/canonical_keys.json +326 -0
  8. counterparty/key_engine/key_detector.py +335 -0
  9. counterparty/key_engine/keys.py +332 -0
  10. counterparty/parsers/LAT_AM/LAT_AM_Entry.py +91 -0
  11. counterparty/parsers/LAT_AM/__init__.py +0 -0
  12. counterparty/parsers/LAT_AM/pattern1.py +169 -0
  13. counterparty/parsers/LAT_AM/pattern10.py +76 -0
  14. counterparty/parsers/LAT_AM/pattern11.py +76 -0
  15. counterparty/parsers/LAT_AM/pattern12.py +99 -0
  16. counterparty/parsers/LAT_AM/pattern2.py +102 -0
  17. counterparty/parsers/LAT_AM/pattern3.py +75 -0
  18. counterparty/parsers/LAT_AM/pattern4.py +128 -0
  19. counterparty/parsers/LAT_AM/pattern5.py +54 -0
  20. counterparty/parsers/LAT_AM/pattern6.py +141 -0
  21. counterparty/parsers/LAT_AM/pattern7.py +116 -0
  22. counterparty/parsers/LAT_AM/pattern8.py +134 -0
  23. counterparty/parsers/LAT_AM/pattern9.py +86 -0
  24. counterparty/parsers/__init__.py +0 -0
  25. counterparty/parsers/ach/__init__.py +0 -0
  26. counterparty/parsers/ach/ach_parser.py +190 -0
  27. counterparty/parsers/avidpay/__init__.py +0 -0
  28. counterparty/parsers/avidpay/avidp_check_parser.py +82 -0
  29. counterparty/parsers/avidpay/avidp_gen_parser.py +59 -0
  30. counterparty/parsers/directdebit/__init__.py +0 -0
  31. counterparty/parsers/directdebit/directdeb.py +80 -0
  32. counterparty/parsers/disbursement/__init__.py +0 -0
  33. counterparty/parsers/disbursement/disb_parser.py +72 -0
  34. counterparty/parsers/fundsTransfer/__init__.py +0 -0
  35. counterparty/parsers/fundsTransfer/fundsTrans_parser.py +80 -0
  36. counterparty/parsers/generic/__init__.py +0 -0
  37. counterparty/parsers/generic/all_parser.py +91 -0
  38. counterparty/parsers/merchref/__init__.py +0 -0
  39. counterparty/parsers/merchref/merch_ref_parser.py +47 -0
  40. counterparty/parsers/misc/__init__.py +0 -0
  41. counterparty/parsers/misc/cardp.py +61 -0
  42. counterparty/parsers/misc/invo.py +78 -0
  43. counterparty/parsers/misc/webt.py +55 -0
  44. counterparty/parsers/paypal/__init__.py +0 -0
  45. counterparty/parsers/paypal/paypal.py +118 -0
  46. counterparty/parsers/processor_eft/__init__.py +0 -0
  47. counterparty/parsers/processor_eft/peft.py +110 -0
  48. counterparty/parsers/remittance/__init__.py +0 -0
  49. counterparty/parsers/remittance/remi.py +79 -0
  50. counterparty/parsers/swift/__init__.py +0 -0
  51. counterparty/parsers/swift/swift_parser.py +97 -0
  52. counterparty/parsers/vendorpay/__init__.py +0 -0
  53. counterparty/parsers/vendorpay/vp_parser.py +54 -0
  54. counterparty/parsers/vendorpymt/__init__.py +0 -0
  55. counterparty/parsers/vendorpymt/vpymt_parser.py +132 -0
  56. counterparty/parsers/wire/__init__.py +0 -0
  57. counterparty/parsers/wire/wire_parser.py +137 -0
  58. counterparty/route.py +116 -0
  59. counterparty/routines.py +72 -0
  60. counterparty/util.py +40 -0
  61. counterparty-0.1.6.dist-info/METADATA +9 -0
  62. counterparty-0.1.6.dist-info/RECORD +64 -0
  63. counterparty-0.1.6.dist-info/WHEEL +5 -0
  64. counterparty-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,335 @@
1
+ import json
2
+ import re
3
+ from pathlib import Path
4
+ from rapidfuzz import fuzz
5
+
6
+
7
+ BASE_DIR = Path(__file__).parent
8
+ CANONICAL_FILE = BASE_DIR / "canonical_keys.json"
9
+
10
+
11
+ class KeyDetector:
12
+ def __init__(self, threshold: int = 85, max_words: int = 7):
13
+ self.threshold = threshold
14
+ self.max_words = max_words
15
+ self._load_keys()
16
+
17
+
18
+ # LOAD CANONICAL KEYS
19
+
20
+ def _load_keys(self):
21
+ with open(CANONICAL_FILE, "r", encoding="utf-8") as f:
22
+ self.canonical_map = json.load(f)
23
+
24
+ self.match_keys = []
25
+ self.all_variants = set()
26
+
27
+ for canon, variants in self.canonical_map.items():
28
+ for v in variants:
29
+ self.match_keys.append((canon, v))
30
+ self.all_variants.add(self._clean(v))
31
+
32
+
33
+ # PUBLIC ENTRY
34
+
35
+ def rewrite(self, text: str):
36
+ text = self._normalize_text(text)
37
+
38
+ windows = self._generate_windows(text)
39
+ candidates = self._match_windows(text, windows)
40
+ accepted = self._resolve_conflicts(candidates)
41
+
42
+ rewritten_text = self._rewrite_text(text, accepted)
43
+
44
+ unknown_hitl = self._collect_unknown_keys_for_hitl(text, windows, accepted)
45
+
46
+ if unknown_hitl:
47
+ return rewritten_text.upper(), (True, unknown_hitl)
48
+ else:
49
+ return rewritten_text.upper(), False
50
+
51
+
52
+
53
+ # NORMALIZATION
54
+
55
+ def _normalize_text(self, text: str) -> str:
56
+ text = text.lower()
57
+ text = re.sub(r"\s+", " ", text)
58
+ return text
59
+
60
+
61
+ # CLEAN STRING
62
+
63
+ def _clean(self, s: str) -> str:
64
+ return re.sub(r"[^a-z0-9]", "", s)
65
+
66
+
67
+ # WINDOW GENERATION
68
+
69
+ def _generate_windows(self, text: str):
70
+ tokens = []
71
+ for m in re.finditer(r"[a-z0-9]+", text):
72
+ tokens.append((m.group(), m.start(), m.end()))
73
+
74
+ windows = []
75
+ for i in range(len(tokens)):
76
+ for w in range(1, self.max_words + 1):
77
+ if i + w <= len(tokens):
78
+ phrase = " ".join(tokens[j][0] for j in range(i, i + w))
79
+ start = tokens[i][1]
80
+ end = tokens[i + w - 1][2]
81
+ windows.append((phrase, start, end))
82
+
83
+ return windows
84
+
85
+
86
+ # DELIMITER CHECK
87
+ def _has_delimiter_after(self, text: str, end: int) -> bool:
88
+ tail = text[end:]
89
+
90
+ i = 0
91
+ while i < len(tail) and tail[i].isspace():
92
+ i += 1
93
+
94
+ # delimiter immediately → OK
95
+ if i < len(tail) and tail[i] in ":=":
96
+ return True
97
+
98
+ # word continues → NOT a key end
99
+ if i < len(tail) and tail[i].isalnum():
100
+ return False
101
+
102
+ # skip punctuation, but stop on word
103
+ while i < len(tail):
104
+ if tail[i] in ":=":
105
+ return True
106
+ if tail[i].isalnum():
107
+ return False
108
+ i += 1
109
+
110
+ return False
111
+
112
+
113
+ # MATCH WINDOWS AGAINST CANONICAL VARIANTS
114
+ def _match_windows(self, text, windows):
115
+ candidates = []
116
+
117
+ for phrase, start, end in windows:
118
+ if not self._has_delimiter_after(text, end):
119
+ continue
120
+
121
+ phrase_clean = self._clean(phrase)
122
+
123
+ best_canon = None
124
+ best_score = 0
125
+
126
+ for canon, variant in self.match_keys:
127
+ score = fuzz.ratio(phrase_clean, self._clean(variant))
128
+ if score >= self.threshold and score > best_score:
129
+ best_canon = variant
130
+ best_score = score
131
+
132
+ if best_canon:
133
+ candidates.append({
134
+ "raw": phrase,
135
+ "canonical": best_canon,
136
+ "score": best_score,
137
+ "start": start,
138
+ "end": end,
139
+ "tokens": len(phrase.split())
140
+ })
141
+ # print(json.dumps(candidates,indent=3))
142
+ return candidates
143
+
144
+
145
+
146
+
147
+ # CONFLICT RESOLUTION
148
+ def _resolve_conflicts(self, candidates):
149
+ """
150
+ Conflict resolution rules:
151
+ 1. Higher fuzzy score wins
152
+ 2. If score ties, longer (more tokens) wins
153
+ """
154
+
155
+ # sort by start position only (grouping purpose)
156
+ candidates.sort(key=lambda c: c["start"])
157
+
158
+ accepted = []
159
+
160
+ for c in candidates:
161
+ keep = True
162
+
163
+ for a in accepted[:]:
164
+ overlap = not (c["end"] <= a["start"] or c["start"] >= a["end"])
165
+
166
+ if not overlap:
167
+ continue
168
+
169
+ # -------- conflict detected --------
170
+
171
+ # Rule 1: higher score wins
172
+ if c["score"] > a["score"]:
173
+ accepted.remove(a)
174
+ continue
175
+
176
+ if c["score"] < a["score"]:
177
+ keep = False
178
+ break
179
+
180
+ # Rule 2: score tie → more tokens wins
181
+ if c["tokens"] > a["tokens"]:
182
+ accepted.remove(a)
183
+ continue
184
+ else:
185
+ keep = False
186
+ break
187
+
188
+ if keep:
189
+ accepted.append(c)
190
+
191
+ # optional debug
192
+ # print(json.dumps(accepted, indent=2))
193
+
194
+ return accepted
195
+
196
+
197
+
198
+
199
+ # REWRITE TEXT
200
+
201
+ def _rewrite_text(self, text: str, accepted):
202
+ accepted.sort(key=lambda c: c["start"], reverse=True)
203
+
204
+ for c in accepted:
205
+ text = text[:c["start"]] + c["canonical"] + text[c["end"]:]
206
+
207
+ return text
208
+
209
+
210
+ # HITL — AMBIGUOUS MATCHES
211
+
212
+ def _collect_hitl(self, candidates, accepted, text):
213
+ accepted_spans = [(a["start"], a["end"]) for a in accepted]
214
+ hitl = []
215
+
216
+ for phrase, start, end in self._generate_windows(text):
217
+ # must be followed by delimiter
218
+ if not self._has_delimiter_after(text, end):
219
+ continue
220
+
221
+ # ignore single-word junk
222
+ if len(phrase.split()) < 2:
223
+ continue
224
+
225
+ # ignore non-alpha phrases
226
+ if not re.search(r"[a-z]", phrase):
227
+ continue
228
+
229
+ # ignore if fully inside accepted key
230
+ inside = False
231
+ for a_start, a_end in accepted_spans:
232
+ if start >= a_start and end <= a_end:
233
+ inside = True
234
+ break
235
+ if inside:
236
+ continue
237
+
238
+ # check if this phrase matched ANY canonical key
239
+ matched = False
240
+ phrase_clean = self._clean(phrase)
241
+
242
+ for _, variant in self.match_keys:
243
+ if fuzz.ratio(phrase_clean, self._clean(variant)) >= self.threshold:
244
+ matched = True
245
+ break
246
+
247
+ if matched:
248
+ continue
249
+
250
+ # REAL HITL CASE
251
+ hitl.append({
252
+ "type": "NEW_KEY",
253
+ "raw_phrase": phrase.upper(),
254
+ "suggested": None,
255
+ "confidence": None
256
+ })
257
+
258
+ return hitl
259
+
260
+
261
+
262
+ # HITL — COMPLETELY UNKNOWN KEYS
263
+
264
+ def _collect_unknown_keys_for_hitl(self, text, windows, accepted):
265
+ accepted_spans = [(a["start"], a["end"]) for a in accepted]
266
+ hitl = {}
267
+
268
+ # all canonical words flattened (for NAME vs CUST NAME)
269
+ canonical_tokens = set()
270
+ for v in self.all_variants:
271
+ canonical_tokens.update(re.findall(r"[a-z]+", v))
272
+
273
+ for m in re.finditer(r"\b([a-z][a-z0-9]*)\s*[:=]", text):
274
+ key_start, key_end = m.span(1)
275
+ raw_key = m.group(1)
276
+
277
+ # ❌ numeric or starts with digit → ignore
278
+ if raw_key[0].isdigit():
279
+ continue
280
+
281
+ clean_key = self._clean(raw_key)
282
+
283
+ # ❌ already known or part of known key
284
+ if clean_key in self.all_variants:
285
+ continue
286
+ if raw_key in canonical_tokens:
287
+ continue
288
+
289
+ # ❌ overlaps accepted canonical match
290
+ overlap = False
291
+ for a_start, a_end in accepted_spans:
292
+ if not (key_end <= a_start or key_start >= a_end):
293
+ overlap = True
294
+ break
295
+ if overlap:
296
+ continue
297
+
298
+ # tokenize safely
299
+ words = list(re.finditer(r"[a-z0-9]+|[:=]", text))
300
+ idx = None
301
+ for i, w in enumerate(words):
302
+ if w.start() == key_start:
303
+ idx = i
304
+ break
305
+ if idx is None:
306
+ continue
307
+
308
+ variants = [raw_key]
309
+
310
+ # look back max 4 words, stop at delimiter
311
+ back = 0
312
+ j = idx - 1
313
+ while j >= 0 and back < 4:
314
+ if words[j].group() in {":", "="}:
315
+ break
316
+ variants.append(
317
+ text[words[j].start():words[idx].end()]
318
+ )
319
+ back += 1
320
+ j -= 1
321
+
322
+ # normalize + dedupe
323
+ variants = [
324
+ re.sub(r"\s+", " ", v.strip()).upper()
325
+ for v in variants
326
+ ]
327
+ variants = list(dict.fromkeys(variants))
328
+
329
+ hitl[raw_key.upper()] = variants
330
+
331
+ return hitl
332
+
333
+
334
+ if __name__=='__main__':
335
+ pass
@@ -0,0 +1,332 @@
1
+ # All keys
2
+
3
+ # these keys are used to separate key-value pair from delimiter based narratives and parse them (like ach,wire,swift)
4
+
5
+ KEYS = [
6
+ "INDIVIDUAL OR RECEIVING COMPANY NAME",
7
+ "SWEEP TRANSFR FROM INVESTMENT ACCT",
8
+ "CREDITOR FINANCIAL INSTITUTION ID",
9
+ "RECEIVER FINANCIAL INSTITUTION ID",
10
+ "DEBTOR FINANCIAL INSTITUTION ID",
11
+ "SENDER FINANCIAL INSTITUTION ID",
12
+ "INSTRUCTION FOR CREDITOR AGENT",
13
+ "COMPANY DISCRETIONARY DATA",
14
+ "SWEEP TO INVESTMENT ACCT",
15
+ "INITIATING PARTY ORG ID",
16
+ "ADDITIONAL INFORMATION",
17
+ "REMITTANCE INFORMATION",
18
+ "ACCOUNT TRANSFER FROM",
19
+ "ACTUAL DEPOSIT AMOUNT",
20
+ "ORIGINATING BANK NAME",
21
+ "DAILY INTEREST SWEEP",
22
+ "EFFECTIVE ENTRY DATE",
23
+ "ITEM SEQUENCE NUMBER",
24
+ "ACCOUNT TRANSFER TO",
25
+ "CREDITOR CR ADDRESS",
26
+ "DATE/TIME COMPLETED",
27
+ "FDMS-SETTLEMENT FEE",
28
+ "MERCHANT CHARGEBACK",
29
+ "DATE/TIME RECEIVED",
30
+ "DISCRETIONARY DATA",
31
+ "MERCHANT INTERCHNG",
32
+ "PURPOSE OF PAYMENT",
33
+ "TRANSACTION REF NO",
34
+ "Transaction Ref No",
35
+ "CORRECTION REASON",
36
+ "DEBTOR CR ADDRESS",
37
+ "ENTRY DESCRIPTION",
38
+ "INSTRUCTED AMOUNT",
39
+ "INSTRUCTING AGENT",
40
+ "MERCHANT DISCOUNT",
41
+ "RECEIVER ACCT NUM",
42
+ "SENDERS REFERENCE",
43
+ "STANDBY L/C DEBIT",
44
+ "ALOK HEMANT SIDD",
45
+ "BANK DESCRIPTION",
46
+ "BILLING LOCATION",
47
+ "CREDITOR ACCOUNT",
48
+ "INSTRUCTED AGENT",
49
+ "MERCHANT DEPOSIT",
50
+ "ORIGINATING BANK",
51
+ "RECEIVER ABA NUM",
52
+ "RECEIVER ACCOUNT",
53
+ "RECEIVER ADDRESS",
54
+ "TRANSACTION CODE",
55
+ "ADVANCE DEPOSIT",
56
+ "CHARGING OPTION",
57
+ "EXCHANGE AMOUNT",
58
+ "FULL REFERENCE#",
59
+ "MORE DAY FLOATT",
60
+ "PAYMENT DETAILS",
61
+ "SENDER BANK REF",
62
+ "SENDING CO NAME",
63
+ "TRANSACTION REF",
64
+ "BANK TRACE NUM",
65
+ "BBKBENEFICIARY",
66
+ "CONTROL NUMBER",
67
+ "CREDITOR AGENT",
68
+ "DEBTOR ACCOUNT",
69
+ "EFFECTIVE DATE",
70
+ "FULL REFERENCE",
71
+ "PAYMENT METHOD",
72
+ "RECEIVING BANK",
73
+ "REMOTE CAPTURE",
74
+ "SENDER ACCOUNT",
75
+ "SENDER ADDRESS",
76
+ "SUBSTITUTE CHK",
77
+ "SWEEP INTEREST",
78
+ "CO DISCR DATA",
79
+ "CREDITOR NAME",
80
+ "CURRENCY DESC",
81
+ "CUSTOMER NAME",
82
+ "EXCHANGE RATE",
83
+ "IMAGE DEPOSIT",
84
+ "INDIVIDUAL ID",
85
+ "ORIG BANK ABA",
86
+ "Ordering Bank",
87
+ "Ordering Cust",
88
+ "RECEIVER NAME",
89
+ "REMIT DETAILS",
90
+ "RETURN REASON",
91
+ "WIRE CURRENCY",
92
+ "B/O CUSTOMER",
93
+ "BBKCUST NAME",
94
+ "CASHED CHECK",
95
+ "COMPANY DATA",
96
+ "COMPANY NAME",
97
+ "CREDITOR REF",
98
+ "DEBTOR AGENT",
99
+ "DEPOSIT DATE",
100
+ "DISBURSEMENT",
101
+ "ENTRY DESC R",
102
+ "INITIATED BY",
103
+ "MAIL DEPOSIT",
104
+ "MERCHANT FEE",
105
+ "NJ LOAN PMTS",
106
+ "ORIG CO NAME",
107
+ "PAYMENT DATE",
108
+ "PAYMENT/PURP",
109
+ "Payment Type",
110
+ "REC BANK ABA",
111
+ "REMIT METHOD",
112
+ "SRFCUST NAME",
113
+ "TRACE NUMBER",
114
+ "1 DAY FLOAT",
115
+ "2 DAY FLOAT",
116
+ "ACCT NUMBER",
117
+ "APPROVED BY",
118
+ "BATCH DISCR",
119
+ "BENEFICIARY",
120
+ "BILLING LOC",
121
+ "COMM CHARGE",
122
+ "DEBTOR NAME",
123
+ "DESCRIPTION",
124
+ "DR BANK REF",
125
+ "DR CUST REF",
126
+ "DR TRANS ID",
127
+ "Description",
128
+ "ENTRY CLASS",
129
+ "ENTRY DESCR",
130
+ "FEES LIFTED",
131
+ "FX CURRENCY",
132
+ "NJ FEE PMTS",
133
+ "ORG DEP AMT",
134
+ "RCV BK NAME",
135
+ "RECEIVED AT",
136
+ "RECEIVER ID",
137
+ "RELATED REF",
138
+ "SENDER NAME",
139
+ "SERVICE REF",
140
+ "SND BK NAME",
141
+ "WIRE AMOUNT",
142
+ "ACCT PARTY",
143
+ "BILL LOCAL",
144
+ "BNF STREET",
145
+ "BNK STREET",
146
+ "CLASS CODE",
147
+ "COMPANY ID",
148
+ "CR ADDRESS",
149
+ "CR BANK ID",
150
+ "DR BANK ID",
151
+ "ENTRY DATE",
152
+ "FED REF NO",
153
+ "Fed Ref No",
154
+ "IND I D NO",
155
+ "INTRMY BNK",
156
+ "ITEM TRACE",
157
+ "MISC DEBIT",
158
+ "ORG STREET",
159
+ "ORIG BA NK",
160
+ "ORIGINATOR",
161
+ "PAR NUMBER",
162
+ "PARTIALREF",
163
+ "PAY METHOD",
164
+ "RCV BK ABA",
165
+ "RECV ID NO",
166
+ "RETURN REF",
167
+ "SENDER REF",
168
+ "SND BK ABA",
169
+ "TRANS TYPE",
170
+ "USD AMOUNT",
171
+ "VALUE DATE",
172
+ "ACC PARTY",
173
+ "ACCT TYPE",
174
+ "CHECK NUM",
175
+ "COMP NAME",
176
+ "CUST NAME",
177
+ "DATE/TIME",
178
+ "DEBIT REF",
179
+ "DESC DATE",
180
+ "FILE NAME",
181
+ "FROM ACCT",
182
+ "IND ID NO",
183
+ "INSTG BNK",
184
+ "LISTED AS",
185
+ "ORIG BANK",
186
+ "RECV BANK",
187
+ "RECV NAME",
188
+ "RECVID NO",
189
+ "REFERENCE",
190
+ "RMT USTRD",
191
+ "Reference",
192
+ "SENDER AC",
193
+ "SHIP DATE",
194
+ "SHOULD BE",
195
+ "SWIFT REF",
196
+ "TRACE NUM",
197
+ "ULTI BENE",
198
+ "WIRE TYPE",
199
+ "B/O BANK",
200
+ "BATCH ID",
201
+ "CHECK NO",
202
+ "CHIP REF",
203
+ "CHIP SEQ",
204
+ "CREDITOR",
205
+ "CUST RRN",
206
+ "FED IMAD",
207
+ "IND NAME",
208
+ "REC BANK",
209
+ "REC FROM",
210
+ "REC G FP",
211
+ "RECV BNK",
212
+ "SEC CODE",
213
+ "SNDR REF",
214
+ "TRACE NO",
215
+ "YOUR REF",
216
+ "ACCOUNT",
217
+ "ADDENDA",
218
+ "AMT/CUR",
219
+ "BANK ID",
220
+ "BENEBNK",
221
+ "COMP ID",
222
+ "CONFIRM",
223
+ "CR ACCT",
224
+ "CR NAME",
225
+ "CREATED",
226
+ "CUST ID",
227
+ "DEP AMT",
228
+ "DEPDATE",
229
+ "DEPOSIT",
230
+ "DR ACCT",
231
+ "DR NAME",
232
+ "FED REF",
233
+ "INVOICE",
234
+ "ISO AMT",
235
+ "LBX DEP",
236
+ "MRN SEQ",
237
+ "ORG BNK",
238
+ "ORIG ID",
239
+ "ORIGIN#",
240
+ "PAID TO",
241
+ "R EMARK",
242
+ "REC GFP",
243
+ "RECV BK",
244
+ "RECVBNK",
245
+ "REMA RK",
246
+ "REMAR K",
247
+ "SENT AT",
248
+ "SND BNK",
249
+ "AMOUNT",
250
+ "BNF BK",
251
+ "Branch",
252
+ "CO EFF",
253
+ "CR BNK",
254
+ "DB BNK",
255
+ "DEBTOR",
256
+ "DR BNK",
257
+ "FED ID",
258
+ "INS BK",
259
+ "INT BK",
260
+ "ORG BK",
261
+ "ORG ID",
262
+ "ORIGIN",
263
+ "REASON",
264
+ "REF NO",
265
+ "REMARK",
266
+ "SND BK",
267
+ "SOURCE",
268
+ "AS OF",
269
+ "CHECK",
270
+ "PAYER",
271
+ "PSTCD",
272
+ "VLTID",
273
+ "ACCT",
274
+ "ADDR",
275
+ "BN F",
276
+ "CTRY",
277
+ "DATE",
278
+ "DESC",
279
+ "IMAD",
280
+ "INDN",
281
+ "INFO",
282
+ "OMAD",
283
+ "ORIG",
284
+ "PLAN",
285
+ "RATE",
286
+ "S AC",
287
+ "SETT",
288
+ "TIME",
289
+ "TYPE",
290
+ "UETR",
291
+ "ULID",
292
+ "ABA",
293
+ "ADV",
294
+ "BAG",
295
+ "BBI",
296
+ "BBK",
297
+ "BIC",
298
+ "BLK",
299
+ "BNF",
300
+ "CHG",
301
+ "CTY",
302
+ "DOB",
303
+ "IBK",
304
+ "INS",
305
+ "LTN",
306
+ "OBI",
307
+ "OBK",
308
+ "OGB",
309
+ "ORF",
310
+ "ORG",
311
+ "POB",
312
+ "QID",
313
+ "REF",
314
+ "RFB",
315
+ "SBK",
316
+ "SBR",
317
+ "SEC",
318
+ "SRC",
319
+ "SRF",
320
+ "TCN",
321
+ "TRN",
322
+ "TTM",
323
+ "TYP",
324
+ "BC",
325
+ "PR",
326
+ ]
327
+
328
+ INLINE_KEYS = ["ID","PURPOSE CODE","PURPOSE","AC"]
329
+
330
+ if __name__=='__main__':
331
+ print(sorted(KEYS,key=len,reverse=True))
332
+ pass