2026-08-22 14:53:23 +02:00
|
|
|
import { describe, expect, it } from 'vitest'
|
2026-08-22 16:02:10 +02:00
|
|
|
import { readFileSync } from 'node:fs'
|
2026-08-22 14:53:23 +02:00
|
|
|
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,
|
|
|
|
|
}],
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-22 16:02:10 +02:00
|
|
|
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[0]
|
|
|
|
|
const patentRule = { id:'rule-patent',...authoredPatentRule,
|
|
|
|
|
anchors:authoredPatentRule.anchors.map((anchor,index) => ({ id:`anchor-${index}`,...anchor })) }
|
|
|
|
|
|
2026-08-22 14:53:23 +02:00
|
|
|
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)
|
|
|
|
|
})
|
2026-08-22 16:02:10 +02:00
|
|
|
|
|
|
|
|
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('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])[0])
|
|
|
|
|
.toMatchObject({ 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)
|
|
|
|
|
})
|
2026-08-22 14:53:23 +02:00
|
|
|
})
|