lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests domain messgrundlageWegnachweis.test.ts

6,9 KB Rohdatei
tests/domain/messgrundlageWegnachweis.test.ts — 184 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { messgrundlageBefunde } from '@/domain/validation/rules';
3 import { validateProject } from '@/domain/validation/engine';
4 import type { Lageplan, Planlinie } from '@/domain/geometrie/lageplan';
5 import { createStandardIntersectionProject } from '@/domain/model/factory';
6 import {
7 WEGNACHWEIS_HANDEINGABE,
8 wegnachweisAusLageplan,
9 type Project,
10 } from '@/domain/model/project';
11
12 /**
13 * Befund 7, behoben in Fassung 5.10.0.
14 *
15 * Beide Messgrundlage-Regeln hingen allein an `istAuswertbar(project.lageplan)`
16 * - also daran, dass auf der Unterlage noch eine Fahrlinie mit zwei Punkten
17 * LIEGT. Seit Schemastand 9 fuehrt das Modell aber den dauerhaften Nachweis
18 * `Conflict.wegnachweis.herkunft`, und `removePlanlinie` loescht nur die
19 * Zeichnung, nicht den Nachweis. Wer nach dem Uebernehmen der Wege aufraeumt -
20 * oder die Kalibrierung entfernt -, verlor damit den Fehler und mit ihm die
21 * Ausgabesperre, waehrend der Ausdruck in der Spalte "Herkunft der Wege"
22 * weiter "Aus dem Lageplan vermessen" druckt.
23 *
24 * Die Richtung ist die unsichere: Aus einem Uebersichtsbild abgegriffen faellt
25 * der Raeumweg einer Furt zu kurz aus und mit ihm die Zwischenzeit.
26 */
27
28 const STICHTAG = new Date('2026-01-01T00:00:00Z');
29
30 /** 1 m je Bildpunkt - reines Uebersichtsbild, auch fuer Kfz zu grob. */
31 const SEHR_GROB = 1.0;
32 /** 28,6 cm je Bildpunkt - fuer Kfz brauchbar, fuer die Furt nicht. */
33 const GROB = 2000 / 7000;
34
35 /** Fahrlinie mit `punkte` Punkten; ab zwei Punkten ist der Lageplan auswertbar. */
36 function fahrlinie(punkte: number): Planlinie {
37 return {
38 id: 'l1',
39 name: 'l1',
40 mode: 'kfz',
41 movement: 'geradeaus',
42 breiteMeter: 3.25,
43 punkte: [
44 { x: 10, y: 10 },
45 { x: 10, y: 90 },
46 ].slice(0, punkte),
47 haltlinieId: null,
48 startT: 0.5,
49 signalGroupId: null,
50 };
51 }
52
53 /** Bildunterlage mit Massstab `meterJeBildpunkt` und `linienPunkte` Punkten in einer Fahrlinie. */
54 function lageplan(meterJeBildpunkt: number, linienPunkte: number, kalibriert = true): Lageplan {
55 return {
56 arbeitsbereiche: [],
57 signalgeber: [],
58 bild: {
59 datenUrl: 'data:image/png;base64,AAAA',
60 breite: 7000,
61 hoehe: 7000,
62 herkunft: 'Pruefbild',
63 geladenAm: '2026-03-04T09:15:00.000Z',
64 },
65 kalibrierung: kalibriert
66 ? {
67 von: { x: 0, y: 0 },
68 bis: { x: 100, y: 0 },
69 laengeMeter: meterJeBildpunkt * 100,
70 herkunft: 'georeferenziert',
71 }
72 : null,
73 haltlinien: [],
74 linien: linienPunkte > 0 ? [fahrlinie(linienPunkte)] : [],
75 };
76 }
77
78 /**
79 * Regelknotenpunkt mit Unterlage. `uebernommen` setzt an allen Konflikten den
80 * Nachweis, den `uebernimmWege` setzt.
81 */
82 function prueffall(options: {
83 meterJeBildpunkt?: number;
84 linienPunkte?: number;
85 kalibriert?: boolean;
86 uebernommen?: boolean;
87 }): Project {
88 const basis = createStandardIntersectionProject('Prueffall', STICHTAG);
89 const nachweis = options.uebernommen === true ? wegnachweisAusLageplan(STICHTAG) : undefined;
90 return {
91 ...basis,
92 lageplan: lageplan(
93 options.meterJeBildpunkt ?? SEHR_GROB,
94 options.linienPunkte ?? 0,
95 options.kalibriert ?? true,
96 ),
97 ...(nachweis === undefined
98 ? {}
99 : { conflicts: basis.conflicts.map((c) => ({ ...c, wegnachweis: nachweis })) }),
100 };
101 }
102
103 describe('Messgrundlage: der Wegnachweis haelt die Sperre, wenn die Zeichnung fort ist', () => {
104 it('meldet den Uebersichtsfall auch ohne Fahrlinie, sobald Wege uebernommen sind', () => {
105 // Der Ausloeser: Uebersichtsbild, Wege uebernommen, danach die einzige
106 // Fahrlinie geloescht. Die Wege und ihre Zwischenzeiten bleiben im Plan.
107 const befunde = messgrundlageBefunde(prueffall({ uebernommen: true }));
108
109 expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-uebersicht']);
110 expect(befunde[0]!.severity).toBe('fehler');
111 // Der Satz darf keine Fahrlinie behaupten, die es nicht mehr gibt.
112 expect(befunde[0]!.message).not.toContain('sind bereits Fahrlinien gezeichnet');
113 expect(befunde[0]!.message).toContain('übernommen');
114 });
115
116 it('sperrt die Ausgabe in diesem Zustand', () => {
117 const bericht = validateProject(prueffall({ uebernommen: true }), undefined, STICHTAG);
118
119 expect(
120 bericht.findings.some(
121 (f) => f.rule === 'zwischenzeiten.messgrundlage-uebersicht' && f.severity === 'fehler',
122 ),
123 ).toBe(true);
124 expect(bericht.exportBlocked).toBe(true);
125 });
126
127 it('meldet ebenso, wenn die Fahrlinie auf einen Punkt zusammengeschrumpft ist', () => {
128 // `istAuswertbar` verlangt zwei Punkte. Eine Linie auf einen Punkt zu
129 // kuerzen ist derselbe Weg zum selben Zustand wie das Loeschen.
130 const befunde = messgrundlageBefunde(prueffall({ linienPunkte: 1, uebernommen: true }));
131
132 expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-uebersicht']);
133 expect(befunde[0]!.severity).toBe('fehler');
134 });
135
136 it('stuft den Fall "nur Kfz" ohne Fahrlinie als Fehler ein, wenn Wege uebernommen sind', () => {
137 // Ohne Nachweis bleibt es bei der Warnung (messgrundlage.test.ts). Steht
138 // der Nachweis, ist abgegriffen worden - dann gilt dieselbe Einstufung wie
139 // bei liegender Fahrlinie.
140 const befunde = messgrundlageBefunde(prueffall({ meterJeBildpunkt: GROB, uebernommen: true }));
141
142 expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-nur-kfz']);
143 expect(befunde[0]!.severity).toBe('fehler');
144 expect(befunde[0]!.message).not.toContain('sind bereits Fahrlinien gezeichnet');
145 });
146
147 it('meldet den fehlenden Massstab, solange Wege als vermessen ausgewiesen sind', () => {
148 // Zweiter Weg zum selben Zustand: die Kalibrierung entfernen. Dann liefert
149 // `messgenauigkeit` null, die Eignung ist nicht mehr beurteilbar - und der
150 // Ausdruck behauptet trotzdem eine Vermessung.
151 const befunde = messgrundlageBefunde(prueffall({ kalibriert: false, uebernommen: true }));
152
153 expect(befunde.map((f) => f.rule)).toEqual(['zwischenzeiten.messgrundlage-ohne-massstab']);
154 expect(befunde[0]!.severity).toBe('fehler');
155 expect(befunde[0]!.message).toContain('Herkunft der Wege');
156 expect(befunde[0]!.suggestion).not.toBe('');
157
158 const bericht = validateProject(
159 prueffall({ kalibriert: false, uebernommen: true }),
160 undefined,
161 STICHTAG,
162 );
163 expect(bericht.exportBlocked).toBe(true);
164 });
165
166 it('schweigt weiter, wenn kein Weg aus dem Lageplan stammt', () => {
167 // Die Regel darf nicht ins Blaue melden: Ohne Zeichnung und ohne Nachweis
168 // ist nichts abgegriffen worden.
169 const basis = prueffall({});
170 const vonHand: Project = {
171 ...basis,
172 conflicts: basis.conflicts.map((c) => ({ ...c, wegnachweis: WEGNACHWEIS_HANDEINGABE })),
173 };
174
175 expect(messgrundlageBefunde(basis)).toHaveLength(0);
176 expect(messgrundlageBefunde(vonHand)).toHaveLength(0);
177 expect(
178 messgrundlageBefunde({
179 ...vonHand,
180 lageplan: lageplan(SEHR_GROB, 0, false),
181 }),
182 ).toHaveLength(0);
183 });
184 });