@natlibfi/melinda-record-matching 5.0.1-alpha.1 → 5.0.2-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/candidate-search/index.js +6 -2
- package/dist/candidate-search/index.js.map +2 -2
- package/dist/candidate-search/query-list/bib.js +10 -2
- package/dist/candidate-search/query-list/bib.js.map +3 -3
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +2 -2
- package/dist/match-detection/features/bib/authors.js +2 -2
- package/dist/match-detection/features/bib/authors.js.map +2 -2
- package/dist/match-detection/features/bib/bibliographic-level.js +12 -1
- package/dist/match-detection/features/bib/bibliographic-level.js.map +2 -2
- package/dist/match-detection/features/bib/host-component.js +1 -1
- package/dist/match-detection/features/bib/host-component.js.map +2 -2
- package/dist/match-detection/features/bib/isbn.js +122 -15
- package/dist/match-detection/features/bib/isbn.js.map +2 -2
- package/dist/match-detection/features/bib/issn.js +62 -5
- package/dist/match-detection/features/bib/issn.js.map +2 -2
- package/dist/match-detection/features/bib/language.js +176 -59
- package/dist/match-detection/features/bib/language.js.map +3 -3
- package/dist/match-detection/features/bib/title-version-original.js +2 -2
- package/dist/match-detection/features/bib/title-version-original.js.map +2 -2
- package/dist/match-detection/features/bib/title.js +2 -2
- package/dist/match-detection/features/bib/title.js.map +2 -2
- package/dist/match-detection/index.js +5 -5
- package/dist/match-detection/index.js.map +2 -2
- package/package.json +9 -4
- package/src/candidate-search/index.js +7 -2
- package/src/candidate-search/query-list/bib.js +22 -15
- package/src/cli.js +1 -1
- package/src/index.js +3 -3
- package/src/match-detection/features/bib/authors.js +2 -2
- package/src/match-detection/features/bib/bibliographic-level.js +13 -1
- package/src/match-detection/features/bib/host-component.js +1 -1
- package/src/match-detection/features/bib/isbn.js +162 -14
- package/src/match-detection/features/bib/issn.js +83 -1
- package/src/match-detection/features/bib/language.js +261 -73
- package/src/match-detection/features/bib/title-version-original.js +2 -2
- package/src/match-detection/features/bib/title.js +5 -4
- package/src/match-detection/index.js +5 -5
|
@@ -78,8 +78,12 @@ export default async ({ record, searchSpec, url, maxCandidates, maxRecordsPerReq
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
function getRecordId(record2) {
|
|
81
|
-
const [
|
|
82
|
-
|
|
81
|
+
const [f001] = record2.get(/^001$/u);
|
|
82
|
+
const [f003] = record2.get(/^003$/u);
|
|
83
|
+
if (f001 && f003) {
|
|
84
|
+
return `${f003.value}-${f001.value}`;
|
|
85
|
+
}
|
|
86
|
+
return f001 ? f001.value : "";
|
|
83
87
|
}
|
|
84
88
|
};
|
|
85
89
|
export function retrieveRecords(client, query, resultSetOffset) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/candidate-search/index.js"],
|
|
4
|
-
"sourcesContent": ["import createDebugLogger from 'debug';\nimport createClient, {SruSearchError} from '@natlibfi/sru-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\nimport generateQueryList from './query-list/index.js';\nimport chooseQueries from './choose-queries.js';\n\nexport {searchTypes} from './query-list/index.js';\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\nexport default async ({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 client = createClient({\n url,\n maxRecordsPerRequest: adjustedMaxRecordsPerRequest,\n version: '2.0',\n retrieveAll: false,\n metadataFormat: 'marcJson'\n });\n\n const inputRecordId = getRecordId(record);\n const queryListResult = await generateQueryList(record, searchSpec, client);\n const queryList = queryListResult[0]?.queryList ? queryListResult[0].queryList : queryListResult;\n const queryListType = queryListResult[0]?.queryListType ? queryListResult[0].queryListType : undefined;\n\n // if generateQueryList errored we should throw 422\n if (queryList.length === 0) {\n debug(`Empty list`);\n throw new CandidateSearchError(`Generated query list contains no queries`);\n }\n if (queryListType && queryListType !== 'alternates') {\n debug(`Unknown queryListType`);\n throw new CandidateSearchError(`Generated query list has invalid type`);\n }\n\n debug(`Searching matches for ${inputRecordId}`);\n const chosenQueryList = await filterQueryList({queryList, queryListType});\n debug(`Chosen queries: ${JSON.stringify(chosenQueryList)}`);\n\n async function filterQueryList({queryList, queryListType, maxCandidates}) {\n debug(`Generated queryList (type: ${queryListType}) ${JSON.stringify(queryList)}`);\n\n if (queryListType === 'alternates' && queryList.length > 1) {\n const queryListResult = await chooseQueries({url, queryList, queryListType, maxCandidates});\n debug(`queryListResult: ${JSON.stringify(queryListResult)}`);\n return queryListResult.map(elem => elem.query);\n }\n return queryList;\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 candidates (records+failures) retrieved from SRU for matching for current query, including the current record+failure 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 return {search};\n\n // eslint-disable-next-line max-statements\n async function search({queryOffset = 0, resultSetOffset = 1, totalRecords = 0, searchCounter = 0, queryCandidateCounter = 0, queryCounter = 0, maxedQueries = []}) {\n const query = chosenQueryList[queryOffset];\n debug(`Running query ${JSON.stringify(query)} (${queryOffset})`);\n\n if (query) {\n const {records, nextOffset, total} = await retrieveRecords(client, query, resultSetOffset);\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\n function checkMaxedQuery(query, total, serverMaxResult) {\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 function getRecordId(record) {\n const [
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import createDebugLogger from 'debug';\nimport createClient, {SruSearchError} from '@natlibfi/sru-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\nimport generateQueryList from './query-list/index.js';\nimport chooseQueries from './choose-queries.js';\n\nexport {searchTypes} from './query-list/index.js';\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\nexport default async ({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 client = createClient({\n url,\n maxRecordsPerRequest: adjustedMaxRecordsPerRequest,\n version: '2.0',\n retrieveAll: false,\n metadataFormat: 'marcJson'\n });\n\n const inputRecordId = getRecordId(record);\n const queryListResult = await generateQueryList(record, searchSpec, client);\n const queryList = queryListResult[0]?.queryList ? queryListResult[0].queryList : queryListResult;\n const queryListType = queryListResult[0]?.queryListType ? queryListResult[0].queryListType : undefined;\n\n // if generateQueryList errored we should throw 422\n if (queryList.length === 0) {\n debug(`Empty list`);\n throw new CandidateSearchError(`Generated query list contains no queries`);\n }\n if (queryListType && queryListType !== 'alternates') {\n debug(`Unknown queryListType`);\n throw new CandidateSearchError(`Generated query list has invalid type`);\n }\n\n debug(`Searching matches for ${inputRecordId}`);\n const chosenQueryList = await filterQueryList({queryList, queryListType});\n debug(`Chosen queries: ${JSON.stringify(chosenQueryList)}`);\n\n async function filterQueryList({queryList, queryListType, maxCandidates}) {\n debug(`Generated queryList (type: ${queryListType}) ${JSON.stringify(queryList)}`);\n\n if (queryListType === 'alternates' && queryList.length > 1) {\n const queryListResult = await chooseQueries({url, queryList, queryListType, maxCandidates});\n debug(`queryListResult: ${JSON.stringify(queryListResult)}`);\n return queryListResult.map(elem => elem.query);\n }\n return queryList;\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 candidates (records+failures) retrieved from SRU for matching for current query, including the current record+failure 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 return {search};\n\n // eslint-disable-next-line max-statements\n async function search({queryOffset = 0, resultSetOffset = 1, totalRecords = 0, searchCounter = 0, queryCandidateCounter = 0, queryCounter = 0, maxedQueries = []}) {\n const query = chosenQueryList[queryOffset];\n debug(`Running query ${JSON.stringify(query)} (${queryOffset})`);\n\n if (query) {\n const {records, nextOffset, total} = await retrieveRecords(client, query, resultSetOffset);\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\n function checkMaxedQuery(query, total, serverMaxResult) {\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 function getRecordId(record) {\n const [f001] = record.get(/^001$/u);\n const [f003] = record.get(/^003$/u);\n if (f001 && f003) {\n return `${f003.value}-${f001.value}`;\n }\n return f001 ? f001.value : '';\n }\n\n};\n\nexport function retrieveRecords(client, query, resultSetOffset) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:retrieveRecords');\n // eslint-disable-next-line no-unused-vars\n const debugData = debug.extend('data');\n\n return new Promise((resolve, reject) => {\n const records = [];\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 if (err instanceof SruSearchError) {\n debug(`SRU SruSearchError for query: ${query}: ${err}`);\n reject(new CandidateSearchError(`SRU SruSearchError for query: ${query}: ${err}`));\n }\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', nextOffset => {\n try {\n debug(`Found ${records.length} records`);\n\n resolve({nextOffset, records, total: totalRecords});\n } catch (err) {\n debug(`Error caught on END`);\n reject(err);\n }\n })\n .on('record', record => {\n // MarcRecord Error handling\n try {\n const marcRecord = new MarcRecord(record);\n const [field] = marcRecord.get(/^001$/u);\n debug(field);\n const id = field.value ? field.value : '';\n records.push({record: marcRecord, id});\n } catch (error) {\n debug(`Sru => record error: ${error}`);\n debug(`Sru => record: ${record}`);\n }\n });\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["queryList", "queryListType", "maxCandidates", "queryListResult", "serverMaxResult", "record"]
|
|
7
7
|
}
|
|
@@ -104,7 +104,11 @@ export function bibTitleAuthorPublisher({ record, onlyTitleLength, addYear = fal
|
|
|
104
104
|
debug(`bibTitleAuthorPublisher, onlyTitleLength: ${onlyTitleLength}, addYear: ${addYear}, alternates: ${alternates}`);
|
|
105
105
|
const title = getTitle();
|
|
106
106
|
if (testStringOrNumber(title)) {
|
|
107
|
-
|
|
107
|
+
let getFormatted = function(title2) {
|
|
108
|
+
const formatted2 = String(title2).replace(/[\-+ !"(){}\[\]<>;:.?/@*%=^_`~]/gu, " ").replace(/[^\w\s\p{Alphabetic}]/gu, "").replace(/ +/gu, " ").trim().replace(/^(.{30}\S*) .*$/, "$1").trim();
|
|
109
|
+
return formatted2;
|
|
110
|
+
};
|
|
111
|
+
const formatted = getFormatted(title);
|
|
108
112
|
const useWordSearch = checkUseWordSearch(formatted);
|
|
109
113
|
if (formatted.length >= onlyTitleLength && !alternates) {
|
|
110
114
|
return [`dc.title="${useWordSearch ? "" : "^"}${formatted}*"`];
|
|
@@ -157,6 +161,9 @@ export function bibTitleAuthorPublisher({ record, onlyTitleLength, addYear = fal
|
|
|
157
161
|
const [field] = record.get(/^245$/u);
|
|
158
162
|
if (field) {
|
|
159
163
|
const titleString = field.subfields.filter(({ code }) => ["a", "b"].includes(code)).map(({ value }) => testStringOrNumber(value) ? String(value) : "").filter((value) => value).join(" ");
|
|
164
|
+
if (/^[1-9]$/u.test(field.ind2)) {
|
|
165
|
+
return titleString.slice(parseInt(field.ind2));
|
|
166
|
+
}
|
|
160
167
|
return titleString;
|
|
161
168
|
}
|
|
162
169
|
return false;
|
|
@@ -227,8 +234,9 @@ export function bibYear(record) {
|
|
|
227
234
|
}
|
|
228
235
|
}
|
|
229
236
|
export function checkUseWordSearch(formatted) {
|
|
237
|
+
const lowercased = formatted.toLowerCase();
|
|
230
238
|
const booleanStartWords = ["and ", "or ", "nor ", "not "];
|
|
231
|
-
return booleanStartWords.some((word) =>
|
|
239
|
+
return booleanStartWords.some((word) => lowercased.startsWith(word));
|
|
232
240
|
}
|
|
233
241
|
export function bibStandardIdentifiers(record) {
|
|
234
242
|
const debug2 = createDebugLogger("@natlibfi/melinda-record-matching:candidate-search:query:bibStandardIdentifiers");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/candidate-search/query-list/bib.js"],
|
|
4
|
-
"sourcesContent": ["import createDebugLogger from 'debug';\nimport {toQueries} from '../candidate-search-utils.js';\nimport {getMelindaIdsF035, validateSidFieldSubfieldCounts, getSubfieldValues, testStringOrNumber} from '../../matching-utils.js';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query');\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 = testStringOrNumber(subfieldValue) ? String(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 testStringOrNumber(subfieldValue) ? String(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 debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibHostComponents');\n const debugData = debug.extend('data');\n debug(`Creating queries for hostIds`);\n\n const id = getHostId();\n debugData(`Found id: ${JSON.stringify(id)}`);\n\n return testStringOrNumber(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 (testStringOrNumber(value) && (/^\\(FI-MELINDA\\)/u).test(String(value))) {\n return String(value).replace(/^\\(FI-MELINDA\\)/u, '');\n }\n\n if (testStringOrNumber(value) && (/^\\(FIN01\\)/u).test(String(value))) {\n return String(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 // We get author/publisher only when formatted title is shorter than 5 chars\n return bibTitleAuthorPublisher({record, onlyTitleLength: 5});\n}\n\nexport function bibTitleAuthor(record) {\n debug('bibTitleAuthor');\n // We use onlyTitleLength that is longer than our formatted length to\n // get an author or an publisher always\n return bibTitleAuthorPublisher({record, onlyTitleLength: 100});\n}\n\nexport function bibTitleAuthorYear(record) {\n debug('bibTitleAuthorYearAlternates');\n // We use onlyTitleLength that is longer than our formatted length to\n // get an author or an publisher always\n\n return bibTitleAuthorPublisher({record, onlyTitleLength: 100, addYear: true});\n}\n\nexport function bibTitleAuthorYearAlternates(record) {\n debug('bibTitleAuthorYearAlternates');\n // We use onlyTitleLength that is longer than our formatted length to\n // get an author or an publisher always\n const origQueryList = bibTitleAuthorPublisher({record, onlyTitleLength: 100, addYear: true, alternates: true, alternateQueries: []});\n debug(`${JSON.stringify(origQueryList)}`);\n return {queryList: Array.from(origQueryList).reverse(), queryListType: 'alternates'};\n}\n\nexport function bibTitleAuthorPublisher({record, onlyTitleLength, addYear = false, alternates = false, alternateQueries = []}) {\n debug(`bibTitleAuthorPublisher, onlyTitleLength: ${onlyTitleLength}, addYear: ${addYear}, alternates: ${alternates}`);\n const title = getTitle();\n if (testStringOrNumber(title)) {\n const formatted = String(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 = checkUseWordSearch(formatted);\n // Prevent too many matches / SRU crashing by having a minimum length\n // Note that currently this fails matching if there are no matches from previous matchers\n if (formatted.length >= onlyTitleLength && !alternates) {\n return [`dc.title=\"${useWordSearch ? '' : '^'}${formatted}*\"`];\n }\n const queryIsOkAlone = formatted.length >= 5;\n\n // use word search without ending * also in combination searches to avoid SRU-server crashes [MRA-189]\n const query = `dc.title=\"${useWordSearch || !queryIsOkAlone ? '' : '^'}${formatted}${queryIsOkAlone ? '*' : ''}\"`;\n debug(`query: ${query}`);\n const newAlternateQueries = alternates ? [...alternateQueries, query] : alternateQueries;\n\n return addAuthorsToSearch({query, queryIsOkAlone, addYear, alternates, alternateQueries: newAlternateQueries});\n }\n\n return [];\n\n function addAuthorsToSearch({query, queryIsOkAlone = false, addYear = false, alternates = false, alternateQueries = []}) {\n debug('addAuthorsToSearch');\n const [authorQuery] = bibAuthors(record);\n if (authorQuery !== undefined) {\n if (addYear) {\n const newAlternateQueries = alternates ? [...alternateQueries, `${authorQuery} AND ${query}`] : alternateQueries;\n return addYearToSearch({query: `${authorQuery} AND ${query}`, queryIsOkAlone: true, alternates, alternateQueries: newAlternateQueries});\n }\n return alternates ? alternateQueries : [`${authorQuery} AND ${query}`];\n }\n return addPublisherToSearch({query, queryIsOkAlone, addYear, alternates, alternateQueries});\n //return [];\n }\n\n function addPublisherToSearch({query, queryIsOkAlone = false, addYear = false, alternates = false, alternateQueries = []}) {\n const [publisherQuery] = bibPublishers(record);\n if (publisherQuery !== undefined) {\n if (addYear) {\n const newAlternateQueries = alternates ? [...alternateQueries, `${publisherQuery} AND ${query}`] : alternateQueries;\n return addYearToSearch({query: `${publisherQuery} AND ${query}`, queryIsOkAlone: true, alternates, alternateQueries: newAlternateQueries});\n }\n return alternates ? alternateQueries : [`${publisherQuery} AND ${query}`];\n }\n if (queryIsOkAlone && !addYear) {\n return alternates ? alternateQueries : [`${query}`];\n }\n return addYearToSearch({query, queryIsOkAlone, alternates, alternateQueries});\n }\n\n function addYearToSearch({query, queryIsOkAlone = false, alternates = false, alternateQueries = []}) {\n const [yearQuery] = bibYear(record);\n if (yearQuery !== undefined) {\n const newAlternateQueries = alternates ? [...alternateQueries, `${yearQuery} AND ${query}`] : alternateQueries;\n return alternates ? newAlternateQueries : [`${yearQuery} AND ${query}`];\n }\n if (queryIsOkAlone) {\n return alternates ? alternateQueries : [`${query}`];\n }\n return [];\n }\n\n function getTitle() {\n const [field] = record.get(/^245$/u);\n\n if (field) {\n const titleString = field.subfields\n //.filter(({code}) => ['a', 'b', 'n', 'p'].includes(code))\n .filter(({code}) => ['a', 'b'].includes(code))\n //.filter(({code}) => ['a'].includes(code))\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .filter(value => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n return titleString;\n }\n return false;\n }\n}\n\nexport function bibAuthors(record) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibAuthors');\n const debugData = debug.extend('data');\n debug(`Creating query for the first author`);\n //debugData(record);\n\n const author = getAuthor(record);\n\n if (testStringOrNumber(author)) {\n const formatted = String(author)\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 authors starting with a boolean\n const useWordSearch = checkUseWordSearch(formatted);\n // Prevent too many matches by having a minimum length\n debugData(`Author string: ${formatted}`);\n if (formatted.length >= 5) {\n return [`dc.author=\"${useWordSearch ? '' : '^'}${formatted}*\"`];\n }\n return [];\n }\n\n return [];\n\n function getAuthor(record) {\n //debugData(record);\n // eslint-disable-next-line prefer-named-capture-group\n const [field] = record.get(/^(100)|(110)|(111)|(700)|(710)|(711)$/u);\n //debugData(field);\n\n if (field) {\n const authorString = field.subfields\n .filter(({code}) => ['a'].includes(code))\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .filter(value => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n return authorString;\n }\n return false;\n }\n}\n\nexport function bibPublishers(record) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibPublishers');\n const debugData = debug.extend('data');\n debug(`Creating query for the first publisher`);\n //debugData(record);\n\n const publisher = getPublisher(record);\n if (testStringOrNumber(publisher)) {\n const formatted = String(publisher)\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 debugData(`Publisher string: ${formatted}`);\n // use non-wildcard word search from dc.publisher\n return [`dc.publisher=\"${formatted}\"`];\n }\n\n return [];\n\n function getPublisher(record) {\n //debugData(record);\n const [field] = record.get(/^(?:260)|(?:264)$/u);\n //debugData(field);\n\n if (field) {\n const publisherString = field.subfields\n .filter(({code}) => ['b'].includes(code))\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .filter(value => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n return publisherString;\n }\n return false;\n }\n}\n\nexport function bibYear(record) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibYear');\n const debugData = debug.extend('data');\n debug(`Creating query for the publishing year`);\n\n const year = getYear(record);\n if (year) {\n return [`dc.date=\"${year}\"`];\n }\n return [];\n\n function getYear(record) {\n const [f008] = record.get(/^008$/u);\n if (f008 === undefined) {\n debug('f008 missing');\n return false;\n }\n\n debugData(`f008: ${JSON.stringify(f008)}`);\n const {value} = f008;\n return testStringOrNumber(value) ? String(value).slice(7, 11) : undefined;\n }\n}\n\nexport function checkUseWordSearch(formatted) {\n // Note: add a space to startWords to catch just actual boolean words\n const booleanStartWords = ['and ', 'or ', 'nor ', 'not '];\n return booleanStartWords.some(word => formatted.toLowerCase().startsWith(word));\n}\n\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 // DEVELOP: should we query also f015 and f028?\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) && testStringOrNumber(sub.value) && issnIsbnReqExp.test(String(sub.value)))\n .map(({value}) => String(value));\n }\n\n if (tag === '020') {\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && testStringOrNumber(sub.value) && issnIsbnReqExp.test(String(sub.value)))\n .map(({value}) => String(value));\n }\n\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && testStringOrNumber(sub.value) && otherIdReqExp.test(String(sub.value)))\n .map(({value}) => String(value));\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
6
|
-
"names": ["debug", "fSids", "sidStrings", "field", "addYear", "alternates", "alternateQueries", "record"]
|
|
4
|
+
"sourcesContent": ["import createDebugLogger from 'debug';\nimport {toQueries} from '../candidate-search-utils.js';\nimport {getMelindaIdsF035, validateSidFieldSubfieldCounts, getSubfieldValues, testStringOrNumber} from '../../matching-utils.js';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query');\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 = testStringOrNumber(subfieldValue) ? String(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 testStringOrNumber(subfieldValue) ? String(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 debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibHostComponents');\n const debugData = debug.extend('data');\n debug(`Creating queries for hostIds`);\n\n const id = getHostId();\n debugData(`Found id: ${JSON.stringify(id)}`);\n\n return testStringOrNumber(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 (testStringOrNumber(value) && (/^\\(FI-MELINDA\\)/u).test(String(value))) {\n return String(value).replace(/^\\(FI-MELINDA\\)/u, '');\n }\n\n if (testStringOrNumber(value) && (/^\\(FIN01\\)/u).test(String(value))) {\n return String(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 (probably) to *headings* index TIT\n// - Aleph cannot currently handle headings searches starting with a boolean - in these cases use word search\n\nexport function bibTitle(record) {\n // We get author/publisher only when formatted title is shorter than 5 chars\n return bibTitleAuthorPublisher({record, onlyTitleLength: 5});\n}\n\nexport function bibTitleAuthor(record) {\n debug('bibTitleAuthor');\n // We use onlyTitleLength that is longer than our formatted length to\n // get an author or an publisher always\n return bibTitleAuthorPublisher({record, onlyTitleLength: 100});\n}\n\nexport function bibTitleAuthorYear(record) {\n debug('bibTitleAuthorYearAlternates');\n // We use onlyTitleLength that is longer than our formatted length to\n // get an author or an publisher always\n\n return bibTitleAuthorPublisher({record, onlyTitleLength: 100, addYear: true});\n}\n\nexport function bibTitleAuthorYearAlternates(record) {\n debug('bibTitleAuthorYearAlternates');\n // We use onlyTitleLength that is longer than our formatted length to\n // get an author or an publisher always\n const origQueryList = bibTitleAuthorPublisher({record, onlyTitleLength: 100, addYear: true, alternates: true, alternateQueries: []});\n debug(`${JSON.stringify(origQueryList)}`);\n return {queryList: Array.from(origQueryList).reverse(), queryListType: 'alternates'};\n}\n\nexport function bibTitleAuthorPublisher({record, onlyTitleLength, addYear = false, alternates = false, alternateQueries = []}) {\n debug(`bibTitleAuthorPublisher, onlyTitleLength: ${onlyTitleLength}, addYear: ${addYear}, alternates: ${alternates}`);\n const title = getTitle();\n if (testStringOrNumber(title)) {\n const formatted = getFormatted(title);\n\n // use word search for titles starting with a boolean\n const useWordSearch = checkUseWordSearch(formatted);\n // Prevent too many matches / SRU crashing by having a minimum length\n // Note that currently this fails matching if there are no matches from previous matchers\n if (formatted.length >= onlyTitleLength && !alternates) {\n return [`dc.title=\"${useWordSearch ? '' : '^'}${formatted}*\"`];\n }\n const queryIsOkAlone = formatted.length >= 5;\n\n // use word search without ending * also in combination searches to avoid SRU-server crashes [MRA-189]\n const query = `dc.title=\"${useWordSearch || !queryIsOkAlone ? '' : '^'}${formatted}${queryIsOkAlone ? '*' : ''}\"`;\n debug(`query: ${query}`);\n const newAlternateQueries = alternates ? [...alternateQueries, query] : alternateQueries;\n\n return addAuthorsToSearch({query, queryIsOkAlone, addYear, alternates, alternateQueries: newAlternateQueries});\n\n function getFormatted(title) {\n const formatted = String(title)\n .replace(/[\\-+ !\"(){}\\[\\]<>;:.?/@*%=^_`~]/gu, ' ')\n .replace(/[^\\w\\s\\p{Alphabetic}]/gu, '')\n // Clean up concurrent spaces from fe. subfield changes\n .replace(/ +/gu, ' ')\n .trim()\n .replace(/^(.{30}\\S*) .*$/, \"$1\")\n .trim();\n\n return formatted;\n }\n }\n\n return [];\n\n function addAuthorsToSearch({query, queryIsOkAlone = false, addYear = false, alternates = false, alternateQueries = []}) {\n debug('addAuthorsToSearch');\n const [authorQuery] = bibAuthors(record);\n if (authorQuery !== undefined) {\n if (addYear) {\n const newAlternateQueries = alternates ? [...alternateQueries, `${authorQuery} AND ${query}`] : alternateQueries;\n return addYearToSearch({query: `${authorQuery} AND ${query}`, queryIsOkAlone: true, alternates, alternateQueries: newAlternateQueries});\n }\n return alternates ? alternateQueries : [`${authorQuery} AND ${query}`];\n }\n return addPublisherToSearch({query, queryIsOkAlone, addYear, alternates, alternateQueries});\n //return [];\n }\n\n function addPublisherToSearch({query, queryIsOkAlone = false, addYear = false, alternates = false, alternateQueries = []}) {\n const [publisherQuery] = bibPublishers(record);\n if (publisherQuery !== undefined) {\n if (addYear) {\n const newAlternateQueries = alternates ? [...alternateQueries, `${publisherQuery} AND ${query}`] : alternateQueries;\n return addYearToSearch({query: `${publisherQuery} AND ${query}`, queryIsOkAlone: true, alternates, alternateQueries: newAlternateQueries});\n }\n return alternates ? alternateQueries : [`${publisherQuery} AND ${query}`];\n }\n if (queryIsOkAlone && !addYear) {\n return alternates ? alternateQueries : [`${query}`];\n }\n return addYearToSearch({query, queryIsOkAlone, alternates, alternateQueries});\n }\n\n function addYearToSearch({query, queryIsOkAlone = false, alternates = false, alternateQueries = []}) {\n const [yearQuery] = bibYear(record);\n if (yearQuery !== undefined) {\n const newAlternateQueries = alternates ? [...alternateQueries, `${yearQuery} AND ${query}`] : alternateQueries;\n return alternates ? newAlternateQueries : [`${yearQuery} AND ${query}`];\n }\n if (queryIsOkAlone) {\n return alternates ? alternateQueries : [`${query}`];\n }\n return [];\n }\n\n function getTitle() {\n const [field] = record.get(/^245$/u);\n\n if (field) {\n const titleString = field.subfields\n //.filter(({code}) => ['a', 'b', 'n', 'p'].includes(code))\n .filter(({code}) => ['a', 'b'].includes(code))\n //.filter(({code}) => ['a'].includes(code))\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .filter(value => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n\n if (/^[1-9]$/u.test(field.ind2)) { // Skip non-filing characters\n return titleString.slice(parseInt(field.ind2));\n }\n return titleString;\n }\n return false;\n }\n}\n\nexport function bibAuthors(record) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibAuthors');\n const debugData = debug.extend('data');\n debug(`Creating query for the first author`);\n //debugData(record);\n\n const author = getAuthor(record);\n\n if (testStringOrNumber(author)) {\n const formatted = String(author)\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 authors starting with a boolean\n const useWordSearch = checkUseWordSearch(formatted);\n // Prevent too many matches by having a minimum length\n debugData(`Author string: ${formatted}`);\n if (formatted.length >= 5) {\n return [`dc.author=\"${useWordSearch ? '' : '^'}${formatted}*\"`];\n }\n return [];\n }\n\n return [];\n\n function getAuthor(record) {\n //debugData(record);\n // eslint-disable-next-line prefer-named-capture-group\n const [field] = record.get(/^(100)|(110)|(111)|(700)|(710)|(711)$/u);\n //debugData(field);\n\n if (field) {\n const authorString = field.subfields\n .filter(({code}) => ['a'].includes(code)) // We might use different subfield code sets for X00, X10 and X11?\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .filter(value => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n return authorString;\n }\n return false;\n }\n}\n\nexport function bibPublishers(record) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibPublishers');\n const debugData = debug.extend('data');\n debug(`Creating query for the first publisher`);\n //debugData(record);\n\n const publisher = getPublisher(record);\n if (testStringOrNumber(publisher)) {\n const formatted = String(publisher)\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 debugData(`Publisher string: ${formatted}`);\n // use non-wildcard word search from dc.publisher\n return [`dc.publisher=\"${formatted}\"`];\n }\n\n return [];\n\n function getPublisher(record) {\n //debugData(record);\n const [field] = record.get(/^(?:260)|(?:264)$/u);\n //debugData(field);\n\n if (field) {\n const publisherString = field.subfields\n .filter(({code}) => ['b'].includes(code))\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .filter(value => value)\n // In Melinda's index subfield separators are indexed as ' '\n .join(' ');\n return publisherString;\n }\n return false;\n }\n}\n\nexport function bibYear(record) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibYear');\n const debugData = debug.extend('data');\n debug(`Creating query for the publishing year`);\n\n const year = getYear(record);\n if (year) {\n return [`dc.date=\"${year}\"`];\n }\n return [];\n\n function getYear(record) {\n const [f008] = record.get(/^008$/u);\n if (f008 === undefined) {\n debug('f008 missing');\n return false;\n }\n\n debugData(`f008: ${JSON.stringify(f008)}`);\n const {value} = f008;\n return testStringOrNumber(value) ? String(value).slice(7, 11) : undefined;\n }\n}\n\nexport function checkUseWordSearch(formatted) {\n const lowercased = formatted.toLowerCase();\n // Note: add a space to startWords to catch just actual boolean words\n const booleanStartWords = ['and ', 'or ', 'nor ', 'not '];\n return booleanStartWords.some(word => lowercased.startsWith(word));\n}\n\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 // DEVELOP: should we query also f015 and f028?\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) && testStringOrNumber(sub.value) && issnIsbnReqExp.test(String(sub.value)))\n .map(({value}) => String(value));\n }\n\n if (tag === '020') {\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && testStringOrNumber(sub.value) && issnIsbnReqExp.test(String(sub.value)))\n .map(({value}) => String(value));\n }\n\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code) && testStringOrNumber(sub.value) && otherIdReqExp.test(String(sub.value)))\n .map(({value}) => String(value));\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,uBAAuB;AAC9B,SAAQ,iBAAgB;AACxB,SAAQ,mBAAmB,gCAAgC,mBAAmB,0BAAyB;AAEvG,MAAM,QAAQ,kBAAkB,0DAA0D;AAEnF,gBAAS,aAAa,QAAQ;AA0BnC,QAAMA,SAAQ,kBAAkB,qEAAqE;AACrG,QAAM,YAAYA,OAAM,OAAO,MAAM;AAGrC,EAAAA,OAAM,iCAAiC;AAEvC,QAAM,QAAQ,OAAO,IAAI,KAAK;AAC9B,YAAU,eAAe,MAAM,MAAM,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE;AAElE,SAAO,MAAM,SAAS,IAAI,aAAa,KAAK,IAAI,CAAC;AAEjD,WAAS,aAAaC,QAAO;AAC3B,IAAAD,OAAM,wCAAwC;AAE9C,UAAM,aAAa,cAAcC,MAAK;AAEtC,QAAI,WAAW,SAAS,GAAG;AACzB,MAAAD,OAAM,2CAA2C;AACjD,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,aAAa,UAAU,YAAY,kBAAkB;AAE3D,WAAO;AAEP,aAAS,cAAcC,QAAO;AAC5B,MAAAD,OAAM,qCAAqC;AAG3C,YAAME,cAAaD,OAAM,IAAI,WAAW,EAAE,OAAO,iBAAe,WAAW;AAC3E,aAAOC;AAEP,eAAS,YAAY,OAAO;AAC1B,QAAAF,OAAM,6BAA6B;AAEnC,eAAO,+BAA+B,KAAK,IAAI,gBAAgB,KAAK,IAAI;AAExE,iBAAS,gBAAgBG,QAAO;AAC9B,UAAAH,OAAM,8BAA8B;AACpC,gBAAM,CAAC,GAAG,IAAI,kBAAkBG,QAAO,GAAG;AAC1C,gBAAM,CAAC,GAAG,IAAI,kBAAkBA,QAAO,GAAG;AAE1C,gBAAM,aAAa,mBAAmB,0BAA0B,GAAG,CAAC;AACpE,gBAAM,aAAa,0BAA0B,GAAG;AAEhD,oBAAU,GAAG,KAAK,UAAU,GAAG,CAAC,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE;AAC3D,iBAAO,WAAW,OAAO,UAAU;AAAA,QACrC;AAEA,iBAAS,mBAAmB,eAAe;AACzC,gBAAM,oBAAqB;AAC3B,gBAAM,kBAAkB,mBAAmB,aAAa,IAAI,OAAO,aAAa,EAAE,QAAQ,mBAAmB,OAAO,IAAI;AACxH,oBAAU,cAAc,aAAa,OAAO,eAAe,EAAE;AAC7D,iBAAO;AAAA,QACT;AAEA,iBAAS,0BAA0B,eAAe;AAChD,oBAAU,eAAe,aAAa,EAAE;AACxC,gBAAM,qBAAsB;AAC5B,iBAAO,mBAAmB,aAAa,IAAI,OAAO,aAAa,EAAE,QAAQ,oBAAoB,EAAE,IAAI;AAAA,QACrG;AAAA,MAEF;AAAA,IACF;AAAA,EACF;AACF;AAEO,gBAAS,cAAc,QAAQ;AAGpC,QAAMH,SAAQ,kBAAkB,wEAAwE;AACxG,QAAM,YAAYA,OAAM,OAAO,MAAM;AACrC,EAAAA,OAAM,iCAAiC;AAKvC,QAAM,aAAa,kBAAkB,MAAM;AAE3C,YAAU,uBAAuB,WAAW,MAAM,MAAM,KAAK,UAAU,UAAU,CAAC,EAAE;AAEpF,MAAI,WAAW,SAAS,GAAG;AACzB,IAAAA,OAAM,2CAA2C;AACjD,WAAO,CAAC;AAAA,EACV;AAEA,SAAO,UAAU,YAAY,mBAAmB;AAClD;AAKO,gBAAS,kBAAkB,QAAQ;AACxC,QAAMA,SAAQ,kBAAkB,4EAA4E;AAC5G,QAAM,YAAYA,OAAM,OAAO,MAAM;AACrC,EAAAA,OAAM,8BAA8B;AAEpC,QAAM,KAAK,UAAU;AACrB,YAAU,aAAa,KAAK,UAAU,EAAE,CAAC,EAAE;AAE3C,SAAO,mBAAmB,EAAE,IAAI,CAAC,uBAAuB,EAAE,EAAE,IAAI,CAAC;AAEjE,WAAS,YAAY;AACnB,UAAM,CAAC,KAAK,IAAI,OAAO,IAAI,QAAQ;AAEnC,QAAI,OAAO;AACT,YAAM,EAAC,MAAK,IAAI,MAAM,UAAU,KAAK,CAAC,EAAC,KAAI,MAAM,SAAS,GAAG,KAAK,CAAC;AAEnE,UAAI,mBAAmB,KAAK,KAAM,mBAAoB,KAAK,OAAO,KAAK,CAAC,GAAG;AACzE,eAAO,OAAO,KAAK,EAAE,QAAQ,oBAAoB,EAAE;AAAA,MACrD;AAEA,UAAI,mBAAmB,KAAK,KAAM,cAAe,KAAK,OAAO,KAAK,CAAC,GAAG;AACpE,eAAO,OAAO,KAAK,EAAE,QAAQ,eAAe,EAAE;AAAA,MAChD;AAEA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;AAKO,gBAAS,SAAS,QAAQ;AAE/B,SAAO,wBAAwB,EAAC,QAAQ,iBAAiB,EAAC,CAAC;AAC7D;AAEO,gBAAS,eAAe,QAAQ;AACrC,QAAM,gBAAgB;AAGtB,SAAO,wBAAwB,EAAC,QAAQ,iBAAiB,IAAG,CAAC;AAC/D;AAEO,gBAAS,mBAAmB,QAAQ;AACzC,QAAM,8BAA8B;AAIpC,SAAO,wBAAwB,EAAC,QAAQ,iBAAiB,KAAK,SAAS,KAAI,CAAC;AAC9E;AAEO,gBAAS,6BAA6B,QAAQ;AACnD,QAAM,8BAA8B;AAGpC,QAAM,gBAAgB,wBAAwB,EAAC,QAAQ,iBAAiB,KAAK,SAAS,MAAM,YAAY,MAAM,kBAAkB,CAAC,EAAC,CAAC;AACnI,QAAM,GAAG,KAAK,UAAU,aAAa,CAAC,EAAE;AACxC,SAAO,EAAC,WAAW,MAAM,KAAK,aAAa,EAAE,QAAQ,GAAG,eAAe,aAAY;AACrF;AAEO,gBAAS,wBAAwB,EAAC,QAAQ,iBAAiB,UAAU,OAAO,aAAa,OAAO,mBAAmB,CAAC,EAAC,GAAG;AAC7H,QAAM,6CAA6C,eAAe,cAAc,OAAO,iBAAiB,UAAU,EAAE;AACpH,QAAM,QAAQ,SAAS;AACvB,MAAI,mBAAmB,KAAK,GAAG;AAmB7B,QAAS,eAAT,SAAsBI,QAAO;AAC3B,YAAMC,aAAY,OAAOD,MAAK,EAC3B,QAAQ,qCAAqC,GAAG,EAChD,QAAQ,2BAA2B,EAAE,EAErC,QAAQ,SAAS,GAAG,EACpB,KAAK,EACL,QAAQ,mBAAmB,IAAI,EAC/B,KAAK;AAER,aAAOC;AAAA,IACT;AA7BA,UAAM,YAAY,aAAa,KAAK;AAGpC,UAAM,gBAAgB,mBAAmB,SAAS;AAGlD,QAAI,UAAU,UAAU,mBAAmB,CAAC,YAAY;AACtD,aAAO,CAAC,aAAa,gBAAgB,KAAK,GAAG,GAAG,SAAS,IAAI;AAAA,IAC/D;AACA,UAAM,iBAAiB,UAAU,UAAU;AAG3C,UAAM,QAAQ,aAAa,iBAAiB,CAAC,iBAAiB,KAAK,GAAG,GAAG,SAAS,GAAG,iBAAiB,MAAM,EAAE;AAC9G,UAAM,UAAU,KAAK,EAAE;AACvB,UAAM,sBAAsB,aAAa,CAAC,GAAG,kBAAkB,KAAK,IAAI;AAExE,WAAO,mBAAmB,EAAC,OAAO,gBAAgB,SAAS,YAAY,kBAAkB,oBAAmB,CAAC;AAAA,EAc/G;AAEA,SAAO,CAAC;AAER,WAAS,mBAAmB,EAAC,OAAO,iBAAiB,OAAO,SAAAC,WAAU,OAAO,YAAAC,cAAa,OAAO,kBAAAC,oBAAmB,CAAC,EAAC,GAAG;AACvH,UAAM,oBAAoB;AAC1B,UAAM,CAAC,WAAW,IAAI,WAAW,MAAM;AACvC,QAAI,gBAAgB,QAAW;AAC7B,UAAIF,UAAS;AACX,cAAM,sBAAsBC,cAAa,CAAC,GAAGC,mBAAkB,GAAG,WAAW,QAAQ,KAAK,EAAE,IAAIA;AAChG,eAAO,gBAAgB,EAAC,OAAO,GAAG,WAAW,QAAQ,KAAK,IAAI,gBAAgB,MAAM,YAAAD,aAAY,kBAAkB,oBAAmB,CAAC;AAAA,MACxI;AACA,aAAOA,cAAaC,oBAAmB,CAAC,GAAG,WAAW,QAAQ,KAAK,EAAE;AAAA,IACvE;AACA,WAAO,qBAAqB,EAAC,OAAO,gBAAgB,SAAAF,UAAS,YAAAC,aAAY,kBAAAC,kBAAgB,CAAC;AAAA,EAE5F;AAEA,WAAS,qBAAqB,EAAC,OAAO,iBAAiB,OAAO,SAAAF,WAAU,OAAO,YAAAC,cAAa,OAAO,kBAAAC,oBAAmB,CAAC,EAAC,GAAG;AACzH,UAAM,CAAC,cAAc,IAAI,cAAc,MAAM;AAC7C,QAAI,mBAAmB,QAAW;AAChC,UAAIF,UAAS;AACX,cAAM,sBAAsBC,cAAa,CAAC,GAAGC,mBAAkB,GAAG,cAAc,QAAQ,KAAK,EAAE,IAAIA;AACnG,eAAO,gBAAgB,EAAC,OAAO,GAAG,cAAc,QAAQ,KAAK,IAAI,gBAAgB,MAAM,YAAAD,aAAY,kBAAkB,oBAAmB,CAAC;AAAA,MAC3I;AACA,aAAOA,cAAaC,oBAAmB,CAAC,GAAG,cAAc,QAAQ,KAAK,EAAE;AAAA,IAC1E;AACA,QAAI,kBAAkB,CAACF,UAAS;AAC9B,aAAOC,cAAaC,oBAAmB,CAAC,GAAG,KAAK,EAAE;AAAA,IACpD;AACA,WAAO,gBAAgB,EAAC,OAAO,gBAAgB,YAAAD,aAAY,kBAAAC,kBAAgB,CAAC;AAAA,EAC9E;AAEA,WAAS,gBAAgB,EAAC,OAAO,iBAAiB,OAAO,YAAAD,cAAa,OAAO,kBAAAC,oBAAmB,CAAC,EAAC,GAAG;AACnG,UAAM,CAAC,SAAS,IAAI,QAAQ,MAAM;AAClC,QAAI,cAAc,QAAW;AAC3B,YAAM,sBAAsBD,cAAa,CAAC,GAAGC,mBAAkB,GAAG,SAAS,QAAQ,KAAK,EAAE,IAAIA;AAC9F,aAAOD,cAAa,sBAAsB,CAAC,GAAG,SAAS,QAAQ,KAAK,EAAE;AAAA,IACxE;AACA,QAAI,gBAAgB;AAClB,aAAOA,cAAaC,oBAAmB,CAAC,GAAG,KAAK,EAAE;AAAA,IACpD;AACA,WAAO,CAAC;AAAA,EACV;AAEA,WAAS,WAAW;AAClB,UAAM,CAAC,KAAK,IAAI,OAAO,IAAI,QAAQ;AAEnC,QAAI,OAAO;AACT,YAAM,cAAc,MAAM,UAEvB,OAAO,CAAC,EAAC,KAAI,MAAM,CAAC,KAAK,GAAG,EAAE,SAAS,IAAI,CAAC,EAE5C,IAAI,CAAC,EAAC,MAAK,MAAM,mBAAmB,KAAK,IAAI,OAAO,KAAK,IAAI,EAAE,EAC/D,OAAO,WAAS,KAAK,EAErB,KAAK,GAAG;AAEX,UAAI,WAAW,KAAK,MAAM,IAAI,GAAG;AAC/B,eAAO,YAAY,MAAM,SAAS,MAAM,IAAI,CAAC;AAAA,MAC/C;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;AAEO,gBAAS,WAAW,QAAQ;AACjC,QAAMR,SAAQ,kBAAkB,qEAAqE;AACrG,QAAM,YAAYA,OAAM,OAAO,MAAM;AACrC,EAAAA,OAAM,qCAAqC;AAG3C,QAAM,SAAS,UAAU,MAAM;AAE/B,MAAI,mBAAmB,MAAM,GAAG;AAC9B,UAAM,YAAY,OAAO,MAAM,EAC5B,QAAQ,2BAA2B,EAAE,EAErC,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,MAAM,GAAG,EAAE,EACX,KAAK;AAGR,UAAM,gBAAgB,mBAAmB,SAAS;AAElD,cAAU,kBAAkB,SAAS,EAAE;AACvC,QAAI,UAAU,UAAU,GAAG;AACzB,aAAO,CAAC,cAAc,gBAAgB,KAAK,GAAG,GAAG,SAAS,IAAI;AAAA,IAChE;AACA,WAAO,CAAC;AAAA,EACV;AAEA,SAAO,CAAC;AAER,WAAS,UAAUS,SAAQ;AAGzB,UAAM,CAAC,KAAK,IAAIA,QAAO,IAAI,wCAAwC;AAGnE,QAAI,OAAO;AACT,YAAM,eAAe,MAAM,UACxB,OAAO,CAAC,EAAC,KAAI,MAAM,CAAC,GAAG,EAAE,SAAS,IAAI,CAAC,EACvC,IAAI,CAAC,EAAC,MAAK,MAAM,mBAAmB,KAAK,IAAI,OAAO,KAAK,IAAI,EAAE,EAC/D,OAAO,WAAS,KAAK,EAErB,KAAK,GAAG;AACX,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;AAEO,gBAAS,cAAc,QAAQ;AACpC,QAAMT,SAAQ,kBAAkB,wEAAwE;AACxG,QAAM,YAAYA,OAAM,OAAO,MAAM;AACrC,EAAAA,OAAM,wCAAwC;AAG9C,QAAM,YAAY,aAAa,MAAM;AACrC,MAAI,mBAAmB,SAAS,GAAG;AACjC,UAAM,YAAY,OAAO,SAAS,EAC/B,QAAQ,2BAA2B,EAAE,EAErC,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,MAAM,GAAG,EAAE,EACX,KAAK;AAER,cAAU,qBAAqB,SAAS,EAAE;AAE1C,WAAO,CAAC,iBAAiB,SAAS,GAAG;AAAA,EACvC;AAEA,SAAO,CAAC;AAER,WAAS,aAAaS,SAAQ;AAE5B,UAAM,CAAC,KAAK,IAAIA,QAAO,IAAI,oBAAoB;AAG/C,QAAI,OAAO;AACT,YAAM,kBAAkB,MAAM,UAC3B,OAAO,CAAC,EAAC,KAAI,MAAM,CAAC,GAAG,EAAE,SAAS,IAAI,CAAC,EACvC,IAAI,CAAC,EAAC,MAAK,MAAM,mBAAmB,KAAK,IAAI,OAAO,KAAK,IAAI,EAAE,EAC/D,OAAO,WAAS,KAAK,EAErB,KAAK,GAAG;AACX,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;AAEO,gBAAS,QAAQ,QAAQ;AAC9B,QAAMT,SAAQ,kBAAkB,kEAAkE;AAClG,QAAM,YAAYA,OAAM,OAAO,MAAM;AACrC,EAAAA,OAAM,wCAAwC;AAE9C,QAAM,OAAO,QAAQ,MAAM;AAC3B,MAAI,MAAM;AACR,WAAO,CAAC,YAAY,IAAI,GAAG;AAAA,EAC7B;AACA,SAAO,CAAC;AAER,WAAS,QAAQS,SAAQ;AACvB,UAAM,CAAC,IAAI,IAAIA,QAAO,IAAI,QAAQ;AAClC,QAAI,SAAS,QAAW;AACtB,MAAAT,OAAM,cAAc;AACpB,aAAO;AAAA,IACT;AAEA,cAAU,SAAS,KAAK,UAAU,IAAI,CAAC,EAAE;AACzC,UAAM,EAAC,MAAK,IAAI;AAChB,WAAO,mBAAmB,KAAK,IAAI,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI;AAAA,EAClE;AACF;AAEO,gBAAS,mBAAmB,WAAW;AAC5C,QAAM,aAAa,UAAU,YAAY;AAEzC,QAAM,oBAAoB,CAAC,QAAQ,OAAO,QAAQ,MAAM;AACxD,SAAO,kBAAkB,KAAK,UAAQ,WAAW,WAAW,IAAI,CAAC;AACnE;AAEO,gBAAS,uBAAuB,QAAQ;AAE7C,QAAMA,SAAQ,kBAAkB,iFAAiF;AACjH,QAAM,YAAYA,OAAM,OAAO,MAAM;AACrC,EAAAA,OAAM,2CAA2C;AAIjD,QAAM,SAAS,OAAO,IAAI,wBAAwB;AAClD,QAAM,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,IAAI,aAAa,CAAC;AAC1D,QAAM,oBAAoB,CAAC,GAAG,IAAI,IAAI,WAAW,CAAC;AAElD,YAAU,+BAA+B,KAAK,UAAU,MAAM,CAAC,EAAE;AACjE,YAAU,gBAAgB,YAAY,MAAM,MAAM,KAAK,UAAU,WAAW,CAAC,EAAE;AAC/E,YAAU,uBAAuB,kBAAkB,MAAM,MAAM,KAAK,UAAU,iBAAiB,CAAC,EAAE;AAElG,MAAI,kBAAkB,SAAS,GAAG;AAChC,IAAAA,OAAM,2CAA2C;AACjD,WAAO,CAAC;AAAA,EACV;AAEA,SAAO,UAAU,mBAAmB,eAAe;AAEnD,WAAS,cAAc,EAAC,KAAK,UAAS,GAAG;AACvC,UAAM,iBAAkB;AACxB,UAAM,gBAAiB;AAEvB,QAAI,QAAQ,OAAO;AACjB,aAAO,UACJ,OAAO,SAAO,CAAC,KAAK,KAAK,GAAG,EAAE,SAAS,IAAI,IAAI,KAAK,mBAAmB,IAAI,KAAK,KAAK,eAAe,KAAK,OAAO,IAAI,KAAK,CAAC,CAAC,EAC3H,IAAI,CAAC,EAAC,MAAK,MAAM,OAAO,KAAK,CAAC;AAAA,IACnC;AAEA,QAAI,QAAQ,OAAO;AACjB,aAAO,UACJ,OAAO,SAAO,CAAC,KAAK,GAAG,EAAE,SAAS,IAAI,IAAI,KAAK,mBAAmB,IAAI,KAAK,KAAK,eAAe,KAAK,OAAO,IAAI,KAAK,CAAC,CAAC,EACtH,IAAI,CAAC,EAAC,MAAK,MAAM,OAAO,KAAK,CAAC;AAAA,IACnC;AAEA,WAAO,UACJ,OAAO,SAAO,CAAC,KAAK,GAAG,EAAE,SAAS,IAAI,IAAI,KAAK,mBAAmB,IAAI,KAAK,KAAK,cAAc,KAAK,OAAO,IAAI,KAAK,CAAC,CAAC,EACrH,IAAI,CAAC,EAAC,MAAK,MAAM,OAAO,KAAK,CAAC;AAAA,EACnC;AACF;",
|
|
6
|
+
"names": ["debug", "fSids", "sidStrings", "field", "title", "formatted", "addYear", "alternates", "alternateQueries", "record"]
|
|
7
7
|
}
|
package/dist/cli.js
CHANGED
package/dist/cli.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/cli.js"],
|
|
4
|
-
"sourcesContent": ["import fs from 'fs';\nimport yargs from 'yargs';\nimport createMatchOperator, {candidateSearch, matchDetection} from './index.js';\nimport createDebugLogger from 'debug';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\ncli();\n\nasync function cli() {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:cli');\n const args = yargs(process.argv.slice(2))\n .scriptName('melinda-record-matching-js')\n .epilog('Copyright (C) 2022-2023 University Of Helsinki (The National Library Of Finland)')\n .usage('$0 <file> [options] and env variable info in README')\n .usage('Installed globally: $0 <file> [options] and env variable info in README')\n .usage('Not installed: npx $0 <file> [options] and env variable info in README')\n .usage('Build from source: node dist/index.js <file> [options] and env variable info in README')\n .showHelpOnFail(true)\n .example([['$ node /dist/cli.js record.json']])\n .env('MELINDA_RECORD_MATCH')\n .version()\n .positional('file', {type: 'string', describe: 'Json file of records to match'})\n .options({\n t: {type: 'string', default: 'IDS', alias: 'searchType', describe: 'IDS, STANDARD_IDS, COMPONENT, CONTENT or CONTENTALT'},\n m: {type: 'number', default: 1, alias: 'maxMatches', describe: ''},\n c: {type: 'number', default: 1000, alias: 'maxCandidates', describe: ''},\n s: {type: 'boolean', default: false, alias: 'returnStrategy', describe: ''},\n q: {type: 'boolean', default: false, alias: 'returnQuery', describe: ''},\n n: {type: 'boolean', default: false, alias: 'returnNonMatches', describe: ''}\n })\n .check((args) => {\n const [file] = args._;\n if (file === undefined) {\n throw new Error('No file argument given');\n }\n\n if (!fs.existsSync(file)) {\n throw new Error(`File ${file} does not exist`);\n }\n\n if (args.sruUrl === undefined) {\n throw new Error('Setup sru url');\n }\n\n if (!['IDS', 'STANDARD_IDS', 'COMPONENT', 'CONTENT', 'CONTENTALT'].includes(args.searchType)) {\n throw new Error('Invalid search type');\n }\n\n return true;\n })\n .parseSync();\n\n const [file] = args._;\n const {searchType} = args;\n debug(JSON.stringify(args));\n\n const detection = {\n
|
|
5
|
-
"mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,WAAW;AAClB,OAAO,uBAAsB,iBAAiB,sBAAqB;AACnE,OAAO,uBAAuB;AAC9B,SAAQ,kBAAiB;AAEzB,IAAI;AAEJ,eAAe,MAAM;AACnB,QAAM,QAAQ,kBAAkB,uCAAuC;AACvE,QAAM,OAAO,MAAM,QAAQ,KAAK,MAAM,CAAC,CAAC,EACrC,WAAW,4BAA4B,EACvC,OAAO,kFAAkF,EACzF,MAAM,qDAAqD,EAC3D,MAAM,yEAAyE,EAC/E,MAAM,wEAAwE,EAC9E,MAAM,wFAAwF,EAC9F,eAAe,IAAI,EACnB,QAAQ,CAAC,CAAC,iCAAiC,CAAC,CAAC,EAC7C,IAAI,sBAAsB,EAC1B,QAAQ,EACR,WAAW,QAAQ,EAAC,MAAM,UAAU,UAAU,gCAA+B,CAAC,EAC9E,QAAQ;AAAA,IACP,GAAG,EAAC,MAAM,UAAU,SAAS,OAAO,OAAO,cAAc,UAAU,sDAAqD;AAAA,IACxH,GAAG,EAAC,MAAM,UAAU,SAAS,GAAG,OAAO,cAAc,UAAU,GAAE;AAAA,IACjE,GAAG,EAAC,MAAM,UAAU,SAAS,KAAM,OAAO,iBAAiB,UAAU,GAAE;AAAA,IACvE,GAAG,EAAC,MAAM,WAAW,SAAS,OAAO,OAAO,kBAAkB,UAAU,GAAE;AAAA,IAC1E,GAAG,EAAC,MAAM,WAAW,SAAS,OAAO,OAAO,eAAe,UAAU,GAAE;AAAA,IACvE,GAAG,EAAC,MAAM,WAAW,SAAS,OAAO,OAAO,oBAAoB,UAAU,GAAE;AAAA,EAC9E,CAAC,EACA,MAAM,CAACA,UAAS;AACf,UAAM,CAACC,KAAI,IAAID,MAAK;AACpB,QAAIC,UAAS,QAAW;AACtB,YAAM,IAAI,MAAM,wBAAwB;AAAA,IAC1C;AAEA,QAAI,CAAC,GAAG,WAAWA,KAAI,GAAG;AACxB,YAAM,IAAI,MAAM,QAAQA,KAAI,iBAAiB;AAAA,IAC/C;AAEA,QAAID,MAAK,WAAW,QAAW;AAC7B,YAAM,IAAI,MAAM,eAAe;AAAA,IACjC;AAEA,QAAI,CAAC,CAAC,OAAO,gBAAgB,aAAa,WAAW,YAAY,EAAE,SAASA,MAAK,UAAU,GAAG;AAC5F,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AAEA,WAAO;AAAA,EACT,CAAC,EACA,UAAU;AAEb,QAAM,CAAC,IAAI,IAAI,KAAK;AACpB,QAAM,EAAC,WAAU,IAAI;AACrB,QAAM,KAAK,UAAU,IAAI,CAAC;AAE1B,QAAM,YAAY;AAAA,IAChB,
|
|
4
|
+
"sourcesContent": ["import fs from 'fs';\nimport yargs from 'yargs';\nimport createMatchOperator, {candidateSearch, matchDetection} from './index.js';\nimport createDebugLogger from 'debug';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\ncli();\n\nasync function cli() {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:cli');\n const args = yargs(process.argv.slice(2))\n .scriptName('melinda-record-matching-js')\n .epilog('Copyright (C) 2022-2023 University Of Helsinki (The National Library Of Finland)')\n .usage('$0 <file> [options] and env variable info in README')\n .usage('Installed globally: $0 <file> [options] and env variable info in README')\n .usage('Not installed: npx $0 <file> [options] and env variable info in README')\n .usage('Build from source: node dist/index.js <file> [options] and env variable info in README')\n .showHelpOnFail(true)\n .example([['$ node /dist/cli.js record.json']])\n .env('MELINDA_RECORD_MATCH')\n .version()\n .positional('file', {type: 'string', describe: 'Json file of records to match'})\n .options({\n t: {type: 'string', default: 'IDS', alias: 'searchType', describe: 'IDS, STANDARD_IDS, COMPONENT, CONTENT or CONTENTALT'},\n m: {type: 'number', default: 1, alias: 'maxMatches', describe: ''},\n c: {type: 'number', default: 1000, alias: 'maxCandidates', describe: ''},\n s: {type: 'boolean', default: false, alias: 'returnStrategy', describe: ''},\n q: {type: 'boolean', default: false, alias: 'returnQuery', describe: ''},\n n: {type: 'boolean', default: false, alias: 'returnNonMatches', describe: ''}\n })\n .check((args) => {\n const [file] = args._;\n if (file === undefined) {\n throw new Error('No file argument given');\n }\n\n if (!fs.existsSync(file)) {\n throw new Error(`File ${file} does not exist`);\n }\n\n if (args.sruUrl === undefined) {\n throw new Error('Setup sru url');\n }\n\n if (!['IDS', 'STANDARD_IDS', 'COMPONENT', 'CONTENT', 'CONTENTALT'].includes(args.searchType)) {\n throw new Error('Invalid search type');\n }\n\n return true;\n })\n .parseSync();\n\n const [file] = args._;\n const {searchType} = args;\n debug(JSON.stringify(args));\n\n const detection = {\n threshold: 0.8999,\n strategy: generateStrategy(searchType)\n };\n\n const search = {\n url: args.sruUrl, searchSpec: generateSearchSpec(searchType)\n };\n\n const matchOperator = await createMatchOperator({detection, search, ...args});\n\n const fileRaw = fs.readFileSync(file, 'utf8');\n const record = new MarcRecord(JSON.parse(fileRaw), {subfieldValues: false});\n\n const result = await matchOperator({record});\n debug(JSON.stringify(result));\n\n\n function generateStrategy(searchType) {\n if (['IDS'].includes(searchType)) {\n return [\n matchDetection.features.bib.melindaId(),\n matchDetection.features.bib.allSourceIds()\n ];\n }\n\n // We could have differing strategy for STANDARD_IDS\n // Let's not run title in strategy when we found the candidates through standard_ids search\n\n if (['STANDARD_IDS'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.isbn(),\n matchDetection.features.bib.issn(),\n matchDetection.features.bib.otherStandardIdentifier(),\n // Let's not use the same title matchDetection here\n //matchDetection.features.bib.title(),\n matchDetection.features.bib.authors(),\n // We probably should have some leeway here for notated music as BK etc.\n matchDetection.features.bib.recordType(),\n // Use publicationTimeAllowConsYearsMulti to\n // - ignore one year differences in publicationTime\n // - extract publicationTimes from f008, f26x and reprint notes in f500\n // - do not substract points for mismatching (normal) publicationTime, if there's a match between\n // normal publicationTime and a reprintPublication time\n matchDetection.features.bib.publicationTimeAllowConsYearsMulti(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.bibliographicLevel()\n ];\n }\n\n if (['COMPONENT'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.otherStandardIdentifier(),\n matchDetection.features.bib.recordType(),\n matchDetection.features.bib.title(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.authors(),\n matchDetection.features.bib.bibliographicLevel()\n ];\n }\n\n if (['CONTENT', 'CONTENTALT'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.isbn(),\n matchDetection.features.bib.issn(),\n matchDetection.features.bib.otherStandardIdentifier(),\n matchDetection.features.bib.title(),\n matchDetection.features.bib.authors(),\n matchDetection.features.bib.recordType(),\n matchDetection.features.bib.publicationTime(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.bibliographicLevel()\n ];\n }\n\n throw new Error('Unsupported match validation package');\n }\n\n\n function generateSearchSpec(searchType) {\n if (['IDS'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.melindaId,\n candidateSearch.searchTypes.bib.sourceIds\n ];\n }\n\n if (['STANDARD_IDS'].includes(searchType)) {\n return [candidateSearch.searchTypes.bib.standardIdentifiers];\n }\n\n if (['COMPONENT'].includes(searchType)) {\n return [\n //candidateSearch.searchTypes.bib.sourceIds,\n candidateSearch.searchTypes.component.hostIdMelinda,\n candidateSearch.searchTypes.component.hostIdOtherSource\n ];\n }\n\n if (['CONTENT'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.hostComponents,\n //candidateSearch.searchTypes.bib.titleAuthor,\n candidateSearch.searchTypes.bib.title\n ];\n }\n if (['CONTENTALT'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.hostComponents,\n // titleAuthorYearAlternates searches for matchCandidates\n // with alternate queries, starting from more tight searches\n candidateSearch.searchTypes.bib.titleAuthorYearAlternates\n ];\n }\n\n throw new Error('Unsupported match validation package');\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["args", "file", "searchType"]
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -144,7 +144,7 @@ export default ({ detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
144
144
|
debugData(`MatchDetection results for ${candidateId} (${newRecordCount}/${recordSetSize}): ${JSON.stringify(detectionResult)}`);
|
|
145
145
|
if (detectionResult.match || returnNonMatches) {
|
|
146
146
|
debug(`${detectionResult.match ? `Record ${candidateId} (${newRecordCount}/${recordSetSize}) is a match!` : `Record ${candidateId} (${newRecordCount}/${recordSetSize}) is NOT a match!`}`);
|
|
147
|
-
debugData(`Strategy: ${JSON.stringify(detectionResult.strategy)},
|
|
147
|
+
debugData(`Strategy: ${JSON.stringify(detectionResult.strategy)}, Threshold: ${JSON.stringify(detectionResult.threshold)}`);
|
|
148
148
|
const matchResult = {
|
|
149
149
|
probability: detectionResult.probability,
|
|
150
150
|
candidate: {
|
|
@@ -154,7 +154,7 @@ export default ({ detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
154
154
|
};
|
|
155
155
|
const strategyResult = {
|
|
156
156
|
strategy: detectionResult.strategy,
|
|
157
|
-
|
|
157
|
+
threshold: detectionResult.threshold
|
|
158
158
|
};
|
|
159
159
|
const newMatch = returnStrategy ? { ...matchResult, ...strategyResult } : { ...matchResult };
|
|
160
160
|
debugData(`${JSON.stringify(newMatch)}`);
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.js"],
|
|
4
|
-
"sourcesContent": ["import createDebugLogger from 'debug';\nimport createSearchInterface, * as candidateSearch from './candidate-search/index.js';\nimport createDetectionInterface, * as matchDetection from './match-detection/index.js';\n//import inspect from 'util';\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\n const detect = createDetectionInterface(detectionOptions, returnStrategy);\n\n return prepareSearch;\n\n async function prepareSearch({record, recordExternal = {recordSource: 'incomingRecord', label: 'ic'}}) {\n\n const {search} = await createSearchInterface({...searchOptions, record, maxCandidates, recordExternal});\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, nonMatchCount = 0, matchErrors = []}) {\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}, nonMatchCount: ${nonMatchCount}, matchErrors: ${matchErrors.length}`);\n const recordSetSize = records.length;\n const newCandidateCount = candidateCount + recordSetSize;\n\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, nonMatchCount, duplicateCount, matchErrors});\n }\n\n debug(`No (more) candidate records to check, no more queries left, matches: ${matches.length}`);\n return returnResult({matches, state, stopReason: '', nonMatches, nonMatchCount, candidateCount: newCandidateCount, duplicateCount, matchErrors});\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, nonMatchCount});\n\n const newDuplicateCount = duplicateCount + matchResult.duplicateCount;\n const newNonMatchCount = nonMatchCount + matchResult.nonMatchCount;\n const {newMatches, newNonMatches, newMatchErrors} = handleMatchResult(matchResult, matches, nonMatches, matchErrors);\n\n if (maxMatchesFound({matches: newMatches, maxMatches})) {\n return returnResult({matches: newMatches, state, stopReason: 'maxMatches', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});\n }\n\n if (maxCandidatesRetrieved(newCandidateCount, maxCandidates)) {\n return returnResult({matches: newMatches, state, stopReason: 'maxCandidates', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});\n }\n\n return iterate({initialState: state, matches: newMatches, candidateCount: newCandidateCount, nonMatches: newNonMatches, duplicateCount: newDuplicateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});\n }\n\n function handleMatchResult(matchResult, matches, nonMatches, matchErrors) {\n debugData(`- Amount of new matches from record set: ${matchResult.matches.length}`);\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 const newMatchErrors = matchErrors.concat(matchResult.matchErrors);\n\n debugData(`- Total amount of matches: ${newMatches.length}`);\n if (returnNonMatches) {\n debugData(`- Total amount of nonMatches: ${newNonMatches.length}`);\n }\n\n debugData(`MatchResult: ${JSON.stringify(matchResult)}`);\n debugData(`Old matchErrors: ${JSON.stringify(matchErrors)}, matchErrors from matchResult: ${JSON.stringify(matchResult.matchErrors)}, New matchErrors: ${JSON.stringify(newMatchErrors)}`);\n\n debugData(`- Total amount of matchErrors: ${newMatchErrors.length}`);\n\n return {newMatches, newNonMatches, newMatchErrors};\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 // failures: array of conversionFailures from candidate-search and matchErrors from matchDetection in error format {status, payload: {message, id}} if returnFailures 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','conversionFailures', 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, nonMatchCount, matchErrors}) {\n const matchErrorCount = matchErrors.length;\n checkCounts({matches, nonMatches, candidateCount, duplicateCount, nonMatchCount, matchErrorCount});\n const matchStatus = getMatchState(state, stopReason, matchErrorCount);\n // add nonMatches to result only if returnNonMatches is 'true', otherwise nonMatches have not been gathered\n const matchesResult = returnNonMatches ? {matches, matchStatus, nonMatches, candidateCount} : {matches, matchStatus, candidateCount};\n const result = matchesResult;\n debugData(`${JSON.stringify(result)}`);\n return result;\n\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, nonMatchCount, matchErrorCount}) {\n const matchCount = matches.length;\n debugData(`Return nonMatches: ${returnNonMatches}`);\n const chosenNonMatchCount = returnNonMatches ? nonMatches.length : nonMatchCount;\n const totalHandled = matchCount + chosenNonMatchCount + duplicateCount;\n debug(`candidateCount: ${candidateCount}, matches: ${matchCount}, nonMatches: ${chosenNonMatchCount}, duplicateCount: ${duplicateCount}, matchErrorCount: ${matchErrorCount}`);\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 // eslint-disable-next-line max-statements\n function getMatchState(state, stopReason, matchErrorCount) {\n debugData(`${JSON.stringify(state)}`);\n debug(`We had ${matchErrorCount} retrieved candidates that errored in matchDetection.`);\n debug(`Queries left ${state.queriesLeft}, Searches for current query left: ${state.resultSetOffset && state.resultSetOffset <= state.totalRecords}, non-retrieved records: ${state.totalRecords - state.queryCandidateCounter}, maxedQueries (${state.maxedQueries.length}): ${state.maxedQueries}`);\n\n debugData(`StopReason: <${stopReason}>`);\n\n const searchesLeft = state.resultSetOffset && state.resultSetOffset <= state.totalRecords;\n const nonRetrieved = searchesLeft ? state.totalRecords - state.queryCandidateCounter : 0;\n debugData(`nonRetrieved: ${nonRetrieved}`);\n\n // matchStatus.stopReason: string ('maxMatches','maxCandidates','maxedQueries', empty string/undefined), reason for stopping retrieving or handling the candidate records\n // 'maxMatches' and 'maxCandidates' are in stopReason, 'maxedQueries', 'conversionFailures' and 'matchErrors' are created here\n\n if (state.queriesLeft > 0 || nonRetrieved > 0 || state.maxedQueries.length > 0 || matchErrorCount > 0) {\n const maxedQueriesStopReason = state.maxedQueries.length > 0 ? 'maxedQueries' : undefined;\n const matchErrorsStopReason = matchErrorCount > 0 ? 'matchErrors' : undefined;\n const newStopReason = stopReason === '' || stopReason === undefined ? maxedQueriesStopReason || matchErrorsStopReason : stopReason;\n debugData(`MaxedQueriesStopReason: <${maxedQueriesStopReason}>`);\n debugData(`MatchErrorsStopReason <${matchErrorsStopReason}>`);\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 // NOTES:\n // - we could optimize by creating the featureSet for the incoming record once and using it for all database/candidateRecords\n // - if creating the featureSet for the incoming record fails we have an unprocessable entity\n // - if creating the featureSet for a candidate record fails we could skip that candidate - but list the case as a detectionFailure, same as conversionFailures\n\n function iterateRecords({records, recordSetSize, maxMatches, matches = [], nonMatches = [], recordMatches = [], recordNonMatches = [], recordCount = 0, recordDuplicateCount = 0, recordNonMatchCount = 0, recordMatchErrors = []}) {\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 // recordNonMatchCount: amount of records from the current record set that are nonMatches (only is returnNonMatches setting is false)\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 // recordMatchErrors: errored matchDetection in the current record set\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 /* We could optimize and detect all retrieved candidates at once\n const candidateRecords = records.map(record => record.record);\n const recordsIsArray = Array.isArray(candidateRecords);\n debug(`records is an array: ${recordsIsArray}`);\n const result = detect(record, candidateRecords);\n debugData(`${JSON.stringify(result)}`);\n */\n\n if (candidate) {\n if (candidateNotInMatches(matches.concat(nonMatches), candidate)) {\n const {record: candidateRecord, id: candidateId} = candidate;\n const recordBExternal = {id: candidate.id, recordSource: 'databaseRecord', label: `db-${candidate.id}`};\n try {\n debug(`Running matchDetection for record ${candidateId} (${newRecordCount}/${recordSetSize})`);\n // we should handle errors from detection somehow - ie. cases where either record or candidateRecord errors\n const detectionResult = detect({recordA: record, recordB: candidateRecord, recordAExternal: recordExternal, recordBExternal});\n\n return handleDetectionResult(detectionResult, candidateId, candidateRecord);\n } catch (error) {\n debug(`MatchDetection errored: database record ${candidateId}: ${error}`);\n\n const matchError = {status: 422, payload: {message: `Matching errored for database record ${candidateId}. ${error.message}.`, id: candidateId}};\n const newRecordMatchErrors = recordMatchErrors.concat(matchError);\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount, recordNonMatchCount, recordMatchErrors: newRecordMatchErrors});\n }\n }\n\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount: recordDuplicateCount + 1, recordNonMatchCount, recordMatchErrors});\n }\n\n debug(`No more candidates, record set (${recordCount}/${recordSetSize}) done, ${recordMatches.length} matches found, ${recordDuplicateCount} candidates already handled, ${returnNonMatches ? `${recordNonMatches.length}` : `${recordNonMatchCount}`} nonMatches found.`);\n return {matches: recordMatches, nonMatches: returnNonMatches ? recordNonMatches : [], duplicateCount: recordDuplicateCount, nonMatchCount: recordNonMatchCount, matchErrors: recordMatchErrors};\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\n const newRecordNonMatchCount = recordNonMatchCount + 1;\n debugData(`- Total nonMatches after this detection: ${newRecordNonMatchCount}`);\n\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount, recordNonMatchCount: newRecordNonMatchCount, recordMatchErrors});\n }\n\n function handleRecordMatch(isMatch, newMatch) {\n const newRecordMatches = isMatch ? recordMatches.concat(newMatch) : recordMatches;\n const newRecordNonMatches = isMatch ? recordNonMatches : recordNonMatches.concat(newMatch);\n const newRecordNonMatchCount = isMatch ? recordNonMatchCount : recordNonMatchCount + 1;\n\n debugData(`- Total matches after this detection: ${matches.concat(newRecordMatches).length} (max: ${maxMatches})`);\n\n if (returnNonMatches) {\n debugData(`- Total nonMatches after this detection: ${nonMatches.concat(newRecordNonMatches).length}`);\n }\n debugData(`- Total nonMatchCount after this detection: ${recordNonMatchCount}`);\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, nonMatchCount: newRecordNonMatchCount, matchErrors: recordMatchErrors};\n }\n\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches: newRecordMatches, recordCount: newRecordCount, recordNonMatches: returnNonMatches ? newRecordNonMatches : [], duplicateCount: recordDuplicateCount, recordNonMatchCount: newRecordNonMatchCount, matchErrors: recordMatchErrors});\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"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import createDebugLogger from 'debug';\nimport createSearchInterface, * as candidateSearch from './candidate-search/index.js';\nimport createDetectionInterface, * as matchDetection from './match-detection/index.js';\n//import inspect from 'util';\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\n const detect = createDetectionInterface(detectionOptions, returnStrategy);\n\n return prepareSearch;\n\n async function prepareSearch({record, recordExternal = {recordSource: 'incomingRecord', label: 'ic'}}) {\n\n const {search} = await createSearchInterface({...searchOptions, record, maxCandidates, recordExternal});\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, nonMatchCount = 0, matchErrors = []}) {\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}, nonMatchCount: ${nonMatchCount}, matchErrors: ${matchErrors.length}`);\n const recordSetSize = records.length;\n const newCandidateCount = candidateCount + recordSetSize;\n\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, nonMatchCount, duplicateCount, matchErrors});\n }\n\n debug(`No (more) candidate records to check, no more queries left, matches: ${matches.length}`);\n return returnResult({matches, state, stopReason: '', nonMatches, nonMatchCount, candidateCount: newCandidateCount, duplicateCount, matchErrors});\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, nonMatchCount});\n\n const newDuplicateCount = duplicateCount + matchResult.duplicateCount;\n const newNonMatchCount = nonMatchCount + matchResult.nonMatchCount;\n const {newMatches, newNonMatches, newMatchErrors} = handleMatchResult(matchResult, matches, nonMatches, matchErrors);\n\n if (maxMatchesFound({matches: newMatches, maxMatches})) {\n return returnResult({matches: newMatches, state, stopReason: 'maxMatches', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});\n }\n\n if (maxCandidatesRetrieved(newCandidateCount, maxCandidates)) {\n return returnResult({matches: newMatches, state, stopReason: 'maxCandidates', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});\n }\n\n return iterate({initialState: state, matches: newMatches, candidateCount: newCandidateCount, nonMatches: newNonMatches, duplicateCount: newDuplicateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});\n }\n\n function handleMatchResult(matchResult, matches, nonMatches, matchErrors) {\n debugData(`- Amount of new matches from record set: ${matchResult.matches.length}`);\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 const newMatchErrors = matchErrors.concat(matchResult.matchErrors);\n\n debugData(`- Total amount of matches: ${newMatches.length}`);\n if (returnNonMatches) {\n debugData(`- Total amount of nonMatches: ${newNonMatches.length}`);\n }\n\n debugData(`MatchResult: ${JSON.stringify(matchResult)}`);\n debugData(`Old matchErrors: ${JSON.stringify(matchErrors)}, matchErrors from matchResult: ${JSON.stringify(matchResult.matchErrors)}, New matchErrors: ${JSON.stringify(newMatchErrors)}`);\n\n debugData(`- Total amount of matchErrors: ${newMatchErrors.length}`);\n\n return {newMatches, newNonMatches, newMatchErrors};\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 // - threshold (if returnStrategy option is true)\n // - matchQuery (if returnQuery option is true)\n // failures: array of conversionFailures from candidate-search and matchErrors from matchDetection in error format {status, payload: {message, id}} if returnFailures 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','conversionFailures', 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, nonMatchCount, matchErrors}) {\n const matchErrorCount = matchErrors.length;\n checkCounts({matches, nonMatches, candidateCount, duplicateCount, nonMatchCount, matchErrorCount});\n const matchStatus = getMatchState(state, stopReason, matchErrorCount);\n // add nonMatches to result only if returnNonMatches is 'true', otherwise nonMatches have not been gathered\n const matchesResult = returnNonMatches ? {matches, matchStatus, nonMatches, candidateCount} : {matches, matchStatus, candidateCount};\n const result = matchesResult;\n debugData(`${JSON.stringify(result)}`);\n return result;\n\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, nonMatchCount, matchErrorCount}) {\n const matchCount = matches.length;\n debugData(`Return nonMatches: ${returnNonMatches}`);\n const chosenNonMatchCount = returnNonMatches ? nonMatches.length : nonMatchCount;\n const totalHandled = matchCount + chosenNonMatchCount + duplicateCount;\n debug(`candidateCount: ${candidateCount}, matches: ${matchCount}, nonMatches: ${chosenNonMatchCount}, duplicateCount: ${duplicateCount}, matchErrorCount: ${matchErrorCount}`);\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 // eslint-disable-next-line max-statements\n function getMatchState(state, stopReason, matchErrorCount) {\n debugData(`${JSON.stringify(state)}`);\n debug(`We had ${matchErrorCount} retrieved candidates that errored in matchDetection.`);\n debug(`Queries left ${state.queriesLeft}, Searches for current query left: ${state.resultSetOffset && state.resultSetOffset <= state.totalRecords}, non-retrieved records: ${state.totalRecords - state.queryCandidateCounter}, maxedQueries (${state.maxedQueries.length}): ${state.maxedQueries}`);\n\n debugData(`StopReason: <${stopReason}>`);\n\n const searchesLeft = state.resultSetOffset && state.resultSetOffset <= state.totalRecords;\n const nonRetrieved = searchesLeft ? state.totalRecords - state.queryCandidateCounter : 0;\n debugData(`nonRetrieved: ${nonRetrieved}`);\n\n // matchStatus.stopReason: string ('maxMatches','maxCandidates','maxedQueries', empty string/undefined), reason for stopping retrieving or handling the candidate records\n // 'maxMatches' and 'maxCandidates' are in stopReason, 'maxedQueries', 'conversionFailures' and 'matchErrors' are created here\n\n if (state.queriesLeft > 0 || nonRetrieved > 0 || state.maxedQueries.length > 0 || matchErrorCount > 0) {\n const maxedQueriesStopReason = state.maxedQueries.length > 0 ? 'maxedQueries' : undefined;\n const matchErrorsStopReason = matchErrorCount > 0 ? 'matchErrors' : undefined;\n const newStopReason = stopReason === '' || stopReason === undefined ? maxedQueriesStopReason || matchErrorsStopReason : stopReason;\n debugData(`MaxedQueriesStopReason: <${maxedQueriesStopReason}>`);\n debugData(`MatchErrorsStopReason <${matchErrorsStopReason}>`);\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 // NOTES:\n // - we could optimize by creating the featureSet for the incoming record once and using it for all database/candidateRecords\n // - if creating the featureSet for the incoming record fails we have an unprocessable entity\n // - if creating the featureSet for a candidate record fails we could skip that candidate - but list the case as a detectionFailure, same as conversionFailures\n\n function iterateRecords({records, recordSetSize, maxMatches, matches = [], nonMatches = [], recordMatches = [], recordNonMatches = [], recordCount = 0, recordDuplicateCount = 0, recordNonMatchCount = 0, recordMatchErrors = []}) {\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 // recordNonMatchCount: amount of records from the current record set that are nonMatches (only is returnNonMatches setting is false)\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 // recordMatchErrors: errored matchDetection in the current record set\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 /* We could optimize and detect all retrieved candidates at once\n const candidateRecords = records.map(record => record.record);\n const recordsIsArray = Array.isArray(candidateRecords);\n debug(`records is an array: ${recordsIsArray}`);\n const result = detect(record, candidateRecords);\n debugData(`${JSON.stringify(result)}`);\n */\n\n if (candidate) {\n if (candidateNotInMatches(matches.concat(nonMatches), candidate)) {\n const {record: candidateRecord, id: candidateId} = candidate;\n const recordBExternal = {id: candidate.id, recordSource: 'databaseRecord', label: `db-${candidate.id}`};\n try {\n debug(`Running matchDetection for record ${candidateId} (${newRecordCount}/${recordSetSize})`);\n // we should handle errors from detection somehow - ie. cases where either record or candidateRecord errors\n const detectionResult = detect({recordA: record, recordB: candidateRecord, recordAExternal: recordExternal, recordBExternal});\n\n return handleDetectionResult(detectionResult, candidateId, candidateRecord);\n } catch (error) {\n debug(`MatchDetection errored: database record ${candidateId}: ${error}`);\n\n const matchError = {status: 422, payload: {message: `Matching errored for database record ${candidateId}. ${error.message}.`, id: candidateId}};\n const newRecordMatchErrors = recordMatchErrors.concat(matchError);\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount, recordNonMatchCount, recordMatchErrors: newRecordMatchErrors});\n }\n }\n\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount: recordDuplicateCount + 1, recordNonMatchCount, recordMatchErrors});\n }\n\n debug(`No more candidates, record set (${recordCount}/${recordSetSize}) done, ${recordMatches.length} matches found, ${recordDuplicateCount} candidates already handled, ${returnNonMatches ? `${recordNonMatches.length}` : `${recordNonMatchCount}`} nonMatches found.`);\n return {matches: recordMatches, nonMatches: returnNonMatches ? recordNonMatches : [], duplicateCount: recordDuplicateCount, nonMatchCount: recordNonMatchCount, matchErrors: recordMatchErrors};\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)}, Threshold: ${JSON.stringify(detectionResult.threshold)}`);\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 threshold: detectionResult.threshold\n };\n const newMatch = returnStrategy ? {...matchResult, ...strategyResult} : {...matchResult};\n\n debugData(`${JSON.stringify(newMatch)}`);\n\n return handleRecordMatch(detectionResult.match, newMatch);\n }\n\n const newRecordNonMatchCount = recordNonMatchCount + 1;\n debugData(`- Total nonMatches after this detection: ${newRecordNonMatchCount}`);\n\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches, recordCount: newRecordCount, recordNonMatches, recordDuplicateCount, recordNonMatchCount: newRecordNonMatchCount, recordMatchErrors});\n }\n\n function handleRecordMatch(isMatch, newMatch) {\n const newRecordMatches = isMatch ? recordMatches.concat(newMatch) : recordMatches;\n const newRecordNonMatches = isMatch ? recordNonMatches : recordNonMatches.concat(newMatch);\n const newRecordNonMatchCount = isMatch ? recordNonMatchCount : recordNonMatchCount + 1;\n\n debugData(`- Total matches after this detection: ${matches.concat(newRecordMatches).length} (max: ${maxMatches})`);\n\n if (returnNonMatches) {\n debugData(`- Total nonMatches after this detection: ${nonMatches.concat(newRecordNonMatches).length}`);\n }\n debugData(`- Total nonMatchCount after this detection: ${recordNonMatchCount}`);\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, nonMatchCount: newRecordNonMatchCount, matchErrors: recordMatchErrors};\n }\n\n return iterateRecords({records: records.slice(1), recordSetSize, maxMatches, matches, recordMatches: newRecordMatches, recordCount: newRecordCount, recordNonMatches: returnNonMatches ? newRecordNonMatches : [], duplicateCount: recordDuplicateCount, recordNonMatchCount: newRecordNonMatchCount, matchErrors: recordMatchErrors});\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"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["matches", "nonMatches", "matchErrors", "candidateCount", "maxCandidates", "duplicateCount", "nonMatchCount", "matchErrorCount", "state", "stopReason", "maxMatches", "candidate"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import naturalPkg from "natural";
|
|
2
2
|
const { LevenshteinDistance: leven } = naturalPkg;
|
|
3
3
|
import { testStringOrNumber } from "../../../matching-utils.js";
|
|
4
|
-
export default ({
|
|
4
|
+
export default ({ nameThreshold = 10 } = {}) => ({
|
|
5
5
|
name: "Authors",
|
|
6
6
|
extract: ({ record }) => {
|
|
7
7
|
const authors = record.get(/^(?<def>100|700)$/u).map(({ subfields }) => {
|
|
@@ -37,7 +37,7 @@ export default ({ nameTreshold = 10 } = {}) => ({
|
|
|
37
37
|
}
|
|
38
38
|
const maxLength = getMaxLength();
|
|
39
39
|
const percentage = distance / maxLength * 100;
|
|
40
|
-
return percentage <=
|
|
40
|
+
return percentage <= nameThreshold;
|
|
41
41
|
function getMaxLength() {
|
|
42
42
|
return a2.length > b2.length ? a2.length : b2.length;
|
|
43
43
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/match-detection/features/bib/authors.js"],
|
|
4
|
-
"sourcesContent": ["\n\nimport naturalPkg from 'natural';\nconst {LevenshteinDistance: leven} = naturalPkg;\nimport {testStringOrNumber} from '../../../matching-utils.js';\n\n// We should extract also organisational authors (110/710)\n\nexport default ({
|
|
5
|
-
"mappings": "AAEA,OAAO,gBAAgB;AACvB,MAAM,EAAC,qBAAqB,MAAK,IAAI;AACrC,SAAQ,0BAAyB;AAIjC,eAAe,CAAC,EAAC,
|
|
4
|
+
"sourcesContent": ["\n\nimport naturalPkg from 'natural';\nconst {LevenshteinDistance: leven} = naturalPkg;\nimport {testStringOrNumber} from '../../../matching-utils.js';\n\n// We should extract also organisational authors (110/710)\n\nexport default ({nameThreshold = 10} = {}) => ({\n name: 'Authors',\n extract: ({record}) => {\n //const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';\n const authors = record.get(/^(?<def>100|700)$/u)\n .map(({subfields}) => {\n return subfields\n .filter(({code, value}) => code && value)\n .filter(({code}) => ['a', '0'].includes(code))\n .map(toObj)\n .reduce((acc, v) => ({...acc, ...v}), {});\n\n function toObj({code, value}) {\n if (code === 'a') {\n return {name: testStringOrNumber(value) ? String(value).replace(/[^\\p{Letter}\\p{Number}]/gu, '').toLowerCase() : ''};\n }\n\n return {id: value};\n }\n });\n return authors;\n },\n compare: (a, b) => {\n const maxAuthors = a.length > b.length ? a.length : b.length;\n const matchingIds = findMatchingIds();\n\n if (maxAuthors >= 3 && matchingIds >= 3) {\n return 0.3;\n }\n\n const matchingNames = findMatchingNames();\n const idPoints = matchingIds / maxAuthors * 0.3;\n const namePoints = matchingNames / maxAuthors * 0.2;\n const totalPoints = idPoints + namePoints;\n\n return totalPoints <= 0.2 ? totalPoints : 0.2;\n\n function findMatchingIds() {\n return findMatches('id', (a, b) => a === b);\n }\n\n function findMatchingNames() {\n return findMatches('name', (a, b) => {\n const distance = leven(a, b);\n\n if (distance === 0) {\n return true;\n }\n\n const maxLength = getMaxLength();\n const percentage = distance / maxLength * 100;\n\n return percentage <= nameThreshold;\n\n function getMaxLength() {\n return a.length > b.length ? a.length : b.length;\n }\n });\n }\n\n function findMatches(key, cb) {\n const valuesA = a.filter(o => o[key]).map(o => o[key]);\n const valuesB = b.filter(o => o[key]).map(o => o[key]);\n const allValues = valuesA.concat(valuesB);\n\n return allValues.reduce((acc, value) => {\n const occurrance = allValues.filter(otherValue => cb(value, otherValue)).length;\n return occurrance >= 2 ? acc + 1 : acc;\n }, 0);\n }\n }\n});\n"],
|
|
5
|
+
"mappings": "AAEA,OAAO,gBAAgB;AACvB,MAAM,EAAC,qBAAqB,MAAK,IAAI;AACrC,SAAQ,0BAAyB;AAIjC,eAAe,CAAC,EAAC,gBAAgB,GAAE,IAAI,CAAC,OAAO;AAAA,EAC7C,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,OAAM,MAAM;AAErB,UAAM,UAAU,OAAO,IAAI,oBAAoB,EAC5C,IAAI,CAAC,EAAC,UAAS,MAAM;AACpB,aAAO,UACJ,OAAO,CAAC,EAAC,MAAM,MAAK,MAAM,QAAQ,KAAK,EACvC,OAAO,CAAC,EAAC,KAAI,MAAM,CAAC,KAAK,GAAG,EAAE,SAAS,IAAI,CAAC,EAC5C,IAAI,KAAK,EACT,OAAO,CAAC,KAAK,OAAO,EAAC,GAAG,KAAK,GAAG,EAAC,IAAI,CAAC,CAAC;AAE1C,eAAS,MAAM,EAAC,MAAM,MAAK,GAAG;AAC5B,YAAI,SAAS,KAAK;AAChB,iBAAO,EAAC,MAAM,mBAAmB,KAAK,IAAI,OAAO,KAAK,EAAE,QAAQ,6BAA6B,EAAE,EAAE,YAAY,IAAI,GAAE;AAAA,QACrH;AAEA,eAAO,EAAC,IAAI,MAAK;AAAA,MACnB;AAAA,IACF,CAAC;AACH,WAAO;AAAA,EACT;AAAA,EACA,SAAS,CAAC,GAAG,MAAM;AACjB,UAAM,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;AACtD,UAAM,cAAc,gBAAgB;AAEpC,QAAI,cAAc,KAAK,eAAe,GAAG;AACvC,aAAO;AAAA,IACT;AAEA,UAAM,gBAAgB,kBAAkB;AACxC,UAAM,WAAW,cAAc,aAAa;AAC5C,UAAM,aAAa,gBAAgB,aAAa;AAChD,UAAM,cAAc,WAAW;AAE/B,WAAO,eAAe,MAAM,cAAc;AAE1C,aAAS,kBAAkB;AACzB,aAAO,YAAY,MAAM,CAACA,IAAGC,OAAMD,OAAMC,EAAC;AAAA,IAC5C;AAEA,aAAS,oBAAoB;AAC3B,aAAO,YAAY,QAAQ,CAACD,IAAGC,OAAM;AACnC,cAAM,WAAW,MAAMD,IAAGC,EAAC;AAE3B,YAAI,aAAa,GAAG;AAClB,iBAAO;AAAA,QACT;AAEA,cAAM,YAAY,aAAa;AAC/B,cAAM,aAAa,WAAW,YAAY;AAE1C,eAAO,cAAc;AAErB,iBAAS,eAAe;AACtB,iBAAOD,GAAE,SAASC,GAAE,SAASD,GAAE,SAASC,GAAE;AAAA,QAC5C;AAAA,MACF,CAAC;AAAA,IACH;AAEA,aAAS,YAAY,KAAK,IAAI;AAC5B,YAAM,UAAU,EAAE,OAAO,OAAK,EAAE,GAAG,CAAC,EAAE,IAAI,OAAK,EAAE,GAAG,CAAC;AACrD,YAAM,UAAU,EAAE,OAAO,OAAK,EAAE,GAAG,CAAC,EAAE,IAAI,OAAK,EAAE,GAAG,CAAC;AACrD,YAAM,YAAY,QAAQ,OAAO,OAAO;AAExC,aAAO,UAAU,OAAO,CAAC,KAAK,UAAU;AACtC,cAAM,aAAa,UAAU,OAAO,gBAAc,GAAG,OAAO,UAAU,CAAC,EAAE;AACzE,eAAO,cAAc,IAAI,MAAM,IAAI;AAAA,MACrC,GAAG,CAAC;AAAA,IACN;AAAA,EACF;AACF;",
|
|
6
6
|
"names": ["a", "b"]
|
|
7
7
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
export default () => ({
|
|
2
2
|
name: "Bibliographic level",
|
|
3
3
|
extract: ({ record }) => record.leader[7] ? [record.leader[7]] : [],
|
|
4
|
-
compare: (a, b) =>
|
|
4
|
+
compare: (a, b) => {
|
|
5
|
+
if (a[0] === b[0]) {
|
|
6
|
+
return 0.1;
|
|
7
|
+
}
|
|
8
|
+
if (a[0] === "a" && b[0] === "b") {
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
if (a[0] === "b" && b[0] === "a") {
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
return -0.2;
|
|
15
|
+
}
|
|
5
16
|
});
|
|
6
17
|
//# sourceMappingURL=bibliographic-level.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/match-detection/features/bib/bibliographic-level.js"],
|
|
4
|
-
"sourcesContent": ["\nexport default () => ({\n name: 'Bibliographic level',\n extract: ({record}) => record.leader[7] ? [record.leader[7]] : [],\n compare: (a, b) => a[0] === b[0]
|
|
5
|
-
"mappings": "AACA,eAAe,OAAO;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,OAAM,MAAM,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC;AAAA,EAChE,SAAS,CAAC,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,
|
|
4
|
+
"sourcesContent": ["\nexport default () => ({\n name: 'Bibliographic level',\n extract: ({record}) => record.leader[7] ? [record.leader[7]] : [],\n compare: (a, b) => {\n if (a[0] === b[0]) {\n return 0.1;\n }\n // See MELINDA-7427: We have millions of LDR/07='b' where they should be LDR/07='a'. So don't penalize for these...\n if (a[0] === 'a' && b[0] === 'b') {\n return 0.0;\n }\n if (a[0] === 'b' && b[0] === 'a') {\n return 0.0;\n }\n return -0.2;\n }\n});\n"],
|
|
5
|
+
"mappings": "AACA,eAAe,OAAO;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,OAAM,MAAM,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC;AAAA,EAChE,SAAS,CAAC,GAAG,MAAM;AACjB,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;AACjB,aAAO;AAAA,IACT;AAEA,QAAI,EAAE,CAAC,MAAM,OAAO,EAAE,CAAC,MAAM,KAAK;AAChC,aAAO;AAAA,IACT;AACA,QAAI,EAAE,CAAC,MAAM,OAAO,EAAE,CAAC,MAAM,KAAK;AAChC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default () => ({
|
|
2
2
|
name: "Host/Component record",
|
|
3
|
-
extract: ({ record }) => record.get(/^
|
|
3
|
+
extract: ({ record }) => record.get(/^[79]73$/u).length > 0 ? ["component"] : ["host"],
|
|
4
4
|
compare: (a, b) => a[0] === b[0] ? 0 : -1
|
|
5
5
|
});
|
|
6
6
|
//# sourceMappingURL=host-component.js.map
|