/** * E2E-tester för Identify (Skvadron F) * * DoD: * - identify-modell bakom API:t * - Golden-case-regressionssvit * - Konfidenskalibrering rapporterad * - Tills modellen når tröskel: svara med klasskandidat + ärlig konfidens */ const request = require('supertest'); const app = require('../src/app'); const Identify = require('../src/models/identify'); describe('Skvadron F · Vision & identify', () => { describe('POST /v0/identify', () => { test('Case 1: GE-armatur identifierad med hög konfidens', async () => { const res = await request(app) .post('/v0/identify') .send({ bild: '(gatuvy: cobrahead-armatur)', position: { lat: 36.16, lon: -115.14, land: 'US' }, ocr_extraherat: ['GE', 'ERS2', '4000K'] }); expect(res.status).toBe(200); expect(res.body.kandidater).toHaveLength(1); expect(res.body.kandidater[0].lvx_id).toBe('LVX-P-0000002'); expect(res.body.kandidater[0].konfidens).toBe(0.94); expect(res.body.kandidater[0].verifieringsniva).toBe('kallbelagd'); expect(res.body.konfidens_rapporterad).toBe(true); }); test('Case 2: Hydrant åldersbestämd ur gjutgodset', async () => { const res = await request(app) .post('/v0/identify') .send({ bild: '(hydrant, närbild)', position: { lat: 32.78, lon: -96.80, land: 'US' }, ocr_extraherat: ['AMERICAN-DARLING', 'B-84-B-5', '1998', 'OPEN LEFT'] }); expect(res.status).toBe(200); expect(res.body.kandidater[0].lvx_id).toBe('LVX-P-0000016'); expect(res.body.kandidater[0].konfidens).toBe(0.97); expect(res.body.kandidater[0].individdata_ur_ocr).toBeDefined(); }); test('Case 3: Okänd modell — ärligt svar + bounty skapas', async () => { const res = await request(app) .post('/v0/identify') .send({ bild: '(grått gatuskåp utan etikett)', position: { lat: 51.51, lon: -0.13, land: 'UK' }, ocr_extraherat: [], kund_ref: 'kund-789' }); expect(res.status).toBe(200); expect(res.body.kandidater[0].niva).toBe('objektklass'); expect(res.body.kandidater[0].konfidens).toBe(0.71); expect(res.body.kandidater[0].sarskiljning_mot).toBeDefined(); expect(res.body.bounty).toBeDefined(); expect(res.body.bounty.skapad).toBe(true); expect(res.body.bounty.kundnotis).toContain('notifieras'); }); test('konfidens alltid rapporterad — aldrig dold', async () => { const res = await request(app) .post('/v0/identify') .send({ position: { lat: 0, lon: 0 }, ocr_extraherat: ['helt', 'okant', 'ord'] }); expect(res.status).toBe(200); expect(res.body.konfidens_rapporterad).toBe(true); expect(res.body.kandidater[0].konfidens).toBeDefined(); }); }); describe('Golden-case regressionssvit', () => { test('körs och rapporterar resultat', async () => { const res = await request(app) .get('/v0/identify/golden-cases'); expect(res.status).toBe(200); expect(res.body.summary).toBeDefined(); expect(typeof res.body.summary.pass_rate).toBe('number'); }); test('golden cases kan läggas till', () => { const testCase = Identify.addGoldenCase({ namn: 'GE Cobrahead-test', beskrivning: 'Identifiera GE Evolve ERS2', input_ocr: ['GE', 'ERS2'], forvantat_svar: { lvx_id: 'LVX-P-0000002', konfidens: 0.90 } }); expect(testCase.id).toBeDefined(); }); }); describe('Konfidenskalibrering', () => { test('rapporteras via API', async () => { const res = await request(app) .get('/v0/identify/calibration'); expect(res.status).toBe(200); expect(res.body.modell_version).toBeDefined(); expect(res.body.rapport).toBeDefined(); }); test('kan uppdateras med nya resultat', () => { const result = Identify.updateCalibration('v1-test', '0.9-1.0', 9, 10); expect(result.accuracy).toBe(0.9); }); }); describe('Objekt-uppslag', () => { test('GET /v0/objects/:lvx_id', async () => { const res = await request(app) .get('/v0/objects/LVX-TRP-0102'); expect(res.status).toBe(200); expect(res.body.lvx_id).toBe('LVX-TRP-0102'); expect(res.body.namn.sv).toBeDefined(); }); test('GET /v0/objects/:lvx_id/succession', async () => { const res = await request(app) .get('/v0/objects/LVX-P-0000002/succession'); expect(res.status).toBe(200); expect(res.body.kedja).toBeDefined(); }); test('GET /v0/search', async () => { const res = await request(app) .get('/v0/search?q=hydrant'); expect(res.status).toBe(200); expect(res.body.results.length).toBeGreaterThan(0); }); }); describe('Ärlig konfidens tills modell når tröskel', () => { test('svagt svar ger låg konfidens, inte påhittad hög', async () => { const res = await request(app) .post('/v0/identify') .send({ position: { lat: 0, lon: 0 }, ocr_extraherat: [] }); expect(res.status).toBe(200); // Konfidens ska vara låg när inget OCR finns expect(res.body.kandidater[0].konfidens).toBeLessThan(0.8); }); test('klasskandidat ges när modell saknas', async () => { const res = await request(app) .post('/v0/identify') .send({ position: { lat: 51.51, lon: -0.13, land: 'UK' }, ocr_extraherat: [] }); expect(res.status).toBe(200); expect(res.body.kandidater[0].niva).toBe('objektklass'); expect(res.body.kandidater[0].modell).toBeNull(); expect(res.body.kandidater[0].insikt).toContain('obestämd'); }); }); });