lsa-planer

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

/ tests domain wartezeitVerkehrsart.test.ts

10,1 KB Rohdatei
tests/domain/wartezeitVerkehrsart.test.ts — 232 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { buildSignalPlan } from '@/domain/plan/signalPlan';
3 import { wartezeitBefunde } from '@/domain/validation/rules';
4 import {
5 createConflict,
6 createEmptyProject,
7 createPhase,
8 createSignalGroup,
9 } from '@/domain/model/factory';
10 import type { Project } from '@/domain/model/project';
11
12 /**
13 * Befund 29, behoben in Fassung 5.10.0.
14 *
15 * Folge und naechster Schritt der Wartezeitmeldung wurden allein aus
16 * `plan.anlagenart` gewaehlt; die Verkehrsart der beanstandeten Signalgruppe
17 * ging nicht ein. Der gedruckte Pruefbericht behauptete dadurch ueber einen
18 * Kraftfahrzeugstrom, hier gehe jemand "als Fußgänger bei Rot" und die Furt
19 * verliere ihre Schutzwirkung - eine falsche Sachaussage in einer Unterlage,
20 * die einer Behoerde vorgelegt wird. Spiegelbildlich bekam eine Furt an einer
21 * einstreifigen Verkehrsfuehrung den Satz ueber das Einfahren in die Engstelle.
22 *
23 * Die Schwelle selbst bleibt die der Anlagenart: Fuer den Fahrverkehr wirkt sie
24 * eng, und das ist die sichere Seite. Gesagt wird das jetzt aber auch.
25 */
26
27 const DATUM = new Date('2026-01-01T00:00:00Z');
28
29 /** Fussgaengerschutzanlage mit Fahrverkehr K1 und Furt F1, feste Umlaufzeit. */
30 function schutzanlage(umlaufzeit: number, gruenFurt: number): Project {
31 const basis = createEmptyProject('Furt', DATUM, 'fussgaengerschutzanlage');
32 const kfz = createSignalGroup({ name: 'K1', mode: 'kfz', index: 0 });
33 const fuss = createSignalGroup({ name: 'F1', mode: 'fuss', index: 1 });
34 const phase1 = createPhase('Fahrverkehr', [kfz.id]);
35 const phase2 = createPhase('Furt', [fuss.id]);
36 return {
37 ...basis,
38 signalGroups: [kfz, fuss],
39 conflicts: [
40 { ...createConflict(kfz.id, fuss.id), clearingDistance: 25, enteringDistance: 2 },
41 { ...createConflict(fuss.id, kfz.id), clearingDistance: 12, enteringDistance: 5 },
42 ],
43 phases: [phase1, { ...phase2, manualGreen: gruenFurt }],
44 program: {
45 ...basis.program,
46 phaseOrder: [phase1.id, phase2.id],
47 method: 'manuell',
48 manualCycleTime: umlaufzeit,
49 },
50 };
51 }
52
53 /** Einstreifige Verkehrsfuehrung ohne Furt - zwei Richtungen, ein Fahrstreifen. */
54 function engstelle(laenge: number): Project {
55 const basis = createEmptyProject('Arbeitsstelle', DATUM, 'einstreifig');
56 const nord = createSignalGroup({ name: 'A1', mode: 'kfz', index: 0 });
57 const sued = createSignalGroup({ name: 'A2', mode: 'kfz', index: 1 });
58 const p1 = createPhase('Richtung Nord', [nord.id]);
59 const p2 = createPhase('Richtung Sued', [sued.id]);
60 return {
61 ...basis,
62 signalGroups: [nord, sued],
63 conflicts: [
64 { ...createConflict(nord.id, sued.id), clearingDistance: laenge, enteringDistance: 0 },
65 { ...createConflict(sued.id, nord.id), clearingDistance: laenge, enteringDistance: 0 },
66 ],
67 phases: [p1, p2],
68 program: { ...basis.program, phaseOrder: [p1.id, p2.id] },
69 };
70 }
71
72 /** Einstreifige Verkehrsfuehrung mit einer Furt ueber die Engstelle. */
73 function engstelleMitFurt(laenge: number): Project {
74 const basis = createEmptyProject('Arbeitsstelle', DATUM, 'einstreifig');
75 const nord = createSignalGroup({ name: 'A1', mode: 'kfz', index: 0 });
76 const sued = createSignalGroup({ name: 'A2', mode: 'kfz', index: 1 });
77 const furt = createSignalGroup({ name: 'F1', mode: 'fuss', index: 2 });
78 const p1 = createPhase('Richtung Nord', [nord.id]);
79 const p2 = createPhase('Richtung Sued', [sued.id]);
80 const p3 = createPhase('Furt', [furt.id]);
81 return {
82 ...basis,
83 signalGroups: [nord, sued, furt],
84 conflicts: [
85 { ...createConflict(nord.id, sued.id), clearingDistance: laenge, enteringDistance: 0 },
86 { ...createConflict(sued.id, nord.id), clearingDistance: laenge, enteringDistance: 0 },
87 { ...createConflict(nord.id, furt.id), clearingDistance: 20, enteringDistance: 2 },
88 { ...createConflict(furt.id, nord.id), clearingDistance: 8, enteringDistance: 5 },
89 { ...createConflict(sued.id, furt.id), clearingDistance: 20, enteringDistance: 2 },
90 { ...createConflict(furt.id, sued.id), clearingDistance: 8, enteringDistance: 5 },
91 ],
92 phases: [p1, p2, { ...p3, manualGreen: 10 }],
93 program: { ...basis.program, phaseOrder: [p1.id, p2.id, p3.id] },
94 };
95 }
96
97 /**
98 * Einstreifige Verkehrsfuehrung mit einer Radsignalgruppe, die DURCH die
99 * Engstelle faehrt.
100 *
101 * R1 traegt die Vorbelegung `movement: 'geradeaus'` (factory.ts,
102 * defaultMovement) - nach dem Modell faehrt die Gruppe also dieselbe Strecke
103 * wie die beiden Kfz-Richtungen und quert nichts. Eine eigene Phase bekommt
104 * sie, weil sie mit 4 m/s raeumt und dafuer ein Vielfaches der Zeit braucht.
105 */
106 function engstelleMitRad(laenge: number): Project {
107 const basis = createEmptyProject('Arbeitsstelle', DATUM, 'einstreifig');
108 const nord = createSignalGroup({ name: 'A1', mode: 'kfz', index: 0 });
109 const sued = createSignalGroup({ name: 'A2', mode: 'kfz', index: 1 });
110 const rad = createSignalGroup({ name: 'R1', mode: 'rad', index: 2 });
111 const p1 = createPhase('Richtung Nord', [nord.id]);
112 const p2 = createPhase('Richtung Sued', [sued.id]);
113 const p3 = createPhase('Radverkehr', [rad.id]);
114 const durch = (vonId: string, nachId: string) => ({
115 ...createConflict(vonId, nachId),
116 clearingDistance: laenge,
117 enteringDistance: 0,
118 });
119 return {
120 ...basis,
121 signalGroups: [nord, sued, rad],
122 conflicts: [
123 durch(nord.id, sued.id),
124 durch(sued.id, nord.id),
125 durch(nord.id, rad.id),
126 durch(rad.id, nord.id),
127 durch(sued.id, rad.id),
128 durch(rad.id, sued.id),
129 ],
130 phases: [p1, p2, p3],
131 program: { ...basis.program, phaseOrder: [p1.id, p2.id, p3.id] },
132 };
133 }
134
135 describe('Wartezeitmeldung: Folge und naechster Schritt nach der Verkehrsart', () => {
136 it('sagt ueber den Fahrverkehr an der Schutzanlage nichts von Fussgaengern', () => {
137 // 100 s Umlauf, 40 s Furtfreigabe: K1 wartet 55 s, F1 60 s - beide ueber
138 // der Schwelle von 45 s. Der Befund zu K1 ist der, der bisher den falschen
139 // Satz trug.
140 const befunde = wartezeitBefunde(buildSignalPlan(schutzanlage(100, 40)));
141 const kfz = befunde.find((f) => f.title.includes('K1'));
142
143 expect(
144 kfz,
145 'Der Fahrverkehr muss die Schwelle reissen, sonst prueft der Fall nichts',
146 ).toBeDefined();
147 expect(kfz!.message).not.toContain('als Fußgänger');
148 expect(kfz!.message).not.toContain('Schutzwirkung');
149 expect(kfz!.message).toContain('Rotlichtverstöße');
150 // Der naechste Schritt darf nicht mit der Wartezeit der Querenden begruendet
151 // werden - beanstandet ist die des Fahrverkehrs.
152 expect(kfz!.suggestion).not.toContain('nicht die Leistungsfähigkeit der Fahrbahn das Maß');
153 });
154
155 it('behaelt fuer die Furt an derselben Anlage den Fussgaengertext', () => {
156 const befunde = wartezeitBefunde(buildSignalPlan(schutzanlage(100, 40)));
157 const furt = befunde.find((f) => f.title.includes('F1'));
158
159 expect(furt).toBeDefined();
160 expect(furt!.message).toContain('geht bei Rot');
161 expect(furt!.message).toContain('Schutzwirkung');
162 expect(furt!.suggestion).toContain('Wartezeit der Querenden');
163 });
164
165 it('nennt fuer den Fahrverkehr die Herkunft der engen Schwelle', () => {
166 // Die Schwelle bleibt die der Anlagenart (45/90 s) - sie ist an der
167 // Wartezeit der Querenden geeicht und damit fuer den Fahrverkehr eng. Das
168 // ist die sichere Seite, gehoert aber in die Unterlage.
169 const kfz = wartezeitBefunde(buildSignalPlan(schutzanlage(100, 40))).find((f) =>
170 f.title.includes('K1'),
171 )!;
172
173 expect(kfz.message).toContain('Querenden');
174 });
175
176 it('sagt ueber eine Furt an der Engstelle nichts vom Einfahren in die Engstelle', () => {
177 /*
178 * 1000 m Engstelle, 10 s Furtfreigabe und 40 s je Fahrtrichtung: F1 wartet
179 * ueber 180 s und reisst die Schwelle der Anlagenart. Ein zu Fuss Gehender
180 * faehrt nicht in die Engstelle ein, und ihm kommt dort auch kein
181 * Gegenverkehr entgegen.
182 *
183 * DIE 20 S JE RICHTUNG SIND MIT 5.27.0 HINZUGEKOMMEN: Seit die Engstelle
184 * nach RiLSA 2015, Abschnitt 5.2.2 gerechnet wird, ist die Zwischenzeit
185 * bei 1000 m um 10 s kuerzer (94 statt 104 s), und mit den kuerzest
186 * zulaessigen Freigabezeiten blieb die Wartezeit knapp unter der Schwelle.
187 * Der Fall pruefte dann nichts mehr - deshalb eine Freigabezeit, die man
188 * an einer Baustellenampel tatsaechlich anordnen wuerde.
189 */
190 const roh = engstelleMitFurt(1000);
191 const mitFreigabe: Project = {
192 ...roh,
193 phases: roh.phases.map((phase, index) => (index < 2 ? { ...phase, manualGreen: 40 } : phase)),
194 };
195 const befunde = wartezeitBefunde(buildSignalPlan(mitFreigabe));
196 const furt = befunde.find((f) => f.title.includes('F1'));
197
198 expect(furt, 'Die Furt muss die Schwelle reissen, sonst prueft der Fall nichts').toBeDefined();
199 expect(furt!.message).not.toContain('fährt bei Rot in die Engstelle ein');
200 expect(furt!.message).not.toContain('Gegenverkehr');
201 expect(furt!.message).toContain('quert bei Rot');
202 });
203
204 it('sagt ueber eine durchfahrende Radgruppe nichts vom Queren', () => {
205 // Nachtrag zu Befund 29: Die Verzweigung kennt nur die Verkehrsart, nicht
206 // die Fahrbeziehung. R1 faehrt nach dem Modell geradeaus durch die
207 // Engstelle - "quert bei Rot" waere eine Behauptung ueber eine Bewegung,
208 // die das Projekt nicht kennt, und "trifft auf einen Fahrverkehr, der die
209 // Engstelle in einem Zug durchfaehrt" stellt die Gruppe neben einen
210 // Verkehr, in dem sie selbst faehrt.
211 const befunde = wartezeitBefunde(buildSignalPlan(engstelleMitRad(400)));
212 const rad = befunde.find((f) => f.title.includes('R1'));
213
214 expect(
215 rad,
216 'Die Radgruppe muss die Schwelle reissen, sonst prueft der Fall nichts',
217 ).toBeDefined();
218 expect(rad!.message).not.toContain('quert bei Rot');
219 expect(rad!.message).toContain('Engstelle');
220 expect(rad!.message).toContain('Gegenverkehr');
221 });
222
223 it('behaelt fuer den Fahrverkehr an der Engstelle den Engstellensatz', () => {
224 const kfz = wartezeitBefunde(buildSignalPlan(engstelle(800))).find((f) =>
225 f.title.includes('A1'),
226 );
227
228 expect(kfz).toBeDefined();
229 expect(kfz!.message).toContain('Engstelle');
230 expect(kfz!.message).toContain('Gegenverkehr');
231 });
232 });