@natlibfi/melinda-record-matching 2.0.0 → 2.1.0-alpha.2

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/README.md CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  ## License and copyright
4
4
 
5
- Copyright (c) 2020 **University Of Helsinki (The National Library Of Finland)**
5
+ Copyright (c) 2020-2022 **University Of Helsinki (The National Library Of Finland)**
6
6
 
7
7
  This project's source code is licensed under the terms of **GNU Lesser General Public License Version 3** or any later version.
@@ -13,7 +13,7 @@ Object.defineProperty(exports, "searchTypes", {
13
13
 
14
14
  var _debug = _interopRequireDefault(require("debug"));
15
15
 
16
- var _sruClient = _interopRequireDefault(require("@natlibfi/sru-client"));
16
+ var _sruClient = _interopRequireWildcard(require("@natlibfi/sru-client"));
17
17
 
18
18
  var _marcRecord = require("@natlibfi/marc-record");
19
19
 
@@ -33,7 +33,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
33
33
  *
34
34
  * Melinda record matching modules for Javascript
35
35
  *
36
- * Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)
36
+ * Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)
37
37
  *
38
38
  * This file is part of melinda-record-matching-js
39
39
  *
@@ -54,7 +54,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
54
54
  * for the JavaScript code in this file.
55
55
  *
56
56
  */
57
- class CandidateSearchError extends Error {}
57
+ class CandidateSearchError extends Error {} // serverMaxResults : maximum size of total search result available from the server, defaults to Aleph's 20000
58
+ // eslint-disable-next-line max-statements
59
+
58
60
 
59
61
  exports.CandidateSearchError = CandidateSearchError;
60
62
 
