@natlibfi/melinda-record-matching 5.0.0-alpha.1 → 5.0.0-alpha.3
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 → melinda-node-tests-and-publish.yml} +22 -12
- package/dist/candidate-search/candidate-search-utils.js +18 -0
- package/dist/candidate-search/candidate-search-utils.js.map +7 -0
- 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 +118 -0
- package/dist/candidate-search/index.js.map +7 -0
- 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 +260 -0
- package/dist/candidate-search/query-list/bib.js.map +7 -0
- 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 +53 -0
- package/dist/candidate-search/query-list/index.js.map +7 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.js +204 -0
- package/dist/index.js.map +7 -0
- 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 +48 -0
- package/dist/match-detection/features/bib/all-source-ids.js.map +7 -0
- package/dist/match-detection/features/bib/authors.js +57 -0
- package/dist/match-detection/features/bib/authors.js.map +7 -0
- package/dist/match-detection/features/bib/bibliographic-level.js +6 -0
- package/dist/match-detection/features/bib/bibliographic-level.js.map +7 -0
- package/dist/match-detection/features/bib/host-component.js +6 -0
- package/dist/match-detection/features/bib/host-component.js.map +7 -0
- package/dist/match-detection/features/bib/index.js +17 -0
- package/dist/match-detection/features/bib/index.js.map +7 -0
- 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 +21 -0
- package/dist/match-detection/features/bib/isbn.js.map +7 -0
- package/dist/match-detection/features/bib/issn.js +6 -0
- package/dist/match-detection/features/bib/issn.js.map +7 -0
- package/dist/match-detection/features/bib/language.js +80 -0
- package/dist/match-detection/features/bib/language.js.map +7 -0
- package/dist/match-detection/features/bib/media-type.js +31 -0
- package/dist/match-detection/features/bib/media-type.js.map +7 -0
- package/dist/match-detection/features/bib/melinda-id.js +6 -0
- package/dist/match-detection/features/bib/melinda-id.js.map +7 -0
- package/dist/match-detection/features/bib/melinda-identifier-factory.js +38 -0
- package/dist/match-detection/features/bib/melinda-identifier-factory.js.map +7 -0
- package/dist/match-detection/features/bib/other-standard-identifier.js +6 -0
- package/dist/match-detection/features/bib/other-standard-identifier.js.map +7 -0
- package/dist/match-detection/features/bib/publication-time-allow-cons-years-multi.js +116 -0
- package/dist/match-detection/features/bib/publication-time-allow-cons-years-multi.js.map +7 -0
- package/dist/match-detection/features/bib/publication-time-allow-cons-years.js +28 -0
- package/dist/match-detection/features/bib/publication-time-allow-cons-years.js.map +7 -0
- package/dist/match-detection/features/bib/publication-time.js +10 -0
- package/dist/match-detection/features/bib/publication-time.js.map +7 -0
- package/dist/match-detection/features/bib/record-type.js +6 -0
- package/dist/match-detection/features/bib/record-type.js.map +7 -0
- package/dist/match-detection/features/bib/standard-identifier-factory.js +99 -0
- package/dist/match-detection/features/bib/standard-identifier-factory.js.map +7 -0
- package/dist/match-detection/features/bib/title-version-original.js +37 -0
- package/dist/match-detection/features/bib/title-version-original.js.map +7 -0
- package/dist/match-detection/features/bib/title.js +46 -0
- package/dist/match-detection/features/bib/title.js.map +7 -0
- package/dist/match-detection/features/index.js +3 -0
- package/dist/match-detection/features/index.js.map +7 -0
- package/dist/match-detection/index.js +86 -0
- package/dist/match-detection/index.js.map +7 -0
- package/dist/match-detection/index.test.js +44 -0
- package/dist/match-detection/index.test.js.map +7 -0
- package/dist/matching-utils.js +79 -0
- package/dist/matching-utils.js.map +7 -0
- package/package.json +12 -12
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/match-detection/features/bib/publication-time-allow-cons-years-multi.js"],
|
|
4
|
+
"sourcesContent": ["\nimport createDebugLogger from 'debug';\nimport {testStringOrNumber} from '../../../matching-utils.js';\n\n// We should also get copyright time and copyright/publication times from 26x\n// We should also get publishing time type from f008\n// We should get reprint times from f500 $a \"Lis\u00E4painos/Lis\u00E4painokset:\"\n\nexport default () => ({\n name: 'Publication time, allow consequent years, years from multiple sources',\n extract: ({record, recordExternal}) => {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features/bib/publication-time-allow-cons-years-multi');\n const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';\n\n const f008Values = extractF008Values(record);\n debug(`${label}: f008: ${JSON.stringify(f008Values)}`);\n\n const f26xValues = extractF26xValues(record);\n debug(`${label}: f26x: ${JSON.stringify(f26xValues)}`);\n\n const f500Values = extractF500Years(record);\n debug(`${label}: f500: ${JSON.stringify(f500Values)}`);\n\n // We should get copyrightYear from f008Date2 to copyrightYears when f008YearType = 'r'\n // Is the original year (f008Date2) in f008YearType === 'r' comparable to copyrightYear?\n // We should handle unknown years (here or comparison?)\n // We should handle year ranges for continuing resources / collections\n\n const normalYears = [...new Set(f26xValues.normalYears.concat(f008Values.f008Date1).filter(value => value && value !== ' ' && value !== '||||'))].sort();\n const copyrightYears = [...new Set(f26xValues.copyrightYears)].sort();\n const reprintYears = [...new Set(f500Values)].sort();\n\n const combined = {normalYears, copyrightYears, reprintYears};\n\n debug(`Combined: ${JSON.stringify(combined)}`);\n\n return combined;\n\n function extractF008Values(record) {\n // Record should have only one f008 - in case of several, we handle the first one\n const value = record.get(/^008$/u)?.[0]?.value || undefined;\n if (value && testStringOrNumber(value)) {\n const f008Date1 = extractF008Date1(value);\n const f008Date2 = extractF008Date2(value);\n const f008YearType = extractF008YearType(value);\n return {f008Date1, f008Date2, f008YearType};\n }\n return {f008Date1: undefined, f008Date2: undefined, f008YearType: undefined};\n\n function extractF008Date1(value) {\n return String(value).slice(7, 11);\n }\n\n function extractF008Date2(value) {\n return String(value).slice(11, 15);\n }\n\n function extractF008YearType(value) {\n return String(value).slice(6, 7);\n }\n }\n\n function extractF26xValues(record) {\n const copyrightRegex = /^(?<copyrightPrefix>cop|cop.|c|\u00A9|p|\u2117)/u;\n\n const pubNormalSubFieldValues = extractPubNormalSubfieldValues(record, copyrightRegex);\n debug(`Normal years: ${JSON.stringify(pubNormalSubFieldValues)}`);\n\n const pubF264CopySubFieldValues = extractPubF264CopySubfieldValues(record);\n debug(`F264 copyright years: ${JSON.stringify(pubF264CopySubFieldValues)}`);\n\n const pubF260CopySubFieldValues = extractPubF260CopySubfieldValues(record, copyrightRegex);\n debug(`F260 copyright years: ${JSON.stringify(pubF260CopySubFieldValues)}`);\n\n return {normalYears: pubNormalSubFieldValues, copyrightYears: [...pubF260CopySubFieldValues, ...pubF264CopySubFieldValues]};\n\n function extractPubNormalSubfieldValues(record, copyrightRegex) {\n return record.get(/^26[04]$/u)\n .filter((field) => !(field.tag === '264' && field.ind2 === '4'))\n .map(({subfields}) => subfields)\n .flat()\n .filter(({code}) => code && code === 'c')\n .filter(({value}) => value && !copyrightRegex.test(value))\n .map(({value}) => value)\n .map((value) => removeNonAlphaNumeric(value));\n }\n\n function extractPubF264CopySubfieldValues(record, copyrightRegex) {\n return record.get(/^264$/u)\n .filter((field) => field.ind2 === '4')\n .map(({subfields}) => subfields)\n .flat()\n .filter(({code}) => code && code === 'c')\n .filter(({value}) => value)\n .map(({value}) => value)\n .map((value) => value.replace(copyrightRegex, ''))\n .map((value) => removeNonAlphaNumeric(value));\n }\n\n function extractPubF260CopySubfieldValues(record, copyrightRegex) {\n return record.get(/^260$/u)\n .map(({subfields}) => subfields)\n .flat()\n .filter(({code}) => code && code === 'c')\n .filter(({value}) => value && copyrightRegex.test(value))\n .map(({value}) => value)\n .map((value) => value.replace(copyrightRegex, ''))\n .map((value) => removeNonAlphaNumeric(value));\n }\n\n function removeNonAlphaNumeric(value) {\n debug(`Cleaning: ${JSON.stringify(value)}`);\n const nonAlphaNumericRegex = /[^A-Za-z0-9]/ug;\n return value ? value.replace(nonAlphaNumericRegex, '') : value;\n }\n }\n\n function extractF500Years(record) {\n\n const reprintRegex = /(?<reprint>Lis\u00E4painokset:|Lis\u00E4painos:)/u;\n const reprintFieldContents = record.get(/^500$/u)\n .map(({subfields}) => subfields)\n .flat()\n .filter(({code}) => code === 'a')\n .map(({value}) => value);\n //.filter(value => value.test(reprintRegex));\n\n debug(`f500 reprint field contents: ${JSON.stringify(reprintFieldContents)}`);\n\n const filteredF500 = reprintFieldContents.filter((content) => content && content.match(reprintRegex));\n\n debug(`f500 reprint field contents (filtered): ${JSON.stringify(filteredF500)}`);\n\n const reprintYears = extractReprintYears(filteredF500);\n\n return reprintYears;\n }\n\n function extractReprintYears(contents) {\n const yearRegex = /[0-9][0-9][0-9][0-9]/gu;\n const years = contents.map(content => content.match(yearRegex))\n .flat();\n debug(`${JSON.stringify(years)}`);\n const uniqYears = [...new Set(years)].sort();\n debug(`${JSON.stringify(uniqYears)}`);\n return uniqYears;\n }\n\n },\n // eslint-disable-next-line max-statements\n compare: (a, b) => {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features/bib/publication-time-allow-cons-years-multi');\n debug(`Comparing ${JSON.stringify(a)} to ${JSON.stringify(b)}`);\n\n const [firstA] = a.normalYears ? a.normalYears : a;\n const [firstB] = b.normalYears ? b.normalYears : b;\n\n debug(`Comparing ${JSON.stringify(firstA)} to ${JSON.stringify(firstB)}`);\n\n if (firstA === firstB) {\n return 0.1;\n }\n\n // If either of years is a non string/number, values are not comparable\n if (!testStringOrNumber(firstA) || !testStringOrNumber(firstB)) {\n return 0;\n }\n\n const firstANumber = parseInt(firstA, 10);\n const firstBNumber = parseInt(firstB, 10);\n\n // Handle consequent years as a match\n // see publication-time for a version that does not handle consequent years as a match\n if (!(isNaN(firstANumber) || isNaN(firstBNumber)) &&\n (firstANumber + 1 === firstBNumber || firstANumber - 1 === firstBNumber)) {\n return 0.1;\n }\n\n // We should do something with copyrightYears, too\n\n // Do not give minus points if a normal publishing year is found in normal years\n const bNormalInANormal = a.normalYears.filter(aValue => b.normalYears.some(bValue => aValue === bValue));\n const aNormalInBNormal = b.normalYears.filter(bValue => a.normalYears.some(aValue => bValue === aValue));\n debug(`BNorm in ANorm: ${JSON.stringify(bNormalInANormal)}`);\n debug(`ANorm in BNorm: ${JSON.stringify(aNormalInBNormal)}`);\n\n if (bNormalInANormal > 0 || aNormalInBNormal > 0) {\n return 0;\n }\n\n // Do not give minus points if a normal publishing year is found in reprint years\n const bNormalInAReprint = a.reprintYears.filter(aValue => b.normalYears.some(bValue => aValue === bValue));\n const aNormalInBReprint = b.reprintYears.filter(bValue => a.normalYears.some(aValue => bValue === aValue));\n debug(`BNorm in AReprint: ${JSON.stringify(bNormalInAReprint)}`);\n debug(`ANorm in BReprint: ${JSON.stringify(aNormalInBReprint)}`);\n\n if (bNormalInAReprint > 0 || aNormalInBReprint > 0) {\n return 0;\n }\n\n return -1;\n\n }\n});\n\n// https://www.loc.gov/marc/bibliographic/bd008.html\n// field 008\n// 06 - Type of date/Publication status\n// 07-10 - Date 1\n// 11-14 - Date 2\n//\n// 06 - Type of date/Publication status\n// b - No dates given; B.C. date involved\n// c - Continuing resource currently published\n// d - Continuing resource ceased publication\n// e - Detailed date\n// i - Inclusive dates of collection\n// k - Range of years of bulk of collection\n// m - Multiple dates\n// n - Dates unknown\n// p - Date of distribution/release/issue and production/recording session when different\n// q - Questionable date\n// r - Reprint/reissue date and original date\n// s - Single known date/probable date\n// t - Publication date and copyright date\n// u - Continuing resource status unknown\n// | - No attempt to code\n//\n// 07-10 - Date 1\n// 1-9 - Date digit\n// # - Date element is not applicable\n// u - Date element is totally or partially unknown\n// |||| - No attempt to code\n//\n// 11-14 - Date 2\n// 1-9 - Date digit\n// # - Date element is not applicable\n// u - Date element is totally or partially unknown\n// |||| - No attempt to code\n//\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["record", "value", "copyrightRegex", "reprintYears"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import createDebugLogger from "debug";
|
|
2
|
+
import { testStringOrNumber } from "../../../matching-utils.js";
|
|
3
|
+
export default () => ({
|
|
4
|
+
name: "Publication time, allow consequent years",
|
|
5
|
+
extract: ({ record }) => {
|
|
6
|
+
const value = record.get(/^008$/u)?.[0]?.value || void 0;
|
|
7
|
+
return testStringOrNumber(value) ? [String(value).slice(7, 11)] : [];
|
|
8
|
+
},
|
|
9
|
+
compare: (a, b) => {
|
|
10
|
+
const debug = createDebugLogger("@natlibfi/melinda-record-matching:match-detection:features/bib/publication-time-allow-cons-years");
|
|
11
|
+
debug(`Comparing ${a[0]} to ${b[0]}`);
|
|
12
|
+
const [firstA] = a;
|
|
13
|
+
const [firstB] = b;
|
|
14
|
+
if (firstA === firstB) {
|
|
15
|
+
return 0.1;
|
|
16
|
+
}
|
|
17
|
+
if (!testStringOrNumber(firstA) || !testStringOrNumber(firstB)) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
const firstANumber = parseInt(firstA, 10);
|
|
21
|
+
const firstBNumber = parseInt(firstB, 10);
|
|
22
|
+
if (isNaN(firstANumber) || isNaN(firstBNumber)) {
|
|
23
|
+
return -1;
|
|
24
|
+
}
|
|
25
|
+
return firstANumber + 1 === firstBNumber || firstANumber - 1 === firstBNumber ? 0.1 : -1;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=publication-time-allow-cons-years.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/match-detection/features/bib/publication-time-allow-cons-years.js"],
|
|
4
|
+
"sourcesContent": ["\nimport createDebugLogger from 'debug';\nimport {testStringOrNumber} from '../../../matching-utils.js';\n\n// We should also get copyright time and copyright/publication times from 26x\n\nexport default () => ({\n name: 'Publication time, allow consequent years',\n extract: ({record}) => {\n const value = record.get(/^008$/u)?.[0]?.value || undefined;\n return testStringOrNumber(value) ? [String(value).slice(7, 11)] : [];\n },\n compare: (a, b) => {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features/bib/publication-time-allow-cons-years');\n debug(`Comparing ${a[0]} to ${b[0]}`);\n\n const [firstA] = a;\n const [firstB] = b;\n\n if (firstA === firstB) {\n return 0.1;\n }\n\n // If either of years is a non string/number, values are not comparable\n if (!testStringOrNumber(firstA) || !testStringOrNumber(firstB)) {\n return 0;\n }\n\n const firstANumber = parseInt(firstA, 10);\n const firstBNumber = parseInt(firstB, 10);\n\n if (isNaN(firstANumber) || isNaN(firstBNumber)) {\n return -1;\n }\n\n // Handle consequent years as a match\n // see publication-time for a version that does not handle consequent years as a match\n return firstANumber + 1 === firstBNumber || firstANumber - 1 === firstBNumber ? 0.1 : -1;\n }\n});\n"],
|
|
5
|
+
"mappings": "AACA,OAAO,uBAAuB;AAC9B,SAAQ,0BAAyB;AAIjC,eAAe,OAAO;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,OAAM,MAAM;AACrB,UAAM,QAAQ,OAAO,IAAI,QAAQ,IAAI,CAAC,GAAG,SAAS;AAClD,WAAO,mBAAmB,KAAK,IAAI,CAAC,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC;AAAA,EACrE;AAAA,EACA,SAAS,CAAC,GAAG,MAAM;AACjB,UAAM,QAAQ,kBAAkB,kGAAkG;AAClI,UAAM,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE;AAEpC,UAAM,CAAC,MAAM,IAAI;AACjB,UAAM,CAAC,MAAM,IAAI;AAEjB,QAAI,WAAW,QAAQ;AACrB,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,mBAAmB,MAAM,KAAK,CAAC,mBAAmB,MAAM,GAAG;AAC9D,aAAO;AAAA,IACT;AAEA,UAAM,eAAe,SAAS,QAAQ,EAAE;AACxC,UAAM,eAAe,SAAS,QAAQ,EAAE;AAExC,QAAI,MAAM,YAAY,KAAK,MAAM,YAAY,GAAG;AAC9C,aAAO;AAAA,IACT;AAIA,WAAO,eAAe,MAAM,gBAAgB,eAAe,MAAM,eAAe,MAAM;AAAA,EACxF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { testStringOrNumber } from "../../../matching-utils.js";
|
|
2
|
+
export default () => ({
|
|
3
|
+
name: "Publication time",
|
|
4
|
+
extract: ({ record }) => {
|
|
5
|
+
const value = record.get(/^008$/u)?.[0]?.value || void 0;
|
|
6
|
+
return testStringOrNumber(value) ? [String(value).slice(7, 11)] : [];
|
|
7
|
+
},
|
|
8
|
+
compare: (a, b) => a[0] === b[0] ? 0.1 : -1
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=publication-time.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/match-detection/features/bib/publication-time.js"],
|
|
4
|
+
"sourcesContent": ["\n\nimport {testStringOrNumber} from '../../../matching-utils.js';\n\n// We should also get copyright time and copyright/publication times from 26x\n// see publication-time-allow-cons-years for a version allowing consequent years to match\n\nexport default () => ({\n name: 'Publication time',\n extract: ({record}) => {\n const value = record.get(/^008$/u)?.[0]?.value || undefined;\n return testStringOrNumber(value) ? [String(value).slice(7, 11)] : [];\n },\n compare: (a, b) => a[0] === b[0] ? 0.1 : -1.0\n});\n"],
|
|
5
|
+
"mappings": "AAEA,SAAQ,0BAAyB;AAKjC,eAAe,OAAO;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,OAAM,MAAM;AACrB,UAAM,QAAQ,OAAO,IAAI,QAAQ,IAAI,CAAC,GAAG,SAAS;AAClD,WAAO,mBAAmB,KAAK,IAAI,CAAC,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC;AAAA,EACrE;AAAA,EACA,SAAS,CAAC,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,MAAM;AAC3C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/match-detection/features/bib/record-type.js"],
|
|
4
|
+
"sourcesContent": ["\n// we could handle the case of books/notes\n// Recordtype: LDR/06 - Type of Record\n// Note: currently matchValidator fails all mismatching recordTypes, so this won't actually do much\n\nexport default () => ({\n name: 'Record type',\n extract: ({record}) => record.leader[6] ? [record.leader[6]] : [],\n compare: (a, b) => a[0] === b[0] ? 0.1 : -0.5\n});\n"],
|
|
5
|
+
"mappings": "AAKA,eAAe,OAAO;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,OAAM,MAAM,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,CAAC;AAAA,EAChE,SAAS,CAAC,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,MAAM;AAC3C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import createDebugLogger from "debug";
|
|
2
|
+
import { extractSubfieldsFromField, uniqueSubfields } from "../../../matching-utils.js";
|
|
3
|
+
export default ({ pattern, subfieldCodes, identifier, validIdentifierSubfieldCodes = ["a"], invalidIdentifierSubfieldCodes = ["z"], validatorAndNormalizer = void 0 }) => {
|
|
4
|
+
const debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:${identifier}`);
|
|
5
|
+
const debugData = debug.extend("data");
|
|
6
|
+
return { extract, compare };
|
|
7
|
+
function extract({ record, recordExternal }) {
|
|
8
|
+
const label = recordExternal && recordExternal.label ? recordExternal.label : "record";
|
|
9
|
+
const fields = record.get(pattern);
|
|
10
|
+
debugData(`${label}: ${fields.length} ${identifier}-fields `);
|
|
11
|
+
const identifiersFromFields = fields.map((field) => extractSubfieldsFromField(field, subfieldCodes));
|
|
12
|
+
debugData(`${label}: IDs from fields (${identifiersFromFields.length}): ${JSON.stringify(identifiersFromFields)}`);
|
|
13
|
+
const allIdentifiers = identifiersFromFields.flat();
|
|
14
|
+
debugData(`${label}: Flat IDs from fields (${allIdentifiers.length}): ${JSON.stringify(allIdentifiers)}`);
|
|
15
|
+
const validatedAndNormalizedIdentifiers = validateAndNormalizeIdentifiers({ identifierSubs: allIdentifiers, validatorAndNormalizer, validIdentifierSubfieldCodes, invalidIdentifierSubfieldCodes });
|
|
16
|
+
const identifiers = uniqueSubfields(validatedAndNormalizedIdentifiers);
|
|
17
|
+
debugData(`${label}: Unique IDs from fields (${identifiers.length}): ${JSON.stringify(identifiers)}`);
|
|
18
|
+
return identifiers;
|
|
19
|
+
function validateAndNormalizeIdentifiers({ identifierSubs, validatorAndNormalizer: validatorAndNormalizer2, validIdentifierSubfieldCodes: validIdentifierSubfieldCodes2, invalidIdentifierSubfieldCodes: invalidIdentifierSubfieldCodes2 }) {
|
|
20
|
+
if (validatorAndNormalizer2) {
|
|
21
|
+
return identifierSubs.map((idSub) => validateAndNormalizeIdentifier({ idSub, validatorAndNormalizer: validatorAndNormalizer2, validIdentifierSubfieldCodes: validIdentifierSubfieldCodes2, invalidIdentifierSubfieldCodes: invalidIdentifierSubfieldCodes2 }));
|
|
22
|
+
}
|
|
23
|
+
return identifierSubs.map((idSub) => normalizeHyphens(idSub));
|
|
24
|
+
}
|
|
25
|
+
function validateAndNormalizeIdentifier({ idSub, validatorAndNormalizer: validatorAndNormalizer2, validIdentifierSubfieldCodes: validIdentifierSubfieldCodes2, invalidIdentifierSubfieldCodes: invalidIdentifierSubfieldCodes2 }) {
|
|
26
|
+
const { valid, value } = validatorAndNormalizer2(idSub.value);
|
|
27
|
+
if (validIdentifierSubfieldCodes2.includes(idSub.code) && valid === false) {
|
|
28
|
+
const [code] = invalidIdentifierSubfieldCodes2;
|
|
29
|
+
return { code, value };
|
|
30
|
+
}
|
|
31
|
+
return { code: idSub.code, value };
|
|
32
|
+
}
|
|
33
|
+
function normalizeHyphens(idSub) {
|
|
34
|
+
return { code: idSub.code, value: idSub.value.replace(/-/ug, "") };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function compare(a, b) {
|
|
38
|
+
debug(`Comparing A and B`);
|
|
39
|
+
if (a.length === 0 || b.length === 0) {
|
|
40
|
+
debugData(`No standardidentifiers (${identifier}) to compare`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
debugData(`A: ${JSON.stringify(a)}`);
|
|
44
|
+
debugData(`B: ${JSON.stringify(b)}`);
|
|
45
|
+
if (bothHaveValidIdentifiers()) {
|
|
46
|
+
const { maxValues: maxValues2, possibleMatchValues, matchingValues: matchingValues2 } = getValueCount(true);
|
|
47
|
+
if (matchingValues2 < 1) {
|
|
48
|
+
debug(`Both have valid standardidentifiers (${identifier}), but none of these match.`);
|
|
49
|
+
return -0.75;
|
|
50
|
+
}
|
|
51
|
+
debug(`Both have valid standardidentifiers (${identifier}), ${matchingValues2}/${possibleMatchValues} valid identifiers match.`);
|
|
52
|
+
debug(`Possible matches: ${possibleMatchValues}/${maxValues2}`);
|
|
53
|
+
const penaltyForMissing = 0.1 * (maxValues2 - possibleMatchValues);
|
|
54
|
+
const penaltyForMisMatch = 0.2 * (possibleMatchValues - matchingValues2);
|
|
55
|
+
debug(` points: penaltyForMissing: ${penaltyForMissing}`);
|
|
56
|
+
debug(` points: penaltyForMisMatch: ${penaltyForMisMatch}`);
|
|
57
|
+
return 0.75 - penaltyForMisMatch - penaltyForMissing;
|
|
58
|
+
}
|
|
59
|
+
const { maxValues, matchingValues } = getValueCount();
|
|
60
|
+
debug(`Both do NOT have valid standardidentifiers (${identifier}), ${matchingValues}/${maxValues} valid/invalid identifiers match.`);
|
|
61
|
+
return matchingValues / maxValues * 0.2;
|
|
62
|
+
function bothHaveValidIdentifiers() {
|
|
63
|
+
const aValues = a.filter(({ code }) => validIdentifierSubfieldCodes.includes(code));
|
|
64
|
+
const bValues = a.filter(({ code }) => validIdentifierSubfieldCodes.includes(code));
|
|
65
|
+
debug(`A: ${aValues.length} valid ${identifier} identifiers`);
|
|
66
|
+
debug(`B: ${bValues.length} valid ${identifier} identifiers`);
|
|
67
|
+
return aValues.length > 0 && bValues.length > 0;
|
|
68
|
+
}
|
|
69
|
+
function getValueCount(validOnly = false) {
|
|
70
|
+
const aValues = getIdentifiers(a, validOnly);
|
|
71
|
+
const bValues = getIdentifiers(b, validOnly);
|
|
72
|
+
const matchingValues2 = getMatchingValuesAmount(aValues, bValues);
|
|
73
|
+
return {
|
|
74
|
+
maxValues: aValues.length > bValues.length ? aValues.length : bValues.length,
|
|
75
|
+
// possibleMatchingValues: amount of identifiers in set of less identifiers (we cannot more values than these)
|
|
76
|
+
possibleMatchValues: aValues.length > bValues.length ? bValues.length : aValues.length,
|
|
77
|
+
matchingValues: matchingValues2
|
|
78
|
+
};
|
|
79
|
+
function getMatchingValuesAmount(aValues2, bValues2) {
|
|
80
|
+
if (bValues2.length > aValues2.length) {
|
|
81
|
+
return aValues2.filter((aValue) => bValues2.some((bValue) => aValue === bValue)).length;
|
|
82
|
+
}
|
|
83
|
+
if (aValues2.length > bValues2.length) {
|
|
84
|
+
return bValues2.filter((bValue) => aValues2.some((aValue) => bValue === aValue)).length;
|
|
85
|
+
}
|
|
86
|
+
const aToB = aValues2.filter((aValue) => bValues2.some((bValue) => aValue === bValue)).length;
|
|
87
|
+
const bToA = bValues2.filter((bValue) => aValues2.some((aValue) => bValue === aValue)).length;
|
|
88
|
+
return aToB < bToA ? aToB : bToA;
|
|
89
|
+
}
|
|
90
|
+
function getIdentifiers(values, validOnly2) {
|
|
91
|
+
if (validOnly2) {
|
|
92
|
+
return values.filter(({ code }) => validIdentifierSubfieldCodes.includes(code)).map(({ value }) => value);
|
|
93
|
+
}
|
|
94
|
+
return values.map(({ value }) => value);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=standard-identifier-factory.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/match-detection/features/bib/standard-identifier-factory.js"],
|
|
4
|
+
"sourcesContent": ["/* eslint-disable max-statements */\n\nimport createDebugLogger from 'debug';\nimport {extractSubfieldsFromField, uniqueSubfields} from '../../../matching-utils.js';\n\n// Note about validity of standardIdentifiers:\n// We have three types of invalid standardIdentifiers:\n// 1. Formally invalid standardIdentifiers (ie. typos either in the resource or the record)\n// 2. Formally valid standardIdentifiers that are used in a wrong resource\n// 3. Canceled standardIdentifiers\n\n// Matcher could and should check that a standardIdentifier found in a subfield for a valid identifier is formally valid, and if it's not formally valid, handle it as an invalid standardIdentifier\n// Formally valid standardIdentifiers found in subfield for invalid identifier cannot be handled as valid standardIdentifiers, because they can be a case of type 2) or 3) invalid standardIdentifiers\n// We could also do a separate handling for formally valid an formally invalid standardIdentifiers\n\nexport default ({pattern, subfieldCodes, identifier, validIdentifierSubfieldCodes = ['a'], invalidIdentifierSubfieldCodes = ['z'], validatorAndNormalizer = undefined}) => {\n const debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:${identifier}`);\n const debugData = debug.extend('data');\n\n return {extract, compare};\n\n function extract({record, recordExternal}) {\n const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';\n const fields = record.get(pattern);\n debugData(`${label}: ${fields.length} ${identifier}-fields `);\n\n // extractIdentifierSubfield normalizes hyphens away from the subfield values\n const identifiersFromFields = fields.map(field => extractSubfieldsFromField(field, subfieldCodes));\n debugData(`${label}: IDs from fields (${identifiersFromFields.length}): ${JSON.stringify(identifiersFromFields)}`);\n const allIdentifiers = identifiersFromFields.flat();\n debugData(`${label}: Flat IDs from fields (${allIdentifiers.length}): ${JSON.stringify(allIdentifiers)}`);\n\n const validatedAndNormalizedIdentifiers = validateAndNormalizeIdentifiers({identifierSubs: allIdentifiers, validatorAndNormalizer, validIdentifierSubfieldCodes, invalidIdentifierSubfieldCodes});\n\n const identifiers = uniqueSubfields(validatedAndNormalizedIdentifiers);\n\n debugData(`${label}: Unique IDs from fields (${identifiers.length}): ${JSON.stringify(identifiers)}`);\n return identifiers;\n\n function validateAndNormalizeIdentifiers({identifierSubs, validatorAndNormalizer, validIdentifierSubfieldCodes, invalidIdentifierSubfieldCodes}) {\n if (validatorAndNormalizer) {\n return identifierSubs.map((idSub) => validateAndNormalizeIdentifier({idSub, validatorAndNormalizer, validIdentifierSubfieldCodes, invalidIdentifierSubfieldCodes}));\n }\n return identifierSubs.map((idSub) => normalizeHyphens(idSub));\n }\n\n function validateAndNormalizeIdentifier({idSub, validatorAndNormalizer, validIdentifierSubfieldCodes, invalidIdentifierSubfieldCodes}) {\n const {valid, value} = validatorAndNormalizer(idSub.value);\n if (validIdentifierSubfieldCodes.includes(idSub.code) && valid === false) {\n const [code] = invalidIdentifierSubfieldCodes;\n return {code, value};\n }\n return {code: idSub.code, value};\n }\n\n function normalizeHyphens(idSub) {\n return {code: idSub.code, value: idSub.value.replace(/-/ug, '')};\n }\n\n\n }\n\n function compare(a, b) {\n debug(`Comparing A and B`);\n if (a.length === 0 || b.length === 0) {\n debugData(`No standardidentifiers (${identifier}) to compare`);\n return 0;\n }\n\n debugData(`A: ${JSON.stringify(a)}`);\n debugData(`B: ${JSON.stringify(b)}`);\n\n\n if (bothHaveValidIdentifiers()) {\n // Compare only valid identifiers, if both have valid idenfiers\n const {maxValues, possibleMatchValues, matchingValues} = getValueCount(true);\n if (matchingValues < 1) {\n debug(`Both have valid standardidentifiers (${identifier}), but none of these match.`);\n return -0.75;\n }\n debug(`Both have valid standardidentifiers (${identifier}), ${matchingValues}/${possibleMatchValues} valid identifiers match.`);\n // ignore non-matches if there is mismatching amount of values\n debug(`Possible matches: ${possibleMatchValues}/${maxValues}`);\n //we give some kind of penalty for mismatching amount of values instead of simple divide?\n const penaltyForMissing = 0.1 * (maxValues - possibleMatchValues);\n const penaltyForMisMatch = 0.2 * (possibleMatchValues - matchingValues);\n debug(`\\t points: penaltyForMissing: ${penaltyForMissing}`);\n debug(`\\t points: penaltyForMisMatch: ${penaltyForMisMatch}`);\n\n return 0.75 - penaltyForMisMatch - penaltyForMissing;\n //return matchingValues / possibleMatchValues * 0.75;\n }\n // If both do not have valid identifiers, compare all identifiers\n const {maxValues, matchingValues} = getValueCount();\n debug(`Both do NOT have valid standardidentifiers (${identifier}), ${matchingValues}/${maxValues} valid/invalid identifiers match.`);\n\n return matchingValues / maxValues * 0.2;\n\n function bothHaveValidIdentifiers() {\n const aValues = a.filter(({code}) => validIdentifierSubfieldCodes.includes(code));\n const bValues = a.filter(({code}) => validIdentifierSubfieldCodes.includes(code));\n debug(`A: ${aValues.length} valid ${identifier} identifiers`);\n debug(`B: ${bValues.length} valid ${identifier} identifiers`);\n return aValues.length > 0 && bValues.length > 0;\n }\n\n function getValueCount(validOnly = false) {\n const aValues = getIdentifiers(a, validOnly);\n const bValues = getIdentifiers(b, validOnly);\n\n const matchingValues = getMatchingValuesAmount(aValues, bValues);\n\n return {\n maxValues: aValues.length > bValues.length ? aValues.length : bValues.length,\n // possibleMatchingValues: amount of identifiers in set of less identifiers (we cannot more values than these)\n possibleMatchValues: aValues.length > bValues.length ? bValues.length : aValues.length,\n matchingValues\n };\n\n function getMatchingValuesAmount(aValues, bValues) {\n if (bValues.length > aValues.length) {\n return aValues.filter(aValue => bValues.some(bValue => aValue === bValue)).length;\n }\n if (aValues.length > bValues.length) {\n return bValues.filter(bValue => aValues.some(aValue => bValue === aValue)).length;\n }\n\n // If we have same amount of values, we'll check matches both ways, to avoid mixups in cases\n // there would be duplicate values\n const aToB = aValues.filter(aValue => bValues.some(bValue => aValue === bValue)).length;\n const bToA = bValues.filter(bValue => aValues.some(aValue => bValue === aValue)).length;\n\n return aToB < bToA ? aToB : bToA;\n }\n\n function getIdentifiers(values, validOnly) {\n if (validOnly) {\n return values\n .filter(({code}) => validIdentifierSubfieldCodes.includes(code))\n .map(({value}) => value);\n }\n\n return values.map(({value}) => value);\n }\n }\n }\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["validatorAndNormalizer", "validIdentifierSubfieldCodes", "invalidIdentifierSubfieldCodes", "maxValues", "matchingValues", "aValues", "bValues", "validOnly"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import createDebugLogger from "debug";
|
|
2
|
+
import naturalPkg from "natural";
|
|
3
|
+
const { LevenshteinDistance: leven } = naturalPkg;
|
|
4
|
+
export default ({ treshold = 10 } = {}) => ({
|
|
5
|
+
name: "titleVersionOriginal",
|
|
6
|
+
extract: ({ record }) => {
|
|
7
|
+
const title = getTitle();
|
|
8
|
+
if (title) {
|
|
9
|
+
return [title.replace(/[^\p{Letter}\p{Number}]/gu, "").toLowerCase()];
|
|
10
|
+
}
|
|
11
|
+
return [];
|
|
12
|
+
function getTitle() {
|
|
13
|
+
const [field] = record.get(/^245$/u);
|
|
14
|
+
if (field) {
|
|
15
|
+
return field.subfields.filter(({ code }) => ["a", "b"].includes(code)).map(({ value }) => value).join("");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
compare: (a, b) => {
|
|
20
|
+
const debug = createDebugLogger("@natlibfi/melinda-record-matching:match-detection:features/bib/title-version-original");
|
|
21
|
+
const distance = leven(a[0], b[0]);
|
|
22
|
+
if (distance === 0) {
|
|
23
|
+
return 0.5;
|
|
24
|
+
}
|
|
25
|
+
const maxLength = getMaxLength();
|
|
26
|
+
const percentage = distance / maxLength * 100;
|
|
27
|
+
debug(`'${a}' vs '${b}': Max length = ${maxLength}, distance = ${distance}, percentage = ${percentage}`);
|
|
28
|
+
if (percentage <= treshold) {
|
|
29
|
+
return 0.3;
|
|
30
|
+
}
|
|
31
|
+
return -0.5;
|
|
32
|
+
function getMaxLength() {
|
|
33
|
+
return a[0].length > b[0].length ? a[0].length : b[0].length;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=title-version-original.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/match-detection/features/bib/title-version-original.js"],
|
|
4
|
+
"sourcesContent": ["\nimport createDebugLogger from 'debug';\nimport naturalPkg from 'natural';\nconst {LevenshteinDistance: leven} = naturalPkg;\n\nexport default ({treshold = 10} = {}) => ({\n name: 'titleVersionOriginal',\n extract: ({record}) => {\n const title = getTitle();\n\n if (title) {\n return [title.replace(/[^\\p{Letter}\\p{Number}]/gu, '').toLowerCase()];\n }\n\n return [];\n\n function getTitle() {\n const [field] = record.get(/^245$/u);\n\n if (field) {\n return field.subfields\n .filter(({code}) => ['a', 'b'].includes(code))\n .map(({value}) => value)\n .join('');\n }\n }\n },\n compare: (a, b) => {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:features/bib/title-version-original');\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"],
|
|
5
|
+
"mappings": "AACA,OAAO,uBAAuB;AAC9B,OAAO,gBAAgB;AACvB,MAAM,EAAC,qBAAqB,MAAK,IAAI;AAErC,eAAe,CAAC,EAAC,WAAW,GAAE,IAAI,CAAC,OAAO;AAAA,EACxC,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,OAAM,MAAM;AACrB,UAAM,QAAQ,SAAS;AAEvB,QAAI,OAAO;AACT,aAAO,CAAC,MAAM,QAAQ,6BAA6B,EAAE,EAAE,YAAY,CAAC;AAAA,IACtE;AAEA,WAAO,CAAC;AAER,aAAS,WAAW;AAClB,YAAM,CAAC,KAAK,IAAI,OAAO,IAAI,QAAQ;AAEnC,UAAI,OAAO;AACT,eAAO,MAAM,UACV,OAAO,CAAC,EAAC,KAAI,MAAM,CAAC,KAAK,GAAG,EAAE,SAAS,IAAI,CAAC,EAC5C,IAAI,CAAC,EAAC,MAAK,MAAM,KAAK,EACtB,KAAK,EAAE;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAS,CAAC,GAAG,MAAM;AACjB,UAAM,QAAQ,kBAAkB,uFAAuF;AACvH,UAAM,WAAW,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAEjC,QAAI,aAAa,GAAG;AAClB,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,aAAa;AAC/B,UAAM,aAAa,WAAW,YAAY;AAE1C,UAAM,IAAI,CAAC,SAAS,CAAC,mBAAmB,SAAS,gBAAgB,QAAQ,kBAAkB,UAAU,EAAE;AAEvG,QAAI,cAAc,UAAU;AAC1B,aAAO;AAAA,IACT;AAEA,WAAO;AAEP,aAAS,eAAe;AACtB,aAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;AAAA,IACxD;AAAA,EAEF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import createDebugLogger from "debug";
|
|
2
|
+
import naturalPkg from "natural";
|
|
3
|
+
const { LevenshteinDistance: leven } = naturalPkg;
|
|
4
|
+
import { testStringOrNumber } from "../../../matching-utils.js";
|
|
5
|
+
const debug = createDebugLogger("@natlibfi/melinda-record-matching:match-detection:features:title");
|
|
6
|
+
const debugData = debug.extend("data");
|
|
7
|
+
export default ({ treshold = 10 } = {}) => ({
|
|
8
|
+
name: "Title",
|
|
9
|
+
extract: ({ record, recordExternal }) => {
|
|
10
|
+
const label = recordExternal && recordExternal.label ? recordExternal.label : "record";
|
|
11
|
+
const title = getTitle();
|
|
12
|
+
debug(`${label}: title: ${title}`);
|
|
13
|
+
if (testStringOrNumber(title)) {
|
|
14
|
+
const titleAsNormalizedString = String(title).normalize("NFD").replace(/[^\p{Letter}\p{Number}]/gu, "").toLowerCase();
|
|
15
|
+
debug(`${label}: titleString: ${titleAsNormalizedString}`);
|
|
16
|
+
return [titleAsNormalizedString];
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
function getTitle() {
|
|
20
|
+
const [field] = record.get(/^245$/u);
|
|
21
|
+
debugData(`${label}: titleField: ${JSON.stringify(field)}`);
|
|
22
|
+
if (field) {
|
|
23
|
+
return field.subfields.filter(({ code }) => ["a", "b", "n", "p"].includes(code)).map(({ value }) => testStringOrNumber(value) ? String(value) : "").join("");
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
compare: (a, b) => {
|
|
29
|
+
const debug2 = createDebugLogger("@natlibfi/melinda-record-matching:match-detection:features/bib/title");
|
|
30
|
+
const distance = leven(a[0], b[0]);
|
|
31
|
+
if (distance === 0) {
|
|
32
|
+
return 0.5;
|
|
33
|
+
}
|
|
34
|
+
const maxLength = getMaxLength();
|
|
35
|
+
const percentage = distance / maxLength * 100;
|
|
36
|
+
debug2(`'${a}' vs '${b}': Max length = ${maxLength}, distance = ${distance}, percentage = ${percentage}`);
|
|
37
|
+
if (percentage <= treshold) {
|
|
38
|
+
return 0.3;
|
|
39
|
+
}
|
|
40
|
+
return -0.5;
|
|
41
|
+
function getMaxLength() {
|
|
42
|
+
return a[0].length > b[0].length ? a[0].length : b[0].length;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=title.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/match-detection/features/bib/title.js"],
|
|
4
|
+
"sourcesContent": ["import createDebugLogger from 'debug';\nimport naturalPkg from 'natural';\nconst {LevenshteinDistance: leven} = naturalPkg;\nimport {testStringOrNumber} from '../../../matching-utils.js';\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\u00E4\u00E4' 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"],
|
|
5
|
+
"mappings": "AAAA,OAAO,uBAAuB;AAC9B,OAAO,gBAAgB;AACvB,MAAM,EAAC,qBAAqB,MAAK,IAAI;AACrC,SAAQ,0BAAyB;AAEjC,MAAM,QAAQ,kBAAkB,kEAAkE;AAClG,MAAM,YAAY,MAAM,OAAO,MAAM;AAGrC,eAAe,CAAC,EAAC,WAAW,GAAE,IAAI,CAAC,OAAO;AAAA,EACxC,MAAM;AAAA,EACN,SAAS,CAAC,EAAC,QAAQ,eAAc,MAAM;AACrC,UAAM,QAAQ,kBAAkB,eAAe,QAAQ,eAAe,QAAQ;AAC9E,UAAM,QAAQ,SAAS;AACvB,UAAM,GAAG,KAAK,YAAY,KAAK,EAAE;AAEjC,QAAI,mBAAmB,KAAK,GAAG;AAC7B,YAAM,0BAA0B,OAAO,KAAK,EAEzC,UAAU,KAAK,EAIf,QAAQ,6BAA6B,EAAE,EACvC,YAAY;AACf,YAAM,GAAG,KAAK,kBAAkB,uBAAuB,EAAE;AACzD,aAAO,CAAC,uBAAuB;AAAA,IACjC;AAEA,WAAO,CAAC;AAER,aAAS,WAAW;AAClB,YAAM,CAAC,KAAK,IAAI,OAAO,IAAI,QAAQ;AACnC,gBAAU,GAAG,KAAK,iBAAiB,KAAK,UAAU,KAAK,CAAC,EAAE;AAE1D,UAAI,OAAO;AACT,eAAO,MAAM,UAEV,OAAO,CAAC,EAAC,KAAI,MAAM,CAAC,KAAK,KAAK,KAAK,GAAG,EAAE,SAAS,IAAI,CAAC,EACtD,IAAI,CAAC,EAAC,MAAK,MAAM,mBAAmB,KAAK,IAAI,OAAO,KAAK,IAAI,EAAE,EAC/D,KAAK,EAAE;AAAA,MACZ;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,SAAS,CAAC,GAAG,MAAM;AACjB,UAAMA,SAAQ,kBAAkB,sEAAsE;AACtG,UAAM,WAAW,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAEjC,QAAI,aAAa,GAAG;AAClB,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,aAAa;AAC/B,UAAM,aAAa,WAAW,YAAY;AAE1C,IAAAA,OAAM,IAAI,CAAC,SAAS,CAAC,mBAAmB,SAAS,gBAAgB,QAAQ,kBAAkB,UAAU,EAAE;AAEvG,QAAI,cAAc,UAAU;AAC1B,aAAO;AAAA,IACT;AAEA,WAAO;AAEP,aAAS,eAAe;AACtB,aAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;AAAA,IACxD;AAAA,EAEF;AACF;",
|
|
6
|
+
"names": ["debug"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import createDebugLogger from "debug";
|
|
2
|
+
import * as features from "./features/index.js";
|
|
3
|
+
export { features };
|
|
4
|
+
export default ({ strategy, treshold = 0.9 }, returnStrategy = false, localSettings = {}) => ({ recordA, recordB, recordAExternal = {}, recordBExternal = {} }) => {
|
|
5
|
+
const minProbabilityQuantifier = 0.5;
|
|
6
|
+
const debug = createDebugLogger("@natlibfi/melinda-record-matching:match-detection");
|
|
7
|
+
const debugData = debug.extend("data");
|
|
8
|
+
debugData(`Strategy: ${JSON.stringify(strategy)}, Treshold: ${JSON.stringify(treshold)}, ReturnStrategy: ${JSON.stringify(returnStrategy)}`);
|
|
9
|
+
debugData(`Records: A: ${recordA}
|
|
10
|
+
B: ${recordB}`);
|
|
11
|
+
debug(`Externals: A: ${JSON.stringify(recordAExternal)}, B: ${JSON.stringify(recordBExternal)}`);
|
|
12
|
+
const featuresA = extractFeatures({ record: recordA, recordExternal: recordAExternal, localSettings });
|
|
13
|
+
debug(`We got an array of records: ${Array.isArray(recordB)}`);
|
|
14
|
+
const recordsB = Array.isArray(recordB) ? recordB : [recordB];
|
|
15
|
+
const recordsBExternal = Array.isArray(recordB) ? recordBExternal : [recordBExternal];
|
|
16
|
+
const detectionResults = recordsB.map((record, index) => actualDetection({ featuresA, recordAExternal, record, recordExternal: recordsBExternal[index], index }));
|
|
17
|
+
return Array.isArray(recordB) ? detectionResults : detectionResults[0];
|
|
18
|
+
function actualDetection({ featuresA: featuresA2, record, recordExternal, index, localSettings: localSettings2 }) {
|
|
19
|
+
const labelA = recordAExternal && recordAExternal.label ? recordAExternal.label : "a";
|
|
20
|
+
const labelB = recordExternal && recordExternal.label ? recordExternal.label : "b";
|
|
21
|
+
debug(`Actual detection for record ${index + 1} ${labelB}`);
|
|
22
|
+
const featuresB = extractFeatures({ record, recordExternal, localSettings: localSettings2 });
|
|
23
|
+
debugData(`Features (a: ${labelA}): ${JSON.stringify(featuresA2)}`);
|
|
24
|
+
debugData(`Features (b: ${labelB}): ${JSON.stringify(featuresB)}`);
|
|
25
|
+
const featurePairs = generateFeaturePairs(featuresA2, featuresB);
|
|
26
|
+
const similarityVector = generateSimilarityVector(featurePairs);
|
|
27
|
+
if (similarityVector.some((v) => v >= minProbabilityQuantifier)) {
|
|
28
|
+
const probability = calculateProbability(similarityVector);
|
|
29
|
+
debug(`probability: ${probability} (Treshold: ${treshold})`);
|
|
30
|
+
return returnResult({ match: probability >= treshold, probability });
|
|
31
|
+
}
|
|
32
|
+
debugData(`No feature yielded minimum probability amount of points (${minProbabilityQuantifier})`);
|
|
33
|
+
return returnResult({ match: false, probability: 0 });
|
|
34
|
+
}
|
|
35
|
+
function extractFeatures({ record, recordExternal }) {
|
|
36
|
+
return strategy.reduce((acc, { name, extract }) => acc.concat({ name, value: extract({ record, recordExternal }) }), []);
|
|
37
|
+
}
|
|
38
|
+
function returnResult(result) {
|
|
39
|
+
if (returnStrategy) {
|
|
40
|
+
debug(`Returning detection strategy with the result`);
|
|
41
|
+
const resultWithStrategy = { match: result.match, probability: result.probability, strategy: formatStrategy(strategy), treshold };
|
|
42
|
+
debugData(`${JSON.stringify(resultWithStrategy)}`);
|
|
43
|
+
return resultWithStrategy;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function formatStrategy(strategy2) {
|
|
48
|
+
const strategyNames = strategy2.map((element) => element.name);
|
|
49
|
+
return strategyNames || [];
|
|
50
|
+
}
|
|
51
|
+
function calculateProbability(similarityVector) {
|
|
52
|
+
const probability = similarityVector.reduce((acc, v) => acc + v, 0);
|
|
53
|
+
return probability > 1 ? 1 : probability;
|
|
54
|
+
}
|
|
55
|
+
function generateSimilarityVector(featurePairs) {
|
|
56
|
+
const compared = featurePairs.map(({ name, a, b }) => {
|
|
57
|
+
const { compare } = strategy.find(({ name: featureName }) => name === featureName);
|
|
58
|
+
const points = compare(a, b);
|
|
59
|
+
return { name, points };
|
|
60
|
+
});
|
|
61
|
+
debugData(`Points: ${JSON.stringify(compared)}`);
|
|
62
|
+
return compared.map(({ points }) => points);
|
|
63
|
+
}
|
|
64
|
+
function generateFeaturePairs(featuresA2, featuresB) {
|
|
65
|
+
const pairs = generatePairs();
|
|
66
|
+
const missingFeatures = findMissing();
|
|
67
|
+
debug(`Not comparing the following features because one, or both records are missing features: ${JSON.stringify(missingFeatures)}`);
|
|
68
|
+
return pairs;
|
|
69
|
+
function generatePairs() {
|
|
70
|
+
return featuresA2.reduce((acc, { name, value }, index) => acc.concat({
|
|
71
|
+
name,
|
|
72
|
+
a: value,
|
|
73
|
+
b: featuresB[index].value
|
|
74
|
+
}), []).filter(({ a, b }) => {
|
|
75
|
+
if (a.length === 0 || b.length === 0) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function findMissing() {
|
|
82
|
+
return featuresA2.map(({ name }) => name).filter((v) => pairs.some(({ name }) => name === v) === false);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/match-detection/index.js"],
|
|
4
|
+
"sourcesContent": ["\nimport createDebugLogger from 'debug';\nimport * as features from './features/index.js';\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"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["featuresA", "localSettings", "strategy"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe } from "node:test";
|
|
3
|
+
import createDebugLogger from "debug";
|
|
4
|
+
import { inspect } from "util";
|
|
5
|
+
import generateTests from "@natlibfi/fixugen";
|
|
6
|
+
import { READERS } from "@natlibfi/fixura";
|
|
7
|
+
import { MarcRecord } from "@natlibfi/marc-record";
|
|
8
|
+
import createDetectionInterface from "./index.js";
|
|
9
|
+
import * as features from "./features/index.js";
|
|
10
|
+
const debug = createDebugLogger("@natlibfi/melinda-record-matching:match-detection:test");
|
|
11
|
+
const debugData = debug.extend("data");
|
|
12
|
+
describe("match-detection", () => {
|
|
13
|
+
generateTests({
|
|
14
|
+
path: [import.meta.dirname, "..", "..", "test-fixtures", "match-detection", "index"],
|
|
15
|
+
useMetadataFile: true,
|
|
16
|
+
recurse: false,
|
|
17
|
+
fixura: {
|
|
18
|
+
reader: READERS.JSON
|
|
19
|
+
},
|
|
20
|
+
callback: ({ getFixture, options, expectedResults, array, enabled = true }) => {
|
|
21
|
+
if (!enabled) {
|
|
22
|
+
debug(`*** DISABLED TEST! ***`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const detect = createDetectionInterface(formatOptions());
|
|
26
|
+
const recordA = new MarcRecord(getFixture("recordA.json"), { subfieldValues: false });
|
|
27
|
+
debugData(inspect(recordA));
|
|
28
|
+
debug(`Our recordB is an array of records: ${array}`);
|
|
29
|
+
const recordB = array ? getFixture("recordB.json").map((recordJson) => new MarcRecord(recordJson, { subfieldValues: false })) : new MarcRecord(getFixture("recordB.json"), { subfieldValues: false });
|
|
30
|
+
debugData(inspect(recordB));
|
|
31
|
+
const results = detect({ recordA, recordB });
|
|
32
|
+
debugData(`${JSON.stringify(results)}`);
|
|
33
|
+
assert.deepStrictEqual(results, expectedResults);
|
|
34
|
+
function formatOptions() {
|
|
35
|
+
const contextFeatures = features[options.strategy.type];
|
|
36
|
+
return {
|
|
37
|
+
...options,
|
|
38
|
+
strategy: options.strategy.features.map((v) => contextFeatures[v]())
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/match-detection/index.test.js"],
|
|
4
|
+
"sourcesContent": ["import assert from 'node:assert';\nimport {describe} from 'node:test';\nimport createDebugLogger from 'debug';\nimport {inspect} from 'util';\nimport generateTests from '@natlibfi/fixugen';\nimport {READERS} from '@natlibfi/fixura';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\nimport createDetectionInterface from './index.js';\nimport * as features from './features/index.js';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:match-detection:test');\nconst debugData = debug.extend('data');\n\ndescribe('match-detection', () => {\n generateTests({\n path: [import.meta.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 assert.deepStrictEqual(results, 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"],
|
|
5
|
+
"mappings": "AAAA,OAAO,YAAY;AACnB,SAAQ,gBAAe;AACvB,OAAO,uBAAuB;AAC9B,SAAQ,eAAc;AACtB,OAAO,mBAAmB;AAC1B,SAAQ,eAAc;AACtB,SAAQ,kBAAiB;AAEzB,OAAO,8BAA8B;AACrC,YAAY,cAAc;AAE1B,MAAM,QAAQ,kBAAkB,wDAAwD;AACxF,MAAM,YAAY,MAAM,OAAO,MAAM;AAErC,SAAS,mBAAmB,MAAM;AAChC,gBAAc;AAAA,IACZ,MAAM,CAAC,YAAY,SAAS,MAAM,MAAM,iBAAiB,mBAAmB,OAAO;AAAA,IACnF,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT,QAAQ;AAAA,MACN,QAAQ,QAAQ;AAAA,IAClB;AAAA,IACA,UAAU,CAAC,EAAC,YAAY,SAAS,iBAAiB,OAAO,UAAU,KAAI,MAAM;AAE3E,UAAI,CAAC,SAAS;AACZ,cAAM,wBAAwB;AAC9B;AAAA,MACF;AAEA,YAAM,SAAS,yBAAyB,cAAc,CAAC;AACvD,YAAM,UAAU,IAAI,WAAW,WAAW,cAAc,GAAG,EAAC,gBAAgB,MAAK,CAAC;AAClF,gBAAU,QAAQ,OAAO,CAAC;AAE1B,YAAM,uCAAuC,KAAK,EAAE;AACpD,YAAM,UAAU,QACZ,WAAW,cAAc,EAAE,IAAI,gBAAc,IAAI,WAAW,YAAY,EAAC,gBAAgB,MAAK,CAAC,CAAC,IAChG,IAAI,WAAW,WAAW,cAAc,GAAG,EAAC,gBAAgB,MAAK,CAAC;AACtE,gBAAU,QAAQ,OAAO,CAAC;AAE1B,YAAM,UAAU,OAAO,EAAC,SAAS,QAAO,CAAC;AACzC,gBAAU,GAAG,KAAK,UAAU,OAAO,CAAC,EAAE;AAEtC,aAAO,gBAAgB,SAAS,eAAe;AAE/C,eAAS,gBAAgB;AACvB,cAAM,kBAAkB,SAAS,QAAQ,SAAS,IAAI;AAEtD,eAAO;AAAA,UACL,GAAG;AAAA,UACH,UAAU,QAAQ,SAAS,SAAS,IAAI,OAAK,gBAAgB,CAAC,EAAE,CAAC;AAAA,QACnE;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|