@natlibfi/melinda-record-matching 5.1.0 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -59,6 +59,7 @@ async function cli() {
59
59
  matchDetection.features.bib.hostComponent(),
60
60
  matchDetection.features.bib.isbn(),
61
61
  matchDetection.features.bib.issn(),
62
+ // matchDetection.features.bib.sid(),
62
63
  matchDetection.features.bib.otherStandardIdentifier(),
63
64
  // Let's not use the same title matchDetection here
64
65
  //matchDetection.features.bib.title(),
@@ -95,6 +96,7 @@ async function cli() {
95
96
  matchDetection.features.bib.hostComponent(),
96
97
  matchDetection.features.bib.isbn(),
97
98
  matchDetection.features.bib.issn(),
99
+ // matchDetection.features.bib.sid(),
98
100
  matchDetection.features.bib.otherStandardIdentifier(),
99
101
  matchDetection.features.bib.title(),
100
102
  matchDetection.features.bib.authors(),
@@ -108,6 +110,7 @@ async function cli() {
108
110
  }
109
111
  if (["YSO"]) {
110
112
  return [
113
+ // matchDetection.features.bib.sid(), // SIDs might be relevant on the auth side as well?
111
114
  matchDetection.features.auth.yso()
112
115
  ];
113
116
  }
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cli.js"],
4
- "sourcesContent": ["import fs from 'fs';\nimport yargs from 'yargs';\nimport createMatchOperator, {candidateSearch, matchDetection} from './index.js';\nimport createDebugLogger from 'debug';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\ncli();\n\nasync function cli() {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:cli');\n const args = yargs(process.argv.slice(2))\n .scriptName('melinda-record-matching-js')\n .epilog('Copyright (C) 2022-2023 University Of Helsinki (The National Library Of Finland)')\n .usage('$0 <file> [options] and env variable info in README')\n .usage('Installed globally: $0 <file> [options] and env variable info in README')\n .usage('Not installed: npx $0 <file> [options] and env variable info in README')\n .usage('Build from source: node dist/index.js <file> [options] and env variable info in README')\n .showHelpOnFail(true)\n .example([['$ node /dist/cli.js record.json']])\n .env('MELINDA_RECORD_MATCH')\n .version()\n .positional('file', {type: 'string', describe: 'Json file of records to match'})\n .options({\n t: {type: 'string', default: 'IDS', alias: 'searchType', describe: 'IDS, STANDARD_IDS, COMPONENT, CONTENT or CONTENTALT'},\n m: {type: 'number', default: 1, alias: 'maxMatches', describe: ''},\n c: {type: 'number', default: 1000, alias: 'maxCandidates', describe: ''},\n s: {type: 'boolean', default: false, alias: 'returnStrategy', describe: ''},\n q: {type: 'boolean', default: false, alias: 'returnQuery', describe: ''},\n n: {type: 'boolean', default: false, alias: 'returnNonMatches', describe: ''}\n })\n .check((args) => {\n const [file] = args._;\n if (file === undefined) {\n throw new Error('No file argument given');\n }\n\n if (!fs.existsSync(file)) {\n throw new Error(`File ${file} does not exist`);\n }\n\n if (args.sruUrl === undefined) {\n throw new Error('Setup sru url');\n }\n\n if (!['IDS', 'STANDARD_IDS', 'COMPONENT', 'CONTENT', 'CONTENTALT', 'YSO'].includes(args.searchType)) {\n throw new Error('Invalid search type');\n }\n\n return true;\n })\n .parseSync();\n\n const [file] = args._;\n const {searchType} = args;\n debug(JSON.stringify(args));\n\n const detection = {\n threshold: 0.8999,\n strategy: generateStrategy(searchType)\n };\n\n const search = {\n url: args.sruUrl, searchSpec: generateSearchSpec(searchType)\n };\n\n const matchOperator = await createMatchOperator({detection, search, ...args});\n\n const fileRaw = fs.readFileSync(file, 'utf8');\n const record = new MarcRecord(JSON.parse(fileRaw), {subfieldValues: false});\n\n const result = await matchOperator({record});\n debug(JSON.stringify(result));\n\n\n function generateStrategy(searchType) {\n if (['IDS'].includes(searchType)) {\n return [\n matchDetection.features.bib.melindaId(),\n matchDetection.features.bib.allSourceIds(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n // We could have differing strategy for STANDARD_IDS\n // Let's not run title in strategy when we found the candidates through standard_ids search\n\n if (['STANDARD_IDS'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.isbn(),\n matchDetection.features.bib.issn(),\n matchDetection.features.bib.otherStandardIdentifier(),\n // Let's not use the same title matchDetection here\n //matchDetection.features.bib.title(),\n matchDetection.features.bib.authors(),\n // We probably should have some leeway here for notated music as BK etc.\n matchDetection.features.bib.recordType(),\n // Use publicationTimeAllowConsYearsMulti to\n // - ignore one year differences in publicationTime\n // - extract publicationTimes from f008, f26x and reprint notes in f500\n // - do not substract points for mismatching (normal) publicationTime, if there's a match between\n // normal publicationTime and a reprintPublication time\n matchDetection.features.bib.publicationTimeAllowConsYearsMulti(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.bibliographicLevel(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n if (['COMPONENT'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.otherStandardIdentifier(),\n matchDetection.features.bib.recordType(),\n matchDetection.features.bib.title(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.authors(),\n matchDetection.features.bib.bibliographicLevel(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n if (['CONTENT', 'CONTENTALT'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.isbn(),\n matchDetection.features.bib.issn(),\n matchDetection.features.bib.otherStandardIdentifier(),\n matchDetection.features.bib.title(),\n matchDetection.features.bib.authors(),\n matchDetection.features.bib.recordType(),\n matchDetection.features.bib.publicationTime(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.bibliographicLevel(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n if (['YSO']) {\n return [\n matchDetection.features.auth.yso()\n ];\n }\n\n throw new Error('Unsupported match validation package');\n }\n\n\n function generateSearchSpec(searchType) {\n if (['IDS'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.melindaId,\n candidateSearch.searchTypes.bib.sourceIds\n ];\n }\n\n if (['STANDARD_IDS'].includes(searchType)) {\n return [candidateSearch.searchTypes.bib.standardIdentifiers];\n }\n\n if (['COMPONENT'].includes(searchType)) {\n return [\n //candidateSearch.searchTypes.bib.sourceIds,\n candidateSearch.searchTypes.component.hostIdMelinda,\n candidateSearch.searchTypes.component.hostIdOtherSource\n ];\n }\n\n if (['CONTENT'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.hostComponents,\n //candidateSearch.searchTypes.bib.titleAuthor,\n candidateSearch.searchTypes.bib.title\n ];\n }\n if (['CONTENTALT'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.hostComponents,\n // titleAuthorYearAlternates searches for matchCandidates\n // with alternate queries, starting from more tight searches\n candidateSearch.searchTypes.bib.titleAuthorYearAlternates\n ];\n }\n\n if (searchType === 'YSO') {\n return [\n candidateSearch.searchTypes.auth.authURX\n ];\n }\n\n throw new Error('Unsupported match validation package');\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import fs from 'fs';\nimport yargs from 'yargs';\nimport createMatchOperator, {candidateSearch, matchDetection} from './index.js';\nimport createDebugLogger from 'debug';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\ncli();\n\nasync function cli() {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:cli');\n const args = yargs(process.argv.slice(2))\n .scriptName('melinda-record-matching-js')\n .epilog('Copyright (C) 2022-2023 University Of Helsinki (The National Library Of Finland)')\n .usage('$0 <file> [options] and env variable info in README')\n .usage('Installed globally: $0 <file> [options] and env variable info in README')\n .usage('Not installed: npx $0 <file> [options] and env variable info in README')\n .usage('Build from source: node dist/index.js <file> [options] and env variable info in README')\n .showHelpOnFail(true)\n .example([['$ node /dist/cli.js record.json']])\n .env('MELINDA_RECORD_MATCH')\n .version()\n .positional('file', {type: 'string', describe: 'Json file of records to match'})\n .options({\n t: {type: 'string', default: 'IDS', alias: 'searchType', describe: 'IDS, STANDARD_IDS, COMPONENT, CONTENT or CONTENTALT'},\n m: {type: 'number', default: 1, alias: 'maxMatches', describe: ''},\n c: {type: 'number', default: 1000, alias: 'maxCandidates', describe: ''},\n s: {type: 'boolean', default: false, alias: 'returnStrategy', describe: ''},\n q: {type: 'boolean', default: false, alias: 'returnQuery', describe: ''},\n n: {type: 'boolean', default: false, alias: 'returnNonMatches', describe: ''}\n })\n .check((args) => {\n const [file] = args._;\n if (file === undefined) {\n throw new Error('No file argument given');\n }\n\n if (!fs.existsSync(file)) {\n throw new Error(`File ${file} does not exist`);\n }\n\n if (args.sruUrl === undefined) {\n throw new Error('Setup sru url');\n }\n\n if (!['IDS', 'STANDARD_IDS', 'COMPONENT', 'CONTENT', 'CONTENTALT', 'YSO'].includes(args.searchType)) {\n throw new Error('Invalid search type');\n }\n\n return true;\n })\n .parseSync();\n\n const [file] = args._;\n const {searchType} = args;\n debug(JSON.stringify(args));\n\n const detection = {\n threshold: 0.8999,\n strategy: generateStrategy(searchType)\n };\n\n const search = {\n url: args.sruUrl, searchSpec: generateSearchSpec(searchType)\n };\n\n const matchOperator = await createMatchOperator({detection, search, ...args});\n\n const fileRaw = fs.readFileSync(file, 'utf8');\n const record = new MarcRecord(JSON.parse(fileRaw), {subfieldValues: false});\n\n const result = await matchOperator({record});\n debug(JSON.stringify(result));\n\n\n function generateStrategy(searchType) {\n if (['IDS'].includes(searchType)) {\n return [\n matchDetection.features.bib.melindaId(),\n matchDetection.features.bib.allSourceIds(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n // We could have differing strategy for STANDARD_IDS\n // Let's not run title in strategy when we found the candidates through standard_ids search\n\n if (['STANDARD_IDS'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.isbn(),\n matchDetection.features.bib.issn(),\n // matchDetection.features.bib.sid(),\n matchDetection.features.bib.otherStandardIdentifier(),\n // Let's not use the same title matchDetection here\n //matchDetection.features.bib.title(),\n matchDetection.features.bib.authors(),\n // We probably should have some leeway here for notated music as BK etc.\n matchDetection.features.bib.recordType(),\n // Use publicationTimeAllowConsYearsMulti to\n // - ignore one year differences in publicationTime\n // - extract publicationTimes from f008, f26x and reprint notes in f500\n // - do not substract points for mismatching (normal) publicationTime, if there's a match between\n // normal publicationTime and a reprintPublication time\n matchDetection.features.bib.publicationTimeAllowConsYearsMulti(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.bibliographicLevel(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n if (['COMPONENT'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.otherStandardIdentifier(),\n matchDetection.features.bib.recordType(),\n matchDetection.features.bib.title(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.authors(),\n matchDetection.features.bib.bibliographicLevel(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n if (['CONTENT', 'CONTENTALT'].includes(searchType)) {\n return [\n matchDetection.features.bib.hostComponent(),\n matchDetection.features.bib.isbn(),\n matchDetection.features.bib.issn(),\n // matchDetection.features.bib.sid(),\n matchDetection.features.bib.otherStandardIdentifier(),\n matchDetection.features.bib.title(),\n matchDetection.features.bib.authors(),\n matchDetection.features.bib.recordType(),\n matchDetection.features.bib.publicationTime(),\n matchDetection.features.bib.language(),\n matchDetection.features.bib.bibliographicLevel(),\n matchDetection.features.bib.f773() // Host data\n ];\n }\n\n if (['YSO']) {\n return [\n // matchDetection.features.bib.sid(), // SIDs might be relevant on the auth side as well?\n matchDetection.features.auth.yso()\n ];\n }\n\n throw new Error('Unsupported match validation package');\n }\n\n\n function generateSearchSpec(searchType) {\n if (['IDS'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.melindaId,\n candidateSearch.searchTypes.bib.sourceIds\n ];\n }\n\n if (['STANDARD_IDS'].includes(searchType)) {\n return [candidateSearch.searchTypes.bib.standardIdentifiers];\n }\n\n if (['COMPONENT'].includes(searchType)) {\n return [\n //candidateSearch.searchTypes.bib.sourceIds,\n candidateSearch.searchTypes.component.hostIdMelinda,\n candidateSearch.searchTypes.component.hostIdOtherSource\n ];\n }\n\n if (['CONTENT'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.hostComponents,\n //candidateSearch.searchTypes.bib.titleAuthor,\n candidateSearch.searchTypes.bib.title\n ];\n }\n if (['CONTENTALT'].includes(searchType)) {\n return [\n candidateSearch.searchTypes.bib.hostComponents,\n // titleAuthorYearAlternates searches for matchCandidates\n // with alternate queries, starting from more tight searches\n candidateSearch.searchTypes.bib.titleAuthorYearAlternates\n ];\n }\n\n if (searchType === 'YSO') {\n return [\n candidateSearch.searchTypes.auth.authURX\n ];\n }\n\n throw new Error('Unsupported match validation package');\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["args", "file", "searchType"]
7
7
  }
@@ -1,6 +1,7 @@
1
1
  export { default as hostComponent } from "./host-component.js";
2
2
  export { default as isbn } from "./isbn.js";
3
3
  export { default as issn } from "./issn.js";
4
+ export { default as sid } from "./sid.js";
4
5
  export { default as otherStandardIdentifier } from "./other-standard-identifier.js";
5
6
  export { default as title } from "./title.js";
6
7
  export { default as authors } from "./authors.js";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/match-detection/features/bib/index.js"],
4
- "sourcesContent": ["\nexport {default as hostComponent} from './host-component.js';\nexport {default as isbn} from './isbn.js';\nexport {default as issn} from './issn.js';\nexport {default as otherStandardIdentifier} from './other-standard-identifier.js';\nexport {default as title} from './title.js';\nexport {default as authors} from './authors.js';\nexport {default as recordType} from './record-type.js';\nexport {default as publicationTime} from './publication-time.js';\nexport {default as publicationTimeAllowConsYears} from './publication-time-allow-cons-years.js';\nexport {default as publicationTimeAllowConsYearsMulti} from './publication-time-allow-cons-years-multi.js';\nexport {default as language} from './language.js';\nexport {default as bibliographicLevel} from './bibliographic-level.js';\nexport {default as melindaId} from './melinda-id.js';\nexport {default as allSourceIds} from './all-source-ids.js';\nexport {default as mediaType} from './media-type.js';\nexport {default as f773} from './f773.js';\n"],
5
- "mappings": "AACA,SAAQ,WAAW,qBAAoB;AACvC,SAAQ,WAAW,YAAW;AAC9B,SAAQ,WAAW,YAAW;AAC9B,SAAQ,WAAW,+BAA8B;AACjD,SAAQ,WAAW,aAAY;AAC/B,SAAQ,WAAW,eAAc;AACjC,SAAQ,WAAW,kBAAiB;AACpC,SAAQ,WAAW,uBAAsB;AACzC,SAAQ,WAAW,qCAAoC;AACvD,SAAQ,WAAW,0CAAyC;AAC5D,SAAQ,WAAW,gBAAe;AAClC,SAAQ,WAAW,0BAAyB;AAC5C,SAAQ,WAAW,iBAAgB;AACnC,SAAQ,WAAW,oBAAmB;AACtC,SAAQ,WAAW,iBAAgB;AACnC,SAAQ,WAAW,YAAW;",
4
+ "sourcesContent": ["\nexport {default as hostComponent} from './host-component.js';\nexport {default as isbn} from './isbn.js';\nexport {default as issn} from './issn.js';\nexport {default as sid} from './sid.js';\nexport {default as otherStandardIdentifier} from './other-standard-identifier.js';\nexport {default as title} from './title.js';\nexport {default as authors} from './authors.js';\nexport {default as recordType} from './record-type.js';\nexport {default as publicationTime} from './publication-time.js';\nexport {default as publicationTimeAllowConsYears} from './publication-time-allow-cons-years.js';\nexport {default as publicationTimeAllowConsYearsMulti} from './publication-time-allow-cons-years-multi.js';\nexport {default as language} from './language.js';\nexport {default as bibliographicLevel} from './bibliographic-level.js';\nexport {default as melindaId} from './melinda-id.js';\nexport {default as allSourceIds} from './all-source-ids.js';\nexport {default as mediaType} from './media-type.js';\nexport {default as f773} from './f773.js';\n"],
5
+ "mappings": "AACA,SAAQ,WAAW,qBAAoB;AACvC,SAAQ,WAAW,YAAW;AAC9B,SAAQ,WAAW,YAAW;AAC9B,SAAQ,WAAW,WAAU;AAC7B,SAAQ,WAAW,+BAA8B;AACjD,SAAQ,WAAW,aAAY;AAC/B,SAAQ,WAAW,eAAc;AACjC,SAAQ,WAAW,kBAAiB;AACpC,SAAQ,WAAW,uBAAsB;AACzC,SAAQ,WAAW,qCAAoC;AACvD,SAAQ,WAAW,0CAAyC;AAC5D,SAAQ,WAAW,gBAAe;AAClC,SAAQ,WAAW,0BAAyB;AAC5C,SAAQ,WAAW,iBAAgB;AACnC,SAAQ,WAAW,oBAAmB;AACtC,SAAQ,WAAW,iBAAgB;AACnC,SAAQ,WAAW,YAAW;",
6
6
  "names": []
7
7
  }
@@ -4,13 +4,23 @@ import { uniqArray } from "./issn.js";
4
4
  const debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:ISBN`);
5
5
  const debugData = debug.extend("data");
6
6
  const MAX_SCORE = 0.75;
7
+ const setRegexp = /(?:complete set|hela verket|helhet|koko kartasto|koko paketti|koko sarja|koko setti|koko teoksen isbn|koko teos|kokonaisuus|^set\b|\bset$|\bsetti\b|vol.*set)/ui;
7
8
  export default () => ({
8
9
  name: "ISBN",
9
10
  extract: ({
10
11
  record
11
12
  /*, recordExternal*/
12
13
  }) => {
13
- return record.get("020").filter((f) => f.subfields?.some((sf) => ["a", "z"].includes(sf.code) && sf.value));
14
+ const all = record.get("020").filter((f) => f.subfields?.some((sf) => ["a", "z"].includes(sf.code) && sf.value));
15
+ const sets = all.filter((f) => isSetIsbnField(f));
16
+ const noSets = all.filter((f) => !isSetIsbnField(f));
17
+ if (sets.length > 0 && noSets.length > 0) {
18
+ return noSets;
19
+ }
20
+ return all;
21
+ function isSetIsbnField(field) {
22
+ return field.subfields.some((sf) => sf.code === "q" && setRegexp.test(sf.value));
23
+ }
14
24
  },
15
25
  compare: (aa, bb) => {
16
26
  debugData(`Comparing ISBN sets ${JSON.stringify(aa)} and ${JSON.stringify(bb)}`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/match-detection/features/bib/isbn.js"],
4
- "sourcesContent": ["\nimport createDebugLogger from 'debug';\nimport {parse as isbnParse} from 'isbn3';\n\nimport {uniqArray} from './issn.js';\n\nconst debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:ISBN`);\nconst debugData = debug.extend('data');\n\nconst MAX_SCORE = 0.75;\n\nexport default () => ({\n name: 'ISBN',\n extract: ({record/*, recordExternal*/}) => {\n //const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';\n\n return record.get('020').filter(f => f.subfields?.some(sf => ['a', 'z'].includes(sf.code) && sf.value));\n },\n compare: (aa, bb) => {\n debugData(`Comparing ISBN sets ${JSON.stringify(aa)} and ${JSON.stringify(bb)}`);\n if (aa.length === 0 || bb.length === 0) {\n // No data for decision\n return 0;\n }\n\n const subfieldCodeForGoodValues = 'a';\n const subfieldCodeForBadValues = 'z';\n\n const [aValidValuesA, aInvalidValuesA, zValidValuesA, zInvalidValuesA] = getValuesWrapper(aa, 'AA'); // initial 'a' and 'z' refer to 020 subfields codes\n const [aValidValuesB, aInvalidValuesB, zValidValuesB, zInvalidValuesB] = getValuesWrapper(bb, 'BB');\n\n function getValuesWrapper(data, prefix) {\n const [aValidValues, aInvalidValues, zValidValues, zInvalidValues] = getValues(data);\n if (aValidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForGoodValues} VALID: ${aValidValues.join(', ')}`);\n }\n if (aInvalidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForGoodValues} INVALID: ${aInvalidValues.join(', ')}`);\n }\n if (zValidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForBadValues} VALID: ${zValidValues.join(', ')}`);\n }\n if (zInvalidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForBadValues} INVALID: ${zInvalidValues.join(', ')}`);\n }\n return [aValidValues, aInvalidValues, zValidValues, zInvalidValues];\n }\n\n const [sharedGoodValues, goodValuesAOnly, goodValuesBOnly] = getUnionData(aValidValuesA, aValidValuesB);\n\n debug(`GOOD\\tBOTH: ${sharedGoodValues.length}, A only: ${goodValuesAOnly.length}, B only: ${goodValuesBOnly.length}`);\n\n if (sharedGoodValues.length > 0) {\n // Third argument (aka 'N times') is >= 0\n return scoreData(MAX_SCORE, 0.8, goodValuesAOnly.length + goodValuesBOnly.length);\n }\n\n const hitScore = scoreSuboptimalHit();\n\n function scoreSuboptimalHit() {\n // One record consider ISBN good and the other record considered it's canceled:\n if (aValidValuesA.some(valA => zValidValuesB.includes(valA)) || aValidValuesB.some(valB => zValidValuesA.includes(valB))) {\n return MAX_SCORE;\n }\n\n // Subfield is for cancelled/whatever values, but the value is syntactically valid:\n // Could happen for two canceled ISBNs for example. I'll give this two thirds of the full score\n const zzValid = zValidValuesA.find(valA => zValidValuesB.includes(valA));\n if (zzValid) {\n debug(`Both contain a valid value in 020$z: ${zzValid}`);\n return MAX_SCORE * 2 / 3;\n }\n // Shared invalid identifiers:\n const aaInvalid = aInvalidValuesA.find(valA => aInvalidValuesB.includes(valA) || zInvalidValuesB.includes(valA)) || aInvalidValuesB.find(valB => zInvalidValuesA.includes(valB));\n if (aaInvalid) {\n debug(`Shared invalid value in 020$a and 020$a-or-$z subfields: ${aaInvalid}`);\n return MAX_SCORE * 2 / 3;\n }\n\n /* // Currently I think that paired invalid idenfiers in 020$z are meaningless...\n const zzInvalid = zInvalidValuesB.find(valB => zInvalidValuesA.includes(valB)) || zInvalidValuesA.find(valA => zInvalidValuesB.includes(valA));\n if (zzInvalid) {\n debug(`Shared invalid value in 020$z subfields: ${zzInvalid}`);\n return MAX_SCORE / 3;\n }\n */\n\n return 0;\n }\n\n\n\n if (hitScore === MAX_SCORE) {\n // -1 is needed to make the third argument >= 0 (otherwise min val would be 0)\n return scoreData(hitScore, 0.8, goodValuesAOnly.length + goodValuesBOnly.length - 1);\n }\n\n if (hitScore > 0) { // Canceled/invalid ISBNs match\n // Note that this is not (currently) penalized for non-matching canceled/invalid ISBNs. Maybe it should be.\n return scoreData(hitScore, 0.8, goodValuesAOnly.length + goodValuesBOnly.length);\n }\n\n // No match:\n\n if (aValidValuesA.length + aInvalidValuesA.length === 0 || aValidValuesB.length + aInvalidValuesB.length === 0) { // At least one record did not have any good ISBNs, so not penalizing here! (Invalid 020$as are counted a bad.)\n return 0.0;\n }\n // We have same matching invalid identifiers. Don't penalize:\n const allA = [...aValidValuesA, ...aInvalidValuesA, ...zValidValuesA, ...zInvalidValuesA];\n const allB = [...aValidValuesB, ...aInvalidValuesB, ...zValidValuesB, ...zInvalidValuesB];\n const [sharedValues, tmp1, tmp2] = getUnionData(allA, allB);\n debug(`WHATEVER\\tBOTH: ${sharedGoodValues.length}, A only: ${tmp1.length}, B only: ${tmp2.length}`);\n\n if (sharedValues.length > 0) {\n return 0;\n }\n // We have values but they disagree:\n return -0.75; // Has good ISBNs on both records, but they did not match\n\n function getValues(fields) {\n // Valid values are normalized to their isbn-13 form. Invalid values get their '-'s removed.\n const goodValues = fields.flatMap(f => f.subfields.filter(sf => sf.code === subfieldCodeForGoodValues)).map(sf => validatorAndNormalizer(sf.value));\n const trueGoodValues = goodValues.filter(val => val.valid).map(val => val.value);\n const wannabeGoodValues = goodValues.filter(val => !val.valid).map(val => val.value);\n if (!subfieldCodeForBadValues) { // 773\n return [trueGoodValues, wannabeGoodValues, [], []];\n }\n const badValues = fields.flatMap(f => f.subfields.filter(sf => sf.code === subfieldCodeForBadValues)).map(sf => validatorAndNormalizer(sf.value));\n const validBadValues = badValues.filter(val => val.valid).map(val => val.value);\n const invalidBadValues = badValues.filter(val => !val.valid).map(val => val.value);\n //const badValues = fields.flatMap(f => f.subfields.filter(sf => sf.code === subfieldCodeForBadValues)).map(sf => validatorAndNormalizer(sf.value).value);\n return [uniqArray(trueGoodValues), uniqArray(wannabeGoodValues), uniqArray(validBadValues), uniqArray(invalidBadValues)];\n }\n\n function validatorAndNormalizer(string) {\n const string2 = string.replace(/\\. -$/u, ''); // Remove punctuation (773$z)\n\n // Hack: Historically we 020$a \"1234567890 sidottu\" etc. Try the LHS alone:\n const string3 = string2.replace(/ .*$/u, '');\n if (string2 !== string3) {\n const altResult = validatorAndNormalizer(string3);\n if (altResult.valid) {\n return altResult;\n }\n }\n\n const isbnParseResult = isbnParse(string2, '') || '';\n debugData(`isbnParseResult: ${JSON.stringify(isbnParseResult)}`);\n if (!isbnParseResult.isValid) {\n debug(`Not parseable ISBN '${string2}', just removing hyphens`);\n return {valid: false, value: string2.replace(/-/ug, '')};\n }\n\n debug(`Parseable ISBN '${string2}', normalizing to ISBN-13 '${isbnParseResult.isbn13}'`);\n return {valid: true, value: isbnParseResult.isbn13};\n\n\n }\n }\n});\n\n// These are outside the default function as I'll probably want to export these later on\n\nfunction getUnionData(set1, set2) {\n const shared = set1.filter(val => set2.includes(val));\n const onlyInSet1 = set1.filter(val => !shared.includes(val));\n const onlyInSet2 = set2.filter(val => !shared.includes(val));\n return [shared, onlyInSet1, onlyInSet2];\n}\n\nfunction scoreData(score, factor, n) {\n return innerScoreData(score, n);\n function innerScoreData(currScore, remaining) {\n if (remaining > 0) {\n return innerScoreData(currScore * factor, remaining-1);\n }\n return Math.round(currScore * 100)/100; // 0.600000000001 => 0.6\n }\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\nimport createDebugLogger from 'debug';\nimport {parse as isbnParse} from 'isbn3';\n\nimport {uniqArray} from './issn.js';\n\nconst debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:ISBN`);\nconst debugData = debug.extend('data');\n\nconst MAX_SCORE = 0.75;\n\nconst setRegexp = /(?:complete set|hela verket|helhet|koko kartasto|koko paketti|koko sarja|koko setti|koko teoksen isbn|koko teos|kokonaisuus|^set\\b|\\bset$|\\bsetti\\b|vol.*set)/ui;\n\nexport default () => ({\n name: 'ISBN',\n extract: ({record/*, recordExternal*/}) => {\n //const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';\n\n const all = record.get('020').filter(f => f.subfields?.some(sf => ['a', 'z'].includes(sf.code) && sf.value));\n const sets = all.filter(f => isSetIsbnField(f));\n const noSets = all.filter(f => !isSetIsbnField(f));\n if (sets.length > 0 && noSets.length > 0) {\n return noSets;\n }\n return all;\n\n function isSetIsbnField(field) {\n return field.subfields.some(sf => sf.code === 'q' && setRegexp.test(sf.value));\n }\n },\n compare: (aa, bb) => {\n debugData(`Comparing ISBN sets ${JSON.stringify(aa)} and ${JSON.stringify(bb)}`);\n if (aa.length === 0 || bb.length === 0) {\n // No data for decision\n return 0;\n }\n\n const subfieldCodeForGoodValues = 'a';\n const subfieldCodeForBadValues = 'z';\n\n const [aValidValuesA, aInvalidValuesA, zValidValuesA, zInvalidValuesA] = getValuesWrapper(aa, 'AA'); // initial 'a' and 'z' refer to 020 subfields codes\n const [aValidValuesB, aInvalidValuesB, zValidValuesB, zInvalidValuesB] = getValuesWrapper(bb, 'BB');\n\n function getValuesWrapper(data, prefix) {\n const [aValidValues, aInvalidValues, zValidValues, zInvalidValues] = getValues(data);\n if (aValidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForGoodValues} VALID: ${aValidValues.join(', ')}`);\n }\n if (aInvalidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForGoodValues} INVALID: ${aInvalidValues.join(', ')}`);\n }\n if (zValidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForBadValues} VALID: ${zValidValues.join(', ')}`);\n }\n if (zInvalidValues.length) {\n debug(`${prefix}: ${aa[0].tag}$${subfieldCodeForBadValues} INVALID: ${zInvalidValues.join(', ')}`);\n }\n return [aValidValues, aInvalidValues, zValidValues, zInvalidValues];\n }\n\n const [sharedGoodValues, goodValuesAOnly, goodValuesBOnly] = getUnionData(aValidValuesA, aValidValuesB);\n\n debug(`GOOD\\tBOTH: ${sharedGoodValues.length}, A only: ${goodValuesAOnly.length}, B only: ${goodValuesBOnly.length}`);\n\n if (sharedGoodValues.length > 0) {\n // Third argument (aka 'N times') is >= 0\n return scoreData(MAX_SCORE, 0.8, goodValuesAOnly.length + goodValuesBOnly.length);\n }\n\n const hitScore = scoreSuboptimalHit();\n\n function scoreSuboptimalHit() {\n // One record consider ISBN good and the other record considered it's canceled:\n if (aValidValuesA.some(valA => zValidValuesB.includes(valA)) || aValidValuesB.some(valB => zValidValuesA.includes(valB))) {\n return MAX_SCORE;\n }\n\n // Subfield is for cancelled/whatever values, but the value is syntactically valid:\n // Could happen for two canceled ISBNs for example. I'll give this two thirds of the full score\n const zzValid = zValidValuesA.find(valA => zValidValuesB.includes(valA));\n if (zzValid) {\n debug(`Both contain a valid value in 020$z: ${zzValid}`);\n return MAX_SCORE * 2 / 3;\n }\n // Shared invalid identifiers:\n const aaInvalid = aInvalidValuesA.find(valA => aInvalidValuesB.includes(valA) || zInvalidValuesB.includes(valA)) || aInvalidValuesB.find(valB => zInvalidValuesA.includes(valB));\n if (aaInvalid) {\n debug(`Shared invalid value in 020$a and 020$a-or-$z subfields: ${aaInvalid}`);\n return MAX_SCORE * 2 / 3;\n }\n\n /* // Currently I think that paired invalid idenfiers in 020$z are meaningless...\n const zzInvalid = zInvalidValuesB.find(valB => zInvalidValuesA.includes(valB)) || zInvalidValuesA.find(valA => zInvalidValuesB.includes(valA));\n if (zzInvalid) {\n debug(`Shared invalid value in 020$z subfields: ${zzInvalid}`);\n return MAX_SCORE / 3;\n }\n */\n\n return 0;\n }\n\n\n\n if (hitScore === MAX_SCORE) {\n // -1 is needed to make the third argument >= 0 (otherwise min val would be 0)\n return scoreData(hitScore, 0.8, goodValuesAOnly.length + goodValuesBOnly.length - 1);\n }\n\n if (hitScore > 0) { // Canceled/invalid ISBNs match\n // Note that this is not (currently) penalized for non-matching canceled/invalid ISBNs. Maybe it should be.\n return scoreData(hitScore, 0.8, goodValuesAOnly.length + goodValuesBOnly.length);\n }\n\n // No match:\n\n if (aValidValuesA.length + aInvalidValuesA.length === 0 || aValidValuesB.length + aInvalidValuesB.length === 0) { // At least one record did not have any good ISBNs, so not penalizing here! (Invalid 020$as are counted a bad.)\n return 0.0;\n }\n // We have same matching invalid identifiers. Don't penalize:\n const allA = [...aValidValuesA, ...aInvalidValuesA, ...zValidValuesA, ...zInvalidValuesA];\n const allB = [...aValidValuesB, ...aInvalidValuesB, ...zValidValuesB, ...zInvalidValuesB];\n const [sharedValues, tmp1, tmp2] = getUnionData(allA, allB);\n debug(`WHATEVER\\tBOTH: ${sharedGoodValues.length}, A only: ${tmp1.length}, B only: ${tmp2.length}`);\n\n if (sharedValues.length > 0) {\n return 0;\n }\n // We have values but they disagree:\n return -0.75; // Has good ISBNs on both records, but they did not match\n\n function getValues(fields) {\n // Valid values are normalized to their isbn-13 form. Invalid values get their '-'s removed.\n const goodValues = fields.flatMap(f => f.subfields.filter(sf => sf.code === subfieldCodeForGoodValues)).map(sf => validatorAndNormalizer(sf.value));\n const trueGoodValues = goodValues.filter(val => val.valid).map(val => val.value);\n const wannabeGoodValues = goodValues.filter(val => !val.valid).map(val => val.value);\n if (!subfieldCodeForBadValues) { // 773\n return [trueGoodValues, wannabeGoodValues, [], []];\n }\n const badValues = fields.flatMap(f => f.subfields.filter(sf => sf.code === subfieldCodeForBadValues)).map(sf => validatorAndNormalizer(sf.value));\n const validBadValues = badValues.filter(val => val.valid).map(val => val.value);\n const invalidBadValues = badValues.filter(val => !val.valid).map(val => val.value);\n //const badValues = fields.flatMap(f => f.subfields.filter(sf => sf.code === subfieldCodeForBadValues)).map(sf => validatorAndNormalizer(sf.value).value);\n return [uniqArray(trueGoodValues), uniqArray(wannabeGoodValues), uniqArray(validBadValues), uniqArray(invalidBadValues)];\n }\n\n function validatorAndNormalizer(string) {\n const string2 = string.replace(/\\. -$/u, ''); // Remove punctuation (773$z)\n\n // Hack: Historically we 020$a \"1234567890 sidottu\" etc. Try the LHS alone:\n const string3 = string2.replace(/ .*$/u, '');\n if (string2 !== string3) {\n const altResult = validatorAndNormalizer(string3);\n if (altResult.valid) {\n return altResult;\n }\n }\n\n const isbnParseResult = isbnParse(string2, '') || '';\n debugData(`isbnParseResult: ${JSON.stringify(isbnParseResult)}`);\n if (!isbnParseResult.isValid) {\n debug(`Not parseable ISBN '${string2}', just removing hyphens`);\n return {valid: false, value: string2.replace(/-/ug, '')};\n }\n\n debug(`Parseable ISBN '${string2}', normalizing to ISBN-13 '${isbnParseResult.isbn13}'`);\n return {valid: true, value: isbnParseResult.isbn13};\n\n\n }\n }\n});\n\n// These are outside the default function as I'll probably want to export these later on\n\nfunction getUnionData(set1, set2) {\n const shared = set1.filter(val => set2.includes(val));\n const onlyInSet1 = set1.filter(val => !shared.includes(val));\n const onlyInSet2 = set2.filter(val => !shared.includes(val));\n return [shared, onlyInSet1, onlyInSet2];\n}\n\nfunction scoreData(score, factor, n) {\n return innerScoreData(score, n);\n function innerScoreData(currScore, remaining) {\n if (remaining > 0) {\n return innerScoreData(currScore * factor, remaining-1);\n }\n return Math.round(currScore * 100)/100; // 0.600000000001 => 0.6\n }\n}"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,36 @@
1
+ import { fieldToString } from "@natlibfi/marc-record-validators-melinda";
2
+ import createDebugLogger from "debug";
3
+ const debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:SID`);
4
+ const debugData = debug.extend("data");
5
+ export default () => ({
6
+ name: "SID",
7
+ extract: ({
8
+ record
9
+ /*, recordExternal*/
10
+ }) => {
11
+ const SIDs = record.get("SID");
12
+ return SIDs;
13
+ },
14
+ compare: (aa, bb) => {
15
+ debugData(`Comparing SID sets ${JSON.stringify(aa)} and ${JSON.stringify(bb)}`);
16
+ if (aa.length === 0 || bb.length === 0) {
17
+ return 0;
18
+ }
19
+ if (aa.some((field1) => bb.some((field2) => hasConflict(field1, field2)))) {
20
+ return -1;
21
+ }
22
+ return 0;
23
+ function hasConflict(f1, f2) {
24
+ const b1 = f1.subfields.find((sf) => sf.code === "b");
25
+ const b2 = f2.subfields.find((sf) => sf.code === "b");
26
+ if (!b1 || !b1.value || !b2 || !b2.value || b1.value !== b2.value) {
27
+ return false;
28
+ }
29
+ const c1 = f1.subfields.find((sf) => sf.code === "c");
30
+ const c2 = f2.subfields.find((sf) => sf.code === "c");
31
+ const result = c1 && c2 && c1.value && c2.value && c1.value !== c2.value;
32
+ return result;
33
+ }
34
+ }
35
+ });
36
+ //# sourceMappingURL=sid.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/match-detection/features/bib/sid.js"],
4
+ "sourcesContent": ["// SID checker: if organization ($c) is same, also id ($b) should always be same.\n// Return values: 0 (no problems) and -1.0 (conflict, press panic button)\n// Raison d'\u00EAtre: SID check is also done in match validator, but as I (nvolk) somehow managed to bypass it with Saamelaisbibliografia imports.\n// Also the earlier we filter these weeds, the better.\n// Re-implementation is simpler (and lighter) than importing and using match validator's implementation (sigh!).\n// But of course, there should be but one implementation...\n\nimport { fieldToString } from '@natlibfi/marc-record-validators-melinda';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:SID`);\nconst debugData = debug.extend('data');\n\n\nexport default () => ({\n name: 'SID',\n extract: ({record/*, recordExternal*/}) => {\n const SIDs = record.get('SID');\n return SIDs;\n },\n compare: (aa, bb) => {\n debugData(`Comparing SID sets ${JSON.stringify(aa)} and ${JSON.stringify(bb)}`);\n if (aa.length === 0 || bb.length === 0) {\n return 0;\n }\n\n if (aa.some(field1 => bb.some(field2 => hasConflict(field1, field2)))) {\n return -1.0; // Big bad number that prevents match\n }\n return 0;\n\n function hasConflict(f1, f2) {\n // Check organisation:\n const b1 = f1.subfields.find(sf => sf.code === 'b');\n const b2 = f2.subfields.find(sf => sf.code === 'b');\n //debug(`SID\\$b: ${b1.value} VS ${b2.value}`);\n if (!b1 || !b1.value || !b2 || !b2.value || b1.value !== b2.value) { // Different orgs can not have a conflict\n return false;\n }\n // Check local ID:\n const c1 = f1.subfields.find(sf => sf.code === 'c');\n const c2 = f2.subfields.find(sf => sf.code === 'c');\n const result = c1 && c2 && c1.value && c2.value && c1.value !== c2.value;\n //debug(`SID\\$c: ${c1.value} VS ${c2.value} => ${result}`);\n return result;\n }\n }\n});\n"],
5
+ "mappings": "AAOA,SAAS,qBAAqB;AAC9B,OAAO,uBAAuB;AAE9B,MAAM,QAAQ,kBAAkB,qFAAqF;AACrH,MAAM,YAAY,MAAM,OAAO,MAAM;AAGrC,eAAe,OAAO;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,CAAC;AAAA,IAAC;AAAA;AAAA,EAA0B,MAAM;AACzC,UAAM,OAAO,OAAO,IAAI,KAAK;AAC7B,WAAO;AAAA,EACT;AAAA,EACA,SAAS,CAAC,IAAI,OAAO;AACnB,cAAU,sBAAsB,KAAK,UAAU,EAAE,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC,EAAE;AAC9E,QAAI,GAAG,WAAW,KAAK,GAAG,WAAW,GAAG;AACtC,aAAO;AAAA,IACT;AAEA,QAAI,GAAG,KAAK,YAAU,GAAG,KAAK,YAAU,YAAY,QAAQ,MAAM,CAAC,CAAC,GAAG;AACrE,aAAO;AAAA,IACT;AACA,WAAO;AAEP,aAAS,YAAY,IAAI,IAAI;AAE3B,YAAM,KAAK,GAAG,UAAU,KAAK,QAAM,GAAG,SAAS,GAAG;AAClD,YAAM,KAAK,GAAG,UAAU,KAAK,QAAM,GAAG,SAAS,GAAG;AAElD,UAAI,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO;AACjE,eAAO;AAAA,MACT;AAEA,YAAM,KAAK,GAAG,UAAU,KAAK,QAAM,GAAG,SAAS,GAAG;AAClD,YAAM,KAAK,GAAG,UAAU,KAAK,QAAM,GAAG,SAAS,GAAG;AAClD,YAAM,SAAS,MAAM,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG;AAEnE,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "url": "https://github.com/NatLibFi/melinda-record-matching-js"
14
14
  },
15
15
  "license": "MIT",
16
- "version": "5.1.0",
16
+ "version": "5.1.1",
17
17
  "type": "module",
18
18
  "main": "./dist/index.js",
19
19
  "bin": "./dist/cli.js",
@@ -39,8 +39,8 @@
39
39
  "dev:debug": "cross-env LOG_LEVEL=debug DEBUG=@natlibfi/* NODE_ENV=test npm run watch:test"
40
40
  },
41
41
  "dependencies": {
42
- "@natlibfi/marc-record": "^10.0.1",
43
- "@natlibfi/marc-record-serializers": "^11.0.2",
42
+ "@natlibfi/marc-record": "^10.0.2",
43
+ "@natlibfi/marc-record-serializers": "^11.0.3",
44
44
  "@natlibfi/marc-record-validators-melinda": "^12.0.15",
45
45
  "@natlibfi/melinda-commons": "^14.0.2",
46
46
  "@natlibfi/sru-client": "^7.0.1",
package/src/cli.js CHANGED
@@ -89,6 +89,7 @@ async function cli() {
89
89
  matchDetection.features.bib.hostComponent(),
90
90
  matchDetection.features.bib.isbn(),
91
91
  matchDetection.features.bib.issn(),
92
+ // matchDetection.features.bib.sid(),
92
93
  matchDetection.features.bib.otherStandardIdentifier(),
93
94
  // Let's not use the same title matchDetection here
94
95
  //matchDetection.features.bib.title(),
@@ -125,6 +126,7 @@ async function cli() {
125
126
  matchDetection.features.bib.hostComponent(),
126
127
  matchDetection.features.bib.isbn(),
127
128
  matchDetection.features.bib.issn(),
129
+ // matchDetection.features.bib.sid(),
128
130
  matchDetection.features.bib.otherStandardIdentifier(),
129
131
  matchDetection.features.bib.title(),
130
132
  matchDetection.features.bib.authors(),
@@ -138,6 +140,7 @@ async function cli() {
138
140
 
139
141
  if (['YSO']) {
140
142
  return [
143
+ // matchDetection.features.bib.sid(), // SIDs might be relevant on the auth side as well?
141
144
  matchDetection.features.auth.yso()
142
145
  ];
143
146
  }
@@ -2,6 +2,7 @@
2
2
  export {default as hostComponent} from './host-component.js';
3
3
  export {default as isbn} from './isbn.js';
4
4
  export {default as issn} from './issn.js';
5
+ export {default as sid} from './sid.js';
5
6
  export {default as otherStandardIdentifier} from './other-standard-identifier.js';
6
7
  export {default as title} from './title.js';
7
8
  export {default as authors} from './authors.js';
@@ -9,12 +9,24 @@ const debugData = debug.extend('data');
9
9
 
10
10
  const MAX_SCORE = 0.75;
11
11
 
12
+ const setRegexp = /(?:complete set|hela verket|helhet|koko kartasto|koko paketti|koko sarja|koko setti|koko teoksen isbn|koko teos|kokonaisuus|^set\b|\bset$|\bsetti\b|vol.*set)/ui;
13
+
12
14
  export default () => ({
13
15
  name: 'ISBN',
14
16
  extract: ({record/*, recordExternal*/}) => {
15
17
  //const label = recordExternal && recordExternal.label ? recordExternal.label : 'record';
16
18
 
17
- return record.get('020').filter(f => f.subfields?.some(sf => ['a', 'z'].includes(sf.code) && sf.value));
19
+ const all = record.get('020').filter(f => f.subfields?.some(sf => ['a', 'z'].includes(sf.code) && sf.value));
20
+ const sets = all.filter(f => isSetIsbnField(f));
21
+ const noSets = all.filter(f => !isSetIsbnField(f));
22
+ if (sets.length > 0 && noSets.length > 0) {
23
+ return noSets;
24
+ }
25
+ return all;
26
+
27
+ function isSetIsbnField(field) {
28
+ return field.subfields.some(sf => sf.code === 'q' && setRegexp.test(sf.value));
29
+ }
18
30
  },
19
31
  compare: (aa, bb) => {
20
32
  debugData(`Comparing ISBN sets ${JSON.stringify(aa)} and ${JSON.stringify(bb)}`);
@@ -0,0 +1,48 @@
1
+ // SID checker: if organization ($c) is same, also id ($b) should always be same.
2
+ // Return values: 0 (no problems) and -1.0 (conflict, press panic button)
3
+ // Raison d'être: SID check is also done in match validator, but as I (nvolk) somehow managed to bypass it with Saamelaisbibliografia imports.
4
+ // Also the earlier we filter these weeds, the better.
5
+ // Re-implementation is simpler (and lighter) than importing and using match validator's implementation (sigh!).
6
+ // But of course, there should be but one implementation...
7
+
8
+ import { fieldToString } from '@natlibfi/marc-record-validators-melinda';
9
+ import createDebugLogger from 'debug';
10
+
11
+ const debug = createDebugLogger(`@natlibfi/melinda-record-matching:match-detection:features:standard-identifiers:SID`);
12
+ const debugData = debug.extend('data');
13
+
14
+
15
+ export default () => ({
16
+ name: 'SID',
17
+ extract: ({record/*, recordExternal*/}) => {
18
+ const SIDs = record.get('SID');
19
+ return SIDs;
20
+ },
21
+ compare: (aa, bb) => {
22
+ debugData(`Comparing SID sets ${JSON.stringify(aa)} and ${JSON.stringify(bb)}`);
23
+ if (aa.length === 0 || bb.length === 0) {
24
+ return 0;
25
+ }
26
+
27
+ if (aa.some(field1 => bb.some(field2 => hasConflict(field1, field2)))) {
28
+ return -1.0; // Big bad number that prevents match
29
+ }
30
+ return 0;
31
+
32
+ function hasConflict(f1, f2) {
33
+ // Check organisation:
34
+ const b1 = f1.subfields.find(sf => sf.code === 'b');
35
+ const b2 = f2.subfields.find(sf => sf.code === 'b');
36
+ //debug(`SID\$b: ${b1.value} VS ${b2.value}`);
37
+ if (!b1 || !b1.value || !b2 || !b2.value || b1.value !== b2.value) { // Different orgs can not have a conflict
38
+ return false;
39
+ }
40
+ // Check local ID:
41
+ const c1 = f1.subfields.find(sf => sf.code === 'c');
42
+ const c2 = f2.subfields.find(sf => sf.code === 'c');
43
+ const result = c1 && c2 && c1.value && c2.value && c1.value !== c2.value;
44
+ //debug(`SID\$c: ${c1.value} VS ${c2.value} => ${result}`);
45
+ return result;
46
+ }
47
+ }
48
+ });