@natlibfi/melinda-record-matching 2.0.1 → 2.1.0-alpha.1

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.
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
27
27
  *
28
28
  * Melinda record matching modules for Javascript
29
29
  *
30
- * Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)
30
+ * Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)
31
31
  *
32
32
  * This file is part of melinda-record-matching-js
33
33
  *
@@ -52,74 +52,392 @@ var _default = ({
52
52
  detection: detectionOptions,
53
53
  search: searchOptions,
54
54
  maxMatches = 1,
55
- maxCandidates = 25
55
+ maxCandidates = 25,
56
+ returnStrategy = false,
57
+ returnQuery = false,
58
+ returnNonMatches = false
56
59
  }) => {
57
60
  const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:index');
58
- const detect = (0, matchDetection.default)(detectionOptions);
61
+ const debugData = debug.extend('data');
62
+ debugData(`DetectionOptions: ${JSON.stringify(detectionOptions)}`);
63
+ debugData(`SearchOptions: ${JSON.stringify(searchOptions)}`);
64
+ debugData(`MaxMatches: ${JSON.stringify(maxMatches)}`);
65
+ debugData(`MaxCandidates: ${JSON.stringify(maxCandidates)}`);
66
+ debugData(`ReturnStrategy: ${JSON.stringify(returnStrategy)}`);
67
+ debugData(`ReturnQuery: ${JSON.stringify(returnQuery)}`);
68
+ debugData(`ReturnNonMatches: ${JSON.stringify(returnNonMatches)}`);
69
+ const detect = (0, matchDetection.default)(detectionOptions, returnStrategy);
59
70
  return record => {
60
71
  const search = (0, candidateSearch.default)({ ...searchOptions,
61
72
  record
62
73
  });
63
- return iterate(); // eslint-disable-next-line max-statements
74
+ return iterate({}); // candidateCount : amount of candidate records retrived from SRU for matching, NOT including current record set
75
+ // matches : candidates that have been detected as matches by current matcher job
76
+ // nonMatches : candidates that have been detected as non-matches by current matcher job (only if returnNonMatches is 'true')
77
+ // duplicateCount : amount of candidate records that were retrieved from the SRU but not handled further because they were already found in the matches/nonMatches
78
+ // state.totalRecords : amount of candidate records available to the current query (undefined, if there was no queries left)
79
+ // state.query : current query (undefined if there was no queries left)
80
+ // state.searchCounter : sequence for current search for current query (undefined, if there we no queries left)
81
+ // state.queryCandidateCounter: amount of candidate records retrieved from SRU for matching for current query, including the current record set (undefined if there were no queries left)
82
+ // state.queriesLeft : amount of queries left
83
+ // state.queryCounter : sequence for current query
84
+ // state.maxedQueries : queries that resulted in more than serverMaxResults hits
64
85
 
65
- async function iterate(initialState = {}, matches = [], candidateCount = 0) {
86
+ async function iterate({
87
+ initialState = {},
88
+ matches = [],
89
+ candidateCount = 0,
90
+ nonMatches = [],
91
+ duplicateCount = 0
92
+ }) {
93
+ debugData(`Starting next matcher iteration.`);
66
94
  const {
67
95
  records,
68
96
  ...state
69
97
  } = await search(initialState);
98
+ debugData(`Current state: ${JSON.stringify(state)}, matches: ${matches.length}, candidateCount: ${candidateCount}, nonMatches: ${nonMatches.length}`);
99
+ const recordSetSize = records.length;
100
+ const newCandidateCount = candidateCount + recordSetSize;
70
101
 
71
- if (records.length > 0 || state.queriesLeft > 0) {
72
- debug(`Checking ${records.length} candidates for matches`);
73
- const matchResult = iterateRecords(records);
102
+ if (recordSetSize > 0) {
103
+ return handleRecordSet();
104
+ }
105
+
106
+ if (state.queriesLeft > 0) {
107
+ debug(`Empty record set ${state.searchCounter} for ${state.query}, but there are ${state.queriesLeft} queries left`);
108
+ return iterate({
109
+ initialState: state,
110
+ matches,
111
+ candidateCount: newCandidateCount,
112
+ nonMatches,
113
+ duplicateCount
114
+ });
115
+ }
116
+
117
+ debug(`No (more) candidate records to check, no more queries left, matches: ${matches.length}`);
118
+ return returnResult({
119
+ matches,
120
+ state,
121
+ stopReason: '',
122
+ nonMatches,
123
+ candidateCount: newCandidateCount,
124
+ duplicateCount
125
+ });
126
+
127
+ function handleRecordSet() {
128
+ debug(`Checking record set of ${recordSetSize} candidate records for possible matches, found by ${state.searchCounter} search for ${state.query}`);
129
+ const matchResult = iterateRecords({
130
+ records,
131
+ recordSetSize,
132
+ maxMatches,
133
+ matches,
134
+ nonMatches
135
+ });
136
+ const newDuplicateCount = duplicateCount + matchResult.duplicateCount;
137
+ const {
138
+ newMatches,
139
+ newNonMatches
140
+ } = handleMatchResult(matchResult, matches, nonMatches);
141
+
142
+ if (maxMatchesFound({
143
+ matches: newMatches,
144
+ maxMatches
145
+ })) {
146
+ return returnResult({
147
+ matches: newMatches,
148
+ state,
149
+ stopReason: 'maxMatches',
150
+ nonMatches: newNonMatches,
151
+ duplicateCount: newDuplicateCount,
152
+ candidateCount: newCandidateCount
153
+ });
154
+ }
74
155
 
75
- if (matchResult) {
76
- const newMatches = matches.concat(matchResult);
156
+ if (maxCandidatesRetrieved(newCandidateCount, maxCandidates)) {
157
+ return returnResult({
158
+ matches: newMatches,
159
+ state,
160
+ stopReason: 'maxCandidates',
161
+ nonMatches: newNonMatches,
162
+ duplicateCount: newDuplicateCount,
163
+ candidateCount: newCandidateCount
164
+ });
165
+ }
166
+
167
+ return iterate({
168
+ initialState: state,
169
+ matches: newMatches,
170
+ candidateCount: newCandidateCount,
171
+ nonMatches: newNonMatches,
172
+ duplicateCount: newDuplicateCount
173
+ });
174
+ }
175
+
176
+ function handleMatchResult(matchResult, matches, nonMatches) {
177
+ debugData(`- Amount of new matches from record set: ${matchResult.matches.length}`); // eslint-disable-next-line functional/no-conditional-statement
178
+
179
+ if (returnNonMatches) {
180
+ debugData(`- Amount of new nonMatches from record set: ${matchResult.nonMatches.length}`);
181
+ }
77
182
 
78
- if (newMatches.length === maxMatches) {
79
- return newMatches;
80
- }
183
+ const newMatches = matches.concat(returnQuery ? addQuery(matchResult.matches) : matchResult.matches);
184
+ const newNonMatches = returnNonMatches ? nonMatches.concat(returnQuery ? addQuery(matchResult.nonMatches) : matchResult.nonMatches) : [];
185
+ debugData(`- Total amount of matches: ${newMatches.length}`); // eslint-disable-next-line functional/no-conditional-statement
81
186
 
82
- return maxCandidatesRetrieved() ? newMatches : iterate(state, newMatches, candidateCount + records.length);
187
+ if (returnNonMatches) {
188
+ debugData(`- Total amount of nonMatches: ${newNonMatches.length}`);
83
189
  }
84
190
 
85
- return maxCandidatesRetrieved() ? matches : iterate(state, matches, candidateCount + records.length);
191
+ return {
192
+ newMatches,
193
+ newNonMatches
194
+ };
86
195
  }
87
196
 
88
- debug(`No (more) candidate records to check, matches: ${matches.length}`);
89
- return matches;
197
+ function addQuery(matches) {
198
+ debugData(`Adding query ${state.query} to matches`);
199
+ return matches.map(match => ({ ...match,
200
+ matchQuery: state.query
201
+ }));
202
+ }
203
+
204
+ function maxCandidatesRetrieved(candidateCount, maxCandidates) {
205
+ debugData(`Total amount of candidate records retrieved: ${newCandidateCount} (max: ${maxCandidates})`);
90
206
 
91
- function maxCandidatesRetrieved() {
92
- if (candidateCount + records.length > maxCandidates) {
93
- debug(`Stopped searching because maximum number of candidates have been retrieved`);
207
+ if (maxCandidates && candidateCount >= maxCandidates) {
208
+ debug(`Stopped matching because maximum number of candidate records ${candidateCount} / ${maxCandidates} have been retrieved`);
94
209
  return true;
95
210
  }
96
211
  }
212
+ } // matches : array of matching candidate records
213
+ // nonMatches : array of nonMatching candidate records (if returnNonMatches option is true, otherwise empty array)
214
+ // - candidate.id
215
+ // - candidate.record
216
+ // - probability
217
+ // - strategy (if returnStrategy option is true)
218
+ // - treshold (if returnStrategy option is true)
219
+ // - matchQuery (if returnQuery option is true)
220
+ // we could have here also returnRecords/returnMatchRecords/returnNonMatchRecord options that could be turned false for not to return actual record data
221
+ // matchStatus.status: boolean, true if matcher retrieved and handled all found candidate records, false if it did not
222
+ // matchStatus.stopReason: string ('maxMatches','maxCandidates','maxedQueries',empty string/undefined), reason for stopping retrieving or handling the candidate records
223
+ // - only one stopReason is returned (if there would be several possible stopReasons, stopReason is picked in the above order)
224
+ // - currently stopReason can be non-empty also in cases where status is true, if matcher hit the stop reason when handling the last available candidate record
225
+
226
+
227
+ function returnResult({
228
+ matches,
229
+ state,
230
+ stopReason,
231
+ nonMatches,
232
+ duplicateCount,
233
+ candidateCount
234
+ }) {
235
+ checkCounts(matches, nonMatches, candidateCount, duplicateCount);
236
+ const matchStatus = getMatchState(state, stopReason); // add nonMatches to result only if returnNonMatches is 'true', otherwise nonMatches have not been gathered
237
+
238
+ const result = returnNonMatches ? {
239
+ matches,
240
+ matchStatus,
241
+ nonMatches
242
+ } : {
243
+ matches,
244
+ matchStatus
245
+ };
246
+ debugData(`${JSON.stringify(result)}`);
247
+ return result; // we could have a nonMatchCount even if nonMatches won't be returned so that checkCounts would make sense
248
+ // note that in cases where the matching has been stopped because of maxMatches checkCounts won't (in most cases) match
249
+
250
+ function checkCounts(matches, nonMatches, candidateCount, duplicateCount) {
251
+ const matchCount = matches.length;
252
+ const nonMatchCount = nonMatches.length;
253
+ const totalHandled = matchCount + nonMatchCount + duplicateCount;
254
+ debug(`candidateCount: ${candidateCount}, matches: ${matchCount}, nonMatches: ${nonMatchCount}, duplicateCount: ${duplicateCount}`);
255
+ debug(`We got result for ${totalHandled} / ${candidateCount} retrieved candidates`);
256
+
257
+ if (totalHandled !== candidateCount) {
258
+ debug(`WARNING: Missing results for ${candidateCount - totalHandled} candidates`);
259
+ return;
260
+ }
261
+
262
+ return;
263
+ }
264
+
265
+ function getMatchState(state, stopReason) {
266
+ debug(`Queries left ${state.queriesLeft}, Searches for current query left: ${state.resultSetOffset >= state.totalRecords}, non-retrieved records: ${state.totalRecords - state.queryCandidateCounter}, maxedQueries (${state.maxedQueries.length}): ${state.maxedQueries}`);
267
+ debugData(`StopReason: <${stopReason}>`);
268
+ const nonRetrieved = state.resultSetOffset >= state.totalRecords ? state.totalRecords - state.queryCandidateCounter : 0;
269
+
270
+ if (state.queriesLeft > 0 || nonRetrieved > 0 || state.maxedQueries.length > 0) {
271
+ const maxedQueriesStopReason = state.maxedQueries.length > 0 ? 'maxedQueries' : '';
272
+ const newStopReason = stopReason === '' ? maxedQueriesStopReason : stopReason;
273
+ debugData(`MaxedQueriesStopReason: <${maxedQueriesStopReason}>`);
274
+ debugData(`NewStopReason: <${newStopReason}>`);
275
+ debug(`Match status: false`);
276
+ return {
277
+ status: false,
278
+ stopReason: newStopReason
279
+ };
280
+ }
281
+
282
+ debug(`Match status: true`);
283
+ return {
284
+ status: true,
285
+ stopReason
286
+ };
287
+ }
288
+ }
97
289
 
98
- function iterateRecords(records) {
99
- const [candidate] = records;
290
+ function iterateRecords({
291
+ records,
292
+ recordSetSize,
293
+ maxMatches,
294
+ matches = [],
295
+ nonMatches = [],
296
+ recordMatches = [],
297
+ recordNonMatches = [],
298
+ recordCount = 0,
299
+ recordDuplicateCount = 0
300
+ }) {
301
+ // recordSetSize : total amount of records in the current record set
302
+ // recordCount : amount of records from the current record set that have been handled
303
+ // maxMatches : setting for maximum amount found by current matcher job before the matcher job is stopped
304
+ // recordDuplicateCount : amount of records from the current record set that are already included in matches/nonMatches results
305
+ // records : non-handled records in the current record set
306
+ // matches : found matches in the current matcher job
307
+ // recordMatches : found matches in the current record set
308
+ // recordNonMatches : found nonMatches in the current record set (only if returnNonMatches setting is true)
309
+ const [candidate] = records;
310
+ const newRecordCount = candidate ? recordCount + 1 : recordCount; // The matcher uses same matchDetection strategy for candidates from all candidate-searches -> matchDetection result for the same candidate is always same
311
+ // Exceptions would happen if the candidate would have been updated in the database between candidate searches
312
+ // Note that if returnNonMatches is false, matcher won't remember candidates that didn't match, so they will be matched again everytime they are retrieved by
313
+ // different candidate search queries. Same candidate search query won't have duplicate records.
100
314
 
101
- if (candidate) {
315
+ if (candidate) {
316
+ if (candidateNotInMatches(matches.concat(nonMatches), candidate)) {
102
317
  const {
103
318
  record: candidateRecord,
104
319
  id: candidateId
105
320
  } = candidate;
106
- const {
107
- match,
108
- probability
109
- } = detect(record, candidateRecord);
110
-
111
- if (match) {
112
- return {
113
- probability,
114
- candidate: {
115
- id: candidateId,
116
- record: candidateRecord
117
- }
118
- };
119
- }
120
-
121
- return iterateRecords(records.slice(1));
321
+ debug(`Running matchDetection for record ${candidateId} (${newRecordCount}/${recordSetSize})`);
322
+ const detectionResult = detect(record, candidateRecord);
323
+ return handleDetectionResult(detectionResult, candidateId, candidateRecord);
324
+ }
325
+
326
+ return iterateRecords({
327
+ records: records.slice(1),
328
+ recordSetSize,
329
+ maxMatches,
330
+ matches,
331
+ recordMatches,
332
+ recordCount: newRecordCount,
333
+ recordNonMatches,
334
+ recordDuplicateCount: recordDuplicateCount + 1
335
+ });
336
+ }
337
+
338
+ debug(`No more candidates, record set (${recordCount}/${recordSetSize}) done, ${recordMatches.length} matches found, ${recordDuplicateCount} candidates already handled${returnNonMatches ? `, ${recordNonMatches.length} nonMatches found.` : '.'}`);
339
+ return {
340
+ matches: recordMatches,
341
+ nonMatches: returnNonMatches ? recordNonMatches : [],
342
+ duplicateCount: recordDuplicateCount
343
+ };
344
+
345
+ function handleDetectionResult(detectionResult, candidateId, candidateRecord) {
346
+ debugData(`MatchDetection results for ${candidateId} (${newRecordCount}/${recordSetSize}): ${JSON.stringify(detectionResult)}`);
347
+
348
+ if (detectionResult.match || returnNonMatches) {
349
+ debug(`${detectionResult.match ? `Record ${candidateId} (${newRecordCount}/${recordSetSize}) is a match!` : `Record ${candidateId} (${newRecordCount}/${recordSetSize}) is NOT a match!`}`);
350
+ debugData(`Strategy: ${JSON.stringify(detectionResult.strategy)}, Treshold: ${JSON.stringify(detectionResult.treshold)}`);
351
+ const matchResult = {
352
+ probability: detectionResult.probability,
353
+ candidate: {
354
+ id: candidateId,
355
+ record: candidateRecord
356
+ }
357
+ };
358
+ const strategyResult = {
359
+ strategy: detectionResult.strategy,
360
+ treshold: detectionResult.treshold
361
+ };
362
+ const newMatch = returnStrategy ? { ...matchResult,
363
+ ...strategyResult
364
+ } : { ...matchResult
365
+ };
366
+ debugData(`${JSON.stringify(newMatch)}`);
367
+ return handleRecordMatch(detectionResult.match, newMatch);
368
+ }
369
+
370
+ return iterateRecords({
371
+ records: records.slice(1),
372
+ recordSetSize,
373
+ maxMatches,
374
+ matches,
375
+ recordMatches,
376
+ recordCount: newRecordCount,
377
+ recordNonMatches,
378
+ recordDuplicateCount
379
+ });
380
+ }
381
+
382
+ function handleRecordMatch(isMatch, newMatch) {
383
+ const newRecordMatches = isMatch ? recordMatches.concat(newMatch) : recordMatches;
384
+ const newRecordNonMatches = isMatch ? recordNonMatches : recordNonMatches.concat(newMatch);
385
+ debugData(`- Total matches after this detection: ${matches.concat(newRecordMatches).length} (max: ${maxMatches})`); // eslint-disable-next-line functional/no-conditional-statement
386
+
387
+ if (returnNonMatches) {
388
+ debugData(`- Total nonMatches after this detection: ${nonMatches.concat(newRecordNonMatches).length}`);
389
+ }
390
+
391
+ if (maxMatchesFound({
392
+ matches: matches.concat(newRecordMatches),
393
+ maxMatches
394
+ })) {
395
+ debug(`MaxMatches (${maxMatches}) reached, handled candidates in record set: ${newRecordCount} non-handled candidates in record set ${recordSetSize - newRecordCount}`);
396
+ return {
397
+ matches: newRecordMatches,
398
+ nonMatches: returnNonMatches ? newRecordNonMatches : [],
399
+ duplicateCount: recordDuplicateCount
400
+ };
122
401
  }
402
+
403
+ return iterateRecords({
404
+ records: records.slice(1),
405
+ recordSetSize,
406
+ maxMatches,
407
+ matches,
408
+ recordMatches: newRecordMatches,
409
+ recordCount: newRecordCount,
410
+ recordNonMatches: returnNonMatches ? newRecordNonMatches : [],
411
+ duplicateCount: recordDuplicateCount
412
+ });
413
+ }
414
+
415
+ function candidateNotInMatches(matches, candidate) {
416
+ debug(`Checking that record ${candidate.id} is not already included in ${matches.length} matches/nonMatches`);
417
+ const newCandidateId = candidate.id;
418
+ debugData(`newCandidateId: ${newCandidateId}`);
419
+ const result = matches.find(({
420
+ candidate
421
+ }) => candidate.id === newCandidateId);
422
+ debugData(`Result: ${result}`);
423
+
424
+ if (result) {
425
+ debug(`${candidate.id} was already handled.`);
426
+ return false;
427
+ }
428
+
429
+ debug(`${candidate.id} not found in matches/nonMatches`);
430
+ return true;
431
+ }
432
+ }
433
+
434
+ function maxMatchesFound({
435
+ matches,
436
+ maxMatches
437
+ }) {
438
+ if (maxMatches && matches.length >= maxMatches) {
439
+ debug(`Stopping recordSet iteration: maxMatches (${maxMatches}) for matcher job found.`);
440
+ return true;
123
441
  }
124
442
  }
125
443
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.js"],"names":["detection","detectionOptions","search","searchOptions","maxMatches","maxCandidates","debug","detect","record","iterate","initialState","matches","candidateCount","records","state","length","queriesLeft","matchResult","iterateRecords","newMatches","concat","maxCandidatesRetrieved","candidate","candidateRecord","id","candidateId","match","probability","slice"],"mappings":";;;;;;;AA4BA;;AACA;;;;AACA;;;;;;;;;;AA9BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;eAQe,CAAC;AAACA,EAAAA,SAAS,EAAEC,gBAAZ;AAA8BC,EAAAA,MAAM,EAAEC,aAAtC;AAAqDC,EAAAA,UAAU,GAAG,CAAlE;AAAqEC,EAAAA,aAAa,GAAG;AAArF,CAAD,KAA8F;AAC3G,QAAMC,KAAK,GAAG,oBAAkB,yCAAlB,CAAd;AACA,QAAMC,MAAM,GAAG,4BAAyBN,gBAAzB,CAAf;AAEA,SAAOO,MAAM,IAAI;AACf,UAAMN,MAAM,GAAG,6BAAsB,EAAC,GAAGC,aAAJ;AAAmBK,MAAAA;AAAnB,KAAtB,CAAf;AACA,WAAOC,OAAO,EAAd,CAFe,CAIf;;AACA,mBAAeA,OAAf,CAAuBC,YAAY,GAAG,EAAtC,EAA0CC,OAAO,GAAG,EAApD,EAAwDC,cAAc,GAAG,CAAzE,EAA4E;AAC1E,YAAM;AAACC,QAAAA,OAAD;AAAU,WAAGC;AAAb,UAAsB,MAAMZ,MAAM,CAACQ,YAAD,CAAxC;;AAEA,UAAIG,OAAO,CAACE,MAAR,GAAiB,CAAjB,IAAsBD,KAAK,CAACE,WAAN,GAAoB,CAA9C,EAAiD;AAC/CV,QAAAA,KAAK,CAAE,YAAWO,OAAO,CAACE,MAAO,yBAA5B,CAAL;AAEA,cAAME,WAAW,GAAGC,cAAc,CAACL,OAAD,CAAlC;;AAEA,YAAII,WAAJ,EAAiB;AACf,gBAAME,UAAU,GAAGR,OAAO,CAACS,MAAR,CAAeH,WAAf,CAAnB;;AAEA,cAAIE,UAAU,CAACJ,MAAX,KAAsBX,UAA1B,EAAsC;AACpC,mBAAOe,UAAP;AACD;;AAED,iBAAOE,sBAAsB,KAAKF,UAAL,GAAkBV,OAAO,CAACK,KAAD,EAAQK,UAAR,EAAoBP,cAAc,GAAGC,OAAO,CAACE,MAA7C,CAAtD;AACD;;AAED,eAAOM,sBAAsB,KAAKV,OAAL,GAAeF,OAAO,CAACK,KAAD,EAAQH,OAAR,EAAiBC,cAAc,GAAGC,OAAO,CAACE,MAA1C,CAAnD;AACD;;AAEDT,MAAAA,KAAK,CAAE,kDAAiDK,OAAO,CAACI,MAAO,EAAlE,CAAL;AACA,aAAOJ,OAAP;;AAEA,eAASU,sBAAT,GAAkC;AAChC,YAAIT,cAAc,GAAGC,OAAO,CAACE,MAAzB,GAAkCV,aAAtC,EAAqD;AACnDC,UAAAA,KAAK,CAAE,4EAAF,CAAL;AACA,iBAAO,IAAP;AACD;AACF;;AAED,eAASY,cAAT,CAAwBL,OAAxB,EAAiC;AAC/B,cAAM,CAACS,SAAD,IAAcT,OAApB;;AAEA,YAAIS,SAAJ,EAAe;AACb,gBAAM;AAACd,YAAAA,MAAM,EAAEe,eAAT;AAA0BC,YAAAA,EAAE,EAAEC;AAA9B,cAA6CH,SAAnD;AACA,gBAAM;AAACI,YAAAA,KAAD;AAAQC,YAAAA;AAAR,cAAuBpB,MAAM,CAACC,MAAD,EAASe,eAAT,CAAnC;;AAEA,cAAIG,KAAJ,EAAW;AACT,mBAAO;AACLC,cAAAA,WADK;AAELL,cAAAA,SAAS,EAAE;AACTE,gBAAAA,EAAE,EAAEC,WADK;AAETjB,gBAAAA,MAAM,EAAEe;AAFC;AAFN,aAAP;AAOD;;AAED,iBAAOL,cAAc,CAACL,OAAO,CAACe,KAAR,CAAc,CAAd,CAAD,CAArB;AACD;AACF;AACF;AACF,GAzDD;AA0DD,C","sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\nimport createSearchInterface, * as candidateSearch from './candidate-search';\nimport createDetectionInterface, * as matchDetection from './match-detection';\n\nexport {candidateSearch, matchDetection};\n\nexport default ({detection: detectionOptions, search: searchOptions, maxMatches = 1, maxCandidates = 25}) => {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:index');\n const detect = createDetectionInterface(detectionOptions);\n\n return record => {\n const search = createSearchInterface({...searchOptions, record});\n return iterate();\n\n // eslint-disable-next-line max-statements\n async function iterate(initialState = {}, matches = [], candidateCount = 0) {\n const {records, ...state} = await search(initialState);\n\n if (records.length > 0 || state.queriesLeft > 0) {\n debug(`Checking ${records.length} candidates for matches`);\n\n const matchResult = iterateRecords(records);\n\n if (matchResult) {\n const newMatches = matches.concat(matchResult);\n\n if (newMatches.length === maxMatches) {\n return newMatches;\n }\n\n return maxCandidatesRetrieved() ? newMatches : iterate(state, newMatches, candidateCount + records.length);\n }\n\n return maxCandidatesRetrieved() ? matches : iterate(state, matches, candidateCount + records.length);\n }\n\n debug(`No (more) candidate records to check, matches: ${matches.length}`);\n return matches;\n\n function maxCandidatesRetrieved() {\n if (candidateCount + records.length > maxCandidates) {\n debug(`Stopped searching because maximum number of candidates have been retrieved`);\n return true;\n }\n }\n\n function iterateRecords(records) {\n const [candidate] = records;\n\n if (candidate) {\n const {record: candidateRecord, id: candidateId} = candidate;\n const {match, probability} = detect(record, candidateRecord);\n\n if (match) {\n return {\n probability,\n candidate: {\n id: candidateId,\n record: candidateRecord\n }\n };\n }\n\n return iterateRecords(records.slice(1));\n }\n }\n }\n };\n};\n"],"file":"index.js"}
1
+ {"version":3,"sources":["../src/index.js"],"names":["detection","detectionOptions","search","searchOptions","maxMatches","maxCandidates","returnStrategy","returnQuery","returnNonMatches","debug","debugData","extend","JSON","stringify","detect","record","iterate","initialState","matches","candidateCount","nonMatches","duplicateCount","records","state","length","recordSetSize","newCandidateCount","handleRecordSet","queriesLeft","searchCounter","query","returnResult","stopReason","matchResult","iterateRecords","newDuplicateCount","newMatches","newNonMatches","handleMatchResult","maxMatchesFound","maxCandidatesRetrieved","concat","addQuery","map","match","matchQuery","checkCounts","matchStatus","getMatchState","result","matchCount","nonMatchCount","totalHandled","resultSetOffset","totalRecords","queryCandidateCounter","maxedQueries","nonRetrieved","maxedQueriesStopReason","newStopReason","status","recordMatches","recordNonMatches","recordCount","recordDuplicateCount","candidate","newRecordCount","candidateNotInMatches","candidateRecord","id","candidateId","detectionResult","handleDetectionResult","slice","strategy","treshold","probability","strategyResult","newMatch","handleRecordMatch","isMatch","newRecordMatches","newRecordNonMatches","newCandidateId","find"],"mappings":"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","sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\nimport createSearchInterface, * as candidateSearch from './candidate-search';\nimport createDetectionInterface, * as matchDetection from './match-detection';\n\nexport {candidateSearch, matchDetection};\n\nexport default ({detection: detectionOptions, search: searchOptions, maxMatches = 1, maxCandidates = 25, returnStrategy = false, returnQuery = false, returnNonMatches = false}) => {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:index');\n const debugData = debug.extend('data');\n\n debugData(`DetectionOptions: ${JSON.stringify(detectionOptions)}`);\n debugData(`SearchOptions: ${JSON.stringify(searchOptions)}`);\n debugData(`MaxMatches: ${JSON.stringify(maxMatches)}`);\n debugData(`MaxCandidates: ${JSON.stringify(maxCandidates)}`);\n debugData(`ReturnStrategy: ${JSON.stringify(returnStrategy)}`);\n debugData(`ReturnQuery: ${JSON.stringify(returnQuery)}`);\n debugData(`ReturnNonMatches: ${JSON.stringify(returnNonMatches)}`);\n\n const detect = createDetectionInterface(detectionOptions, returnStrategy);\n\n return record => {\n const search = createSearchInterface({...searchOptions, record});\n return iterate({});\n\n // candidateCount : amount of candidate records retrived from SRU for matching, NOT including current record set\n // matches : candidates that have been detected as matches by current matcher job\n // nonMatches : candidates that have been detected as non-matches by current matcher job (only if returnNonMatches is 'true')\n // duplicateCount : amount of candidate records that were retrieved from the SRU but not handled further because they were already found in the matches/nonMatches\n\n // state.totalRecords : amount of candidate records available to the current query (undefined, if there was no queries left)\n // state.query : current query (undefined if there was no queries left)\n // state.searchCounter : sequence for current search for current query (undefined, if there we no queries left)\n // state.queryCandidateCounter: amount of candidate records retrieved from SRU for matching for current query, including the current record set (undefined if there were no queries left)\n // state.queriesLeft : amount of queries left\n // state.queryCounter : sequence for current query\n // state.maxedQueries : queries that resulted in more than serverMaxResults hits\n\n async function iterate({initialState = {}, matches = [], candidateCount = 0, nonMatches = [], duplicateCount = 0}) {\n debugData(`Starting next matcher iteration.`);\n const {records, ...state} = await search(initialState);\n\n debugData(`Current state: ${JSON.stringify(state)}, matches: ${matches.length}, candidateCount: ${candidateCount}, nonMatches: ${nonMatches.length}`);\n const recordSetSize = records.length;\n const newCandidateCount = candidateCount + recordSetSize;\n\n if (recordSetSize > 0) {\n return handleRecordSet();\n }\n\n if (state.queriesLeft > 0) {\n debug(`Empty record set ${state.searchCounter} for ${state.query}, but there are ${state.queriesLeft} queries left`);\n return iterate({initialState: state, matches, candidateCount: newCandidateCount, nonMatches, duplicateCount});\n }\n\n debug(`No (more) candidate records to check, no more queries left, matches: ${matches.length}`);\n return returnResult({matches, state, stopReason: '', nonMatches, candidateCount: newCandidateCount, duplicateCount});\n\n function handleRecordSet() {\n debug(`Checking record set of ${recordSetSize} candidate records for possible matches, found by ${state.searchCounter} search for ${state.query}`);\n\n const matchResult = iterateRecords({records, recordSetSize, maxMatches, matches, nonMatches});\n const newDuplicateCount = duplicateCount + matchResult.duplicateCount;\n const {newMatches, newNonMatches} = handleMatchResult(matchResult, matches, nonMatches);\n\n if (maxMatchesFound({matches: newMatches, maxMatches})) {\n return returnResult({matches: newMatches, state, stopReason: 'maxMatches', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount});\n }\n\n if (maxCandidatesRetrieved(newCandidateCount, maxCandidates)) {\n return returnResult({matches: newMatches, state, stopReason: 'maxCandidates', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount});\n }\n\n return iterate({initialState: state, matches: newMatches, candidateCount: newCandidateCount, nonMatches: newNonMatches, duplicateCount: newDuplicateCount});\n }\n\n function handleMatchResult(matchResult, matches, nonMatches) {\n debugData(`- Amount of new matches from record set: ${matchResult.matches.length}`);\n // eslint-disable-next-line functional/no-conditional-statement\n if (returnNonMatches) {\n debugData(`- Amount of new nonMatches from record set: ${matchResult.nonMatches.length}`);\n }\n\n const newMatches = matches.concat(returnQuery ? addQuery(matchResult.matches) : matchResult.matches);\n const newNonMatches = returnNonMatches ? nonMatches.concat(returnQuery ? addQuery(matchResult.nonMatches) : matchResult.nonMatches) : [];\n\n debugData(`- Total amount of matches: ${newMatches.length}`);\n // eslint-disable-next-line functional/no-conditional-statement\n if (returnNonMatches) {\n debugData(`- Total amount of nonMatches: ${newNonMatches.length}`);\n }\n\n return {newMatches, newNonMatches};\n }\n\n function addQuery(matches) {\n debugData(`Adding query ${state.query} to matches`);\n return matches.map((match) => ({...match, matchQuery: state.query}));\n }\n\n function maxCandidatesRetrieved(candidateCount, maxCandidates) {\n debugData(`Total amount of candidate records retrieved: ${newCandidateCount} (max: ${maxCandidates})`);\n if (maxCandidates && candidateCount >= maxCandidates) {\n debug(`Stopped matching because maximum number of candidate records ${candidateCount} / ${maxCandidates} have been retrieved`);\n return true;\n }\n }\n }\n\n // matches : array of matching candidate records\n // nonMatches : array of nonMatching candidate records (if returnNonMatches option is true, otherwise empty array)\n // - candidate.id\n // - candidate.record\n // - probability\n // - strategy (if returnStrategy option is true)\n // - treshold (if returnStrategy option is true)\n // - matchQuery (if returnQuery option is true)\n\n // we could have here also returnRecords/returnMatchRecords/returnNonMatchRecord options that could be turned false for not to return actual record data\n\n // matchStatus.status: boolean, true if matcher retrieved and handled all found candidate records, false if it did not\n // matchStatus.stopReason: string ('maxMatches','maxCandidates','maxedQueries',empty string/undefined), reason for stopping retrieving or handling the candidate records\n // - only one stopReason is returned (if there would be several possible stopReasons, stopReason is picked in the above order)\n // - currently stopReason can be non-empty also in cases where status is true, if matcher hit the stop reason when handling the last available candidate record\n\n function returnResult({matches, state, stopReason, nonMatches, duplicateCount, candidateCount}) {\n checkCounts(matches, nonMatches, candidateCount, duplicateCount);\n const matchStatus = getMatchState(state, stopReason);\n // add nonMatches to result only if returnNonMatches is 'true', otherwise nonMatches have not been gathered\n const result = returnNonMatches ? {matches, matchStatus, nonMatches} : {matches, matchStatus};\n debugData(`${JSON.stringify(result)}`);\n return result;\n\n // we could have a nonMatchCount even if nonMatches won't be returned so that checkCounts would make sense\n // note that in cases where the matching has been stopped because of maxMatches checkCounts won't (in most cases) match\n\n function checkCounts(matches, nonMatches, candidateCount, duplicateCount) {\n const matchCount = matches.length;\n const nonMatchCount = nonMatches.length;\n const totalHandled = matchCount + nonMatchCount + duplicateCount;\n debug(`candidateCount: ${candidateCount}, matches: ${matchCount}, nonMatches: ${nonMatchCount}, duplicateCount: ${duplicateCount}`);\n debug(`We got result for ${totalHandled} / ${candidateCount} retrieved candidates`);\n if (totalHandled !== candidateCount) {\n debug(`WARNING: Missing results for ${candidateCount - totalHandled} candidates`);\n return;\n }\n return;\n }\n\n function getMatchState(state, stopReason) {\n debug(`Queries left ${state.queriesLeft}, Searches for current query left: ${state.resultSetOffset >= state.totalRecords}, non-retrieved records: ${state.totalRecords - state.queryCandidateCounter}, maxedQueries (${state.maxedQueries.length}): ${state.maxedQueries}`);\n debugData(`StopReason: <${stopReason}>`);\n\n const nonRetrieved = state.resultSetOffset >= state.totalRecords ? state.totalRecords - state.queryCandidateCounter : 0;\n\n if (state.queriesLeft > 0 || nonRetrieved > 0 || state.maxedQueries.length > 0) {\n const maxedQueriesStopReason = state.maxedQueries.length > 0 ? 'maxedQueries' : '';\n const newStopReason = stopReason === '' ? maxedQueriesStopReason : stopReason;\n debugData(`MaxedQueriesStopReason: <${maxedQueriesStopReason}>`);\n debugData(`NewStopReason: <${newStopReason}>`);\n debug(`Match status: false`);\n return {status: false, stopReason: newStopReason};\n }\n\n debug(`Match status: true`);\n return {status: true, stopReason};\n }\n }\n\n function iterateRecords({records, recordSetSize, maxMatches, matches = [], nonMatches = [], recordMatches = [], recordNonMatches = [], recordCount = 0, recordDuplicateCount = 0}) {\n\n // recordSetSize : total amount of records in the current record set\n // recordCount : amount of records from the current record set that have been handled\n // maxMatches : setting for maximum amount found by current matcher job before the matcher job is stopped\n // recordDuplicateCount : amount of records from the current record set that are already included in matches/nonMatches results\n\n // records : non-handled records in the current record set\n // matches : found matches in the current matcher job\n // recordMatches : found matches in the current record set\n // recordNonMatches : found nonMatches in the current record set (only if returnNonMatches setting is true)\n\n const [candidate] = records;\n const newRecordCount = candidate ? recordCount + 1 : recordCount;\n\n // The matcher uses same matchDetection strategy for candidates from all candidate-searches -> matchDetection result for the same candidate is always same\n // Exceptions would happen if the candidate would have been updated in the database between candidate searches\n // Note that if returnNonMatches is false, matcher won't remember candidates that didn't match, so they will be matched again everytime they are retrieved by\n // different candidate search queries. Same candidate search query won't have duplicate records.\n\n if (candidate) {\n\n if (candidateNotInMatches(matches.concat(nonMatches), candidate)) {\n const {record: candidateRecord, id: candidateId} = candidate;\n debug(`Running matchDetection for record ${candidateId} (${newRecordCount}/${recordSetSize})`);\n const detectionResult = detect(record, candidateRecord);\n return handleDetectionResult(detectionResult, candidateId, candidateRecord);\n }\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount: recordDuplicateCount + 1});\n }\n\n debug(`No more candidates, record set (${recordCount}/${recordSetSize}) done, ${recordMatches.length} matches found, ${recordDuplicateCount} candidates already handled${returnNonMatches ? `, ${recordNonMatches.length} nonMatches found.` : '.'}`);\n return {matches: recordMatches, nonMatches: returnNonMatches ? recordNonMatches : [], duplicateCount: recordDuplicateCount};\n\n function handleDetectionResult(detectionResult, candidateId, candidateRecord) {\n debugData(`MatchDetection results for ${candidateId} (${newRecordCount}/${recordSetSize}): ${JSON.stringify(detectionResult)}`);\n\n if (detectionResult.match || returnNonMatches) {\n debug(`${detectionResult.match ? `Record ${candidateId} (${newRecordCount}/${recordSetSize}) is a match!` : `Record ${candidateId} (${newRecordCount}/${recordSetSize}) is NOT a match!`}`);\n debugData(`Strategy: ${JSON.stringify(detectionResult.strategy)}, Treshold: ${JSON.stringify(detectionResult.treshold)}`);\n\n const matchResult = {\n probability: detectionResult.probability,\n candidate: {\n id: candidateId,\n record: candidateRecord\n }\n };\n const strategyResult = {\n strategy: detectionResult.strategy,\n treshold: detectionResult.treshold\n };\n const newMatch = returnStrategy ? {...matchResult, ...strategyResult} : {...matchResult};\n\n debugData(`${JSON.stringify(newMatch)}`);\n\n return handleRecordMatch(detectionResult.match, newMatch);\n }\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount});\n }\n\n function handleRecordMatch(isMatch, newMatch) {\n const newRecordMatches = isMatch ? recordMatches.concat(newMatch) : recordMatches;\n const newRecordNonMatches = isMatch ? recordNonMatches : recordNonMatches.concat(newMatch);\n\n debugData(`- Total matches after this detection: ${matches.concat(newRecordMatches).length} (max: ${maxMatches})`);\n\n // eslint-disable-next-line functional/no-conditional-statement\n if (returnNonMatches) {\n debugData(`- Total nonMatches after this detection: ${nonMatches.concat(newRecordNonMatches).length}`);\n }\n\n if (maxMatchesFound({matches: matches.concat(newRecordMatches), maxMatches})) {\n debug(`MaxMatches (${maxMatches}) reached, handled candidates in record set: ${newRecordCount} non-handled candidates in record set ${recordSetSize - newRecordCount}`);\n return {matches: newRecordMatches, nonMatches: returnNonMatches ? newRecordNonMatches : [], duplicateCount: recordDuplicateCount};\n }\n\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches: newRecordMatches, recordCount: newRecordCount, recordNonMatches: returnNonMatches ? newRecordNonMatches : [], duplicateCount: recordDuplicateCount});\n }\n\n function candidateNotInMatches(matches, candidate) {\n debug(`Checking that record ${candidate.id} is not already included in ${matches.length} matches/nonMatches`);\n const newCandidateId = candidate.id;\n debugData(`newCandidateId: ${newCandidateId}`);\n const result = matches.find(({candidate}) => candidate.id === newCandidateId);\n debugData(`Result: ${result}`);\n if (result) {\n debug(`${candidate.id} was already handled.`);\n return false;\n }\n debug(`${candidate.id} not found in matches/nonMatches`);\n return true;\n }\n }\n\n function maxMatchesFound({matches, maxMatches}) {\n if (maxMatches && matches.length >= maxMatches) {\n debug(`Stopping recordSet iteration: maxMatches (${maxMatches}) for matcher job found.`);\n return true;\n }\n }\n };\n};\n"],"file":"index.js"}
@@ -10,6 +10,8 @@ var _marcRecord = require("@natlibfi/marc-record");
10
10
 
11
11
  var _ = _interopRequireWildcard(require("."));
12
12
 
13
+ var _debug = _interopRequireDefault(require("debug"));
14
+
13
15
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
14
16
 
15
17
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
@@ -22,7 +24,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
22
24
  *
23
25
  * Melinda record matching modules for Javascript
24
26
  *
25
- * Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)
27
+ * Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)
26
28
  *
27
29
  * This file is part of melinda-record-matching-js
28
30
  *
@@ -43,6 +45,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
43
45
  * for the JavaScript code in this file.
44
46
  *
45
47
  */
48
+ const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:index:test');
49
+ const debugData = debug.extend('data');
46
50
  describe('INDEX', () => {
47
51
  (0, _fixugenHttpClient.default)({
48
52
  callback,
@@ -56,9 +60,12 @@ describe('INDEX', () => {
56
60
  async function callback({
57
61
  getFixture,
58
62
  options,
59
- enabled = true
63
+ enabled = true,
64
+ expectedMatchStatus,
65
+ expectedStopReason
60
66
  }) {
61
67
  if (!enabled) {
68
+ debug(`Disabled test!`);
62
69
  return;
63
70
  }
64
71
 
@@ -66,35 +73,54 @@ describe('INDEX', () => {
66
73
  subfieldValues: false
67
74
  });
68
75
  const expectedMatches = getFixture('expectedMatches.json');
76
+ const expectedNonMatches = getFixture('expectedNonMatches.json') || [];
69
77
  const match = (0, _.default)(formatOptions());
70
- const matches = await match(record);
71
- (0, _chai.expect)(formatResults()).to.eql(expectedMatches);
78
+ const {
79
+ matches,
80
+ matchStatus,
81
+ nonMatches
82
+ } = await match(record);
83
+ debugData(`${matches.length}, ${matchStatus.status}/${matchStatus.stopReason}, ${nonMatches ? nonMatches.length : nonMatches}`);
84
+ const formattedMatchResult = formatRecordResults(matches);
85
+ (0, _chai.expect)(formattedMatchResult).to.eql(expectedMatches);
86
+ const formattedNonMatchResult = formatRecordResults(nonMatches);
87
+ (0, _chai.expect)(formattedNonMatchResult).to.eql(expectedNonMatches);
88
+ (0, _chai.expect)(matchStatus.status).to.eql(expectedMatchStatus);
89
+ (0, _chai.expect)(matchStatus.stopReason).to.eql(expectedStopReason);
72
90
 
73
91
  function formatOptions() {
74
92
  const contextFeatures = _.matchDetection.features[options.detection.strategy.type];
75
93
  return { ...options,
76
- search: { ...options.search,
77
- maxRecordsPerRequest: 1
94
+ search: { ...options.search
78
95
  },
79
96
  detection: { ...options.detect,
80
97
  strategy: options.detection.strategy.features.map(v => contextFeatures[v]())
81
- },
82
- maxMatches: 2,
83
- maxCandidates: 1
98
+ }
84
99
  };
85
100
  }
86
101
 
87
- function formatResults() {
88
- return matches.map(({
89
- candidate,
90
- probability
91
- }) => ({
92
- probability,
93
- candidate: {
94
- id: candidate.id,
95
- record: candidate.record.toObject()
96
- }
97
- }));
102
+ function formatRecordResults(matches) {
103
+ if (matches) {
104
+ debugData(JSON.stringify(matches));
105
+ return matches.map(match => ({ ...match,
106
+ candidate: formatCandidate(match.candidate)
107
+ }));
108
+ }
109
+
110
+ return [];
111
+ } // Format candidate to remove validationOptions from record
112
+
113
+
114
+ function formatCandidate({
115
+ id,
116
+ record
117
+ }) {
118
+ const newId = id;
119
+ const newRecord = record;
120
+ return {
121
+ id: newId,
122
+ record: newRecord.toObject()
123
+ };
98
124
  }
99
125
  }
100
126
  });