@@ -10,6 +10,7 @@ import (
10
10
"encoding/json"
11
11
"io"
12
12
"io/ioutil"
13
+ "math"
13
14
"sort"
14
15
"strings"
15
16
"unicode/utf8"
@@ -145,6 +146,9 @@ func (f *WIREDictionary) readJSON(r io.Reader) error {
145
146
FundsSettlementOnlyStatus : ps [i ].FundsSettlementOnlyStatus ,
146
147
BookEntrySecuritiesTransferStatus : ps [i ].SecuritiesEligibility ,
147
148
Date : ps [i ].ChangeDate ,
149
+
150
+ // Our Custom Fields
151
+ CleanName : Normalize (ps [i ].CustomerName ),
148
152
}
149
153
f .WIREParticipants = append (f .WIREParticipants , p )
150
154
f .IndexWIRERoutingNumber [p .RoutingNumber ] = p
@@ -200,6 +204,10 @@ func (f *WIREDictionary) parseWIREParticipant(line string) error {
200
204
p .BookEntrySecuritiesTransferStatus = line [92 :93 ]
201
205
// Date YYYYMMDD (8): 122415
202
206
p .Date = line [93 :101 ]
207
+
208
+ // Our custom fields
209
+ p .CleanName = Normalize (p .CustomerName )
210
+
203
211
f .WIREParticipants = append (f .WIREParticipants , p )
204
212
f .IndexWIRERoutingNumber [p .RoutingNumber ] = p
205
213
return nil
@@ -232,7 +240,7 @@ func (f *WIREDictionary) FinancialInstitutionSearchSingle(s string) []*WIREParti
232
240
// RoutingNumberSearch returns FEDWIRE participants if WIREParticipant.RoutingNumber begins with prefix string s.
233
241
// The first 2 digits of the routing number are required.
234
242
// Based on https://www.frbservices.org/EPaymentsDirectory/search.html
235
- func (f * WIREDictionary ) RoutingNumberSearch (s string ) ([]* WIREParticipant , error ) {
243
+ func (f * WIREDictionary ) RoutingNumberSearch (s string , limit int ) ([]* WIREParticipant , error ) {
236
244
s = strings .TrimSpace (s )
237
245
238
246
if utf8 .RuneCountInString (s ) < MinimumRoutingNumberDigits {
@@ -250,58 +258,52 @@ func (f *WIREDictionary) RoutingNumberSearch(s string) ([]*WIREParticipant, erro
250
258
f .errors .Add (ErrRoutingNumberNumeric )
251
259
return nil , f .errors
252
260
}
261
+ exactMatch := len (s ) == 9
253
262
254
- Participants := make ([]* WIREParticipant , 0 )
255
-
263
+ out := make ([]* wireParticipantResult , 0 )
256
264
for _ , wireP := range f .WIREParticipants {
257
- if strings .HasPrefix (wireP .RoutingNumber , s ) {
258
- Participants = append (Participants , wireP )
265
+ if exactMatch {
266
+ if wireP .RoutingNumber == s {
267
+ out = append (out , & wireParticipantResult {
268
+ WIREParticipant : wireP ,
269
+ highestMatch : 1.0 ,
270
+ })
271
+ }
272
+ } else {
273
+ out = append (out , & wireParticipantResult {
274
+ WIREParticipant : wireP ,
275
+ highestMatch : strcmp .JaroWinkler (wireP .RoutingNumber , s ),
276
+ })
259
277
}
260
278
}
261
-
262
- return Participants , nil
279
+ return reduceWIREResults (out , limit ), nil
263
280
}
264
281
265
282
// FinancialInstitutionSearch returns a FEDWIRE participant based on a WIREParticipant.CustomerName
266
- func (f * WIREDictionary ) FinancialInstitutionSearch (s string ) []* WIREParticipant {
283
+ func (f * WIREDictionary ) FinancialInstitutionSearch (s string , limit int ) []* WIREParticipant {
267
284
s = strings .ToLower (s )
268
285
269
- // Participants is a subset WIREDictionary.WIREParticipants that match the search based on JaroWinkler similarity
270
- // and Levenshtein similarity
271
- Participants := make ([]* WIREParticipant , 0 )
286
+ out := make ([]* wireParticipantResult , 0 )
272
287
273
- // JaroWinkler is a more accurate version of the Jaro algorithm. It works by boosting the
274
- // score of exact matches at the beginning of the strings. By doing this, Winkler says that
275
- // typos are less common to happen at the beginning.
276
288
for _ , wireP := range f .WIREParticipants {
277
- if strcmp .JaroWinkler (strings .ToLower (wireP .CustomerName ), s ) > WIREJaroWinklerSimilarity {
278
- Participants = append (Participants , wireP )
289
+ // JaroWinkler is a more accurate version of the Jaro algorithm. It works by boosting the
290
+ // score of exact matches at the beginning of the strings. By doing this, Winkler says that
291
+ // typos are less common to happen at the beginning.
292
+ jaroScore := strcmp .JaroWinkler (strings .ToLower (wireP .CleanName ), s )
293
+
294
+ // Levenshtein is the "edit distance" between two strings. This is the count of operations
295
+ // (insert, delete, replace) needed for two strings to be equal.
296
+ levenScore := strcmp .Levenshtein (strings .ToLower (wireP .CleanName ), s )
297
+
298
+ if jaroScore > ACHJaroWinklerSimilarity || levenScore > ACHLevenshteinSimilarity {
299
+ out = append (out , & wireParticipantResult {
300
+ WIREParticipant : wireP ,
301
+ highestMatch : math .Max (jaroScore , levenScore ),
302
+ })
279
303
}
280
304
}
281
305
282
- // Levenshtein is the "edit distance" between two strings. This is the count of operations
283
- // (insert, delete, replace) needed for two strings to be equal.
284
- for _ , wireP := range f .WIREParticipants {
285
- if strcmp .Levenshtein (strings .ToLower (wireP .CustomerName ), s ) > WIRELevenshteinSimilarity {
286
-
287
- // Only append if the not included in the Participant sub-set
288
- if len (Participants ) != 0 {
289
- for _ , p := range Participants {
290
- if p .CustomerName == wireP .CustomerName && p .RoutingNumber == wireP .RoutingNumber {
291
- break
292
- }
293
- }
294
- Participants = append (Participants , wireP )
295
-
296
- } else {
297
- Participants = append (Participants , wireP )
298
- }
299
- }
300
- }
301
- // Sort the result
302
- sort .SliceStable (Participants , func (i , j int ) bool { return Participants [i ].CustomerName < Participants [j ].CustomerName })
303
-
304
- return Participants
306
+ return reduceWIREResults (out , limit )
305
307
}
306
308
307
309
// WIREParticipantRoutingNumberFilter filters WIREParticipant by Routing Number
@@ -365,3 +367,19 @@ func (f *WIREDictionary) CityFilter(s string) []*WIREParticipant {
365
367
}
366
368
return nsl
367
369
}
370
+
371
+ type wireParticipantResult struct {
372
+ * WIREParticipant
373
+
374
+ highestMatch float64
375
+ }
376
+
377
+ func reduceWIREResults (in []* wireParticipantResult , limit int ) []* WIREParticipant {
378
+ sort .SliceStable (in , func (i , j int ) bool { return in [i ].highestMatch > in [j ].highestMatch })
379
+
380
+ out := make ([]* WIREParticipant , 0 )
381
+ for i := 0 ; i < limit && i < len (in ); i ++ {
382
+ out = append (out , in [i ].WIREParticipant )
383
+ }
384
+ return out
385
+ }
0 commit comments