import { describe, expect, it } from 'vitest' import { readFileSync } from 'node:fs' import { evaluateEvidenceRules, normalizeEvidenceText, scoreEvidenceAnchor } from './evidenceMatching.js' const barricelliRule = { id: 'rule-barricelli', name: 'Contemporary Barricelli fire report', flagKey: 'barricelli.child-rescue-source', minimumAnchorMatches: 1, anchors: [{ id: 'parents', phrase: 'den italienske maler og opfinder Barricelli og frue, født Aall', minimumSimilarity: 0.72, }, { id: 'drink', phrase: 'Han vækkede nemlig sin mor for at faa noget at drikke', minimumSimilarity: 0.72, }], } const sceneSevenFixture = (name: string) => readFileSync( new URL(`../mysteries/barricelli-scene-7/fixtures/${name}`, import.meta.url), 'utf8', ) const sceneSevenManifest = JSON.parse(readFileSync( new URL('../mysteries/barricelli-scene-7/mystery.json', import.meta.url), 'utf8', )) as { documents: unknown[] goals: { key:string;requiredFlags:string[] }[] evidenceMatchRules: { name:string;flagKey:string;minimumAnchorMatches:number;anchors:{ phrase:string;minimumSimilarity:number }[] }[] } const authoredPatentRule = sceneSevenManifest.evidenceMatchRules.find(rule => rule.name.startsWith('Google Patents'))! const authoredNationalLibraryRule = sceneSevenManifest.evidenceMatchRules.find(rule => rule.name.startsWith('Nasjonalbiblioteket'))! const materializeRule = (id:string, rule:typeof authoredPatentRule) => ({ id,...rule, anchors:rule.anchors.map((anchor,index) => ({ id:`${id}-anchor-${index}`,...anchor })) }) const patentRule = materializeRule('rule-patent',authoredPatentRule) const nationalLibraryRule = materializeRule('rule-national-library',authoredNationalLibraryRule) describe('evidence text matching', () => { it('normalizes historical Norwegian characters and page layout noise', () => { expect(normalizeEvidenceText('Født Aall — 2½ aar\n gammel')).toBe('fodt aall 2 1 2 aar gammel') }) it('tolerates plausible OCR substitutions in a distinctive passage', () => { const result = scoreEvidenceAnchor( normalizeEvidenceText('I kvistleiligheden boede den italienske maler og opfinder Barrioelli og frue, født Aall, med sin lille søn.'), barricelliRule.anchors[0].phrase, ) expect(result.similarity).toBeGreaterThan(0.9) }) it('awards the data-defined flag when one configured anchor is present', () => { const evaluations = evaluateEvidenceRules('Han vækkede nemlig sin mor for at faa noget at drikke, og da ser hun huset brænder.', [barricelliRule]) expect(evaluations[0]).toMatchObject({ matched: true, matchedAnchorCount: 1, flagKey: 'barricelli.child-rescue-source' }) }) it('does not match generic words from an unrelated fire report', () => { const evaluations = evaluateEvidenceRules('A family escaped from a boarding-house fire during the night.', [barricelliRule]) expect(evaluations[0]).toMatchObject({ matched: false, matchedAnchorCount: 0 }) }) it('can require multiple anchors for a stricter level rule', () => { const rule = { ...barricelliRule, minimumAnchorMatches: 2 } expect(evaluateEvidenceRules(barricelliRule.anchors[0].phrase, [rule])[0].matched).toBe(false) expect(evaluateEvidenceRules(barricelliRule.anchors.map(anchor => anchor.phrase).join(' '), [rule])[0].matched).toBe(true) }) it('accepts the Scene 7 Google Patents OCR fixture', () => { const evaluation = evaluateEvidenceRules(sceneSevenFixture('google-patents-target-ocr.txt'), [patentRule])[0] expect(evaluation).toMatchObject({ matched: true, flagKey: 'scene7.nils_inventor_proved' }) expect(evaluation.matchedAnchorCount).toBeGreaterThanOrEqual(2) }) it('accepts the Norwegian National Library patent notice found during playtesting', () => { const evaluation = evaluateEvidenceRules(sceneSevenFixture('national-library-patent-ocr.txt'), [nationalLibraryRule])[0] expect(evaluation).toMatchObject({ matched:true,flagKey:'scene7.nils_inventor_proved' }) expect(evaluation.matchedAnchorCount).toBeGreaterThanOrEqual(2) }) it('keeps the Scene 7 assignment empty and ties its goal to the source flag', () => { expect(sceneSevenManifest.documents).toEqual([]) expect(sceneSevenManifest.goals).toEqual([ expect.objectContaining({ key:'barricelli.inventor-proof',requiredFlags:['scene7.nils_inventor_proved'] }), ]) expect(authoredPatentRule.flagKey).toBe('scene7.nils_inventor_proved') }) it('does not confuse the father-only source with proof about Nils', () => { expect(evaluateEvidenceRules(sceneSevenFixture('father-only-negative-ocr.txt'), [patentRule,nationalLibraryRule])) .toEqual([expect.objectContaining({ matched:false }),expect.objectContaining({ matched:false })]) }) it('tolerates a cropped, line-broken patent result without accepting unrelated patents or empty OCR', () => { const cropped = 'GB 695913 A — Improved chest of drawers\nInventor: Nils Aall Barri-\ncelli' expect(evaluateEvidenceRules(cropped, [patentRule])[0].matched).toBe(true) expect(evaluateEvidenceRules('US123456A Improved umbrella stand — Inventor Ada Example', [patentRule])[0].matched).toBe(false) expect(evaluateEvidenceRules('', [patentRule])[0].matched).toBe(false) }) })