lsa-planer

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

/ tests domain vorgabeOhneRaeumweg.test.ts

6,1 KB Rohdatei
tests/domain/vorgabeOhneRaeumweg.test.ts — 137 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { validateProject } from '@/domain/validation/engine';
3 import { buildSignalPlan, intergreenKey } from '@/domain/plan/signalPlan';
4 import { createStandardIntersectionProject } from '@/domain/model/factory';
5 import type { Conflict, Project } from '@/domain/model/project';
6
7 /**
8 * Befund 19, behoben in Fassung 5.10.0.
9 *
10 * 'konflikte.raeumweg-fehlt' greift nur bei `manualIntergreen === null`, und
11 * die Gegenprobe zur Handvorgabe (signalPlan.ts) vergleicht nur bei
12 * `clearingDistance > 0`. Bei "Handvorgabe gesetzt UND Raeumweg 0 m" schwiegen
13 * damit beide gleichzeitig: Der Eintrag im Nachbarfeld verwandelte eine
14 * sperrende Beanstandung in einen ausgabefaehigen Plan, ohne dass irgendeine
15 * Meldung den fehlenden Weg noch erwaehnte. Mit der Vorgabe entfaellt zugleich
16 * der Rechenweg der Beziehung - ein Pruefer kann die Zahl daneben durch nichts
17 * nachrechnen.
18 *
19 * Die Richtung ist die unsichere: Jeder beliebig kleine Wert ab 0 s ist so
20 * meldungsfrei und ohne Sperre eintragbar.
21 */
22
23 const STICHTAG = new Date('2026-01-01T00:00:00Z');
24
25 /** Regelknotenpunkt, in dem genau die Beziehung K1 nach K3 abgewandelt ist. */
26 function mitKonflikt(aenderung: Partial<Conflict>): { projekt: Project; konflikt: Conflict } {
27 const roh = createStandardIntersectionProject('Vorgabe', STICHTAG);
28 const [k1, , k3] = roh.signalGroups;
29 const ziel = roh.conflicts.find((c) => c.fromId === k1!.id && c.toId === k3!.id)!;
30 const geaendert = { ...ziel, ...aenderung };
31 return {
32 konflikt: geaendert,
33 projekt: {
34 ...roh,
35 conflicts: roh.conflicts.map((c) => (c.id === ziel.id ? geaendert : c)),
36 meta: {
37 ...roh.meta,
38 projectNumber: 'VP-2026-001',
39 client: 'Stadt Musterhausen',
40 planner: 'O. Willerding',
41 location: 'Vorgabe',
42 },
43 },
44 };
45 }
46
47 function regeln(projekt: Project): readonly string[] {
48 return validateProject(projekt, buildSignalPlan(projekt), STICHTAG).findings.map((f) => f.rule);
49 }
50
51 describe('Eine Handvorgabe hebt die Raeumweg-Pflicht nicht auf', () => {
52 it('sperrt weiterhin ohne Handvorgabe', () => {
53 // Ausgangslage: derselbe Plan, nur ohne die Zahl im Nachbarfeld.
54 const { projekt } = mitKonflikt({ clearingDistance: 0 });
55 const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG);
56
57 expect(bericht.findings.map((f) => f.rule)).toContain('konflikte.raeumweg-fehlt');
58 expect(bericht.exportBlocked).toBe(true);
59 });
60
61 it('sperrt auch mit einer gueltigen Handvorgabe neben dem Raeumweg 0 m', () => {
62 const { projekt, konflikt } = mitKonflikt({ manualIntergreen: 5, clearingDistance: 0 });
63 const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG);
64
65 const befund = bericht.findings.find((f) => f.rule === 'konflikte.vorgabe-ohne-raeumweg');
66 expect(
67 befund,
68 'Die Vorgabe ohne Vergleichsgrundlage darf nicht stillschweigend bleiben',
69 ).toBeDefined();
70 expect(befund!.severity).toBe('fehler');
71 expect(befund!.category).toBe('konflikte');
72 expect(befund!.target?.kind).toBe('konflikt');
73 expect(befund!.target?.id).toBe(konflikt.id);
74 expect(befund!.target?.label).toBe('K1 nach K3');
75 // Beide Zahlen des Widerspruchs gehoeren in den Text.
76 expect(befund!.message).toContain('5 s');
77 expect(befund!.message).toContain('Räumweg');
78 expect(bericht.exportBlocked).toBe(true);
79 });
80
81 it('meldet dabei nicht den Wortlaut der Raeumwegregel', () => {
82 // 'konflikte.raeumweg-fehlt' sagt "Die Zwischenzeit wird dadurch zu kurz".
83 // Bei einer Handvorgabe wird gar nicht gerechnet; der Satz traefe nicht zu.
84 const { projekt } = mitKonflikt({ manualIntergreen: 5, clearingDistance: 0 });
85
86 expect(regeln(projekt)).not.toContain('konflikte.raeumweg-fehlt');
87 });
88
89 it('behauptet ueber die Planunterlage nur, was in ihr steht', () => {
90 /*
91 * Nachtrag zu Befund 19: Die Meldung nannte "einen Prüfer, dem die
92 * Zwischenzeitenmatrix den Räumweg mit 0,00 m ausweist". Beides gibt die
93 * Unterlage nicht her. Bei vorgegebener Zwischenzeit ist
94 * `resolved.calculation` null; im Ausdruck steht in der Spalte des
95 * Raeumwegs deshalb "vorgegeben" und in den uebrigen Spalten des
96 * Rechenwegs ein Strich (services/export/pdf.ts, drawIntergreenDetails).
97 * Der unvermassene Weg selbst wird im Ausdruck nirgends gedruckt.
98 *
99 * Genau der Fehlertyp, gegen den die Befunde 29 und 30 dieser Gruppe
100 * angetreten sind: ein Prueftext, der etwas ueber die Unterlage behauptet,
101 * was die Unterlage nicht zeigt.
102 */
103 const { projekt, konflikt } = mitKonflikt({ manualIntergreen: 5, clearingDistance: 0 });
104 const plan = buildSignalPlan(projekt);
105 const aufgeloest = plan.intergreens.get(intergreenKey(konflikt.fromId, konflikt.toId))!;
106
107 // Die Tatsache, auf die sich der Satz stuetzen darf.
108 expect(aufgeloest.source).toBe('vorgegeben');
109 expect(aufgeloest.calculation, 'Ohne Rechnung gibt es keinen Rechenweg').toBeNull();
110
111 const befund = validateProject(projekt, plan, STICHTAG).findings.find(
112 (f) => f.rule === 'konflikte.vorgabe-ohne-raeumweg',
113 )!;
114
115 expect(befund.message).not.toContain('0,00');
116 expect(befund.message).not.toContain('Zwischenzeitenmatrix');
117 expect(befund.message).toContain('Rechenweg');
118 });
119
120 it('schweigt, sobald der Raeumweg vermasst ist', () => {
121 const { projekt } = mitKonflikt({ manualIntergreen: 5 });
122
123 expect(regeln(projekt)).not.toContain('konflikte.vorgabe-ohne-raeumweg');
124 });
125
126 it('laesst die unbrauchbare Handvorgabe beim bisherigen Weg', () => {
127 // Eine negative Vorgabe gilt als nicht gesetzt: Es wird gerechnet, und
128 // 'signalplan.raeumweg-null' ist dort die zustaendige Meldung
129 // (tests/domain/doppelmeldungen.test.ts). Eine zweite Meldung
130 // ueber denselben Sachverhalt waere die Doppelung, die dort abgewehrt wird.
131 const { projekt } = mitKonflikt({ manualIntergreen: -1, clearingDistance: 0 });
132 const gefunden = regeln(projekt);
133
134 expect(gefunden).toContain('signalplan.raeumweg-null');
135 expect(gefunden).not.toContain('konflikte.vorgabe-ohne-raeumweg');
136 });
137 });