@natlibfi/melinda-record-matching 2.2.2 → 3.0.1-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 +0 -4
- package/dist/candidate-search/index.js.map +1 -1
- package/dist/candidate-search/index.spec.js +1 -5
- package/dist/candidate-search/index.spec.js.map +1 -1
- package/dist/candidate-search/query-list/bib.js +0 -2
- package/dist/candidate-search/query-list/bib.js.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/index.spec.js +3 -1
- package/dist/index.spec.js.map +1 -1
- package/dist/match-detection/features/bib/all-source-ids.js +6 -3
- package/dist/match-detection/features/bib/all-source-ids.js.map +1 -1
- package/dist/match-detection/features/bib/authors.js +28 -22
- package/dist/match-detection/features/bib/authors.js.map +1 -1
- package/dist/match-detection/features/bib/bibliographic-level.js +5 -2
- package/dist/match-detection/features/bib/bibliographic-level.js.map +1 -1
- package/dist/match-detection/features/bib/host-component.js +5 -2
- package/dist/match-detection/features/bib/host-component.js.map +1 -1
- package/dist/match-detection/features/bib/index.spec.js +3 -2
- package/dist/match-detection/features/bib/index.spec.js.map +1 -1
- package/dist/match-detection/features/bib/isbn.js +2 -1
- package/dist/match-detection/features/bib/isbn.js.map +1 -1
- package/dist/match-detection/features/bib/issn.js +2 -1
- package/dist/match-detection/features/bib/issn.js.map +1 -1
- package/dist/match-detection/features/bib/language.js +12 -8
- package/dist/match-detection/features/bib/language.js.map +1 -1
- package/dist/match-detection/features/bib/melinda-id.js +2 -1
- package/dist/match-detection/features/bib/melinda-id.js.map +1 -1
- package/dist/match-detection/features/bib/melinda-identifier-factory.js +5 -4
- package/dist/match-detection/features/bib/melinda-identifier-factory.js.map +1 -1
- package/dist/match-detection/features/bib/other-standard-identifier.js +2 -1
- package/dist/match-detection/features/bib/other-standard-identifier.js.map +1 -1
- package/dist/match-detection/features/bib/publication-time.js +3 -1
- package/dist/match-detection/features/bib/publication-time.js.map +1 -1
- package/dist/match-detection/features/bib/record-type.js +3 -1
- package/dist/match-detection/features/bib/record-type.js.map +1 -1
- package/dist/match-detection/features/bib/standard-identifier-factory.js +3 -1
- package/dist/match-detection/features/bib/standard-identifier-factory.js.map +1 -1
- package/dist/match-detection/features/bib/title.js +26 -6
- package/dist/match-detection/features/bib/title.js.map +1 -1
- package/dist/match-detection/index.js +54 -15
- package/dist/match-detection/index.js.map +1 -1
- package/dist/match-detection/index.spec.js +4 -1
- package/dist/match-detection/index.spec.js.map +1 -1
- package/dist/matching-utils.js +0 -2
- package/dist/matching-utils.js.map +1 -1
- package/package.json +8 -8
- package/src/candidate-search/index.js +1 -3
- package/src/candidate-search/index.spec.js +1 -3
- package/src/candidate-search/query-list/bib.js +0 -1
- package/src/index.js +5 -4
- package/src/index.spec.js +1 -1
- package/src/match-detection/features/bib/all-source-ids.js +3 -3
- package/src/match-detection/features/bib/authors.js +19 -15
- package/src/match-detection/features/bib/bibliographic-level.js +1 -1
- package/src/match-detection/features/bib/host-component.js +1 -1
- package/src/match-detection/features/bib/index.spec.js +2 -2
- package/src/match-detection/features/bib/language.js +10 -8
- package/src/match-detection/features/bib/melinda-identifier-factory.js +2 -3
- package/src/match-detection/features/bib/publication-time.js +1 -1
- package/src/match-detection/features/bib/record-type.js +1 -1
- package/src/match-detection/features/bib/standard-identifier-factory.js +1 -1
- package/src/match-detection/features/bib/title.js +22 -4
- package/src/match-detection/index.js +23 -15
- package/src/match-detection/index.spec.js +1 -1
- package/src/matching-utils.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standard-identifier-factory.js","names":["debug","createDebugLogger","debugData","extend","pattern","subfieldCodes","extract","compare","record","field","get","subfields","filter","code","includes","map","value","testStringOrNumber","String","replace","a","b","length","bothHaveValidIdentifiers","maxValues","matchingValues","getValueCount","aValues","bValues","validOnly","getIdentifiers","aValue","some","bValue","values"],"sources":["../../../../src/match-detection/features/bib/standard-identifier-factory.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\nimport {testStringOrNumber} from '../../../matching-utils';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers');\nconst debugData = debug.extend('data');\n\nexport default ({pattern, subfieldCodes}) => {\n return {extract, compare};\n\n function extract(record) {\n const [field] = record.get(pattern);\n\n if (field) {\n return field.subfields\n .filter(({code}) => subfieldCodes.includes(code))\n .map(({code, value}) => ({code, value: testStringOrNumber(value) ? String(value).replace(/-/ug, '') : ''}))\n .filter(value => value);\n }\n\n return [];\n }\n\n function compare(a, b) {\n if (a.length === 0 || b.length === 0) {\n debugData(`No standardidentifiers to compare`);\n return 0;\n }\n\n if (bothHaveValidIdentifiers()) {\n const {maxValues, matchingValues} = getValueCount(true);\n return matchingValues / maxValues * 0.75;\n }\n\n const {maxValues, matchingValues} = getValueCount();\n return matchingValues / maxValues * 0.2;\n\n function bothHaveValidIdentifiers() {\n const aValues = a.filter(({code}) => code === 'a');\n const bValues = a.filter(({code}) => code === 'a');\n return aValues.length > 0 && bValues.length > 0;\n }\n\n function getValueCount(validOnly = false) {\n const aValues = getIdentifiers(a);\n const bValues = getIdentifiers(b);\n\n return {\n maxValues: aValues.length > bValues.length ? aValues.length : bValues.length,\n matchingValues: aValues.filter(aValue => bValues.some(bValue => aValue === bValue)).length\n };\n\n function getIdentifiers(values) {\n if (validOnly) {\n return values\n .filter(({code}) => code === 'a')\n .map(({value}) => value);\n }\n\n return values.map(({value}) => value);\n }\n }\n }\n};\n"],"mappings":";;;;;;AA4BA;AACA;AAA2D;AA7B3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA,MAAMA,KAAK,GAAG,IAAAC,cAAiB,EAAC,iFAAiF,CAAC;AAClH,MAAMC,SAAS,GAAGF,KAAK,CAACG,MAAM,CAAC,MAAM,CAAC;AAAC,eAExB,CAAC;EAACC,OAAO;EAAEC;AAAa,CAAC,KAAK;EAC3C,OAAO;IAACC,OAAO;IAAEC;EAAO,CAAC;EAEzB,SAASD,OAAO,
|
|
1
|
+
{"version":3,"file":"standard-identifier-factory.js","names":["debug","createDebugLogger","debugData","extend","pattern","subfieldCodes","extract","compare","record","field","get","subfields","filter","code","includes","map","value","testStringOrNumber","String","replace","a","b","length","bothHaveValidIdentifiers","maxValues","matchingValues","getValueCount","aValues","bValues","validOnly","getIdentifiers","aValue","some","bValue","values"],"sources":["../../../../src/match-detection/features/bib/standard-identifier-factory.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\nimport {testStringOrNumber} from '../../../matching-utils';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers');\nconst debugData = debug.extend('data');\n\nexport default ({pattern, subfieldCodes}) => {\n return {extract, compare};\n\n function extract({record}) {\n const [field] = record.get(pattern);\n\n if (field) {\n return field.subfields\n .filter(({code}) => subfieldCodes.includes(code))\n .map(({code, value}) => ({code, value: testStringOrNumber(value) ? String(value).replace(/-/ug, '') : ''}))\n .filter(value => value);\n }\n\n return [];\n }\n\n function compare(a, b) {\n if (a.length === 0 || b.length === 0) {\n debugData(`No standardidentifiers to compare`);\n return 0;\n }\n\n if (bothHaveValidIdentifiers()) {\n const {maxValues, matchingValues} = getValueCount(true);\n return matchingValues / maxValues * 0.75;\n }\n\n const {maxValues, matchingValues} = getValueCount();\n return matchingValues / maxValues * 0.2;\n\n function bothHaveValidIdentifiers() {\n const aValues = a.filter(({code}) => code === 'a');\n const bValues = a.filter(({code}) => code === 'a');\n return aValues.length > 0 && bValues.length > 0;\n }\n\n function getValueCount(validOnly = false) {\n const aValues = getIdentifiers(a);\n const bValues = getIdentifiers(b);\n\n return {\n maxValues: aValues.length > bValues.length ? aValues.length : bValues.length,\n matchingValues: aValues.filter(aValue => bValues.some(bValue => aValue === bValue)).length\n };\n\n function getIdentifiers(values) {\n if (validOnly) {\n return values\n .filter(({code}) => code === 'a')\n .map(({value}) => value);\n }\n\n return values.map(({value}) => value);\n }\n }\n }\n};\n"],"mappings":"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"}
|
|
@@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
var _debug = _interopRequireDefault(require("debug"));
|
|
8
7
|
var _natural = require("natural");
|
|
9
8
|
var _matchingUtils = require("../../../matching-utils");
|
|
9
|
+
var _debug = _interopRequireDefault(require("debug"));
|
|
10
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
11
|
/**
|
|
12
12
|
*
|
|
@@ -34,22 +34,42 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
34
34
|
* @licend The above is the entire license notice
|
|
35
35
|
* for the JavaScript code in this file.
|
|
36
36
|
*
|
|
37
|
-
*/
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:match-detection:features:title');
|
|
40
|
+
const debugData = debug.extend('data');
|
|
41
|
+
var _default = ({
|
|
38
42
|
treshold = 10
|
|
39
43
|
} = {}) => ({
|
|
40
44
|
name: 'Title',
|
|
41
|
-
extract:
|
|
45
|
+
extract: ({
|
|
46
|
+
record,
|
|
47
|
+
recordExternal
|
|
48
|
+
}) => {
|
|
49
|
+
const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';
|
|
42
50
|
const title = getTitle();
|
|
51
|
+
debug(`${label} title: ${title}`);
|
|
43
52
|
if ((0, _matchingUtils.testStringOrNumber)(title)) {
|
|
44
|
-
|
|
53
|
+
const titleAsNormalizedString = String(title)
|
|
54
|
+
// decompose unicode diacritics
|
|
55
|
+
.normalize('NFD')
|
|
56
|
+
// strip non-letters/numbers
|
|
57
|
+
// - note: combined with decomposing unicode diactics this normalizes both 'saa' and 'sää' as 'saa'
|
|
58
|
+
// - we could precompose the finnish letters back to avoid this
|
|
59
|
+
.replace(/[^\p{Letter}\p{Number}]/gu, '').toLowerCase();
|
|
60
|
+
debug(`${label} titleString: ${titleAsNormalizedString}`);
|
|
61
|
+
return [titleAsNormalizedString];
|
|
45
62
|
}
|
|
46
63
|
return [];
|
|
47
64
|
function getTitle() {
|
|
48
65
|
const [field] = record.get(/^245$/u);
|
|
66
|
+
debugData(`${label} titleField: ${JSON.stringify(field)}`);
|
|
49
67
|
if (field) {
|
|
50
|
-
return field.subfields
|
|
68
|
+
return field.subfields
|
|
69
|
+
// get also $n:s and $p:s here
|
|
70
|
+
.filter(({
|
|
51
71
|
code
|
|
52
|
-
}) => ['a', 'b'].includes(code)).map(({
|
|
72
|
+
}) => ['a', 'b', 'n', 'p'].includes(code)).map(({
|
|
53
73
|
value
|
|
54
74
|
}) => (0, _matchingUtils.testStringOrNumber)(value) ? String(value) : '').join('');
|
|
55
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"title.js","names":["treshold","name","extract","record","title","getTitle","testStringOrNumber","String","replace","toLowerCase","field","get","subfields","filter","code","includes","map","value","join","compare","a","b","
|
|
1
|
+
{"version":3,"file":"title.js","names":["debug","createDebugLogger","debugData","extend","treshold","name","extract","record","recordExternal","label","title","getTitle","testStringOrNumber","titleAsNormalizedString","String","normalize","replace","toLowerCase","field","get","JSON","stringify","subfields","filter","code","includes","map","value","join","compare","a","b","distance","leven","maxLength","getMaxLength","percentage","length"],"sources":["../../../../src/match-detection/features/bib/title.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\n\nimport {LevenshteinDistance as leven} from 'natural';\nimport {testStringOrNumber} from '../../../matching-utils';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features:title');\nconst debugData = debug.extend('data');\n\n\nexport default ({treshold = 10} = {}) => ({\n name: 'Title',\n extract: ({record, recordExternal}) => {\n const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';\n const title = getTitle();\n debug(`${label} title: ${title}`);\n\n if (testStringOrNumber(title)) {\n const titleAsNormalizedString = String(title)\n // decompose unicode diacritics\n .normalize('NFD')\n // strip non-letters/numbers\n // - note: combined with decomposing unicode diactics this normalizes both 'saa' and 'sää' as 'saa'\n // - we could precompose the finnish letters back to avoid this\n .replace(/[^\\p{Letter}\\p{Number}]/gu, '')\n .toLowerCase();\n debug(`${label} titleString: ${titleAsNormalizedString}`);\n return [titleAsNormalizedString];\n }\n\n return [];\n\n function getTitle() {\n const [field] = record.get(/^245$/u);\n debugData(`${label} titleField: ${JSON.stringify(field)}`);\n\n if (field) {\n return field.subfields\n // get also $n:s and $p:s here\n .filter(({code}) => ['a', 'b', 'n', 'p'].includes(code))\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .join('');\n }\n return false;\n }\n },\n compare: (a, b) => {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features/bib/title');\n const distance = leven(a[0], b[0]);\n\n if (distance === 0) {\n return 0.5;\n }\n\n const maxLength = getMaxLength();\n const percentage = distance / maxLength * 100;\n\n debug(`'${a}' vs '${b}': Max length = ${maxLength}, distance = ${distance}, percentage = ${percentage}`);\n\n if (percentage <= treshold) {\n return 0.3;\n }\n\n return -0.5;\n\n function getMaxLength() {\n return a[0].length > b[0].length ? a[0].length : b[0].length;\n }\n\n }\n});\n"],"mappings":"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"}
|
|
@@ -10,7 +10,6 @@ exports.features = features;
|
|
|
10
10
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
11
11
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
12
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
-
/* eslint-disable no-console */
|
|
14
13
|
/**
|
|
15
14
|
*
|
|
16
15
|
* @licstart The following is the entire license notice for the JavaScript code in this file.
|
|
@@ -41,26 +40,60 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
41
40
|
var _default = ({
|
|
42
41
|
strategy,
|
|
43
42
|
treshold = 0.9
|
|
44
|
-
}, returnStrategy = false
|
|
43
|
+
}, returnStrategy = false, localSettings = {}) => ({
|
|
44
|
+
recordA,
|
|
45
|
+
recordB,
|
|
46
|
+
recordAExternal = {},
|
|
47
|
+
recordBExternal = {}
|
|
48
|
+
}) => {
|
|
45
49
|
const minProbabilityQuantifier = 0.5;
|
|
46
50
|
const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:match-detection');
|
|
47
51
|
const debugData = debug.extend('data');
|
|
48
|
-
debugData(`Strategy: ${JSON.stringify(strategy)}`);
|
|
49
|
-
debugData(`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
debugData(`Strategy: ${JSON.stringify(strategy)}, Treshold: ${JSON.stringify(treshold)}, ReturnStrategy: ${JSON.stringify(returnStrategy)}`);
|
|
53
|
+
debugData(`Records: A: ${recordA}\nB: ${recordB}`);
|
|
54
|
+
debug(`Externals: A: ${JSON.stringify(recordAExternal)}, B: ${JSON.stringify(recordBExternal)}`);
|
|
55
|
+
// We could add here labels for records if we didn't get external labels
|
|
56
|
+
|
|
57
|
+
const featuresA = extractFeatures({
|
|
58
|
+
record: recordA,
|
|
59
|
+
recordExternal: recordAExternal,
|
|
60
|
+
localSettings
|
|
61
|
+
});
|
|
52
62
|
debug(`We got an array of records: ${Array.isArray(recordB)}`);
|
|
53
63
|
const recordsB = Array.isArray(recordB) ? recordB : [recordB];
|
|
54
|
-
const
|
|
64
|
+
const recordsBExternal = Array.isArray(recordB) ? recordBExternal : [recordBExternal];
|
|
65
|
+
const detectionResults = recordsB.map((record, index) => actualDetection({
|
|
66
|
+
featuresA,
|
|
67
|
+
recordAExternal,
|
|
68
|
+
record,
|
|
69
|
+
recordExternal: recordsBExternal[index],
|
|
70
|
+
index
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
// if we got array of records, we return an array of result
|
|
74
|
+
// if we got a singular record, we return a singular result
|
|
55
75
|
return Array.isArray(recordB) ? detectionResults : detectionResults[0];
|
|
56
|
-
function actualDetection(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
function actualDetection({
|
|
77
|
+
featuresA,
|
|
78
|
+
record,
|
|
79
|
+
recordExternal,
|
|
80
|
+
index,
|
|
81
|
+
localSettings
|
|
82
|
+
}) {
|
|
83
|
+
const labelA = recordAExternal && recordAExternal.label ? recordAExternal.label : 'a';
|
|
84
|
+
const labelB = recordExternal && recordExternal.label ? recordExternal.label : 'b';
|
|
85
|
+
debug(`Actual detection for record ${index + 1} ${labelB}`);
|
|
86
|
+
const featuresB = extractFeatures({
|
|
87
|
+
record,
|
|
88
|
+
recordExternal,
|
|
89
|
+
localSettings
|
|
90
|
+
});
|
|
91
|
+
debugData(`Features (a: ${labelA}): ${JSON.stringify(featuresA)}`);
|
|
92
|
+
debugData(`Features (b: ${labelB}): ${JSON.stringify(featuresB)}`);
|
|
60
93
|
const featurePairs = generateFeaturePairs(featuresA, featuresB);
|
|
61
94
|
const similarityVector = generateSimilarityVector(featurePairs);
|
|
62
95
|
if (similarityVector.some(v => v >= minProbabilityQuantifier)) {
|
|
63
|
-
const probability =
|
|
96
|
+
const probability = calculateProbability(similarityVector);
|
|
64
97
|
debug(`probability: ${probability} (Treshold: ${treshold})`);
|
|
65
98
|
return returnResult({
|
|
66
99
|
match: probability >= treshold,
|
|
@@ -73,13 +106,19 @@ var _default = ({
|
|
|
73
106
|
probability: 0.0
|
|
74
107
|
});
|
|
75
108
|
}
|
|
76
|
-
function extractFeatures(
|
|
109
|
+
function extractFeatures({
|
|
110
|
+
record,
|
|
111
|
+
recordExternal
|
|
112
|
+
}) {
|
|
77
113
|
return strategy.reduce((acc, {
|
|
78
114
|
name,
|
|
79
115
|
extract
|
|
80
116
|
}) => acc.concat({
|
|
81
117
|
name,
|
|
82
|
-
value: extract(
|
|
118
|
+
value: extract({
|
|
119
|
+
record,
|
|
120
|
+
recordExternal
|
|
121
|
+
})
|
|
83
122
|
}), []);
|
|
84
123
|
}
|
|
85
124
|
function returnResult(result) {
|
|
@@ -100,7 +139,7 @@ var _default = ({
|
|
|
100
139
|
const strategyNames = strategy.map(element => element.name);
|
|
101
140
|
return strategyNames || [];
|
|
102
141
|
}
|
|
103
|
-
function
|
|
142
|
+
function calculateProbability(similarityVector) {
|
|
104
143
|
const probability = similarityVector.reduce((acc, v) => acc + v, 0.0);
|
|
105
144
|
return probability > 1.0 ? 1.0 : probability;
|
|
106
145
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["strategy","treshold","returnStrategy","recordA","recordB","minProbabilityQuantifier","debug","createDebugLogger","debugData","extend","JSON","stringify","featuresA","extractFeatures","Array","isArray","recordsB","detectionResults","map","record","actualDetection","featuresB","featurePairs","generateFeaturePairs","similarityVector","generateSimilarityVector","some","v","probability","calculateprobability","returnResult","match","reduce","acc","name","extract","concat","value","result","resultWithStrategy","formatStrategy","strategyNames","element","compared","a","b","compare","find","featureName","points","pairs","generatePairs","missingFeatures","findMissing","index","filter","length"],"sources":["../../src/match-detection/index.js"],"sourcesContent":["/* eslint-disable no-console */\n/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\nimport * as features from './features';\n\nexport {features};\n\nexport default ({strategy, treshold = 0.9}, returnStrategy = false) => (recordA, recordB) => {\n const minProbabilityQuantifier = 0.5;\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection');\n const debugData = debug.extend('data');\n\n debugData(`Strategy: ${JSON.stringify(strategy)}`);\n debugData(`Treshold: ${JSON.stringify(treshold)}`);\n debugData(`ReturnStrategy: ${JSON.stringify(returnStrategy)}`);\n\n const featuresA = extractFeatures(recordA);\n\n debug(`We got an array of records: ${Array.isArray(recordB)}`);\n const recordsB = Array.isArray(recordB) ? recordB : [recordB];\n const detectionResults = recordsB.map(record => actualDetection(featuresA, record));\n\n return Array.isArray(recordB) ? detectionResults : detectionResults[0];\n\n function actualDetection(featuresA, record) {\n\n const featuresB = extractFeatures(record);\n\n debugData(`Features (a): ${JSON.stringify(featuresA)}`);\n debugData(`Features (b): ${JSON.stringify(featuresB)}`);\n\n const featurePairs = generateFeaturePairs(featuresA, featuresB);\n const similarityVector = generateSimilarityVector(featurePairs);\n\n if (similarityVector.some(v => v >= minProbabilityQuantifier)) {\n const probability = calculateprobability(similarityVector);\n debug(`probability: ${probability} (Treshold: ${treshold})`);\n return returnResult({match: probability >= treshold, probability});\n }\n\n debugData(`No feature yielded minimum probability amount of points (${minProbabilityQuantifier})`);\n return returnResult({match: false, probability: 0.0});\n }\n\n function extractFeatures(record) {\n return strategy.reduce((acc, {name, extract}) => acc.concat({name, value: extract(record)}), []);\n }\n\n function returnResult(result) {\n if (returnStrategy) {\n debug(`Returning detection strategy with the result`);\n const resultWithStrategy = {match: result.match, probability: result.probability, strategy: formatStrategy(strategy), treshold};\n debugData(`${JSON.stringify(resultWithStrategy)}`);\n return resultWithStrategy;\n }\n return result;\n }\n\n function formatStrategy(strategy) {\n const strategyNames = strategy.map(element => element.name);\n return strategyNames || [];\n }\n\n function calculateprobability(similarityVector) {\n const probability = similarityVector.reduce((acc, v) => acc + v, 0.0);\n return probability > 1.0 ? 1.0 : probability;\n }\n\n function generateSimilarityVector(featurePairs) {\n const compared = featurePairs.map(({name, a, b}) => {\n const {compare} = strategy.find(({name: featureName}) => name === featureName);\n const points = compare(a, b);\n return {name, points};\n });\n\n debugData(`Points: ${JSON.stringify(compared)}`);\n return compared.map(({points}) => points);\n }\n\n function generateFeaturePairs(featuresA, featuresB) {\n const pairs = generatePairs();\n const missingFeatures = findMissing();\n\n debug(`Not comparing the following features because one, or both records are missing features: ${JSON.stringify(missingFeatures)}`);\n return pairs;\n\n function generatePairs() {\n return featuresA\n .reduce((acc, {name, value}, index) => acc.concat({\n name,\n a: value,\n b: featuresB[index].value\n }), [])\n .filter(({a, b}) => {\n if (a.length === 0 || b.length === 0) {\n return false;\n }\n\n return true;\n });\n }\n\n function findMissing() {\n return featuresA\n .map(({name}) => name)\n .filter(v => pairs.some(({name}) => name === v) === false);\n }\n }\n\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["strategy","treshold","returnStrategy","localSettings","recordA","recordB","recordAExternal","recordBExternal","minProbabilityQuantifier","debug","createDebugLogger","debugData","extend","JSON","stringify","featuresA","extractFeatures","record","recordExternal","Array","isArray","recordsB","recordsBExternal","detectionResults","map","index","actualDetection","labelA","label","labelB","featuresB","featurePairs","generateFeaturePairs","similarityVector","generateSimilarityVector","some","v","probability","calculateProbability","returnResult","match","reduce","acc","name","extract","concat","value","result","resultWithStrategy","formatStrategy","strategyNames","element","compared","a","b","compare","find","featureName","points","pairs","generatePairs","missingFeatures","findMissing","filter","length"],"sources":["../../src/match-detection/index.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\nimport * as features from './features';\n\nexport {features};\n\nexport default ({strategy, treshold = 0.9}, returnStrategy = false, localSettings = {}) => ({recordA, recordB, recordAExternal = {}, recordBExternal = {}}) => {\n const minProbabilityQuantifier = 0.5;\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection');\n const debugData = debug.extend('data');\n\n debugData(`Strategy: ${JSON.stringify(strategy)}, Treshold: ${JSON.stringify(treshold)}, ReturnStrategy: ${JSON.stringify(returnStrategy)}`);\n debugData(`Records: A: ${recordA}\\nB: ${recordB}`);\n debug(`Externals: A: ${JSON.stringify(recordAExternal)}, B: ${JSON.stringify(recordBExternal)}`);\n // We could add here labels for records if we didn't get external labels\n\n const featuresA = extractFeatures({record: recordA, recordExternal: recordAExternal, localSettings});\n\n debug(`We got an array of records: ${Array.isArray(recordB)}`);\n const recordsB = Array.isArray(recordB) ? recordB : [recordB];\n const recordsBExternal = Array.isArray(recordB) ? recordBExternal : [recordBExternal];\n\n const detectionResults = recordsB.map((record, index) => actualDetection({featuresA, recordAExternal, record, recordExternal: recordsBExternal[index], index}));\n\n // if we got array of records, we return an array of result\n // if we got a singular record, we return a singular result\n return Array.isArray(recordB) ? detectionResults : detectionResults[0];\n\n function actualDetection({featuresA, record, recordExternal, index, localSettings}) {\n const labelA = recordAExternal && recordAExternal.label ? recordAExternal.label : 'a';\n const labelB = recordExternal && recordExternal.label ? recordExternal.label : 'b';\n\n\n debug(`Actual detection for record ${index + 1} ${labelB}`);\n const featuresB = extractFeatures({record, recordExternal, localSettings});\n\n debugData(`Features (a: ${labelA}): ${JSON.stringify(featuresA)}`);\n debugData(`Features (b: ${labelB}): ${JSON.stringify(featuresB)}`);\n\n const featurePairs = generateFeaturePairs(featuresA, featuresB);\n const similarityVector = generateSimilarityVector(featurePairs);\n\n if (similarityVector.some(v => v >= minProbabilityQuantifier)) {\n const probability = calculateProbability(similarityVector);\n debug(`probability: ${probability} (Treshold: ${treshold})`);\n return returnResult({match: probability >= treshold, probability});\n }\n\n debugData(`No feature yielded minimum probability amount of points (${minProbabilityQuantifier})`);\n return returnResult({match: false, probability: 0.0});\n }\n\n function extractFeatures({record, recordExternal}) {\n return strategy.reduce((acc, {name, extract}) => acc.concat({name, value: extract({record, recordExternal})}), []);\n }\n\n function returnResult(result) {\n if (returnStrategy) {\n debug(`Returning detection strategy with the result`);\n const resultWithStrategy = {match: result.match, probability: result.probability, strategy: formatStrategy(strategy), treshold};\n debugData(`${JSON.stringify(resultWithStrategy)}`);\n return resultWithStrategy;\n }\n return result;\n }\n\n function formatStrategy(strategy) {\n const strategyNames = strategy.map(element => element.name);\n return strategyNames || [];\n }\n\n function calculateProbability(similarityVector) {\n const probability = similarityVector.reduce((acc, v) => acc + v, 0.0);\n return probability > 1.0 ? 1.0 : probability;\n }\n\n function generateSimilarityVector(featurePairs) {\n const compared = featurePairs.map(({name, a, b}) => {\n const {compare} = strategy.find(({name: featureName}) => name === featureName);\n const points = compare(a, b);\n return {name, points};\n });\n\n debugData(`Points: ${JSON.stringify(compared)}`);\n return compared.map(({points}) => points);\n }\n\n function generateFeaturePairs(featuresA, featuresB) {\n const pairs = generatePairs();\n const missingFeatures = findMissing();\n\n debug(`Not comparing the following features because one, or both records are missing features: ${JSON.stringify(missingFeatures)}`);\n return pairs;\n\n function generatePairs() {\n return featuresA\n .reduce((acc, {name, value}, index) => acc.concat({\n name,\n a: value,\n b: featuresB[index].value\n }), [])\n .filter(({a, b}) => {\n if (a.length === 0 || b.length === 0) {\n return false;\n }\n\n return true;\n });\n }\n\n function findMissing() {\n return featuresA\n .map(({name}) => name)\n .filter(v => pairs.some(({name}) => name === v) === false);\n }\n }\n\n};\n"],"mappings":"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"}
|
|
@@ -72,7 +72,10 @@ describe('match-detection', () => {
|
|
|
72
72
|
subfieldValues: false
|
|
73
73
|
});
|
|
74
74
|
debugData((0, _util.inspect)(recordB));
|
|
75
|
-
const results = detect(
|
|
75
|
+
const results = detect({
|
|
76
|
+
recordA,
|
|
77
|
+
recordB
|
|
78
|
+
});
|
|
76
79
|
debugData(`${JSON.stringify(results)}`);
|
|
77
80
|
(0, _chai.expect)(results).to.eql(expectedResults);
|
|
78
81
|
function formatOptions() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.spec.js","names":["debug","createDebugLogger","debugData","extend","describe","generateTests","path","__dirname","useMetadataFile","recurse","fixura","reader","READERS","JSON","callback","getFixture","options","expectedResults","array","enabled","detect","createDetectionInterface","formatOptions","recordA","MarcRecord","subfieldValues","inspect","recordB","map","recordJson","results","stringify","expect","to","eql","contextFeatures","features","strategy","type","v"],"sources":["../../src/match-detection/index.spec.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport generateTests from '@natlibfi/fixugen';\nimport {READERS} from '@natlibfi/fixura';\nimport {expect} from 'chai';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport * as features from './features';\nimport createDetectionInterface from '.';\nimport {inspect} from 'util';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:test');\nconst debugData = debug.extend('data');\n\ndescribe('match-detection', () => {\n generateTests({\n path: [__dirname, '..', '..', 'test-fixtures', 'match-detection', 'index'],\n useMetadataFile: true,\n recurse: false,\n fixura: {\n reader: READERS.JSON\n },\n callback: ({getFixture, options, expectedResults, array, enabled = true}) => {\n\n if (!enabled) {\n debug(`*** DISABLED TEST! ***`);\n return;\n }\n\n const detect = createDetectionInterface(formatOptions());\n const recordA = new MarcRecord(getFixture('recordA.json'), {subfieldValues: false});\n debugData(inspect(recordA));\n\n debug(`Our recordB is an array of records: ${array}`);\n const recordB = array\n ? getFixture('recordB.json').map(recordJson => new MarcRecord(recordJson, {subfieldValues: false}))\n : new MarcRecord(getFixture('recordB.json'), {subfieldValues: false});\n debugData(inspect(recordB));\n\n const results = detect(recordA, recordB);\n debugData(`${JSON.stringify(results)}`);\n\n expect(results).to.eql(expectedResults);\n\n function formatOptions() {\n const contextFeatures = features[options.strategy.type];\n\n return {\n ...options,\n strategy: options.strategy.features.map(v => contextFeatures[v]())\n };\n }\n }\n });\n});\n"],"mappings":";;AA4BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAsC;AAAA;AAAA;AAnCtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAWA,MAAMA,KAAK,GAAG,IAAAC,cAAiB,EAAC,wDAAwD,CAAC;AACzF,MAAMC,SAAS,GAAGF,KAAK,CAACG,MAAM,CAAC,MAAM,CAAC;AAEtCC,QAAQ,CAAC,iBAAiB,EAAE,MAAM;EAChC,IAAAC,gBAAa,EAAC;IACZC,IAAI,EAAE,CAACC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,iBAAiB,EAAE,OAAO,CAAC;IAC1EC,eAAe,EAAE,IAAI;IACrBC,OAAO,EAAE,KAAK;IACdC,MAAM,EAAE;MACNC,MAAM,EAAEC,eAAO,CAACC;IAClB,CAAC;IACDC,QAAQ,EAAE,CAAC;MAACC,UAAU;MAAEC,OAAO;MAAEC,eAAe;MAAEC,KAAK;MAAEC,OAAO,GAAG;IAAI,CAAC,KAAK;MAE3E,IAAI,CAACA,OAAO,EAAE;QACZnB,KAAK,CAAE,wBAAuB,CAAC;QAC/B;MACF;MAEA,MAAMoB,MAAM,GAAG,IAAAC,SAAwB,EAACC,aAAa,EAAE,CAAC;MACxD,MAAMC,OAAO,GAAG,IAAIC,sBAAU,CAACT,UAAU,CAAC,cAAc,CAAC,EAAE;QAACU,cAAc,EAAE;MAAK,CAAC,CAAC;MACnFvB,SAAS,CAAC,IAAAwB,aAAO,EAACH,OAAO,CAAC,CAAC;MAE3BvB,KAAK,CAAE,uCAAsCkB,KAAM,EAAC,CAAC;MACrD,MAAMS,OAAO,GAAGT,KAAK,GACjBH,UAAU,CAAC,cAAc,CAAC,CAACa,GAAG,CAACC,UAAU,IAAI,IAAIL,sBAAU,CAACK,UAAU,EAAE;QAACJ,cAAc,EAAE;MAAK,CAAC,CAAC,CAAC,GACjG,IAAID,sBAAU,CAACT,UAAU,CAAC,cAAc,CAAC,EAAE;QAACU,cAAc,EAAE;MAAK,CAAC,CAAC;MACvEvB,SAAS,CAAC,IAAAwB,aAAO,EAACC,OAAO,CAAC,CAAC;MAE3B,MAAMG,OAAO,GAAGV,MAAM,
|
|
1
|
+
{"version":3,"file":"index.spec.js","names":["debug","createDebugLogger","debugData","extend","describe","generateTests","path","__dirname","useMetadataFile","recurse","fixura","reader","READERS","JSON","callback","getFixture","options","expectedResults","array","enabled","detect","createDetectionInterface","formatOptions","recordA","MarcRecord","subfieldValues","inspect","recordB","map","recordJson","results","stringify","expect","to","eql","contextFeatures","features","strategy","type","v"],"sources":["../../src/match-detection/index.spec.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport generateTests from '@natlibfi/fixugen';\nimport {READERS} from '@natlibfi/fixura';\nimport {expect} from 'chai';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport * as features from './features';\nimport createDetectionInterface from '.';\nimport {inspect} from 'util';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:test');\nconst debugData = debug.extend('data');\n\ndescribe('match-detection', () => {\n generateTests({\n path: [__dirname, '..', '..', 'test-fixtures', 'match-detection', 'index'],\n useMetadataFile: true,\n recurse: false,\n fixura: {\n reader: READERS.JSON\n },\n callback: ({getFixture, options, expectedResults, array, enabled = true}) => {\n\n if (!enabled) {\n debug(`*** DISABLED TEST! ***`);\n return;\n }\n\n const detect = createDetectionInterface(formatOptions());\n const recordA = new MarcRecord(getFixture('recordA.json'), {subfieldValues: false});\n debugData(inspect(recordA));\n\n debug(`Our recordB is an array of records: ${array}`);\n const recordB = array\n ? getFixture('recordB.json').map(recordJson => new MarcRecord(recordJson, {subfieldValues: false}))\n : new MarcRecord(getFixture('recordB.json'), {subfieldValues: false});\n debugData(inspect(recordB));\n\n const results = detect({recordA, recordB});\n debugData(`${JSON.stringify(results)}`);\n\n expect(results).to.eql(expectedResults);\n\n function formatOptions() {\n const contextFeatures = features[options.strategy.type];\n\n return {\n ...options,\n strategy: options.strategy.features.map(v => contextFeatures[v]())\n };\n }\n }\n });\n});\n"],"mappings":";;AA4BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAsC;AAAA;AAAA;AAnCtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAWA,MAAMA,KAAK,GAAG,IAAAC,cAAiB,EAAC,wDAAwD,CAAC;AACzF,MAAMC,SAAS,GAAGF,KAAK,CAACG,MAAM,CAAC,MAAM,CAAC;AAEtCC,QAAQ,CAAC,iBAAiB,EAAE,MAAM;EAChC,IAAAC,gBAAa,EAAC;IACZC,IAAI,EAAE,CAACC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,iBAAiB,EAAE,OAAO,CAAC;IAC1EC,eAAe,EAAE,IAAI;IACrBC,OAAO,EAAE,KAAK;IACdC,MAAM,EAAE;MACNC,MAAM,EAAEC,eAAO,CAACC;IAClB,CAAC;IACDC,QAAQ,EAAE,CAAC;MAACC,UAAU;MAAEC,OAAO;MAAEC,eAAe;MAAEC,KAAK;MAAEC,OAAO,GAAG;IAAI,CAAC,KAAK;MAE3E,IAAI,CAACA,OAAO,EAAE;QACZnB,KAAK,CAAE,wBAAuB,CAAC;QAC/B;MACF;MAEA,MAAMoB,MAAM,GAAG,IAAAC,SAAwB,EAACC,aAAa,EAAE,CAAC;MACxD,MAAMC,OAAO,GAAG,IAAIC,sBAAU,CAACT,UAAU,CAAC,cAAc,CAAC,EAAE;QAACU,cAAc,EAAE;MAAK,CAAC,CAAC;MACnFvB,SAAS,CAAC,IAAAwB,aAAO,EAACH,OAAO,CAAC,CAAC;MAE3BvB,KAAK,CAAE,uCAAsCkB,KAAM,EAAC,CAAC;MACrD,MAAMS,OAAO,GAAGT,KAAK,GACjBH,UAAU,CAAC,cAAc,CAAC,CAACa,GAAG,CAACC,UAAU,IAAI,IAAIL,sBAAU,CAACK,UAAU,EAAE;QAACJ,cAAc,EAAE;MAAK,CAAC,CAAC,CAAC,GACjG,IAAID,sBAAU,CAACT,UAAU,CAAC,cAAc,CAAC,EAAE;QAACU,cAAc,EAAE;MAAK,CAAC,CAAC;MACvEvB,SAAS,CAAC,IAAAwB,aAAO,EAACC,OAAO,CAAC,CAAC;MAE3B,MAAMG,OAAO,GAAGV,MAAM,CAAC;QAACG,OAAO;QAAEI;MAAO,CAAC,CAAC;MAC1CzB,SAAS,CAAE,GAAEW,IAAI,CAACkB,SAAS,CAACD,OAAO,CAAE,EAAC,CAAC;MAEvC,IAAAE,YAAM,EAACF,OAAO,CAAC,CAACG,EAAE,CAACC,GAAG,CAACjB,eAAe,CAAC;MAEvC,SAASK,aAAa,GAAG;QACvB,MAAMa,eAAe,GAAGC,QAAQ,CAACpB,OAAO,CAACqB,QAAQ,CAACC,IAAI,CAAC;QAEvD,OAAO;UACL,GAAGtB,OAAO;UACVqB,QAAQ,EAAErB,OAAO,CAACqB,QAAQ,CAACD,QAAQ,CAACR,GAAG,CAACW,CAAC,IAAIJ,eAAe,CAACI,CAAC,CAAC,EAAE;QACnE,CAAC;MACH;IACF;EACF,CAAC,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/matching-utils.js
CHANGED
|
@@ -39,8 +39,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
39
39
|
|
|
40
40
|
const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:utils');
|
|
41
41
|
const debugData = debug.extend('data');
|
|
42
|
-
|
|
43
|
-
// eslint-disable-next-line max-statements
|
|
44
42
|
function getMelindaIdsF035(record) {
|
|
45
43
|
const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:melinda-id');
|
|
46
44
|
const debugData = debug.extend('data');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matching-utils.js","names":["debug","createDebugLogger","debugData","extend","getMelindaIdsF035","record","f035s","getFields","length","allF035MelindaIds","concat","map","toMelindaIds","f035MelindaIds","Set","JSON","stringify","subfields","melindaIdRegExp","filter","sub","includes","code","testStringOrNumber","value","test","String","replace","validateSidFieldSubfieldCounts","field","countC","countSubfields","countB","subfieldCode","getSubfieldValues"],"sources":["../src/matching-utils.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:utils');\nconst debugData = debug.extend('data');\n\
|
|
1
|
+
{"version":3,"file":"matching-utils.js","names":["debug","createDebugLogger","debugData","extend","getMelindaIdsF035","record","f035s","getFields","length","allF035MelindaIds","concat","map","toMelindaIds","f035MelindaIds","Set","JSON","stringify","subfields","melindaIdRegExp","filter","sub","includes","code","testStringOrNumber","value","test","String","replace","validateSidFieldSubfieldCounts","field","countC","countSubfields","countB","subfieldCode","getSubfieldValues"],"sources":["../src/matching-utils.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:utils');\nconst debugData = debug.extend('data');\n\nexport function getMelindaIdsF035(record) {\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:melinda-id');\n const debugData = debug.extend('data');\n\n const f035s = record.getFields('035');\n\n if (f035s.length < 1) {\n debug(`No f035s found.`);\n return [];\n }\n\n const allF035MelindaIds = [].concat(...f035s.map(toMelindaIds));\n const f035MelindaIds = [...new Set(allF035MelindaIds)];\n\n debugData(`Fields (${f035s.length}): ${JSON.stringify(f035s)}`);\n debugData(`Ids (${allF035MelindaIds.length}): ${JSON.stringify(allF035MelindaIds)}`);\n debugData(`Unique ids (${f035MelindaIds.length}): ${JSON.stringify(f035MelindaIds)}`);\n\n return f035MelindaIds;\n\n function toMelindaIds({subfields}) {\n const melindaIdRegExp = /^(?<prefix>\\(FI-MELINDA\\)|FCC)(?<id>\\d{9})$/u;\n\n return subfields\n .filter(sub => ['a', 'z'].includes(sub.code))\n .filter(sub => testStringOrNumber(sub.value) && melindaIdRegExp.test(String(sub.value)))\n .map(({value}) => testStringOrNumber(value) ? String(value).replace(melindaIdRegExp, '$<id>') : '');\n\n }\n}\n\nexport function validateSidFieldSubfieldCounts(field) {\n // Valid SID-fields have just one $c and one $b\n debugData(`Validating SID field ${JSON.stringify(field)}`);\n const countC = countSubfields(field, 'c');\n const countB = countSubfields(field, 'b');\n debug(`Found ${countC} sf $cs and ${countB} sf $bs. IsValid: ${countC === 1 && countB === 1}`);\n\n return countC === 1 && countB === 1;\n}\n\nfunction countSubfields(field, subfieldCode) {\n // debug(`Counting subfields ${subfieldCode}`);\n return field.subfields.filter(({code}) => code === subfieldCode).length;\n}\n\nexport function getSubfieldValues(field, subfieldCode) {\n debugData(`Get subfield(s) $${subfieldCode} from ${JSON.stringify(field)}`);\n return field.subfields\n .filter(({code}) => code === subfieldCode)\n .map(({value}) => testStringOrNumber(value) ? String(value) : '')\n .filter(value => value);\n}\n\nexport function testStringOrNumber(value) {\n if (typeof value === 'string' || typeof value === 'number') {\n return true;\n }\n return false;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "git@github.com:natlibfi/melinda-record-matching-js.git"
|
|
15
15
|
},
|
|
16
16
|
"license": "LGPL-3.0+",
|
|
17
|
-
"version": "
|
|
17
|
+
"version": "3.0.1-alpha.1",
|
|
18
18
|
"main": "./dist/index.js",
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=14"
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"watch:test": "cross-env DEBUG=1 NODE_ENV=test nodemon -w src -w test-fixtures --exec 'npm run test:dev'"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@natlibfi/marc-record": "^7.2.
|
|
40
|
-
"@natlibfi/marc-record-serializers": "^9.0.
|
|
41
|
-
"@natlibfi/melinda-commons": "^12.0.
|
|
39
|
+
"@natlibfi/marc-record": "^7.2.2",
|
|
40
|
+
"@natlibfi/marc-record-serializers": "^9.0.2",
|
|
41
|
+
"@natlibfi/melinda-commons": "^12.0.6",
|
|
42
42
|
"@natlibfi/sru-client": "^5.0.3",
|
|
43
43
|
"debug": "^4.3.4",
|
|
44
44
|
"moment": "^2.29.4",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@babel/cli": "^7.19.3",
|
|
51
|
-
"@babel/core": "^7.19.
|
|
51
|
+
"@babel/core": "^7.19.6",
|
|
52
52
|
"@babel/eslint-parser": "^7.19.1",
|
|
53
|
-
"@babel/node": "^7.
|
|
53
|
+
"@babel/node": "^7.20.0",
|
|
54
54
|
"@babel/preset-env": "^7.19.4",
|
|
55
55
|
"@babel/register": "^7.18.9",
|
|
56
56
|
"@natlibfi/eslint-config-melinda-backend": "^2.0.1",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"chai": "^4.3.6",
|
|
63
63
|
"chai-as-promised": "^7.1.1",
|
|
64
64
|
"cross-env": "^7.0.3",
|
|
65
|
-
"eslint": "^8.
|
|
66
|
-
"mocha": "^10.
|
|
65
|
+
"eslint": "^8.26.0",
|
|
66
|
+
"mocha": "^10.1.0",
|
|
67
67
|
"nodemon": "^2.0.20",
|
|
68
68
|
"nyc": "^15.1.0"
|
|
69
69
|
},
|
|
@@ -39,7 +39,6 @@ export class CandidateSearchError extends Error {}
|
|
|
39
39
|
|
|
40
40
|
// serverMaxResults : maximum size of total search result available from the server, defaults to Aleph's 20000
|
|
41
41
|
|
|
42
|
-
// eslint-disable-next-line max-statements
|
|
43
42
|
export default ({record, searchSpec, url, maxCandidates, maxRecordsPerRequest = 50, serverMaxResult = 20000}) => {
|
|
44
43
|
MarcRecord.setValidationOptions({subfieldValues: false});
|
|
45
44
|
|
|
@@ -69,7 +68,7 @@ export default ({record, searchSpec, url, maxCandidates, maxRecordsPerRequest =
|
|
|
69
68
|
|
|
70
69
|
// if generateQueryList errored we should throw 422
|
|
71
70
|
|
|
72
|
-
if (queryList.length === 0) {
|
|
71
|
+
if (queryList.length === 0) {
|
|
73
72
|
throw new CandidateSearchError(`Generated query list contains no queries`);
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -175,7 +174,6 @@ export default ({record, searchSpec, url, maxCandidates, maxRecordsPerRequest =
|
|
|
175
174
|
};
|
|
176
175
|
|
|
177
176
|
function checkMaxedQuery(query, total, serverMaxResult) {
|
|
178
|
-
// eslint-disable-next-line functional/no-conditional-statement
|
|
179
177
|
if (total >= serverMaxResult) {
|
|
180
178
|
debug(`WARNING: Query ${query} resulted in ${total} hits which meets the serverMaxResult (${serverMaxResult}) `);
|
|
181
179
|
return query;
|
|
@@ -46,7 +46,6 @@ describe('candidate-search', () => {
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
// eslint-disable-next-line max-statements
|
|
50
49
|
async function callback({getFixture, factoryOptions, searchOptions, expectedFactoryError = false, expectedSearchError = false, enabled = true}) {
|
|
51
50
|
const url = 'http://foo.bar';
|
|
52
51
|
|
|
@@ -77,7 +76,6 @@ describe('candidate-search', () => {
|
|
|
77
76
|
};
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
// eslint-disable-next-line max-statements
|
|
81
79
|
async function iterate({searchOptions, expectedSearchError, expectedErrorStatus, count = 1}) {
|
|
82
80
|
const expectedResults = getFixture(`expectedResults${count}.json`);
|
|
83
81
|
|
|
@@ -108,7 +106,7 @@ describe('candidate-search', () => {
|
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
function formatResults(results) {
|
|
111
|
-
debug(results);
|
|
109
|
+
debug(results);
|
|
112
110
|
return {
|
|
113
111
|
...results,
|
|
114
112
|
records: results.records.map(({record, id}) => ({id, record: record.toObject()}))
|
|
@@ -217,7 +217,6 @@ export function bibTitle(record) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
// eslint-disable-next-line max-statements
|
|
221
220
|
export function bibStandardIdentifiers(record) {
|
|
222
221
|
|
|
223
222
|
const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:query:bibStandardIdentifiers');
|
package/src/index.js
CHANGED
|
@@ -49,8 +49,9 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
49
49
|
|
|
50
50
|
const detect = createDetectionInterface(detectionOptions, returnStrategy);
|
|
51
51
|
|
|
52
|
-
return record => {
|
|
53
|
-
|
|
52
|
+
return ({record, recordExternal = {recordSource: 'incomingRecord', label: 'ic'}}) => {
|
|
53
|
+
|
|
54
|
+
const search = createSearchInterface({...searchOptions, record, maxCandidates, recordExternal});
|
|
54
55
|
return iterate({});
|
|
55
56
|
|
|
56
57
|
// candidateCount : amount of candidate records retrived from SRU for matching, NOT including current record set
|
|
@@ -234,7 +235,6 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
234
235
|
// - if creating the featureSet for the incoming record fails we have an unprocessable entity
|
|
235
236
|
// - if creating the featureSet for a candidate record fails we could skip that candidate - but list the case as a detectionFailure, same as conversionFailures
|
|
236
237
|
|
|
237
|
-
// eslint-disable-next-line max-statements
|
|
238
238
|
function iterateRecords({records, recordSetSize, maxMatches, matches = [], nonMatches = [], recordMatches = [], recordNonMatches = [], recordCount = 0, recordDuplicateCount = 0, recordNonMatchCount = 0, recordMatchErrors = []}) {
|
|
239
239
|
|
|
240
240
|
// recordSetSize : total amount of records in the current record set
|
|
@@ -270,10 +270,11 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
270
270
|
// eslint-disable-next-line functional/no-conditional-statement
|
|
271
271
|
if (candidateNotInMatches(matches.concat(nonMatches), candidate)) {
|
|
272
272
|
const {record: candidateRecord, id: candidateId} = candidate;
|
|
273
|
+
const recordBExternal = {id: candidate.id, recordSource: 'databaseRecord', label: `db-${candidate.id}`};
|
|
273
274
|
try {
|
|
274
275
|
debug(`Running matchDetection for record ${candidateId} (${newRecordCount}/${recordSetSize})`);
|
|
275
276
|
// we should handle errors from detection somehow - ie. cases where either record or candidateRecord errors
|
|
276
|
-
const detectionResult = detect(record, candidateRecord);
|
|
277
|
+
const detectionResult = detect({recordA: record, recordB: candidateRecord, recordAExternal: recordExternal, recordBExternal});
|
|
277
278
|
|
|
278
279
|
return handleDetectionResult(detectionResult, candidateId, candidateRecord);
|
|
279
280
|
} catch (error) {
|
package/src/index.spec.js
CHANGED
|
@@ -59,7 +59,7 @@ describe('INDEX', () => {
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
const match = createMatchInterface(formatOptions());
|
|
62
|
-
const {matches, matchStatus, nonMatches, conversionFailures} = await match(record);
|
|
62
|
+
const {matches, matchStatus, nonMatches, conversionFailures} = await match({record});
|
|
63
63
|
debugData(`Matches: ${matches.length}, Status: ${matchStatus.status}/${matchStatus.stopReason}, NonMatches: ${nonMatches ? nonMatches.length : 'not returned'}, ConversionFailures: ${conversionFailures ? conversionFailures.length : 'not returned'}`);
|
|
64
64
|
|
|
65
65
|
expect(matchStatus.status).to.eql(expectedMatchStatus);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable max-statements */
|
|
2
1
|
/**
|
|
3
2
|
*
|
|
4
3
|
* @licstart The following is the entire license notice for the JavaScript code in this file.
|
|
@@ -56,8 +55,9 @@ Records with matching local ids for matching local db are a very good match.
|
|
|
56
55
|
|
|
57
56
|
export default () => ({
|
|
58
57
|
name: 'All source IDs',
|
|
59
|
-
extract: record => {
|
|
60
|
-
|
|
58
|
+
extract: ({record, recordExternal}) => {
|
|
59
|
+
const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';
|
|
60
|
+
debug(`Creating match detection features for all local source id's for ${label}`);
|
|
61
61
|
|
|
62
62
|
const fSids = record.get('SID');
|
|
63
63
|
debugData(`SID-fields (${fSids.length}): ${JSON.stringify(fSids)}`);
|
|
@@ -33,22 +33,26 @@ import {testStringOrNumber} from '../../../matching-utils';
|
|
|
33
33
|
|
|
34
34
|
export default ({nameTreshold = 10} = {}) => ({
|
|
35
35
|
name: 'Authors',
|
|
36
|
-
extract: record =>
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
extract: ({record}) => {
|
|
37
|
+
//const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';
|
|
38
|
+
const authors = record.get(/^(?<def>100|700)$/u)
|
|
39
|
+
.map(({subfields}) => {
|
|
40
|
+
return subfields
|
|
41
|
+
.filter(({code, value}) => code && value)
|
|
42
|
+
.filter(({code}) => ['a', '0'].includes(code))
|
|
43
|
+
.map(toObj)
|
|
44
|
+
.reduce((acc, v) => ({...acc, ...v}), {});
|
|
45
|
+
|
|
46
|
+
function toObj({code, value}) {
|
|
47
|
+
if (code === 'a') {
|
|
48
|
+
return {name: testStringOrNumber(value) ? String(value).replace(/[^\p{Letter}\p{Number}]/gu, '').toLowerCase() : ''};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {id: value};
|
|
47
52
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}),
|
|
53
|
+
});
|
|
54
|
+
return authors;
|
|
55
|
+
},
|
|
52
56
|
compare: (a, b) => {
|
|
53
57
|
const maxAuthors = a.length > b.length ? a.length : b.length;
|
|
54
58
|
const matchingIds = findMatchingIds();
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
|
|
29
29
|
export default () => ({
|
|
30
30
|
name: 'Host/Component record',
|
|
31
|
-
extract:
|
|
31
|
+
extract: ({record}) => record.get(/^773$/u).length > 0 ? ['component'] : ['host'],
|
|
32
32
|
compare: (a, b) => a[0] === b[0] ? 0.0 : -1.0
|
|
33
33
|
});
|