@natlibfi/melinda-record-matching 4.4.0-alpha.1 → 5.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/melinda-node-tests.yml +13 -9
- package/LICENSE.txt +21 -165
- package/README.md +2 -2
- package/dist/candidate-search/candidate-search-utils.js +6 -43
- package/dist/candidate-search/candidate-search-utils.js.map +7 -1
- package/dist/candidate-search/choose-queries.js +75 -0
- package/dist/candidate-search/choose-queries.js.map +7 -0
- package/dist/candidate-search/index.js +89 -225
- package/dist/candidate-search/index.js.map +7 -1
- package/dist/candidate-search/index.test.js +92 -0
- package/dist/candidate-search/index.test.js.map +7 -0
- package/dist/candidate-search/query-list/bib.js +141 -320
- package/dist/candidate-search/query-list/bib.js.map +7 -1
- package/dist/candidate-search/query-list/bib.test.js +34 -0
- package/dist/candidate-search/query-list/bib.test.js.map +7 -0
- package/dist/candidate-search/query-list/component.js +87 -0
- package/dist/candidate-search/query-list/component.js.map +7 -0
- package/dist/candidate-search/query-list/index.js +39 -52
- package/dist/candidate-search/query-list/index.js.map +7 -1
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.js +87 -462
- package/dist/index.js.map +7 -1
- package/dist/index.test.js +69 -0
- package/dist/index.test.js.map +7 -0
- package/dist/match-detection/features/bib/all-source-ids.js +20 -91
- package/dist/match-detection/features/bib/all-source-ids.js.map +7 -1
- package/dist/match-detection/features/bib/authors.js +20 -76
- package/dist/match-detection/features/bib/authors.js.map +7 -1
- package/dist/match-detection/features/bib/bibliographic-level.js +4 -40
- package/dist/match-detection/features/bib/bibliographic-level.js.map +7 -1
- package/dist/match-detection/features/bib/host-component.js +5 -41
- package/dist/match-detection/features/bib/host-component.js.map +7 -1
- package/dist/match-detection/features/bib/index.js +17 -119
- package/dist/match-detection/features/bib/index.js.map +7 -1
- package/dist/match-detection/features/bib/index.test.js +40 -0
- package/dist/match-detection/features/bib/index.test.js.map +7 -0
- package/dist/match-detection/features/bib/isbn.js +13 -69
- package/dist/match-detection/features/bib/isbn.js.map +7 -1
- package/dist/match-detection/features/bib/issn.js +5 -50
- package/dist/match-detection/features/bib/issn.js.map +7 -1
- package/dist/match-detection/features/bib/language.js +25 -86
- package/dist/match-detection/features/bib/language.js.map +7 -1
- package/dist/match-detection/features/bib/media-type.js +12 -64
- package/dist/match-detection/features/bib/media-type.js.map +7 -1
- package/dist/match-detection/features/bib/melinda-id.js +5 -47
- package/dist/match-detection/features/bib/melinda-id.js.map +7 -1
- package/dist/match-detection/features/bib/melinda-identifier-factory.js +16 -67
- package/dist/match-detection/features/bib/melinda-identifier-factory.js.map +7 -1
- package/dist/match-detection/features/bib/other-standard-identifier.js +5 -51
- package/dist/match-detection/features/bib/other-standard-identifier.js.map +7 -1
- package/dist/match-detection/features/bib/publication-time-allow-cons-years-multi.js +49 -189
- package/dist/match-detection/features/bib/publication-time-allow-cons-years-multi.js.map +7 -1
- package/dist/match-detection/features/bib/publication-time-allow-cons-years.js +10 -53
- package/dist/match-detection/features/bib/publication-time-allow-cons-years.js.map +7 -1
- package/dist/match-detection/features/bib/publication-time.js +8 -46
- package/dist/match-detection/features/bib/publication-time.js.map +7 -1
- package/dist/match-detection/features/bib/record-type.js +4 -43
- package/dist/match-detection/features/bib/record-type.js.map +7 -1
- package/dist/match-detection/features/bib/standard-identifier-factory.js +47 -163
- package/dist/match-detection/features/bib/standard-identifier-factory.js.map +7 -1
- package/dist/match-detection/features/bib/title-version-original.js +11 -53
- package/dist/match-detection/features/bib/title-version-original.js.map +7 -1
- package/dist/match-detection/features/bib/title.js +20 -72
- package/dist/match-detection/features/bib/title.js.map +7 -1
- package/dist/match-detection/features/index.js +3 -11
- package/dist/match-detection/features/index.js.map +7 -1
- package/dist/match-detection/index.js +35 -150
- package/dist/match-detection/index.js.map +7 -1
- package/dist/match-detection/index.test.js +44 -0
- package/dist/match-detection/index.test.js.map +7 -0
- package/dist/matching-utils.js +38 -102
- package/dist/matching-utils.js.map +7 -1
- package/eslint.config.js +52 -0
- package/example.env +4 -0
- package/package.json +29 -78
- package/src/candidate-search/candidate-search-utils.js +1 -28
- package/src/candidate-search/choose-queries.js +93 -0
- package/src/candidate-search/index.js +85 -119
- package/src/candidate-search/{index.spec.js → index.test.js} +30 -43
- package/src/candidate-search/query-list/bib.js +54 -55
- package/src/candidate-search/query-list/bib.test.js +38 -0
- package/src/candidate-search/query-list/component.js +127 -0
- package/src/candidate-search/query-list/index.js +31 -35
- package/src/cli.js +177 -0
- package/src/index.js +27 -68
- package/src/{index.spec.js → index.test.js} +11 -42
- package/src/match-detection/features/bib/all-source-ids.js +2 -29
- package/src/match-detection/features/bib/authors.js +5 -30
- package/src/match-detection/features/bib/bibliographic-level.js +0 -27
- package/src/match-detection/features/bib/host-component.js +0 -27
- package/src/match-detection/features/bib/index.js +16 -43
- package/src/match-detection/features/bib/index.test.js +52 -0
- package/src/match-detection/features/bib/isbn.js +2 -29
- package/src/match-detection/features/bib/issn.js +1 -28
- package/src/match-detection/features/bib/language.js +5 -32
- package/src/match-detection/features/bib/media-type.js +2 -28
- package/src/match-detection/features/bib/melinda-id.js +1 -28
- package/src/match-detection/features/bib/melinda-identifier-factory.js +5 -32
- package/src/match-detection/features/bib/other-standard-identifier.js +1 -28
- package/src/match-detection/features/bib/publication-time-allow-cons-years-multi.js +7 -34
- package/src/match-detection/features/bib/publication-time-allow-cons-years.js +1 -28
- package/src/match-detection/features/bib/publication-time.js +2 -28
- package/src/match-detection/features/bib/record-type.js +0 -27
- package/src/match-detection/features/bib/standard-identifier-factory.js +1 -28
- package/src/match-detection/features/bib/title-version-original.js +2 -28
- package/src/match-detection/features/bib/title.js +6 -34
- package/src/match-detection/features/index.js +1 -28
- package/src/match-detection/index.js +1 -28
- package/src/match-detection/{index.spec.js → index.test.js} +9 -35
- package/src/matching-utils.js +8 -37
- package/dist/candidate-search/index.spec.js +0 -144
- package/dist/candidate-search/index.spec.js.map +0 -1
- package/dist/candidate-search/query-list/bib.spec.js +0 -64
- package/dist/candidate-search/query-list/bib.spec.js.map +0 -1
- package/dist/index.spec.js +0 -127
- package/dist/index.spec.js.map +0 -1
- package/dist/match-detection/features/bib/index.spec.js +0 -93
- package/dist/match-detection/features/bib/index.spec.js.map +0 -1
- package/dist/match-detection/index.spec.js +0 -91
- package/dist/match-detection/index.spec.js.map +0 -1
- package/src/candidate-search/query-list/bib.spec.js +0 -54
- package/src/match-detection/features/bib/index.spec.js +0 -78
package/src/cli.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import yargs from 'yargs';
|
|
3
|
+
import createMatchOperator, {candidateSearch, matchDetection} from './index.js';
|
|
4
|
+
import createDebugLogger from 'debug';
|
|
5
|
+
import {MarcRecord} from '@natlibfi/marc-record';
|
|
6
|
+
|
|
7
|
+
cli();
|
|
8
|
+
|
|
9
|
+
async function cli() {
|
|
10
|
+
const debug = createDebugLogger('@natlibfi/melinda-record-matching:cli');
|
|
11
|
+
const args = yargs(process.argv.slice(2))
|
|
12
|
+
.scriptName('melinda-record-matching-js')
|
|
13
|
+
.epilog('Copyright (C) 2022-2023 University Of Helsinki (The National Library Of Finland)')
|
|
14
|
+
.usage('$0 <file> [options] and env variable info in README')
|
|
15
|
+
.usage('Installed globally: $0 <file> [options] and env variable info in README')
|
|
16
|
+
.usage('Not installed: npx $0 <file> [options] and env variable info in README')
|
|
17
|
+
.usage('Build from source: node dist/index.js <file> [options] and env variable info in README')
|
|
18
|
+
.showHelpOnFail(true)
|
|
19
|
+
.example([['$ node /dist/cli.js record.json']])
|
|
20
|
+
.env('MELINDA_RECORD_MATCH')
|
|
21
|
+
.version()
|
|
22
|
+
.positional('file', {type: 'string', describe: 'Json file of records to match'})
|
|
23
|
+
.options({
|
|
24
|
+
t: {type: 'string', default: 'IDS', alias: 'searchType', describe: 'IDS, STANDARD_IDS, COMPONENT, CONTENT or CONTENTALT'},
|
|
25
|
+
m: {type: 'number', default: 1, alias: 'maxMatches', describe: ''},
|
|
26
|
+
c: {type: 'number', default: 1000, alias: 'maxCandidates', describe: ''},
|
|
27
|
+
s: {type: 'boolean', default: false, alias: 'returnStrategy', describe: ''},
|
|
28
|
+
q: {type: 'boolean', default: false, alias: 'returnQuery', describe: ''},
|
|
29
|
+
n: {type: 'boolean', default: false, alias: 'returnNonMatches', describe: ''}
|
|
30
|
+
})
|
|
31
|
+
.check((args) => {
|
|
32
|
+
const [file] = args._;
|
|
33
|
+
if (file === undefined) {
|
|
34
|
+
throw new Error('No file argument given');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(file)) {
|
|
38
|
+
throw new Error(`File ${file} does not exist`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.sruUrl === undefined) {
|
|
42
|
+
throw new Error('Setup sru url');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!['IDS', 'STANDARD_IDS', 'COMPONENT', 'CONTENT', 'CONTENTALT'].includes(args.searchType)) {
|
|
46
|
+
throw new Error('Invalid search type');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
})
|
|
51
|
+
.parseSync();
|
|
52
|
+
|
|
53
|
+
const [file] = args._;
|
|
54
|
+
const {searchType} = args;
|
|
55
|
+
debug(JSON.stringify(args));
|
|
56
|
+
|
|
57
|
+
const detection = {
|
|
58
|
+
treshold: 0.9,
|
|
59
|
+
strategy: generateStrategy(searchType)
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const search = {
|
|
63
|
+
url: args.sruUrl, searchSpec: generateSearchSpec(searchType)
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const matchOperator = await createMatchOperator({detection, search, ...args});
|
|
67
|
+
|
|
68
|
+
const fileRaw = fs.readFileSync(file, 'utf8');
|
|
69
|
+
const record = new MarcRecord(JSON.parse(fileRaw), {subfieldValues: false});
|
|
70
|
+
|
|
71
|
+
const result = await matchOperator({record});
|
|
72
|
+
debug(JSON.stringify(result));
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
function generateStrategy(searchType) {
|
|
76
|
+
if (['IDS'].includes(searchType)) {
|
|
77
|
+
return [
|
|
78
|
+
matchDetection.features.bib.melindaId(),
|
|
79
|
+
matchDetection.features.bib.allSourceIds()
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// We could have differing strategy for STANDARD_IDS
|
|
84
|
+
// Let's not run title in strategy when we found the candidates through standard_ids search
|
|
85
|
+
|
|
86
|
+
if (['STANDARD_IDS'].includes(searchType)) {
|
|
87
|
+
return [
|
|
88
|
+
matchDetection.features.bib.hostComponent(),
|
|
89
|
+
matchDetection.features.bib.isbn(),
|
|
90
|
+
matchDetection.features.bib.issn(),
|
|
91
|
+
matchDetection.features.bib.otherStandardIdentifier(),
|
|
92
|
+
// Let's not use the same title matchDetection here
|
|
93
|
+
//matchDetection.features.bib.title(),
|
|
94
|
+
matchDetection.features.bib.authors(),
|
|
95
|
+
// We probably should have some leeway here for notated music as BK etc.
|
|
96
|
+
matchDetection.features.bib.recordType(),
|
|
97
|
+
// Use publicationTimeAllowConsYearsMulti to
|
|
98
|
+
// - ignore one year differences in publicationTime
|
|
99
|
+
// - extract publicationTimes from f008, f26x and reprint notes in f500
|
|
100
|
+
// - do not substract points for mismatching (normal) publicationTime, if there's a match between
|
|
101
|
+
// normal publicationTime and a reprintPublication time
|
|
102
|
+
matchDetection.features.bib.publicationTimeAllowConsYearsMulti(),
|
|
103
|
+
matchDetection.features.bib.language(),
|
|
104
|
+
matchDetection.features.bib.bibliographicLevel()
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (['COMPONENT'].includes(searchType)) {
|
|
109
|
+
return [
|
|
110
|
+
matchDetection.features.bib.hostComponent(),
|
|
111
|
+
matchDetection.features.bib.otherStandardIdentifier(),
|
|
112
|
+
matchDetection.features.bib.recordType(),
|
|
113
|
+
matchDetection.features.bib.title(),
|
|
114
|
+
matchDetection.features.bib.language(),
|
|
115
|
+
matchDetection.features.bib.authors(),
|
|
116
|
+
matchDetection.features.bib.bibliographicLevel()
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (['CONTENT', 'CONTENTALT'].includes(searchType)) {
|
|
121
|
+
return [
|
|
122
|
+
matchDetection.features.bib.hostComponent(),
|
|
123
|
+
matchDetection.features.bib.isbn(),
|
|
124
|
+
matchDetection.features.bib.issn(),
|
|
125
|
+
matchDetection.features.bib.otherStandardIdentifier(),
|
|
126
|
+
matchDetection.features.bib.title(),
|
|
127
|
+
matchDetection.features.bib.authors(),
|
|
128
|
+
matchDetection.features.bib.recordType(),
|
|
129
|
+
matchDetection.features.bib.publicationTime(),
|
|
130
|
+
matchDetection.features.bib.language(),
|
|
131
|
+
matchDetection.features.bib.bibliographicLevel()
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
throw new Error('Unsupported match validation package');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
function generateSearchSpec(searchType) {
|
|
140
|
+
if (['IDS'].includes(searchType)) {
|
|
141
|
+
return [
|
|
142
|
+
candidateSearch.searchTypes.bib.melindaId,
|
|
143
|
+
candidateSearch.searchTypes.bib.sourceIds
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (['STANDARD_IDS'].includes(searchType)) {
|
|
148
|
+
return [candidateSearch.searchTypes.bib.standardIdentifiers];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (['COMPONENT'].includes(searchType)) {
|
|
152
|
+
return [
|
|
153
|
+
//candidateSearch.searchTypes.bib.sourceIds,
|
|
154
|
+
candidateSearch.searchTypes.component.hostIdMelinda,
|
|
155
|
+
candidateSearch.searchTypes.component.hostIdOtherSource
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (['CONTENT'].includes(searchType)) {
|
|
160
|
+
return [
|
|
161
|
+
candidateSearch.searchTypes.bib.hostComponents,
|
|
162
|
+
//candidateSearch.searchTypes.bib.titleAuthor,
|
|
163
|
+
candidateSearch.searchTypes.bib.title
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
if (['CONTENTALT'].includes(searchType)) {
|
|
167
|
+
return [
|
|
168
|
+
candidateSearch.searchTypes.bib.hostComponents,
|
|
169
|
+
// titleAuthorYearAlternates searches for matchCandidates
|
|
170
|
+
// with alternate queries, starting from more tight searches
|
|
171
|
+
candidateSearch.searchTypes.bib.titleAuthorYearAlternates
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
throw new Error('Unsupported match validation package');
|
|
176
|
+
}
|
|
177
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,39 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* @licstart The following is the entire license notice for the JavaScript code in this file.
|
|
4
|
-
*
|
|
5
|
-
* Melinda record matching modules for Javascript
|
|
6
|
-
*
|
|
7
|
-
* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)
|
|
8
|
-
*
|
|
9
|
-
* This file is part of melinda-record-matching-js
|
|
10
|
-
*
|
|
11
|
-
* melinda-record-matching-js program is free software: you can redistribute it and/or modify
|
|
12
|
-
* it under the terms of the GNU Lesser General Public License as
|
|
13
|
-
* published by the Free Software Foundation, either version 3 of the
|
|
14
|
-
* License, or (at your option) any later version.
|
|
15
|
-
*
|
|
16
|
-
* melinda-record-matching-js is distributed in the hope that it will be useful,
|
|
17
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
* GNU Lesser General Public License for more details.
|
|
20
|
-
*
|
|
21
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
22
|
-
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
*
|
|
24
|
-
* @licend The above is the entire license notice
|
|
25
|
-
* for the JavaScript code in this file.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
1
|
import createDebugLogger from 'debug';
|
|
30
|
-
import createSearchInterface, * as candidateSearch from './candidate-search';
|
|
31
|
-
import createDetectionInterface, * as matchDetection from './match-detection';
|
|
2
|
+
import createSearchInterface, * as candidateSearch from './candidate-search/index.js';
|
|
3
|
+
import createDetectionInterface, * as matchDetection from './match-detection/index.js';
|
|
32
4
|
//import inspect from 'util';
|
|
33
5
|
|
|
34
6
|
export {candidateSearch, matchDetection};
|
|
35
7
|
|
|
36
|
-
export default ({detection: detectionOptions, search: searchOptions, maxMatches = 1, maxCandidates = 25, returnStrategy = false, returnQuery = false, returnNonMatches = false
|
|
8
|
+
export default ({detection: detectionOptions, search: searchOptions, maxMatches = 1, maxCandidates = 25, returnStrategy = false, returnQuery = false, returnNonMatches = false}) => {
|
|
37
9
|
const debug = createDebugLogger('@natlibfi/melinda-record-matching:index');
|
|
38
10
|
const debugData = debug.extend('data');
|
|
39
11
|
|
|
@@ -44,14 +16,15 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
44
16
|
debugData(`ReturnStrategy: ${JSON.stringify(returnStrategy)}`);
|
|
45
17
|
debugData(`ReturnQuery: ${JSON.stringify(returnQuery)}`);
|
|
46
18
|
debugData(`ReturnNonMatches: ${JSON.stringify(returnNonMatches)}`);
|
|
47
|
-
debugData(`ReturnFailures: ${JSON.stringify(returnFailures)}`);
|
|
48
19
|
|
|
49
20
|
|
|
50
21
|
const detect = createDetectionInterface(detectionOptions, returnStrategy);
|
|
51
22
|
|
|
52
|
-
return
|
|
23
|
+
return prepareSearch;
|
|
24
|
+
|
|
25
|
+
async function prepareSearch({record, recordExternal = {recordSource: 'incomingRecord', label: 'ic'}}) {
|
|
53
26
|
|
|
54
|
-
const search = createSearchInterface({...searchOptions, record, maxCandidates, recordExternal});
|
|
27
|
+
const {search} = await createSearchInterface({...searchOptions, record, maxCandidates, recordExternal});
|
|
55
28
|
return iterate({});
|
|
56
29
|
|
|
57
30
|
// candidateCount : amount of candidate records retrived from SRU for matching, NOT including current record set
|
|
@@ -67,17 +40,14 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
67
40
|
// state.queryCounter : sequence for current query
|
|
68
41
|
// state.maxedQueries : queries that resulted in more than serverMaxResults hits
|
|
69
42
|
|
|
70
|
-
async function iterate({initialState = {}, matches = [], candidateCount = 0, nonMatches = [], duplicateCount = 0, nonMatchCount = 0,
|
|
43
|
+
async function iterate({initialState = {}, matches = [], candidateCount = 0, nonMatches = [], duplicateCount = 0, nonMatchCount = 0, matchErrors = []}) {
|
|
71
44
|
debugData(`Starting next matcher iteration.`);
|
|
72
|
-
const {records,
|
|
45
|
+
const {records, ...state} = await search(initialState);
|
|
73
46
|
|
|
74
|
-
debugData(`Current state: ${JSON.stringify(state)}, matches: ${matches.length}, candidateCount: ${candidateCount}, nonMatches: ${nonMatches.length}, nonMatchCount: ${nonMatchCount},
|
|
47
|
+
debugData(`Current state: ${JSON.stringify(state)}, matches: ${matches.length}, candidateCount: ${candidateCount}, nonMatches: ${nonMatches.length}, nonMatchCount: ${nonMatchCount}, matchErrors: ${matchErrors.length}`);
|
|
75
48
|
const recordSetSize = records.length;
|
|
76
|
-
const
|
|
77
|
-
const newCandidateCount = candidateCount + recordSetSize + failureSetSize;
|
|
49
|
+
const newCandidateCount = candidateCount + recordSetSize;
|
|
78
50
|
|
|
79
|
-
const newConversionFailures = conversionFailures.concat(failures);
|
|
80
|
-
debugData(`Failures: ${failures.length}, ConversionFailures: ${conversionFailures.length}, NewConversionFailures: ${newConversionFailures.length}`);
|
|
81
51
|
|
|
82
52
|
if (recordSetSize > 0) {
|
|
83
53
|
return handleRecordSet();
|
|
@@ -85,11 +55,11 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
85
55
|
|
|
86
56
|
if (state.queriesLeft > 0) {
|
|
87
57
|
debug(`Empty record set ${state.searchCounter} for ${state.query}, but there are ${state.queriesLeft} queries left`);
|
|
88
|
-
return iterate({initialState: state, matches, candidateCount: newCandidateCount, nonMatches, nonMatchCount, duplicateCount,
|
|
58
|
+
return iterate({initialState: state, matches, candidateCount: newCandidateCount, nonMatches, nonMatchCount, duplicateCount, matchErrors});
|
|
89
59
|
}
|
|
90
60
|
|
|
91
61
|
debug(`No (more) candidate records to check, no more queries left, matches: ${matches.length}`);
|
|
92
|
-
return returnResult({matches, state, stopReason: '', nonMatches, nonMatchCount, candidateCount: newCandidateCount, duplicateCount,
|
|
62
|
+
return returnResult({matches, state, stopReason: '', nonMatches, nonMatchCount, candidateCount: newCandidateCount, duplicateCount, matchErrors});
|
|
93
63
|
|
|
94
64
|
function handleRecordSet() {
|
|
95
65
|
debug(`Checking record set of ${recordSetSize} candidate records for possible matches, found by ${state.searchCounter} search for ${state.query}`);
|
|
@@ -101,19 +71,18 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
101
71
|
const {newMatches, newNonMatches, newMatchErrors} = handleMatchResult(matchResult, matches, nonMatches, matchErrors);
|
|
102
72
|
|
|
103
73
|
if (maxMatchesFound({matches: newMatches, maxMatches})) {
|
|
104
|
-
return returnResult({matches: newMatches, state, stopReason: 'maxMatches', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount,
|
|
74
|
+
return returnResult({matches: newMatches, state, stopReason: 'maxMatches', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});
|
|
105
75
|
}
|
|
106
76
|
|
|
107
77
|
if (maxCandidatesRetrieved(newCandidateCount, maxCandidates)) {
|
|
108
|
-
return returnResult({matches: newMatches, state, stopReason: 'maxCandidates', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount,
|
|
78
|
+
return returnResult({matches: newMatches, state, stopReason: 'maxCandidates', nonMatches: newNonMatches, duplicateCount: newDuplicateCount, candidateCount: newCandidateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});
|
|
109
79
|
}
|
|
110
80
|
|
|
111
|
-
return iterate({initialState: state, matches: newMatches, candidateCount: newCandidateCount, nonMatches: newNonMatches, duplicateCount: newDuplicateCount, nonMatchCount: newNonMatchCount,
|
|
81
|
+
return iterate({initialState: state, matches: newMatches, candidateCount: newCandidateCount, nonMatches: newNonMatches, duplicateCount: newDuplicateCount, nonMatchCount: newNonMatchCount, matchErrors: newMatchErrors});
|
|
112
82
|
}
|
|
113
83
|
|
|
114
84
|
function handleMatchResult(matchResult, matches, nonMatches, matchErrors) {
|
|
115
85
|
debugData(`- Amount of new matches from record set: ${matchResult.matches.length}`);
|
|
116
|
-
// eslint-disable-next-line functional/no-conditional-statements
|
|
117
86
|
if (returnNonMatches) {
|
|
118
87
|
debugData(`- Amount of new nonMatches from record set: ${matchResult.nonMatches.length}`);
|
|
119
88
|
}
|
|
@@ -123,7 +92,6 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
123
92
|
const newMatchErrors = matchErrors.concat(matchResult.matchErrors);
|
|
124
93
|
|
|
125
94
|
debugData(`- Total amount of matches: ${newMatches.length}`);
|
|
126
|
-
// eslint-disable-next-line functional/no-conditional-statements
|
|
127
95
|
if (returnNonMatches) {
|
|
128
96
|
debugData(`- Total amount of nonMatches: ${newNonMatches.length}`);
|
|
129
97
|
}
|
|
@@ -167,27 +135,24 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
167
135
|
// - only one stopReason is returned (if there would be several possible stopReasons, stopReason is picked in the above order)
|
|
168
136
|
// - 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
|
|
169
137
|
|
|
170
|
-
function returnResult({matches, state, stopReason, nonMatches, duplicateCount, candidateCount, nonMatchCount,
|
|
171
|
-
const conversionFailureCount = conversionFailures.length;
|
|
138
|
+
function returnResult({matches, state, stopReason, nonMatches, duplicateCount, candidateCount, nonMatchCount, matchErrors}) {
|
|
172
139
|
const matchErrorCount = matchErrors.length;
|
|
173
|
-
checkCounts({matches, nonMatches, candidateCount, duplicateCount, nonMatchCount,
|
|
174
|
-
const matchStatus = getMatchState(state, stopReason,
|
|
140
|
+
checkCounts({matches, nonMatches, candidateCount, duplicateCount, nonMatchCount, matchErrorCount});
|
|
141
|
+
const matchStatus = getMatchState(state, stopReason, matchErrorCount);
|
|
175
142
|
// add nonMatches to result only if returnNonMatches is 'true', otherwise nonMatches have not been gathered
|
|
176
143
|
const matchesResult = returnNonMatches ? {matches, matchStatus, nonMatches, candidateCount} : {matches, matchStatus, candidateCount};
|
|
177
|
-
const
|
|
178
|
-
const result = returnFailures ? {...matchesResult, conversionFailures: failures} : matchesResult;
|
|
179
|
-
debugData(`ReturnFailures ${returnFailures}`);
|
|
144
|
+
const result = matchesResult;
|
|
180
145
|
debugData(`${JSON.stringify(result)}`);
|
|
181
146
|
return result;
|
|
182
147
|
|
|
183
148
|
// note that in cases where the matching has been stopped because of maxMatches checkCounts won't (in most cases) match
|
|
184
149
|
|
|
185
|
-
function checkCounts({matches, nonMatches, candidateCount, duplicateCount, nonMatchCount,
|
|
150
|
+
function checkCounts({matches, nonMatches, candidateCount, duplicateCount, nonMatchCount, matchErrorCount}) {
|
|
186
151
|
const matchCount = matches.length;
|
|
187
152
|
debugData(`Return nonMatches: ${returnNonMatches}`);
|
|
188
153
|
const chosenNonMatchCount = returnNonMatches ? nonMatches.length : nonMatchCount;
|
|
189
154
|
const totalHandled = matchCount + chosenNonMatchCount + duplicateCount;
|
|
190
|
-
debug(`candidateCount: ${candidateCount}, matches: ${matchCount}, nonMatches: ${chosenNonMatchCount}, duplicateCount: ${duplicateCount},
|
|
155
|
+
debug(`candidateCount: ${candidateCount}, matches: ${matchCount}, nonMatches: ${chosenNonMatchCount}, duplicateCount: ${duplicateCount}, matchErrorCount: ${matchErrorCount}`);
|
|
191
156
|
debug(`We got result for ${totalHandled} / ${candidateCount} retrieved candidates`);
|
|
192
157
|
if (totalHandled !== candidateCount) {
|
|
193
158
|
debug(`WARNING: Missing results for ${candidateCount - totalHandled} candidates`);
|
|
@@ -197,9 +162,8 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
197
162
|
}
|
|
198
163
|
|
|
199
164
|
// eslint-disable-next-line max-statements
|
|
200
|
-
function getMatchState(state, stopReason,
|
|
165
|
+
function getMatchState(state, stopReason, matchErrorCount) {
|
|
201
166
|
debugData(`${JSON.stringify(state)}`);
|
|
202
|
-
debug(`We had ${conversionFailuresCount} retrieved candidates that could not be converted.`);
|
|
203
167
|
debug(`We had ${matchErrorCount} retrieved candidates that errored in matchDetection.`);
|
|
204
168
|
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}`);
|
|
205
169
|
|
|
@@ -209,16 +173,14 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
209
173
|
const nonRetrieved = searchesLeft ? state.totalRecords - state.queryCandidateCounter : 0;
|
|
210
174
|
debugData(`nonRetrieved: ${nonRetrieved}`);
|
|
211
175
|
|
|
212
|
-
// matchStatus.stopReason: string ('maxMatches','maxCandidates','maxedQueries',
|
|
176
|
+
// matchStatus.stopReason: string ('maxMatches','maxCandidates','maxedQueries', empty string/undefined), reason for stopping retrieving or handling the candidate records
|
|
213
177
|
// 'maxMatches' and 'maxCandidates' are in stopReason, 'maxedQueries', 'conversionFailures' and 'matchErrors' are created here
|
|
214
178
|
|
|
215
|
-
if (state.queriesLeft > 0 || nonRetrieved > 0 || state.maxedQueries.length > 0 ||
|
|
179
|
+
if (state.queriesLeft > 0 || nonRetrieved > 0 || state.maxedQueries.length > 0 || matchErrorCount > 0) {
|
|
216
180
|
const maxedQueriesStopReason = state.maxedQueries.length > 0 ? 'maxedQueries' : undefined;
|
|
217
|
-
const conversionFailuresStopReason = conversionFailureCount > 0 ? 'conversionFailures' : undefined;
|
|
218
181
|
const matchErrorsStopReason = matchErrorCount > 0 ? 'matchErrors' : undefined;
|
|
219
|
-
const newStopReason = stopReason === '' || stopReason === undefined ? maxedQueriesStopReason ||
|
|
182
|
+
const newStopReason = stopReason === '' || stopReason === undefined ? maxedQueriesStopReason || matchErrorsStopReason : stopReason;
|
|
220
183
|
debugData(`MaxedQueriesStopReason: <${maxedQueriesStopReason}>`);
|
|
221
|
-
debugData(`ConversionFailureStopReason <${conversionFailuresStopReason}>`);
|
|
222
184
|
debugData(`MatchErrorsStopReason <${matchErrorsStopReason}>`);
|
|
223
185
|
debugData(`NewStopReason: <${newStopReason}>`);
|
|
224
186
|
debug(`Match status: false`);
|
|
@@ -266,8 +228,6 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
266
228
|
*/
|
|
267
229
|
|
|
268
230
|
if (candidate) {
|
|
269
|
-
|
|
270
|
-
// eslint-disable-next-line functional/no-conditional-statements
|
|
271
231
|
if (candidateNotInMatches(matches.concat(nonMatches), candidate)) {
|
|
272
232
|
const {record: candidateRecord, id: candidateId} = candidate;
|
|
273
233
|
const recordBExternal = {id: candidate.id, recordSource: 'databaseRecord', label: `db-${candidate.id}`};
|
|
@@ -330,7 +290,6 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
330
290
|
|
|
331
291
|
debugData(`- Total matches after this detection: ${matches.concat(newRecordMatches).length} (max: ${maxMatches})`);
|
|
332
292
|
|
|
333
|
-
// eslint-disable-next-line functional/no-conditional-statements
|
|
334
293
|
if (returnNonMatches) {
|
|
335
294
|
debugData(`- Total nonMatches after this detection: ${nonMatches.concat(newRecordNonMatches).length}`);
|
|
336
295
|
}
|
|
@@ -365,5 +324,5 @@ export default ({detection: detectionOptions, search: searchOptions, maxMatches
|
|
|
365
324
|
return true;
|
|
366
325
|
}
|
|
367
326
|
}
|
|
368
|
-
}
|
|
327
|
+
}
|
|
369
328
|
};
|
|
@@ -1,37 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Melinda record matching modules for Javascript
|
|
6
|
-
*
|
|
7
|
-
* Copyright (C) 2020-2023 University Of Helsinki (The National Library Of Finland)
|
|
8
|
-
*
|
|
9
|
-
* This file is part of melinda-record-matching-js
|
|
10
|
-
*
|
|
11
|
-
* melinda-record-matching-js program is free software: you can redistribute it and/or modify
|
|
12
|
-
* it under the terms of the GNU Lesser General Public License as
|
|
13
|
-
* published by the Free Software Foundation, either version 3 of the
|
|
14
|
-
* License, or (at your option) any later version.
|
|
15
|
-
*
|
|
16
|
-
* melinda-record-matching-js is distributed in the hope that it will be useful,
|
|
17
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
* GNU Lesser General Public License for more details.
|
|
20
|
-
*
|
|
21
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
22
|
-
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
*
|
|
24
|
-
* @licend The above is the entire license notice
|
|
25
|
-
* for the JavaScript code in this file.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import {expect} from 'chai';
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import {describe} from 'node:test';
|
|
3
|
+
import createDebugLogger from 'debug';
|
|
30
4
|
import {READERS} from '@natlibfi/fixura';
|
|
31
5
|
import generateTests from '@natlibfi/fixugen-http-client';
|
|
32
6
|
import {MarcRecord} from '@natlibfi/marc-record';
|
|
33
|
-
import createMatchInterface, {matchDetection} from '.';
|
|
34
|
-
import createDebugLogger from 'debug';
|
|
7
|
+
import createMatchInterface, {matchDetection} from './index.js';
|
|
35
8
|
|
|
36
9
|
const debug = createDebugLogger('@natlibfi/melinda-record-matching:index:test');
|
|
37
10
|
const debugData = debug.extend('data');
|
|
@@ -39,7 +12,7 @@ const debugData = debug.extend('data');
|
|
|
39
12
|
describe('INDEX', () => {
|
|
40
13
|
generateTests({
|
|
41
14
|
callback,
|
|
42
|
-
path: [
|
|
15
|
+
path: [import.meta.dirname, '..', 'test-fixtures', 'index'],
|
|
43
16
|
recurse: false,
|
|
44
17
|
fixura: {
|
|
45
18
|
reader: READERS.JSON
|
|
@@ -58,19 +31,18 @@ describe('INDEX', () => {
|
|
|
58
31
|
const {matches, matchStatus, nonMatches, conversionFailures, candidateCount} = await match({record});
|
|
59
32
|
debugData(`Matches: ${matches.length}, Status: ${matchStatus.status}/${matchStatus.stopReason}, NonMatches: ${nonMatches ? nonMatches.length : 'not returned'}, ConversionFailures: ${conversionFailures ? conversionFailures.length : 'not returned'}`);
|
|
60
33
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
34
|
+
assert.equal(matchStatus.status, expectedMatchStatus);
|
|
35
|
+
assert.equal(matchStatus.stopReason, expectedStopReason);
|
|
36
|
+
assert.equal(candidateCount, expectedCandidateCount);
|
|
64
37
|
|
|
65
38
|
const formattedMatchResult = formatRecordResults(matches);
|
|
66
|
-
|
|
39
|
+
assert.deepStrictEqual(formattedMatchResult, expectedMatches);
|
|
67
40
|
|
|
68
41
|
const formattedNonMatchResult = formatRecordResults(nonMatches);
|
|
69
|
-
|
|
42
|
+
assert.deepStrictEqual(formattedNonMatchResult, expectedNonMatches);
|
|
70
43
|
|
|
71
|
-
// eslint-disable-next-line functional/no-conditional-statements
|
|
72
44
|
if (expectedFailures) {
|
|
73
|
-
|
|
45
|
+
assert.deepStrictEqual(conversionFailures, expectedFailures);
|
|
74
46
|
}
|
|
75
47
|
|
|
76
48
|
function formatOptions() {
|
|
@@ -108,8 +80,5 @@ describe('INDEX', () => {
|
|
|
108
80
|
record: newRecord.toObject()
|
|
109
81
|
};
|
|
110
82
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
83
|
}
|
|
114
|
-
|
|
115
84
|
});
|
|
@@ -1,33 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* @licstart The following is the entire license notice for the JavaScript code in this file.
|
|
4
|
-
*
|
|
5
|
-
* Melinda record matching modules for Javascript
|
|
6
|
-
*
|
|
7
|
-
* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)
|
|
8
|
-
*
|
|
9
|
-
* This file is part of melinda-record-matching-js
|
|
10
|
-
*
|
|
11
|
-
* melinda-record-matching-js program is free software: you can redistribute it and/or modify
|
|
12
|
-
* it under the terms of the GNU Lesser General Public License as
|
|
13
|
-
* published by the Free Software Foundation, either version 3 of the
|
|
14
|
-
* License, or (at your option) any later version.
|
|
15
|
-
*
|
|
16
|
-
* melinda-record-matching-js is distributed in the hope that it will be useful,
|
|
17
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
* GNU Lesser General Public License for more details.
|
|
20
|
-
*
|
|
21
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
22
|
-
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
*
|
|
24
|
-
* @licend The above is the entire license notice
|
|
25
|
-
* for the JavaScript code in this file.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
1
|
|
|
29
2
|
import createDebugLogger from 'debug';
|
|
30
|
-
import {validateSidFieldSubfieldCounts, getSubfieldValues} from '../../../matching-utils';
|
|
3
|
+
import {validateSidFieldSubfieldCounts, getSubfieldValues} from '../../../matching-utils.js';
|
|
31
4
|
|
|
32
5
|
const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features:all-source-ids');
|
|
33
6
|
const debugData = debug.extend('data');
|
|
@@ -98,7 +71,7 @@ export default () => ({
|
|
|
98
71
|
debugData(`Comparing ${JSON.stringify(a)} and ${JSON.stringify(b)}`);
|
|
99
72
|
|
|
100
73
|
const matches = a.filter(sidA => b.some(sidB => sidA.sourceDb === sidB.sourceDb &&
|
|
101
|
-
|
|
74
|
+
sidA.sourceId === sidB.sourceId));
|
|
102
75
|
debugData(`Matches (${matches.length}): ${JSON.stringify(matches)}`);
|
|
103
76
|
|
|
104
77
|
const mismatches = a.filter(sidA => b.some(sidB => sidA.sourceDb === sidB.sourceDb &&
|
|
@@ -1,33 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)
|
|
8
|
-
*
|
|
9
|
-
* This file is part of melinda-record-matching-js
|
|
10
|
-
*
|
|
11
|
-
* melinda-record-matching-js program is free software: you can redistribute it and/or modify
|
|
12
|
-
* it under the terms of the GNU Lesser General Public License as
|
|
13
|
-
* published by the Free Software Foundation, either version 3 of the
|
|
14
|
-
* License, or (at your option) any later version.
|
|
15
|
-
*
|
|
16
|
-
* melinda-record-matching-js is distributed in the hope that it will be useful,
|
|
17
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
* GNU Lesser General Public License for more details.
|
|
20
|
-
*
|
|
21
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
22
|
-
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
*
|
|
24
|
-
* @licend The above is the entire license notice
|
|
25
|
-
* for the JavaScript code in this file.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import {LevenshteinDistance as leven} from 'natural';
|
|
30
|
-
import {testStringOrNumber} from '../../../matching-utils';
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import naturalPkg from 'natural';
|
|
4
|
+
const {LevenshteinDistance: leven} = naturalPkg;
|
|
5
|
+
import {testStringOrNumber} from '../../../matching-utils.js';
|
|
31
6
|
|
|
32
7
|
// We should extract also organisational authors (110/710)
|
|
33
8
|
|
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* @licstart The following is the entire license notice for the JavaScript code in this file.
|
|
4
|
-
*
|
|
5
|
-
* Melinda record matching modules for Javascript
|
|
6
|
-
*
|
|
7
|
-
* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)
|
|
8
|
-
*
|
|
9
|
-
* This file is part of melinda-record-matching-js
|
|
10
|
-
*
|
|
11
|
-
* melinda-record-matching-js program is free software: you can redistribute it and/or modify
|
|
12
|
-
* it under the terms of the GNU Lesser General Public License as
|
|
13
|
-
* published by the Free Software Foundation, either version 3 of the
|
|
14
|
-
* License, or (at your option) any later version.
|
|
15
|
-
*
|
|
16
|
-
* melinda-record-matching-js is distributed in the hope that it will be useful,
|
|
17
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
* GNU Lesser General Public License for more details.
|
|
20
|
-
*
|
|
21
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
22
|
-
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
*
|
|
24
|
-
* @licend The above is the entire license notice
|
|
25
|
-
* for the JavaScript code in this file.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
1
|
|
|
29
2
|
export default () => ({
|
|
30
3
|
name: 'Bibliographic level',
|
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* @licstart The following is the entire license notice for the JavaScript code in this file.
|
|
4
|
-
*
|
|
5
|
-
* Melinda record matching modules for Javascript
|
|
6
|
-
*
|
|
7
|
-
* Copyright (C) 2020 University Of Helsinki (The National Library Of Finland)
|
|
8
|
-
*
|
|
9
|
-
* This file is part of melinda-record-matching-js
|
|
10
|
-
*
|
|
11
|
-
* melinda-record-matching-js program is free software: you can redistribute it and/or modify
|
|
12
|
-
* it under the terms of the GNU Lesser General Public License as
|
|
13
|
-
* published by the Free Software Foundation, either version 3 of the
|
|
14
|
-
* License, or (at your option) any later version.
|
|
15
|
-
*
|
|
16
|
-
* melinda-record-matching-js is distributed in the hope that it will be useful,
|
|
17
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
* GNU Lesser General Public License for more details.
|
|
20
|
-
*
|
|
21
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
22
|
-
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
*
|
|
24
|
-
* @licend The above is the entire license notice
|
|
25
|
-
* for the JavaScript code in this file.
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
1
|
|
|
29
2
|
export default () => ({
|
|
30
3
|
name: 'Host/Component record',
|