import { describe, expect, it } from 'vitest'; import { messgrundlageBefunde } from '@/domain/validation/rules'; import { validateProject } from '@/domain/validation/engine'; import type { Lageplan, Planlinie } from '@/domain/geometrie/lageplan'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import { WEGNACHWEIS_HANDEINGABE, wegnachweisAusLageplan, type Project, } from '@/domain/model/project'; /** * Befund 7, behoben in Fassung 5.10.0. * * Beide Messgrundlage-Regeln hingen allein an `istAuswertbar(project.lageplan)` * - also daran, dass auf der Unterlage noch eine Fahrlinie mit zwei Punkten * LIEGT. Seit Schemastand 9 fuehrt das Modell aber den dauerhaften Nachweis * `Conflict.wegnachweis.herkunft`, und `removePlanlinie` loescht nur die * Zeichnung, nicht den Nachweis. Wer nach dem Uebernehmen der Wege aufraeumt - * oder die Kalibrierung entfernt -, verlor damit den Fehler und mit ihm die * Ausgabesperre, waehrend der Ausdruck in der Spalte "Herkunft der Wege" * weiter "Aus dem Lageplan vermessen" druckt. * * Die Richtung ist die unsichere: Aus einem Uebersichtsbild abgegriffen faellt * der Raeumweg einer Furt zu kurz aus und mit ihm die Zwischenzeit. */ const STICHTAG = new Date('2026-01-01T00:00:00Z'); /** 1 m je Bildpunkt - reines Uebersichtsbild, auch fuer Kfz zu grob. */ const SEHR_GROB = 1.0; /** 28,6 cm je Bildpunkt - fuer Kfz brauchbar, fuer die Furt nicht. */ const GROB = 2000 / 7000; /** Fahrlinie mit `punkte` Punkten; ab zwei Punkten ist der Lageplan auswertbar. */ function fahrlinie(punkte: number): Planlinie { return { id: 'l1', name: 'l1', mode: 'kfz', movement: 'geradeaus', breiteMeter: 3.25, punkte: [ { x: 10, y: 10 }, { x: 10, y: 90 }, ].slice(0, punkte), haltlinieId: null, startT: 0.5, signalGroupId: null, }; } /** Bildunterlage mit Massstab `meterJeBildpunkt` und `linienPunkte` Punkten in einer Fahrlinie. */ function lageplan(meterJeBildpunkt: number, linienPunkte: number, kalibriert = true): Lageplan { return { arbeitsbereiche: [], signalgeber: [], bild: { datenUrl: 'data:image/png;base64,AAAA', breite: 7000, hoehe: 7000, herkunft: 'Pruefbild', geladenAm: '2026-03-04T09:15:00.000Z', }, kalibrierung: kalibriert ? { von: { x: 0, y: 0 }, bis: { x: 100, y: 0 }, laengeMeter: meterJeBildpunkt * 100, herkunft: 'georeferenziert', } : null, haltlinien: [], linien: linienPunkte > 0 ? [fahrlinie(linienPunkte)] : [], }; } /** * Regelknotenpunkt mit Unterlage. `uebernommen` setzt an allen Konflikten den * Nachweis, den `uebernimmWege` setzt. */ function prueffall(options: { meterJeBildpunkt?: number; linienPunkte?: number; kalibriert?: boolean; uebernommen?: boolean; }): Project { const basis = createStandardIntersectionProject('Prueffall', STICHTAG); const nachweis = options.uebernommen === true ? wegnachweisAusLageplan(STICHTAG) : undefined; return { ...basis, lageplan: lageplan( options.meterJeBildpunkt ?? SEHR_GROB, options.linienPunkte ?? 0, options.kalibriert ?? true, ), ...(nachweis === undefined ? {} : { conflicts: basis.conflicts.map((c) => ({ ...c, wegnachweis: nachweis })) }), }; } describe('Messgrundlage: der Wegnachweis haelt die Sperre, wenn die Zeichnung fort ist', () => { it('meldet den Uebersichtsfall auch ohne Fahrlinie, sobald Wege uebernommen sind', () => { // Der Ausloeser: Uebersichtsbild, Wege uebernommen, danach die einzige // Fahrlinie geloescht. Die Wege und ihre Zwischenzeiten bleiben im Plan. const befunde = messgrundlageBefunde(prueffall({ uebernommen: true })); expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-uebersicht']); expect(befunde[0]!.severity).toBe('fehler'); // Der Satz darf keine Fahrlinie behaupten, die es nicht mehr gibt. expect(befunde[0]!.message).not.toContain('sind bereits Fahrlinien gezeichnet'); expect(befunde[0]!.message).toContain('übernommen'); }); it('sperrt die Ausgabe in diesem Zustand', () => { const bericht = validateProject(prueffall({ uebernommen: true }), undefined, STICHTAG); expect( bericht.findings.some( (f) => f.rule === 'zwischenzeiten.messgrundlage-uebersicht' && f.severity === 'fehler', ), ).toBe(true); expect(bericht.exportBlocked).toBe(true); }); it('meldet ebenso, wenn die Fahrlinie auf einen Punkt zusammengeschrumpft ist', () => { // `istAuswertbar` verlangt zwei Punkte. Eine Linie auf einen Punkt zu // kuerzen ist derselbe Weg zum selben Zustand wie das Loeschen. const befunde = messgrundlageBefunde(prueffall({ linienPunkte: 1, uebernommen: true })); expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-uebersicht']); expect(befunde[0]!.severity).toBe('fehler'); }); it('stuft den Fall "nur Kfz" ohne Fahrlinie als Fehler ein, wenn Wege uebernommen sind', () => { // Ohne Nachweis bleibt es bei der Warnung (messgrundlage.test.ts). Steht // der Nachweis, ist abgegriffen worden - dann gilt dieselbe Einstufung wie // bei liegender Fahrlinie. const befunde = messgrundlageBefunde(prueffall({ meterJeBildpunkt: GROB, uebernommen: true })); expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-nur-kfz']); expect(befunde[0]!.severity).toBe('fehler'); expect(befunde[0]!.message).not.toContain('sind bereits Fahrlinien gezeichnet'); }); it('meldet den fehlenden Massstab, solange Wege als vermessen ausgewiesen sind', () => { // Zweiter Weg zum selben Zustand: die Kalibrierung entfernen. Dann liefert // `messgenauigkeit` null, die Eignung ist nicht mehr beurteilbar - und der // Ausdruck behauptet trotzdem eine Vermessung. const befunde = messgrundlageBefunde(prueffall({ kalibriert: false, uebernommen: true })); expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-ohne-massstab']); expect(befunde[0]!.severity).toBe('fehler'); expect(befunde[0]!.message).toContain('Herkunft der Wege'); expect(befunde[0]!.suggestion).not.toBe(''); const bericht = validateProject( prueffall({ kalibriert: false, uebernommen: true }), undefined, STICHTAG, ); expect(bericht.exportBlocked).toBe(true); }); it('schweigt weiter, wenn kein Weg aus dem Lageplan stammt', () => { // Die Regel darf nicht ins Blaue melden: Ohne Zeichnung und ohne Nachweis // ist nichts abgegriffen worden. const basis = prueffall({}); const vonHand: Project = { ...basis, conflicts: basis.conflicts.map((c) => ({ ...c, wegnachweis: WEGNACHWEIS_HANDEINGABE })), }; expect(messgrundlageBefunde(basis)).toHaveLength(0); expect(messgrundlageBefunde(vonHand)).toHaveLength(0); expect( messgrundlageBefunde({ ...vonHand, lageplan: lageplan(SEHR_GROB, 0, false), }), ).toHaveLength(0); }); });