Skip to content

Commit 37255e8

Browse files
aborrusoclaude
andcommitted
test(scoring): add readDcatExtra and holder/publisher tests
- Export readDcatExtra for direct unit testing - 10 tests for readDcatExtra: extras-first, root-fallback, empty/non-string values, missing key, absent extras, empty dataset - 7 tests for scoreDatasetRelevance: federated-portal bug case (extras≠root), non-DCAT portal (holder/publisher=0), default weights (holder=4, publisher=2) - Update 3 existing tests: sums-scores, custom-weights, returns-structure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fccf8db commit 37255e8

2 files changed

Lines changed: 191 additions & 4 deletions

File tree

src/tools/package.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export const scoreTextField = (text: string | undefined, terms: string[], weight
165165
* when extras don't carry the key. The fallback is important for non-DCAT-AP_IT CKAN portals
166166
* (e.g. data.gov, open.canada.ca) where root-level holder/publisher are correct.
167167
*/
168-
const readDcatExtra = (dataset: CkanPackage, key: "holder_name" | "publisher_name"): string => {
168+
export const readDcatExtra = (dataset: CkanPackage, key: "holder_name" | "publisher_name"): string => {
169169
const extras = Array.isArray(dataset.extras) ? dataset.extras : [];
170170
for (const e of extras) {
171171
if (e && typeof e === "object" && (e as { key?: unknown }).key === key) {

tests/unit/package-scoring.test.ts

Lines changed: 190 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
escapeRegExp,
55
textMatchesTerms,
66
scoreTextField,
7-
scoreDatasetRelevance
7+
scoreDatasetRelevance,
8+
readDcatExtra
89
} from '../../src/tools/package';
910

1011
describe('extractQueryTerms', () => {
@@ -263,7 +264,9 @@ describe('scoreDatasetRelevance', () => {
263264
result.breakdown.title +
264265
result.breakdown.notes +
265266
result.breakdown.tags +
266-
result.breakdown.organization
267+
result.breakdown.organization +
268+
result.breakdown.holder +
269+
result.breakdown.publisher
267270
);
268271
});
269272

@@ -287,7 +290,7 @@ describe('scoreDatasetRelevance', () => {
287290
tags: [],
288291
organization: null
289292
};
290-
const weights = { title: 10, notes: 5, tags: 3, organization: 1 };
293+
const weights = { title: 10, notes: 5, tags: 3, organization: 1, holder: 4, publisher: 2 };
291294
const result = scoreDatasetRelevance('health', dataset, weights);
292295

293296
expect(result.breakdown.title).toBe(10);
@@ -379,6 +382,8 @@ describe('scoreDatasetRelevance', () => {
379382
expect(result.breakdown).toHaveProperty('notes');
380383
expect(result.breakdown).toHaveProperty('tags');
381384
expect(result.breakdown).toHaveProperty('organization');
385+
expect(result.breakdown).toHaveProperty('holder');
386+
expect(result.breakdown).toHaveProperty('publisher');
382387
});
383388

384389
it('includes extracted terms in result', () => {
@@ -391,3 +396,185 @@ describe('scoreDatasetRelevance', () => {
391396
});
392397
});
393398
});
399+
400+
describe('readDcatExtra', () => {
401+
it('returns extras value when key is present', () => {
402+
const dataset = {
403+
extras: [
404+
{ key: 'holder_name', value: 'Comune di Lecce' }
405+
]
406+
};
407+
expect(readDcatExtra(dataset, 'holder_name')).toBe('Comune di Lecce');
408+
});
409+
410+
it('prefers extras over root field when both are present', () => {
411+
const dataset = {
412+
holder_name: 'Regione Puglia',
413+
extras: [
414+
{ key: 'holder_name', value: 'Comune di Lecce' }
415+
]
416+
};
417+
expect(readDcatExtra(dataset, 'holder_name')).toBe('Comune di Lecce');
418+
});
419+
420+
it('falls back to root field when key not in extras', () => {
421+
const dataset = {
422+
holder_name: 'Health Canada',
423+
extras: [
424+
{ key: 'other_field', value: 'something' }
425+
]
426+
};
427+
expect(readDcatExtra(dataset, 'holder_name')).toBe('Health Canada');
428+
});
429+
430+
it('falls back to root field when extras is empty', () => {
431+
const dataset = {
432+
holder_name: 'data.gov',
433+
extras: []
434+
};
435+
expect(readDcatExtra(dataset, 'holder_name')).toBe('data.gov');
436+
});
437+
438+
it('falls back to root field when extras is absent', () => {
439+
const dataset = {
440+
publisher_name: 'Open Government'
441+
};
442+
expect(readDcatExtra(dataset, 'publisher_name')).toBe('Open Government');
443+
});
444+
445+
it('returns empty string when neither extras nor root have the key', () => {
446+
const dataset = {
447+
extras: [{ key: 'other', value: 'x' }]
448+
};
449+
expect(readDcatExtra(dataset, 'holder_name')).toBe('');
450+
});
451+
452+
it('returns empty string for completely empty dataset', () => {
453+
const dataset = {};
454+
expect(readDcatExtra(dataset, 'holder_name')).toBe('');
455+
});
456+
457+
it('skips extras entry with empty string value', () => {
458+
const dataset = {
459+
holder_name: 'Root Value',
460+
extras: [
461+
{ key: 'holder_name', value: '' }
462+
]
463+
};
464+
expect(readDcatExtra(dataset, 'holder_name')).toBe('Root Value');
465+
});
466+
467+
it('skips extras entry with non-string value', () => {
468+
const dataset = {
469+
holder_name: 'Root Value',
470+
extras: [
471+
{ key: 'holder_name', value: 42 }
472+
]
473+
};
474+
expect(readDcatExtra(dataset, 'holder_name')).toBe('Root Value');
475+
});
476+
477+
it('handles publisher_name key', () => {
478+
const dataset = {
479+
publisher_name: 'Root Publisher',
480+
extras: [
481+
{ key: 'publisher_name', value: 'Extras Publisher' }
482+
]
483+
};
484+
expect(readDcatExtra(dataset, 'publisher_name')).toBe('Extras Publisher');
485+
});
486+
});
487+
488+
describe('scoreDatasetRelevance — holder and publisher', () => {
489+
it('scores holder from extras (DCAT-AP_IT pattern)', () => {
490+
const dataset = {
491+
title: 'Defibrillatori DAE',
492+
organization: { name: 'regione-puglia', title: 'Regione Puglia' },
493+
extras: [{ key: 'holder_name', value: 'Comune di Lecce' }]
494+
};
495+
const result = scoreDatasetRelevance('Comune di Lecce', dataset);
496+
497+
expect(result.breakdown.holder).toBeGreaterThan(0);
498+
expect(result.breakdown.organization).toBe(0);
499+
});
500+
501+
it('extras holder wins over root holder when they differ (federated portal fix)', () => {
502+
const dataset = {
503+
title: 'Dataset',
504+
holder_name: 'Regione Puglia',
505+
extras: [{ key: 'holder_name', value: 'Comune di Mesagne' }]
506+
};
507+
const withFix = scoreDatasetRelevance('Mesagne', dataset);
508+
expect(withFix.breakdown.holder).toBeGreaterThan(0);
509+
510+
const datasetRootOnly = {
511+
title: 'Dataset',
512+
holder_name: 'Comune di Mesagne'
513+
};
514+
const withRoot = scoreDatasetRelevance('Mesagne', datasetRootOnly);
515+
expect(withRoot.breakdown.holder).toBeGreaterThan(0);
516+
});
517+
518+
it('scores publisher from extras', () => {
519+
const dataset = {
520+
title: 'Dataset',
521+
extras: [{ key: 'publisher_name', value: 'Regione Siciliana' }]
522+
};
523+
const result = scoreDatasetRelevance('Siciliana', dataset);
524+
525+
expect(result.breakdown.publisher).toBeGreaterThan(0);
526+
});
527+
528+
it('holder and publisher contribute to total', () => {
529+
const dataset = {
530+
title: 'Dataset',
531+
extras: [
532+
{ key: 'holder_name', value: 'Comune di Lecce' },
533+
{ key: 'publisher_name', value: 'Comune di Lecce' }
534+
]
535+
};
536+
const result = scoreDatasetRelevance('Lecce', dataset);
537+
538+
expect(result.breakdown.holder).toBeGreaterThan(0);
539+
expect(result.breakdown.publisher).toBeGreaterThan(0);
540+
expect(result.total).toBe(
541+
result.breakdown.title +
542+
result.breakdown.notes +
543+
result.breakdown.tags +
544+
result.breakdown.organization +
545+
result.breakdown.holder +
546+
result.breakdown.publisher
547+
);
548+
});
549+
550+
it('non-DCAT portal: holder and publisher score 0 when fields absent', () => {
551+
const dataset = {
552+
title: 'Health Dataset',
553+
organization: { name: 'health-canada', title: 'Health Canada' }
554+
};
555+
const result = scoreDatasetRelevance('health', dataset);
556+
557+
expect(result.breakdown.holder).toBe(0);
558+
expect(result.breakdown.publisher).toBe(0);
559+
});
560+
561+
it('uses default weight 4 for holder', () => {
562+
const dataset = {
563+
title: 'Data',
564+
extras: [{ key: 'holder_name', value: 'Comune di Lecce' }]
565+
};
566+
const result = scoreDatasetRelevance('Lecce', dataset);
567+
568+
expect(result.breakdown.holder).toBe(4);
569+
});
570+
571+
it('uses default weight 2 for publisher', () => {
572+
const dataset = {
573+
title: 'Data',
574+
extras: [{ key: 'publisher_name', value: 'Comune di Lecce' }]
575+
};
576+
const result = scoreDatasetRelevance('Lecce', dataset);
577+
578+
expect(result.breakdown.publisher).toBe(2);
579+
});
580+
});

0 commit comments

Comments
 (0)