@@ -62,18 +64,28 @@ var _default = ({
62
64
  record,
63
65
  searchSpec,
64
66
  url,
65
- maxRecordsPerRequest = 50
67
+ maxCandidates,
68
+ maxRecordsPerRequest = 50,
69
+ serverMaxResult = 20000
66
70
  }) => {
67
71
  _marcRecord.MarcRecord.setValidationOptions({
68
72
  subfieldValues: false
69
73
  });
70
74
 
71
75
  const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:candidate-search');
76
+ const debugData = debug.extend('data');
77
+ debugData(`SearchSpec: ${JSON.stringify(searchSpec)}`);
78
+ debugData(`Url: ${url}`);
79
+ debugData(`MaxRecordsPerRequest ${maxRecordsPerRequest}`);
80
+ debugData(`ServerMaxResult: ${serverMaxResult}`);
81
+ debugData(`MaxCandidates: ${maxCandidates}`); // Do not retrieve more candidates than defined in maxCandidates
82
+
83
+ const adjustedMaxRecordsPerRequest = maxRecordsPerRequest >= maxCandidates ? maxCandidates : maxRecordsPerRequest;
72
84
  const inputRecordId = getRecordId(record);
73
85
  const queryList = (0, _queryList.default)(record, searchSpec);
74
86
  const client = (0, _sruClient.default)({
75
87
  url,
76
- maxRecordsPerRequest,
88
+ maxRecordsPerRequest: adjustedMaxRecordsPerRequest,
77
89
  version: '2.0',
78
90
  retrieveAll: false
79
91
  });
@@ -83,52 +95,101 @@ var _default = ({
83
95
  if (queryList.length === 0) {
84
96
  // eslint-disable-line functional/no-conditional-statement
85
97
  throw new CandidateSearchError(`Generated query list contains no queries`);
86
- } // eslint-disable-next-line max-statements
98
+ } // state.totalRecords : amount of candidate records available to the current query (undefined, if there was no queries left)
99
+ // state.query : current query (undefined if there was no queries left)
100
+ // state.searchCounter : sequence for current search for current query (undefined, if there we no queries left)
101
+ // 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)
102
+ // state.queriesLeft : amount of queries left
103
+ // state.queryCounter : sequence for current query
104
+ // state.maxedQueries : queries that resulted in more than serverMaxResults hits
105
+ // eslint-disable-next-line max-statements
87
106
 
88
107
 
89
108
  return async ({
90
109
  queryOffset = 0,
91
- resultSetOffset = 1
110
+ resultSetOffset = 1,
111
+ totalRecords = 0,
112
+ searchCounter = 0,
113
+ queryCandidateCounter = 0,
114
+ queryCounter = 0,
115
+ maxedQueries = []
92
116
  }) => {
93
117
  const query = queryList[queryOffset];
94
118
 
95
119
  if (query) {
96
120
  const {
97
121
  records,
98
- nextOffset
99
- } = await retrieveRecords();
122
+ nextOffset,
123
+ total
124
+ } = await retrieveRecords(); // If resultSetOffset === 1 this is the first search for the current query
125
+
126
+ debugData(`ResultSetOffset: ${resultSetOffset}`);
127
+ const newTotalRecords = resultSetOffset === 1 ? total : totalRecords;
128
+ const newQueryCounter = resultSetOffset === 1 ? queryCounter + 1 : queryCounter;
129
+ const newSearchCounter = resultSetOffset === 1 ? 1 : searchCounter + 1;
130
+ const newQueryCandidateCounter = resultSetOffset === 1 ? records.length : queryCandidateCounter + records.length;
131
+ const maxedQuery = resultSetOffset === 1 ? checkMaxedQuery(query, total, serverMaxResult) : undefined;
132
+ const newMaxedQueries = maxedQuery ? maxedQueries.concat(maxedQuery) : maxedQueries;
100
133
 
101
134
  if (typeof nextOffset === 'number') {
102
- debug(`Running next search for query ${queryOffset} ${query}`);
135
+ debug(`Next search will be for query ${queryOffset} ${query}, starting from record ${nextOffset}`);
103
136
  return {
104
137
  records,
105
138
  queryOffset,
106
139
  resultSetOffset: nextOffset,
107
- queriesLeft: queryList.length - (queryOffset + 1)
140
+ queriesLeft: queryList.length - (queryOffset + 1),
141
+ totalRecords: newTotalRecords,
142
+ query,
143
+ searchCounter: newSearchCounter,
144
+ queryCandidateCounter: newQueryCandidateCounter,
145
+ queryCounter: newQueryCounter,
146
+ maxedQueries: newMaxedQueries
108
147
  };
109
148
  }
110
149
 
111
- debug(`Query ${queryOffset} ${query} done, moving to next query. (${queryList.length - (queryOffset + 1)} queries left)`);
150
+ debug(`Query ${queryOffset} ${query} done.`);
151
+ debug(`There are (${queryList.length - (queryOffset + 1)} queries left)`);
112
152
  return {
113
153
  records,
114
154
  queryOffset: queryOffset + 1,
115
- queriesLeft: queryList.length - (queryOffset + 1)
155
+ queriesLeft: queryList.length - (queryOffset + 1),
156
+ totalRecords: newTotalRecords,
157
+ query,
158
+ searchCounter: newSearchCounter,
159
+ queryCandidateCounter: newQueryCandidateCounter,
160
+ queryCounter: newQueryCounter,
161
+ maxedQueries: newMaxedQueries
116
162
  };
117
163
  }
118
164
 
119
165
  debug(`All ${queryList.length} queries done, there's no query for ${queryOffset}`);
120
166
  return {
121
- records: []
167
+ records: [],
168
+ queriesLeft: 0,
169
+ queryCounter,
170
+ maxedQueries
122
171
  };
123
172
 
124
173
  function retrieveRecords() {
125
174
  return new Promise((resolve, reject) => {
126
- const promises = [];
175
+ const promises = []; // eslint-disable-next-line functional/no-let
176
+
177
+ let totalRecords = 0;
127
178
  debug(`Searching for candidates with query: ${query} (Offset ${resultSetOffset})`);
128
179
  client.searchRetrieve(query, {
129
180
  startRecord: resultSetOffset
130
181
  }).on('error', err => {
182
+ // eslint-disable-next-line functional/no-conditional-statement
183
+ if (err instanceof _sruClient.SruSearchError) {
184
+ debug(`SRU SruSearchError for query: ${query}: ${err}`);
185
+ reject(new CandidateSearchError(`SRU SruSearchError for query: ${query}: ${err}`));
186
+ }
187
+
188
+ debug(`SRU error for query: ${query}: ${err}`);
131
189
  reject(new CandidateSearchError(`SRU error for query: ${query}: ${err}`));
190
+ }).on('total', total => {
191
+ debug(`Got total: ${total}`);
192
+ totalRecords += total;
132
193
  }).on('end', async nextOffset => {
133
194
  try {
134
195
  const records = await Promise.all(promises);
@@ -136,7 +197,8 @@ var _default = ({
136
197
  debug(`Found ${filtered.length} candidates`);
137
198
  resolve({
138
199
  nextOffset,
139
- records: filtered
200
+ records: filtered,
201
+ total: totalRecords
140
202
  });
141
203
  } catch (err) {
142
204
  reject(err);
@@ -149,16 +211,7 @@ var _default = ({
149
211
  const foundRecordMarc = await _marcRecordSerializers.MARCXML.from(foundRecord, {
150
212
  subfieldValues: false
151
213
  });
152
- const foundRecordId = getRecordId(foundRecordMarc); // This does not work and might cause problems:
153
- // Record *should* match itself AND in REST the input record is given id 000000001 always
154
-
155
- debug(`Checking ${inputRecordId} vs ${foundRecordId}`);
156
-
157
- if (inputRecordId === foundRecordId) {
158
- debug(`Input and candidate are the same record per 001. Discarding candidate`);
159
- return;
160
- }
161
-
214
+ const foundRecordId = getRecordId(foundRecordMarc);
162
215
  return {
163
216
  record: foundRecordMarc,
164
217
  id: foundRecordId
@@ -172,6 +225,14 @@ var _default = ({
172
225
  }
173
226
  };
174
227
 
228
+ function checkMaxedQuery(query, total, serverMaxResult) {
229
+ // eslint-disable-next-line functional/no-conditional-statement
230
+ if (total >= serverMaxResult) {
231
+ debug(`WARNING: Query ${query} resulted in ${total} hits which meets the serverMaxResult (${serverMaxResult}) `);
232
+ return query;
233
+ }
234
+ }
235
+
175
236
  function getRecordId(record) {
176
237
  const [field] = record.get(/^001$/u);
177
238
  return field ? field.value : '';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/candidate-search/index.js"],"names":["CandidateSearchError","Error","record","searchSpec","url","maxRecordsPerRequest","MarcRecord","setValidationOptions","subfieldValues","debug","inputRecordId","getRecordId","queryList","client","version","retrieveAll","JSON","stringify","length","queryOffset","resultSetOffset","query","records","nextOffset","retrieveRecords","queriesLeft","Promise","resolve","reject","promises","searchRetrieve","startRecord","on","err","all","filtered","filter","r","foundRecord","push","handleRecord","foundRecordMarc","MARCXML","from","foundRecordId","id","field","get","value"],"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 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 createClient from '@natlibfi/sru-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport {MARCXML} from '@natlibfi/marc-record-serializers';\nimport generateQueryList from './query-list';\n\nexport {searchTypes} from './query-list';\n\nexport class CandidateSearchError extends Error {}\n\nexport default ({record, searchSpec, url, maxRecordsPerRequest = 50}) => {\n MarcRecord.setValidationOptions({subfieldValues: false});\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search');\n const inputRecordId = getRecordId(record);\n const queryList = generateQueryList(record, searchSpec);\n const client = createClient({\n url, maxRecordsPerRequest,\n version: '2.0',\n retrieveAll: false\n });\n\n debug(`Searching matches for ${inputRecordId}`);\n debug(`Generated queryList ${JSON.stringify(queryList)}`);\n if (queryList.length === 0) { // eslint-disable-line functional/no-conditional-statement\n throw new CandidateSearchError(`Generated query list contains no queries`);\n }\n\n // eslint-disable-next-line max-statements\n return async ({queryOffset = 0, resultSetOffset = 1}) => {\n const query = queryList[queryOffset];\n\n if (query) {\n const {records, nextOffset} = await retrieveRecords();\n\n if (typeof nextOffset === 'number') {\n debug(`Running next search for query ${queryOffset} ${query}`);\n return {records, queryOffset, resultSetOffset: nextOffset, queriesLeft: queryList.length - (queryOffset + 1)};\n }\n debug(`Query ${queryOffset} ${query} done, moving to next query. (${queryList.length - (queryOffset + 1)} queries left)`);\n return {records, queryOffset: queryOffset + 1, queriesLeft: queryList.length - (queryOffset + 1)};\n }\n\n debug(`All ${queryList.length} queries done, there's no query for ${queryOffset}`);\n return {records: []};\n\n function retrieveRecords() {\n return new Promise((resolve, reject) => {\n const promises = [];\n\n debug(`Searching for candidates with query: ${query} (Offset ${resultSetOffset})`);\n\n client.searchRetrieve(query, {startRecord: resultSetOffset})\n .on('error', err => {\n reject(new CandidateSearchError(`SRU error for query: ${query}: ${err}`));\n })\n .on('end', async nextOffset => {\n try {\n const records = await Promise.all(promises);\n const filtered = records.filter(r => r);\n\n debug(`Found ${filtered.length} candidates`);\n\n resolve({nextOffset, records: filtered});\n } catch (err) {\n reject(err);\n }\n })\n .on('record', foundRecord => {\n promises.push(handleRecord()); // eslint-disable-line functional/immutable-data\n\n async function handleRecord() {\n try {\n const foundRecordMarc = await MARCXML.from(foundRecord, {subfieldValues: false});\n const foundRecordId = getRecordId(foundRecordMarc);\n\n // This does not work and might cause problems:\n // Record *should* match itself AND in REST the input record is given id 000000001 always\n debug(`Checking ${inputRecordId} vs ${foundRecordId}`);\n if (inputRecordId === foundRecordId) {\n debug(`Input and candidate are the same record per 001. Discarding candidate`);\n return;\n }\n\n return {record: foundRecordMarc, id: foundRecordId};\n } catch (err) {\n throw new Error(`Failed converting record: ${err}, record: ${foundRecord}`);\n }\n }\n });\n });\n }\n };\n\n function getRecordId(record) {\n const [field] = record.get(/^001$/u);\n return field ? field.value : '';\n }\n};\n"],"file":"index.js"}
1
+ {"version":3,"sources":["../../src/candidate-search/index.js"],"names":["CandidateSearchError","Error","record","searchSpec","url","maxCandidates","maxRecordsPerRequest","serverMaxResult","MarcRecord","setValidationOptions","subfieldValues","debug","debugData","extend","JSON","stringify","adjustedMaxRecordsPerRequest","inputRecordId","getRecordId","queryList","client","version","retrieveAll","length","queryOffset","resultSetOffset","totalRecords","searchCounter","queryCandidateCounter","queryCounter","maxedQueries","query","records","nextOffset","total","retrieveRecords","newTotalRecords","newQueryCounter","newSearchCounter","newQueryCandidateCounter","maxedQuery","checkMaxedQuery","undefined","newMaxedQueries","concat","queriesLeft","Promise","resolve","reject","promises","searchRetrieve","startRecord","on","err","SruSearchError","all","filtered","filter","r","foundRecord","push","handleRecord","foundRecordMarc","MARCXML","from","foundRecordId","id","field","get","value"],"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 createClient, {SruSearchError} from '@natlibfi/sru-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport {MARCXML} from '@natlibfi/marc-record-serializers';\nimport generateQueryList from './query-list';\n\nexport {searchTypes} from './query-list';\n\nexport class CandidateSearchError extends Error {}\n\n// serverMaxResults : maximum size of total search result available from the server, defaults to Aleph's 20000\n\n// eslint-disable-next-line max-statements\nexport default ({record, searchSpec, url, maxCandidates, maxRecordsPerRequest = 50, serverMaxResult = 20000}) => {\n MarcRecord.setValidationOptions({subfieldValues: false});\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search');\n const debugData = debug.extend('data');\n\n debugData(`SearchSpec: ${JSON.stringify(searchSpec)}`);\n debugData(`Url: ${url}`);\n debugData(`MaxRecordsPerRequest ${maxRecordsPerRequest}`);\n debugData(`ServerMaxResult: ${serverMaxResult}`);\n debugData(`MaxCandidates: ${maxCandidates}`);\n\n // Do not retrieve more candidates than defined in maxCandidates\n const adjustedMaxRecordsPerRequest = maxRecordsPerRequest >= maxCandidates ? maxCandidates : maxRecordsPerRequest;\n\n const inputRecordId = getRecordId(record);\n const queryList = generateQueryList(record, searchSpec);\n const client = createClient({\n url,\n maxRecordsPerRequest: adjustedMaxRecordsPerRequest,\n version: '2.0',\n retrieveAll: false\n });\n\n debug(`Searching matches for ${inputRecordId}`);\n debug(`Generated queryList ${JSON.stringify(queryList)}`);\n if (queryList.length === 0) { // eslint-disable-line functional/no-conditional-statement\n throw new CandidateSearchError(`Generated query list contains no queries`);\n }\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\n // eslint-disable-next-line max-statements\n return async ({queryOffset = 0, resultSetOffset = 1, totalRecords = 0, searchCounter = 0, queryCandidateCounter = 0, queryCounter = 0, maxedQueries = []}) => {\n const query = queryList[queryOffset];\n\n if (query) {\n const {records, nextOffset, total} = await retrieveRecords();\n\n // If resultSetOffset === 1 this is the first search for the current query\n debugData(`ResultSetOffset: ${resultSetOffset}`);\n const newTotalRecords = resultSetOffset === 1 ? total : totalRecords;\n const newQueryCounter = resultSetOffset === 1 ? queryCounter + 1 : queryCounter;\n const newSearchCounter = resultSetOffset === 1 ? 1 : searchCounter + 1;\n const newQueryCandidateCounter = resultSetOffset === 1 ? records.length : queryCandidateCounter + records.length;\n\n const maxedQuery = resultSetOffset === 1 ? checkMaxedQuery(query, total, serverMaxResult) : undefined;\n const newMaxedQueries = maxedQuery ? maxedQueries.concat(maxedQuery) : maxedQueries;\n\n if (typeof nextOffset === 'number') {\n debug(`Next search will be for query ${queryOffset} ${query}, starting from record ${nextOffset}`);\n return {records, queryOffset, resultSetOffset: nextOffset, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries};\n }\n debug(`Query ${queryOffset} ${query} done.`);\n debug(`There are (${queryList.length - (queryOffset + 1)} queries left)`);\n return {records, queryOffset: queryOffset + 1, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries};\n }\n\n debug(`All ${queryList.length} queries done, there's no query for ${queryOffset}`);\n return {records: [], queriesLeft: 0, queryCounter, maxedQueries};\n\n function retrieveRecords() {\n return new Promise((resolve, reject) => {\n const promises = [];\n // eslint-disable-next-line functional/no-let\n let totalRecords = 0;\n\n debug(`Searching for candidates with query: ${query} (Offset ${resultSetOffset})`);\n\n client.searchRetrieve(query, {startRecord: resultSetOffset})\n .on('error', err => {\n // eslint-disable-next-line functional/no-conditional-statement\n if (err instanceof SruSearchError) {\n debug(`SRU SruSearchError for query: ${query}: ${err}`);\n reject(new CandidateSearchError(`SRU SruSearchError for query: ${query}: ${err}`));\n }\n debug(`SRU error for query: ${query}: ${err}`);\n reject(new CandidateSearchError(`SRU error for query: ${query}: ${err}`));\n })\n .on('total', total => {\n debug(`Got total: ${total}`);\n totalRecords += total;\n })\n .on('end', async nextOffset => {\n try {\n const records = await Promise.all(promises);\n const filtered = records.filter(r => r);\n\n debug(`Found ${filtered.length} candidates`);\n\n resolve({nextOffset, records: filtered, total: totalRecords});\n } catch (err) {\n reject(err);\n }\n })\n .on('record', foundRecord => {\n promises.push(handleRecord()); // eslint-disable-line functional/immutable-data\n\n async function handleRecord() {\n try {\n const foundRecordMarc = await MARCXML.from(foundRecord, {subfieldValues: false});\n const foundRecordId = getRecordId(foundRecordMarc);\n\n return {record: foundRecordMarc, id: foundRecordId};\n } catch (err) {\n throw new Error(`Failed converting record: ${err}, record: ${foundRecord}`);\n }\n }\n });\n });\n }\n };\n\n function checkMaxedQuery(query, total, serverMaxResult) {\n // eslint-disable-next-line functional/no-conditional-statement\n if (total >= serverMaxResult) {\n debug(`WARNING: Query ${query} resulted in ${total} hits which meets the serverMaxResult (${serverMaxResult}) `);\n return query;\n }\n }\n\n\n function getRecordId(record) {\n const [field] = record.get(/^001$/u);\n return field ? field.value : '';\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,7 @@ 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:candidate-search:test');
46
49
  describe('candidate-search', () => {
47
50
  (0, _fixugenHttpClient.default)({
48
51
  callback,
@@ -57,8 +60,8 @@ describe('candidate-search', () => {
57
60
  getFixture,
58
61
  factoryOptions,
59
62
  searchOptions,
60
- expectedFactoryError,
61
- expectedSearchError,
63
+ expectedFactoryError = false,
64
+ expectedSearchError = false,
62
65
  enabled = true
63
66
  }) {
64
67
  const url = 'http://foo.bar';
@@ -85,12 +88,15 @@ describe('candidate-search', () => {
85
88
  url
86
89
  });
87
90
  await iterate({
88
- searchOptions
91
+ searchOptions,
92
+ expectedSearchError
89
93
  });
90
94
 
91
95
  function formatFactoryOptions() {
96
+ debug(`Using factoryOptions: ${JSON.stringify(factoryOptions)}`);
92
97
  return { ...factoryOptions,
93
- maxRecordsPerRequest: 1,
98
+ maxRecordsPerRequest: factoryOptions.maxRecordsPerRequest || 1,
99
+ maxServerResults: factoryOptions.maxServerResults || undefined,
94
100
  record: new _marcRecord.MarcRecord(factoryOptions.record, {
95
101
  subfieldValues: false
96
102
  })
@@ -100,6 +106,7 @@ describe('candidate-search', () => {
100
106
 
101
107
  async function iterate({
102
108
  searchOptions,
109
+ expectedSearchError,
103
110
  count = 1
104
111
  }) {
105
112
  const expectedResults = getFixture(`expectedResults${count}.json`);
@@ -114,16 +121,12 @@ describe('candidate-search', () => {
114
121
  (0, _chai.expect)(err.message).to.match(new RegExp(expectedSearchError, 'u'));
115
122
  return;
116
123
  }
117
- }
124
+ } // eslint-disable-next-line functional/no-conditional-statement
118
125
 
119
- const results = await search(searchOptions);
120
- (0, _chai.expect)(formatResults(results)).to.eql(expectedResults);
121
126
 
122
- if (results.records.length > 0) {
123
- return iterate({
124
- searchOptions: resultsToOptions(results),
125
- count: count + 1
126
- });
127
+ if (!expectedSearchError) {
128
+ const results = await search(searchOptions);
129
+ (0, _chai.expect)(formatResults(results)).to.eql(expectedResults);
127
130
  }
128
131
 
129
132
  function formatResults(results) {
@@ -138,13 +141,6 @@ describe('candidate-search', () => {
138
141
  }))
139
142
  };
140
143
  }
141
-
142
- function resultsToOptions(results) {
143
- return Object.entries(results).filter(([k]) => k === 'records' === false) // If key is 'records' return false
144
- .reduce((acc, [k, v]) => ({ ...acc,
145
- [k]: v
146
- }), {});
147
- }
148
144
  }
149
145
  }
150
146
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/candidate-search/index.spec.js"],"names":["describe","callback","path","__dirname","recurse","fixura","reader","READERS","JSON","getFixture","factoryOptions","searchOptions","expectedFactoryError","expectedSearchError","enabled","url","isCandidateSearchError","formatFactoryOptions","to","throw","CandidateSearchError","RegExp","search","iterate","maxRecordsPerRequest","record","MarcRecord","subfieldValues","count","expectedResults","Error","err","be","an","message","match","results","formatResults","eql","records","length","resultsToOptions","map","id","toObject","Object","entries","filter","k","reduce","acc","v"],"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 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 {expect} from 'chai';\nimport {READERS} from '@natlibfi/fixura';\nimport generateTests from '@natlibfi/fixugen-http-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport createSearchInterface, {CandidateSearchError} from '.';\n\ndescribe('candidate-search', () => {\n generateTests({\n callback,\n path: [__dirname, '..', '..', 'test-fixtures', 'candidate-search', 'index'],\n recurse: false,\n fixura: {\n reader: READERS.JSON\n }\n });\n\n // eslint-disable-next-line max-statements\n async function callback({getFixture, factoryOptions, searchOptions, expectedFactoryError, expectedSearchError, enabled = true}) {\n const url = 'http://foo.bar';\n\n if (!enabled) {\n return;\n }\n\n if (expectedFactoryError) {\n if (expectedFactoryError.isCandidateSearchError) {\n expect(() => createSearchInterface({...formatFactoryOptions(), url})).to.throw(CandidateSearchError, new RegExp(expectedFactoryError, 'u'));\n return;\n }\n\n expect(() => createSearchInterface({...formatFactoryOptions(), url})).to.throw(new RegExp(expectedFactoryError, 'u'));\n return;\n }\n\n const search = createSearchInterface({...formatFactoryOptions(), url});\n await iterate({searchOptions});\n\n function formatFactoryOptions() {\n return {\n ...factoryOptions,\n maxRecordsPerRequest: 1,\n record: new MarcRecord(factoryOptions.record, {subfieldValues: false})\n };\n }\n\n // eslint-disable-next-line max-statements\n async function iterate({searchOptions, count = 1}) {\n const expectedResults = getFixture(`expectedResults${count}.json`);\n\n if (expectedSearchError) { // eslint-disable-line functional/no-conditional-statement\n try {\n await search(searchOptions);\n throw new Error('Expected an error');\n } catch (err) {\n expect(err).to.be.an('error');\n expect(err.message).to.match(new RegExp(expectedSearchError, 'u'));\n return;\n }\n }\n\n const results = await search(searchOptions);\n\n expect(formatResults(results)).to.eql(expectedResults);\n\n if (results.records.length > 0) {\n return iterate({\n searchOptions: resultsToOptions(results),\n count: count + 1\n });\n }\n\n function formatResults(results) {\n // console.log(results); //eslint-disable-line\n return {\n ...results,\n records: results.records.map(({record, id}) => ({id, record: record.toObject()}))\n };\n }\n\n function resultsToOptions(results) {\n return Object.entries(results)\n .filter(([k]) => k === 'records' === false) // If key is 'records' return false\n .reduce((acc, [k, v]) => ({...acc, [k]: v}), {});\n }\n }\n }\n});\n"],"file":"index.spec.js"}
1
+ {"version":3,"sources":["../../src/candidate-search/index.spec.js"],"names":["debug","describe","callback","path","__dirname","recurse","fixura","reader","READERS","JSON","getFixture","factoryOptions","searchOptions","expectedFactoryError","expectedSearchError","enabled","url","isCandidateSearchError","formatFactoryOptions","to","throw","CandidateSearchError","RegExp","search","iterate","stringify","maxRecordsPerRequest","maxServerResults","undefined","record","MarcRecord","subfieldValues","count","expectedResults","Error","err","be","an","message","match","results","formatResults","eql","records","map","id","toObject"],"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 {expect} from 'chai';\nimport {READERS} from '@natlibfi/fixura';\nimport generateTests from '@natlibfi/fixugen-http-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport createSearchInterface, {CandidateSearchError} from '.';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:test');\n\ndescribe('candidate-search', () => {\n generateTests({\n callback,\n path: [__dirname, '..', '..', 'test-fixtures', 'candidate-search', 'index'],\n recurse: false,\n fixura: {\n reader: READERS.JSON\n }\n });\n\n // eslint-disable-next-line max-statements\n async function callback({getFixture, factoryOptions, searchOptions, expectedFactoryError = false, expectedSearchError = false, enabled = true}) {\n const url = 'http://foo.bar';\n\n if (!enabled) {\n return;\n }\n\n if (expectedFactoryError) {\n if (expectedFactoryError.isCandidateSearchError) {\n expect(() => createSearchInterface({...formatFactoryOptions(), url})).to.throw(CandidateSearchError, new RegExp(expectedFactoryError, 'u'));\n return;\n }\n\n expect(() => createSearchInterface({...formatFactoryOptions(), url})).to.throw(new RegExp(expectedFactoryError, 'u'));\n return;\n }\n\n const search = createSearchInterface({...formatFactoryOptions(), url});\n await iterate({searchOptions, expectedSearchError});\n\n function formatFactoryOptions() {\n debug(`Using factoryOptions: ${JSON.stringify(factoryOptions)}`);\n return {\n ...factoryOptions,\n maxRecordsPerRequest: factoryOptions.maxRecordsPerRequest || 1,\n maxServerResults: factoryOptions.maxServerResults || undefined,\n record: new MarcRecord(factoryOptions.record, {subfieldValues: false})\n };\n }\n\n // eslint-disable-next-line max-statements\n async function iterate({searchOptions, expectedSearchError, count = 1}) {\n const expectedResults = getFixture(`expectedResults${count}.json`);\n\n if (expectedSearchError) { // eslint-disable-line functional/no-conditional-statement\n try {\n await search(searchOptions);\n throw new Error('Expected an error');\n } catch (err) {\n expect(err).to.be.an('error');\n expect(err.message).to.match(new RegExp(expectedSearchError, 'u'));\n return;\n }\n }\n\n // eslint-disable-next-line functional/no-conditional-statement\n if (!expectedSearchError) {\n const results = await search(searchOptions);\n expect(formatResults(results)).to.eql(expectedResults);\n }\n\n function formatResults(results) {\n // console.log(results); //eslint-disable-line\n return {\n ...results,\n records: results.records.map(({record, id}) => ({id, record: record.toObject()}))\n };\n }\n\n }\n }\n});\n"],"file":"index.spec.js"}
@@ -169,6 +169,7 @@ function bibHostComponents(record) {
169
169
  }
170
170
  } // SRU search dc.title with a search phrase starting with ^ maps currently in Melinda to
171
171
  // (probably) to *headings* index TIT
172
+ // - Aleph cannot currently handle headings searches starting with a boolean - in these cases use word search
172
173
  // Headings index TIT drops articles etc. from the start of the title according to the filing indicator
173
174
  // Currently filing indicator is not implemented - if the title starts with an article and the Melinda
174
175
  // record is correctly catalogued using a filing indicator -> dc.title search won't match
@@ -176,12 +177,15 @@ function bibHostComponents(record) {
176
177
 
177
178
  function bibTitle(record) {
178
179
  const title = getTitle();
180
+ const booleanStartWords = ['and', 'or', 'nor', 'not'];
179
181
 
180
182
  if (title) {
181
183
  const formatted = title.replace(/[^\w\s\p{Alphabetic}]/gu, '') // Clean up concurrent spaces from fe. subfield changes
182
- .replace(/ +/gu, ' ').trim().slice(0, 30).trim(); // Prevent too many matches by having a minimum length requirement
184
+ .replace(/ +/gu, ' ').trim().slice(0, 30).trim(); // use word search for titles starting with a boolean
183
185
 
184
- return formatted.length >= 5 ? [`dc.title="^${formatted}*"`] : [];
186
+ const useWordSearch = booleanStartWords.some(word => formatted.toLowerCase().startsWith(word)); // Prevent too many matches by having a minimum length requirement
187
+
188
+ return formatted.length >= 5 ? [`dc.title="${useWordSearch ? '' : '^'}${formatted}*"`] : [];
185
189
  }
186
190
 
187
191
  return [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/candidate-search/query-list/bib.js"],"names":["bibSourceIds","record","debug","debugData","extend","fSids","get","length","JSON","stringify","toSidQueries","sidStrings","getSidStrings","sidQueries","map","toSidString","filter","nonEmptySid","field","createSidString","sfC","sfB","cleanedSfC","removeSourcePrefix","normalizeSidSubfieldValue","cleanedSfB","concat","subfieldValue","sourcePrefixRegex","normalizedValue","replace","normalizeAwayRegex","bibMelindaIds","melindaIds","bibHostComponents","id","getHostId","value","subfields","find","code","test","bibTitle","title","getTitle","formatted","trim","slice","includes","join","bibStandardIdentifiers","fields","identifiers","toIdentifiers","uniqueIdentifiers","Set","tag","issnIsbnReqExp","otherIdReqExp","sub","undefined"],"mappings":"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","sourcesContent":["\n/**\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*/\nimport createDebugLogger from 'debug';\nimport {toQueries} from '../candidate-search-utils';\nimport {getMelindaIdsF035, validateSidFieldSubfieldCounts, getSubfieldValues} from '../../matching-utils';\n\n\nexport function bibSourceIds(record) {\n\n /* Melinda's SRU-index melinda.sourceid includes source IDs from SID fields in Melinda records\n SID-fields in Melinda have sf $c with local id and sf $b with a code for the local db:\n\n SID__ $c 123457 $b helka\n SID__ $c (ANDL100020)1077305 $b sata\n SID__ $c VER999999 $ FI-KV\n SID__ $c /10024/508126 $ REPO_THESEUS\n\n In melinda.sourceid -index case is kept, sourceprefixes in brackets and hyphens are normalized away:\n Note: slashes are not normalized away, but a SRU-search-string including slashes needs to be quoted\n\n 1234567helka\n 1077305sata\n VER999999FIKV\n /10024/508126REPO_THESEUS\n\n Note: All Melinda records that have a matching records in a local db do NOT have SID for that local records,\n existence of a SID field depends on how the record has been added to Melinda and how it has been handled\n afterwards. SIDs are also not reliably maintained. A record might or might not have a SID for a local db\n after the matching record is removed from the local db.\n\n */\n\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:source-ids');\n const debugData = debug.extend('data');\n //const debugInfo = debug.extend('info');\n\n debug(`Creating queries for sourceid's`);\n\n const fSids = record.get('SID');\n debugData(`SID-fields (${fSids.length}): ${JSON.stringify(fSids)}`);\n\n return fSids.length > 0 ? toSidQueries(fSids) : [];\n\n function toSidQueries(fSids) {\n debug(`Creating actual queries for sourceid's`);\n\n const sidStrings = getSidStrings(fSids);\n\n if (sidStrings.length < 1) {\n debug(`No identifiers found, no queries created.`);\n return [];\n }\n\n const sidQueries = toQueries(sidStrings, 'melinda.sourceid');\n\n return sidQueries;\n\n function getSidStrings(fSids) {\n debug(`Getting Sid strings from SID fields`);\n\n // Map SID fields to valid sidStrings, filter out empty strings\n const sidStrings = fSids.map(toSidString).filter(nonEmptySid => nonEmptySid);\n return sidStrings;\n\n function toSidString(field) {\n debug(`Getting string from a field`);\n\n return validateSidFieldSubfieldCounts(field) ? createSidString(field) : '';\n\n function createSidString(field) {\n debug(`Creating string from a field`);\n const [sfC] = getSubfieldValues(field, 'c');\n const [sfB] = getSubfieldValues(field, 'b');\n\n const cleanedSfC = removeSourcePrefix(normalizeSidSubfieldValue(sfC));\n const cleanedSfB = normalizeSidSubfieldValue(sfB);\n\n debugData(`${JSON.stringify(sfC)} + ${JSON.stringify(sfB)}`);\n return cleanedSfC.concat(cleanedSfB);\n }\n\n function removeSourcePrefix(subfieldValue) {\n const sourcePrefixRegex = (/^(?<sourcePrefix>\\([A-Za-z0-9-]+\\))(?<id>.+)$/u);\n const normalizedValue = subfieldValue.replace(sourcePrefixRegex, '$<id>');\n debugData(`Normalized ${subfieldValue} to ${normalizedValue}`);\n return normalizedValue;\n }\n\n function normalizeSidSubfieldValue(subfieldValue) {\n debugData(`Normalizing ${subfieldValue}`);\n const normalizeAwayRegex = (/[- ]/u);\n return subfieldValue.replace(normalizeAwayRegex, '');\n }\n\n }\n }\n }\n}\n\nexport function bibMelindaIds(record) {\n // Melinda's SRU-index melinda.melindaid includes f001 controlnumbers and old Melinda-IDs from f035z's for all non-deleted Melinda-records\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibMelindaIds');\n const debugData = debug.extend('data');\n debug(`Creating queries for MelindaIds`);\n\n // Note: Melinda-ID's for search queries are created just from records f035a's and f035z's\n // Both (FI-MELINDA)- and FCC-prefixed forms are found\n // f001 controlnumber is not currently included, even if record's f003 is FI-MELINDA\n const melindaIds = getMelindaIdsF035(record);\n\n debugData(`Unique identifiers (${melindaIds.length}): ${JSON.stringify(melindaIds)}`);\n\n if (melindaIds.length < 1) {\n debug(`No identifiers found, no queries created.`);\n return [];\n }\n\n return toQueries(melindaIds, 'melinda.melindaid');\n}\n\n\n// bibHostComponents returns host id from the first subfield $w of first field f773, see test-fixtures 04 and 05\n// bibHostComponents should search all 773 $ws for possible host id, but what should it do in case of multiple host ids?\nexport function bibHostComponents(record) {\n const id = getHostId();\n return id ? [`melinda.partsofhost=${id}`] : [];\n\n function getHostId() {\n const [field] = record.get(/^773$/u);\n\n if (field) {\n const {value} = field.subfields.find(({code}) => code === 'w') || {};\n\n if (value && (/^\\(FI-MELINDA\\)/u).test(value)) {\n return value.replace(/^\\(FI-MELINDA\\)/u, '');\n }\n\n if (value && (/^\\(FIN01\\)/u).test(value)) {\n return value.replace(/^\\(FIN01\\)/u, '');\n }\n\n return false;\n }\n return false;\n }\n}\n\n// SRU search dc.title with a search phrase starting with ^ maps currently in Melinda to\n// (probably) to *headings* index TIT\n// Headings index TIT drops articles etc. from the start of the title according to the filing indicator\n// Currently filing indicator is not implemented - if the title starts with an article and the Melinda\n// record is correctly catalogued using a filing indicator -> dc.title search won't match\n\nexport function bibTitle(record) {\n const title = getTitle();\n\n if (title) {\n const formatted = title\n .replace(/[^\\w\\s\\p{Alphabetic}]/gu, '')\n // Clean up concurrent spaces from fe. subfield changes\n .replace(/ +/gu, ' ')\n .trim()\n .slice(0, 30)\n .trim();\n\n // Prevent too many matches by having a minimum length requirement\n return formatted.length >= 5 ? [`dc.title=\"^${formatted}*\"`] : [];\n }\n\n return [];\n\n function getTitle() {\n const [field] = record.get(/^245$/u);\n\n if (field) {\n return field.subfields\n .filter(({code}) => ['a', 'b'].includes(code))\n .map(({value}) => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n }\n return false;\n }\n}\n\n// eslint-disable-next-line max-statements\nexport function bibStandardIdentifiers(record) {\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibStandardIdentifiers');\n const debugData = debug.extend('data');\n debug(`Creating queries for standard identifiers`);\n\n const fields = record.get(/^(?<def>020|022|024)$/u);\n const identifiers = [].concat(...fields.map(toIdentifiers));\n const uniqueIdentifiers = [...new Set(identifiers)];\n\n debugData(`Standard identifier fields: ${JSON.stringify(fields)}`);\n debugData(`Identifiers (${identifiers.length}): ${JSON.stringify(identifiers)}`);\n debugData(`Unique identifiers (${uniqueIdentifiers.length}): ${JSON.stringify(uniqueIdentifiers)}`);\n\n if (uniqueIdentifiers.length < 1) {\n debug(`No identifiers found, no queries created.`);\n return [];\n }\n\n return toQueries(uniqueIdentifiers, 'dc.identifier');\n\n function toIdentifiers({tag, subfields}) {\n const issnIsbnReqExp = (/^[A-Za-z0-9-]+$/u);\n const otherIdReqExp = (/^[A-Za-z0-9-:]+$/u);\n\n if (tag === '022') {\n return subfields\n .filter(sub => ['a', 'z', 'y'].includes(sub.code) && issnIsbnReqExp.test(sub.value) && sub.value !== undefined)\n .map(({value}) => value);\n }\n\n if (tag === '020') {\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && issnIsbnReqExp.test(sub.value) && sub.value !== undefined)\n .map(({value}) => value);\n }\n\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && otherIdReqExp.test(sub.value) && sub.value !== undefined)\n .map(({value}) => value);\n }\n}\n"],"file":"bib.js"}
1
+ {"version":3,"sources":["../../../src/candidate-search/query-list/bib.js"],"names":["bibSourceIds","record","debug","debugData","extend","fSids","get","length","JSON","stringify","toSidQueries","sidStrings","getSidStrings","sidQueries","map","toSidString","filter","nonEmptySid","field","createSidString","sfC","sfB","cleanedSfC","removeSourcePrefix","normalizeSidSubfieldValue","cleanedSfB","concat","subfieldValue","sourcePrefixRegex","normalizedValue","replace","normalizeAwayRegex","bibMelindaIds","melindaIds","bibHostComponents","id","getHostId","value","subfields","find","code","test","bibTitle","title","getTitle","booleanStartWords","formatted","trim","slice","useWordSearch","some","word","toLowerCase","startsWith","includes","join","bibStandardIdentifiers","fields","identifiers","toIdentifiers","uniqueIdentifiers","Set","tag","issnIsbnReqExp","otherIdReqExp","sub","undefined"],"mappings":"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","sourcesContent":["\n/**\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*/\nimport createDebugLogger from 'debug';\nimport {toQueries} from '../candidate-search-utils';\nimport {getMelindaIdsF035, validateSidFieldSubfieldCounts, getSubfieldValues} from '../../matching-utils';\n\n\nexport function bibSourceIds(record) {\n\n /* Melinda's SRU-index melinda.sourceid includes source IDs from SID fields in Melinda records\n SID-fields in Melinda have sf $c with local id and sf $b with a code for the local db:\n\n SID__ $c 123457 $b helka\n SID__ $c (ANDL100020)1077305 $b sata\n SID__ $c VER999999 $ FI-KV\n SID__ $c /10024/508126 $ REPO_THESEUS\n\n In melinda.sourceid -index case is kept, sourceprefixes in brackets and hyphens are normalized away:\n Note: slashes are not normalized away, but a SRU-search-string including slashes needs to be quoted\n\n 1234567helka\n 1077305sata\n VER999999FIKV\n /10024/508126REPO_THESEUS\n\n Note: All Melinda records that have a matching records in a local db do NOT have SID for that local records,\n existence of a SID field depends on how the record has been added to Melinda and how it has been handled\n afterwards. SIDs are also not reliably maintained. A record might or might not have a SID for a local db\n after the matching record is removed from the local db.\n\n */\n\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:source-ids');\n const debugData = debug.extend('data');\n //const debugInfo = debug.extend('info');\n\n debug(`Creating queries for sourceid's`);\n\n const fSids = record.get('SID');\n debugData(`SID-fields (${fSids.length}): ${JSON.stringify(fSids)}`);\n\n return fSids.length > 0 ? toSidQueries(fSids) : [];\n\n function toSidQueries(fSids) {\n debug(`Creating actual queries for sourceid's`);\n\n const sidStrings = getSidStrings(fSids);\n\n if (sidStrings.length < 1) {\n debug(`No identifiers found, no queries created.`);\n return [];\n }\n\n const sidQueries = toQueries(sidStrings, 'melinda.sourceid');\n\n return sidQueries;\n\n function getSidStrings(fSids) {\n debug(`Getting Sid strings from SID fields`);\n\n // Map SID fields to valid sidStrings, filter out empty strings\n const sidStrings = fSids.map(toSidString).filter(nonEmptySid => nonEmptySid);\n return sidStrings;\n\n function toSidString(field) {\n debug(`Getting string from a field`);\n\n return validateSidFieldSubfieldCounts(field) ? createSidString(field) : '';\n\n function createSidString(field) {\n debug(`Creating string from a field`);\n const [sfC] = getSubfieldValues(field, 'c');\n const [sfB] = getSubfieldValues(field, 'b');\n\n const cleanedSfC = removeSourcePrefix(normalizeSidSubfieldValue(sfC));\n const cleanedSfB = normalizeSidSubfieldValue(sfB);\n\n debugData(`${JSON.stringify(sfC)} + ${JSON.stringify(sfB)}`);\n return cleanedSfC.concat(cleanedSfB);\n }\n\n function removeSourcePrefix(subfieldValue) {\n const sourcePrefixRegex = (/^(?<sourcePrefix>\\([A-Za-z0-9-]+\\))(?<id>.+)$/u);\n const normalizedValue = subfieldValue.replace(sourcePrefixRegex, '$<id>');\n debugData(`Normalized ${subfieldValue} to ${normalizedValue}`);\n return normalizedValue;\n }\n\n function normalizeSidSubfieldValue(subfieldValue) {\n debugData(`Normalizing ${subfieldValue}`);\n const normalizeAwayRegex = (/[- ]/u);\n return subfieldValue.replace(normalizeAwayRegex, '');\n }\n\n }\n }\n }\n}\n\nexport function bibMelindaIds(record) {\n // Melinda's SRU-index melinda.melindaid includes f001 controlnumbers and old Melinda-IDs from f035z's for all non-deleted Melinda-records\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibMelindaIds');\n const debugData = debug.extend('data');\n debug(`Creating queries for MelindaIds`);\n\n // Note: Melinda-ID's for search queries are created just from records f035a's and f035z's\n // Both (FI-MELINDA)- and FCC-prefixed forms are found\n // f001 controlnumber is not currently included, even if record's f003 is FI-MELINDA\n const melindaIds = getMelindaIdsF035(record);\n\n debugData(`Unique identifiers (${melindaIds.length}): ${JSON.stringify(melindaIds)}`);\n\n if (melindaIds.length < 1) {\n debug(`No identifiers found, no queries created.`);\n return [];\n }\n\n return toQueries(melindaIds, 'melinda.melindaid');\n}\n\n\n// bibHostComponents returns host id from the first subfield $w of first field f773, see test-fixtures 04 and 05\n// bibHostComponents should search all 773 $ws for possible host id, but what should it do in case of multiple host ids?\nexport function bibHostComponents(record) {\n const id = getHostId();\n return id ? [`melinda.partsofhost=${id}`] : [];\n\n function getHostId() {\n const [field] = record.get(/^773$/u);\n\n if (field) {\n const {value} = field.subfields.find(({code}) => code === 'w') || {};\n\n if (value && (/^\\(FI-MELINDA\\)/u).test(value)) {\n return value.replace(/^\\(FI-MELINDA\\)/u, '');\n }\n\n if (value && (/^\\(FIN01\\)/u).test(value)) {\n return value.replace(/^\\(FIN01\\)/u, '');\n }\n\n return false;\n }\n return false;\n }\n}\n\n// SRU search dc.title with a search phrase starting with ^ maps currently in Melinda to\n// (probably) to *headings* index TIT\n// - Aleph cannot currently handle headings searches starting with a boolean - in these cases use word search\n\n// Headings index TIT drops articles etc. from the start of the title according to the filing indicator\n// Currently filing indicator is not implemented - if the title starts with an article and the Melinda\n// record is correctly catalogued using a filing indicator -> dc.title search won't match\n\nexport function bibTitle(record) {\n const title = getTitle();\n const booleanStartWords = ['and', 'or', 'nor', 'not'];\n\n if (title) {\n const formatted = title\n .replace(/[^\\w\\s\\p{Alphabetic}]/gu, '')\n // Clean up concurrent spaces from fe. subfield changes\n .replace(/ +/gu, ' ')\n .trim()\n .slice(0, 30)\n .trim();\n\n // use word search for titles starting with a boolean\n const useWordSearch = booleanStartWords.some(word => formatted.toLowerCase().startsWith(word));\n // Prevent too many matches by having a minimum length requirement\n return formatted.length >= 5 ? [`dc.title=\"${useWordSearch ? '' : '^'}${formatted}*\"`] : [];\n }\n\n return [];\n\n function getTitle() {\n const [field] = record.get(/^245$/u);\n\n if (field) {\n return field.subfields\n .filter(({code}) => ['a', 'b'].includes(code))\n .map(({value}) => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n }\n return false;\n }\n}\n\n// eslint-disable-next-line max-statements\nexport function bibStandardIdentifiers(record) {\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibStandardIdentifiers');\n const debugData = debug.extend('data');\n debug(`Creating queries for standard identifiers`);\n\n const fields = record.get(/^(?<def>020|022|024)$/u);\n const identifiers = [].concat(...fields.map(toIdentifiers));\n const uniqueIdentifiers = [...new Set(identifiers)];\n\n debugData(`Standard identifier fields: ${JSON.stringify(fields)}`);\n debugData(`Identifiers (${identifiers.length}): ${JSON.stringify(identifiers)}`);\n debugData(`Unique identifiers (${uniqueIdentifiers.length}): ${JSON.stringify(uniqueIdentifiers)}`);\n\n if (uniqueIdentifiers.length < 1) {\n debug(`No identifiers found, no queries created.`);\n return [];\n }\n\n return toQueries(uniqueIdentifiers, 'dc.identifier');\n\n function toIdentifiers({tag, subfields}) {\n const issnIsbnReqExp = (/^[A-Za-z0-9-]+$/u);\n const otherIdReqExp = (/^[A-Za-z0-9-:]+$/u);\n\n if (tag === '022') {\n return subfields\n .filter(sub => ['a', 'z', 'y'].includes(sub.code) && issnIsbnReqExp.test(sub.value) && sub.value !== undefined)\n .map(({value}) => value);\n }\n\n if (tag === '020') {\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && issnIsbnReqExp.test(sub.value) && sub.value !== undefined)\n .map(({value}) => value);\n }\n\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && otherIdReqExp.test(sub.value) && sub.value !== undefined)\n .map(({value}) => value);\n }\n}\n"],"file":"bib.js"}