lsa-planer

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

/ tests domain geschwindigkeitsschranken.test.ts

13,1 KB Rohdatei
tests/domain/geschwindigkeitsschranken.test.ts — 320 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { clearingSpeedFor, computeIntergreen } from '@/domain/rilsa/intergreen';
3 import { RILSA_DEFAULTS, SAFETY_FLOORS, type RilsaDefaults } from '@/domain/rilsa/constants';
4 import { resolveRilsaSettings } from '@/domain/rilsa/settings';
5 import { ueberfahrzeitHerkunft } from '@/domain/rilsa/ansaetze';
6 import type { ClearingStream, EnteringStream } from '@/domain/rilsa/types';
7
8 /**
9 * Geschwindigkeiten und Schranken, behoben in Fassung 5.9.0.
10 *
11 * Befund 23 Zwischen "Raeumgeschwindigkeit Fussgaenger" und "Raeumgeschwindig-
12 * keit Fussgaenger mit erhoehtem Zeitbedarf" bestand keine Schranke.
13 * Wer den allgemeinen Wert unter den des Merkmals senkte, liess den
14 * mobilitaetseingeschraenkten Fussgaenger rechnerisch SCHNELLER
15 * raeumen als den Regelfall: Das angehakte Merkmal VERKUERZTE die
16 * Zwischenzeit. Dieselbe Luecke bestand zwischen "Kfz geradeaus" und
17 * "Kfz abbiegend", obwohl der Quelltext daneben zusagt, abbiegende
18 * Kraftfahrzeuge raeumten langsamer.
19 * Befund 28 Eine von Hand eingetragene Raeumgeschwindigkeit wurde nur gegen
20 * die Schranke der VERKEHRSART geprueft, nicht gegen den Regelwert
21 * der Fahrbeziehung.
22 * Befund 29 Zwischen Regelwert und Schranke - abbiegendes Kfz 7,0 bis 10,0 m/s,
23 * Furt mit erhoehtem Zeitbedarf 1,0 bis 1,2 m/s - lief die Eingabe
24 * ohne eine einzige Meldung durch und verkuerzte die Zwischenzeit.
25 * Befund 58 Eine verworfene Ueberfahrzeit-Handeingabe wurde im Rechenweg
26 * weiterhin als "von Hand eingetragen" ausgewiesen, obwohl mit dem
27 * Regelwert gerechnet wurde.
28 * Befund 59 Die Meldung zum OePNV mit Vmax ueber 70 km/h behauptete auch im
29 * Haltefall eine Ueberfahrzeit aus der obersten Staffelstufe.
30 */
31
32 function fussRaeumend(extra: Partial<ClearingStream> = {}): ClearingStream {
33 return {
34 mode: 'fuss',
35 movement: 'geradeaus',
36 clearingDistance: 12,
37 vehicleClass: 'keine',
38 vZul: 0,
39 ...extra,
40 };
41 }
42
43 function abbiegerRaeumend(extra: Partial<ClearingStream> = {}): ClearingStream {
44 return {
45 mode: 'kfz',
46 movement: 'rechts',
47 clearingDistance: 40,
48 vehicleClass: 'pkw',
49 vZul: 50,
50 ...extra,
51 };
52 }
53
54 function bahnRaeumend(extra: Partial<ClearingStream> = {}): ClearingStream {
55 return {
56 mode: 'oepnv',
57 movement: 'geradeaus',
58 clearingDistance: 30,
59 vehicleClass: 'strassenbahn',
60 vZul: 80,
61 ...extra,
62 };
63 }
64
65 function kfzEinfahrend(enteringDistance = 8): EnteringStream {
66 return { mode: 'kfz', movement: 'geradeaus', enteringDistance };
67 }
68
69 /** Kennwertsatz mit abgesenkter Raeumgeschwindigkeit der Fussgaenger. */
70 function mitLangsamenFussgaengern(fuss: number): RilsaDefaults {
71 return {
72 ...RILSA_DEFAULTS,
73 clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, fuss },
74 };
75 }
76
77 describe('Befund 23 - erhoehter Zeitbedarf darf die Zwischenzeit nie verkuerzen', () => {
78 it('setzt fuer die Furt mit erhoehtem Zeitbedarf hoechstens die Raeumgeschwindigkeit des Regelfalls an', () => {
79 // Ohne Riegel raeumte der mobilitaetseingeschraenkte Fussgaenger mit
80 // 1,0 m/s, waehrend der Regelfall nur 0,8 m/s bekam.
81 const defaults = mitLangsamenFussgaengern(0.8);
82 expect(clearingSpeedFor({ mode: 'fuss', movement: 'geradeaus' }, defaults)).toBe(0.8);
83 expect(
84 clearingSpeedFor({ mode: 'fuss', movement: 'geradeaus', reducedMobility: true }, defaults),
85 ).toBe(0.8);
86 });
87
88 it('verkuerzt die Zwischenzeit der Furt mit erhoehtem Zeitbedarf nicht', () => {
89 const defaults = mitLangsamenFussgaengern(0.8);
90 const ohne = computeIntergreen(fussRaeumend(), kfzEinfahrend(6), defaults);
91 const mit = computeIntergreen(
92 fussRaeumend({ reducedMobility: true }),
93 kfzEinfahrend(6),
94 defaults,
95 );
96
97 // 12 m / 0,8 m/s = 15 s in beiden Faellen; zuvor 12 m / 1,0 m/s = 12 s mit
98 // dem Merkmal - drei Sekunden kuerzer fuer die schutzbeduerftigste
99 // Verkehrsart.
100 expect(ohne.clearingTime).toBe(15);
101 expect(mit.clearingTime).toBe(15);
102 expect(mit.intergreen).toBeGreaterThanOrEqual(ohne.intergreen);
103 });
104
105 it('meldet den Widerspruch am Kennwertsatz, statt ihn still zu kappen', () => {
106 const ergebnis = computeIntergreen(
107 fussRaeumend({ reducedMobility: true }),
108 kfzEinfahrend(6),
109 mitLangsamenFussgaengern(0.8),
110 );
111 const notiz = ergebnis.notes.find(
112 (n) => n.code === 'erhoehter-zeitbedarf-schneller-als-regelfall',
113 );
114 expect(notiz?.severity).toBe('fehler');
115 expect(notiz?.message).toContain('erhöhtem Zeitbedarf');
116 });
117
118 it('kappt die Vorgabe "erhoehter Zeitbedarf" auf den Fussgaengerwert und weist die Abweichung aus', () => {
119 const { defaults, notes, deviations } = resolveRilsaSettings({ clearingSpeedFuss: 0.8 });
120
121 expect(defaults.clearingSpeed.fuss).toBe(0.8);
122 expect(defaults.clearingSpeed.fussReducedMobility).toBe(0.8);
123 const notiz = notes.find(
124 (n) => n.code === 'vorgabe-ueberschritten:clearingSpeedFussReducedMobility',
125 );
126 expect(notiz?.severity).toBe('fehler');
127 const zeile = deviations.find((d) => d.key === 'clearingSpeedFussReducedMobility');
128 expect(zeile?.appliedValue).toBe(0.8);
129 });
130
131 it('haelt die Obergrenze des Feldes an der Sicherheitsschranke aus constants.ts', () => {
132 const { defaults, notes } = resolveRilsaSettings({
133 clearingSpeedFussReducedMobility: SAFETY_FLOORS.maxClearingSpeedFuss + 0.3,
134 });
135 expect(defaults.clearingSpeed.fussReducedMobility).toBe(SAFETY_FLOORS.maxClearingSpeedFuss);
136 expect(
137 notes.some((n) => n.code === 'vorgabe-ueberschritten:clearingSpeedFussReducedMobility'),
138 ).toBe(true);
139 });
140
141 it('laesst abbiegende Kraftfahrzeuge nie schneller raeumen als geradeaus fahrende', () => {
142 const defaults: RilsaDefaults = {
143 ...RILSA_DEFAULTS,
144 clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, kfzStraight: 6 },
145 };
146 expect(clearingSpeedFor({ mode: 'kfz', movement: 'rechts' }, defaults)).toBe(6);
147
148 const { defaults: aufgeloest, notes } = resolveRilsaSettings({ clearingSpeedKfzStraight: 6 });
149 expect(aufgeloest.clearingSpeed.kfzTurning).toBe(6);
150 expect(
151 notes.some(
152 (n) =>
153 n.code === 'vorgabe-ueberschritten:clearingSpeedKfzTurning' && n.severity === 'fehler',
154 ),
155 ).toBe(true);
156 });
157 });
158
159 describe('Befunde 28 und 29 - Handeingabe ueber dem Regelwert des Stroms', () => {
160 it('meldet eine Raeumgeschwindigkeit oberhalb des Regelwerts der Fahrbeziehung', () => {
161 // Regelwert des Abbiegers 7,0 m/s, Schranke der Verkehrsart 10,0 m/s.
162 // 9 m/s lag dazwischen und lief bislang ohne ein Wort durch: tz fiel von
163 // 9 s auf 8 s.
164 const ergebnis = computeIntergreen(
165 abbiegerRaeumend({ clearingSpeedOverride: 9 }),
166 kfzEinfahrend(),
167 );
168 expect(ergebnis.clearingSpeed).toBe(9);
169 expect(ergebnis.intergreen).toBe(8);
170
171 const notiz = ergebnis.notes.find((n) => n.code === 'raeumgeschwindigkeit-ueber-regelwert');
172 expect(notiz?.severity).toBe('warnung');
173 expect(notiz?.message).toContain('9');
174 expect(notiz?.message).toContain('7');
175 expect(notiz?.message).toContain('zu begründen');
176 });
177
178 it('meldet eine Handeingabe, die den erhoehten Zeitbedarf aufhebt', () => {
179 // Regelwert der Furt mit erhoehtem Zeitbedarf 1,0 m/s, Schranke 1,2 m/s.
180 const ergebnis = computeIntergreen(
181 fussRaeumend({ reducedMobility: true, clearingSpeedOverride: 1.2 }),
182 kfzEinfahrend(6),
183 );
184 expect(ergebnis.clearingSpeed).toBe(1.2);
185 const notiz = ergebnis.notes.find((n) => n.code === 'raeumgeschwindigkeit-ueber-regelwert');
186 expect(notiz?.severity).toBe('warnung');
187 expect(notiz?.message).toContain('1');
188 });
189
190 it('meldet auch die gekappte Eingabe, wenn die Obergrenze ueber dem Regelwert liegt', () => {
191 const ergebnis = computeIntergreen(
192 abbiegerRaeumend({ clearingSpeedOverride: 25 }),
193 kfzEinfahrend(),
194 );
195 expect(ergebnis.clearingSpeed).toBe(SAFETY_FLOORS.maxClearingSpeedKfz);
196 expect(ergebnis.notes.some((n) => n.code === 'raeumgeschwindigkeit-zu-hoch')).toBe(true);
197 expect(ergebnis.notes.some((n) => n.code === 'raeumgeschwindigkeit-ueber-regelwert')).toBe(
198 true,
199 );
200 });
201
202 it('schweigt bei einer Eingabe unterhalb des Regelwerts', () => {
203 const ergebnis = computeIntergreen(
204 abbiegerRaeumend({ clearingSpeedOverride: 5 }),
205 kfzEinfahrend(),
206 );
207 expect(ergebnis.notes.some((n) => n.code === 'raeumgeschwindigkeit-ueber-regelwert')).toBe(
208 false,
209 );
210 });
211
212 it('nennt bei gesetztem engem Innenradius den Regelwert des Stroms, nicht den Praxisansatz', () => {
213 /*
214 * Der Praxisansatz "enger Innenradius" (5,0 m/s) ist ausdruecklich kein
215 * Regelwerkswert - quellen.ts fuehrt ihn unter 'praxis-enger-radius' mit
216 * dem Pruefstand "abzugleichen". Die Meldung nannte ihn trotzdem
217 * "Regelwert 5 m/s dieses Stroms", waehrend das Feld im selben Fenster
218 * korrekt "ohne diesen Eintrag 5 m/s" sagt. Der wirkliche Regelwert des
219 * abbiegenden Kfz-Stroms (7,0 m/s) kam in der Meldung nicht vor, obwohl
220 * sie die begruendungspflichtige Abweichung ausweisen soll.
221 */
222 const ergebnis = computeIntergreen(
223 abbiegerRaeumend({ engerRadius: true, clearingSpeedOverride: 9 }),
224 kfzEinfahrend(),
225 );
226 const notiz = ergebnis.notes.find((n) => n.code === 'raeumgeschwindigkeit-ueber-regelwert');
227 expect(notiz?.severity).toBe('warnung');
228 expect(notiz?.message).toContain('Regelwert 7 m/s');
229 expect(notiz?.message).not.toContain('Regelwert 5 m/s');
230 });
231
232 it('schweigt bei engem Innenradius, solange die Handeingabe unter dem Regelwert bleibt', () => {
233 /*
234 * 6 m/s liegen unter dem Regelwert des Abbiegers (7,0 m/s) und damit auf
235 * der sicheren Seite - kein Regelwertverstoss. Dass die Handeingabe das
236 * Merkmal aushebelt, meldet 'enger-radius-durch-vorgabe-ersetzt' samt
237 * Richtung; zwei Warnungen zu einer Tatsache stuenden sonst
238 * nebeneinander in Pruefbericht und Planunterlage.
239 */
240 const ergebnis = computeIntergreen(
241 abbiegerRaeumend({ engerRadius: true, clearingSpeedOverride: 6 }),
242 kfzEinfahrend(),
243 );
244 expect(ergebnis.notes.some((n) => n.code === 'raeumgeschwindigkeit-ueber-regelwert')).toBe(
245 false,
246 );
247 const ersetzt = ergebnis.notes.find((n) => n.code === 'enger-radius-durch-vorgabe-ersetzt');
248 expect(ersetzt?.message).toContain('6 m/s');
249 expect(ersetzt?.message).toContain('5 m/s');
250 });
251
252 it('schweigt, wo Regelwert und Schranke zusammenfallen', () => {
253 const ergebnis = computeIntergreen(
254 abbiegerRaeumend({ movement: 'geradeaus', clearingSpeedOverride: 10 }),
255 kfzEinfahrend(),
256 );
257 expect(ergebnis.notes.some((n) => n.code === 'raeumgeschwindigkeit-ueber-regelwert')).toBe(
258 false,
259 );
260 });
261 });
262
263 /*
264 * NACHGEZOGEN (Fassung 5.43.0): `ueberfahrzeitHerkunft` nimmt das
265 * VERFAHREN als erstes Argument, weil an einer Engstelle nicht der Kfz-Ansatz
266 * gilt, sondern die feste Ueberfahrzeit des Abschnitts 5.2.2. Diese drei Faelle
267 * fragen nach dem Knotenpunkt und geben es deshalb als 'knotenpunkt' mit; ihre
268 * Aussage ist unveraendert. Den neuen Zweig prueft
269 * tests/domain/bezugswerteVerfahren.test.ts.
270 */
271 describe('Befund 58 - Herkunft der Ueberfahrzeit am angesetzten Wert', () => {
272 it('nennt eine uebernommene Handeingabe weiterhin als solche', () => {
273 expect(
274 ueberfahrzeitHerkunft('knotenpunkt', 'kfz', 'gelbzeit', {
275 vorgegeben: true,
276 vorgabewert: 4,
277 angesetzt: 4,
278 }),
279 ).toBe('an dieser Beziehung von Hand eingetragen');
280 });
281
282 it('weist eine verworfene Handeingabe nicht mehr als angesetzten Wert aus', () => {
283 // Eingetragen waren 0 s, gerechnet wurde mit der Gelbzeit von 3 s.
284 const text = ueberfahrzeitHerkunft('knotenpunkt', 'kfz', 'gelbzeit', {
285 vorgegeben: true,
286 vorgabewert: 0,
287 angesetzt: 3,
288 });
289 expect(text).toContain('verworfen');
290 expect(text).not.toBe('an dieser Beziehung von Hand eingetragen');
291 });
292
293 it('nennt im verworfenen Fall die Herleitung des angesetzten Werts', () => {
294 const text = ueberfahrzeitHerkunft('knotenpunkt', 'fuss', 'gelbzeit', {
295 vorgegeben: true,
296 vorgabewert: -1,
297 angesetzt: 1,
298 });
299 expect(text).toContain('Fußgänger');
300 expect(text).toContain('verworfen');
301 });
302 });
303
304 describe('Befund 59 - OePNV ueber 70 km/h im Haltefall', () => {
305 it('nennt im Haltefall den Anfahransatz und die Ueberfahrzeit 0 s', () => {
306 const ergebnis = computeIntergreen(bahnRaeumend({ haltVorKnoten: true }), kfzEinfahrend(10));
307 expect(ergebnis.crossingTime).toBe(0);
308 const notiz = ergebnis.notes.find((n) => n.code === 'oepnv-vmax-ueber-70');
309 expect(notiz?.message).not.toContain('Staffelstufe');
310 expect(notiz?.message).toContain('Anfahransatz');
311 expect(notiz?.message).toContain('0 s');
312 });
313
314 it('nennt ohne Halt weiterhin die oberste Staffelstufe', () => {
315 const ergebnis = computeIntergreen(bahnRaeumend(), kfzEinfahrend(10));
316 expect(ergebnis.crossingTime).toBe(7);
317 const notiz = ergebnis.notes.find((n) => n.code === 'oepnv-vmax-ueber-70');
318 expect(notiz?.message).toContain('obersten Staffelstufe');
319 });
320 });