lsa-planer

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

/ tests domain variationenBewertungUndVerkehrsart.test.ts

19,5 KB Rohdatei
tests/domain/variationenBewertungUndVerkehrsart.test.ts — 420 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { buildSignalPlan, intergreenKey, type SignalPlan } from '@/domain/plan/signalPlan';
3 import { validateProject } from '@/domain/validation';
4 import { defaultVehicleClass } from '@/domain/model/factory';
5 import { ASSISTENT_VORGABE, projektAusAngaben, type Anlagenform } from '@/ui/assistent';
6 import type { Project } from '@/domain/model/project';
7 import type { ValidationFinding } from '@/domain/validation';
8
9 /**
10 * Zwei Ecken der Variationsmatrix, die bisher leer standen.
11 *
12 * DIE LAGE, GEMESSEN: Der Fachkern ist dicht bewacht, aber fast alles davon an
13 * EINER Achse - dem Knotenpunkt. Das Bewertungsverfahren HCM wurde in keinem
14 * Test an einer anderen Anlagenart GERECHNET, und eine OePNV-Signalgruppe
15 * stand in keinem Projekt, das kein Knotenpunkt war. (Beide Kombinationen
16 * kommen in `tests/services/projektdatei.test.ts` vor - dort aber nur als
17 * Rundreise durch Datei und Einlesen, ohne Plan, Bericht oder Ausdruck.)
18 *
19 * Erreichbar sind beide Einstellungen ueberall: Das Auswahlfeld
20 * "Bewertungsverfahren" steht in der Projektansicht ohne jeden Bezug zur
21 * Anlagenart, und die Schaltflaeche "+ ÖPNV" steht in der
22 * Signalgruppentabelle jeder Anlage.
23 *
24 * Was hier geprueft wird, ist deshalb nicht "rechnet es ueberhaupt" - das tut
25 * es -, sondern WAS dabei herauskommt. In beiden Faellen ist das Ergebnis
26 * bemerkenswert genug, um es festzuhalten.
27 */
28
29 const STICHTAG = new Date('2026-01-01T12:00:00Z');
30
31 /** Das Projekt, das der gefuehrte Einstieg mit seinen Vorgaben anlegt. */
32 function ausDemAssistenten(
33 form: Anlagenform,
34 teil: Partial<typeof ASSISTENT_VORGABE> = {},
35 ): Project {
36 return projektAusAngaben({ ...ASSISTENT_VORGABE, form, ...teil }, 'Pruefstueck', STICHTAG);
37 }
38
39 function mitVerfahren(project: Project, verfahren: 'HBS' | 'HCM'): Project {
40 return { ...project, settings: { ...project.settings, serviceLevelScale: verfahren } };
41 }
42
43 /** Plan und Bericht in einem Zug - wie die Oberflaeche sie bildet. */
44 function gerechnet(project: Project): {
45 readonly plan: SignalPlan;
46 readonly regeln: readonly string[];
47 /** Nur die Befunde zur Leistungsfaehigkeit, mit dem Ziel dahinter. */
48 readonly bewertung: readonly string[];
49 } {
50 const plan = buildSignalPlan(project);
51 const bericht = validateProject(project, plan, STICHTAG);
52 return {
53 plan,
54 regeln: bericht.findings.map((f) => f.rule).sort(),
55 bewertung: bericht.findings
56 .filter((f) => f.rule.startsWith('leistungsfaehigkeit.'))
57 .map((f) => `${f.rule.replace('leistungsfaehigkeit.', '')}@${f.target?.label ?? '-'}`)
58 .sort(),
59 };
60 }
61
62 /** Die Signalgruppe dieses Namens im gerechneten Plan. */
63 function gruppe(plan: SignalPlan, name: string): SignalPlan['groups'][number] {
64 const treffer = plan.groups.find((g) => g.name === name);
65 expect(treffer, `keine Signalgruppe ${name} im Plan`).toBeDefined();
66 return treffer!;
67 }
68
69 /** Der Befund dieser Regel im Pruefbericht - hoechstens einer je Regel hier. */
70 function leistungsbefund(project: Project, regel: string): ValidationFinding | undefined {
71 const plan = buildSignalPlan(project);
72 return validateProject(project, plan, STICHTAG).findings.find((f) => f.rule === regel);
73 }
74
75 /** Die Zwischenzeit, in der die eine Gruppe raeumt und die andere einfaehrt. */
76 function zwischenzeit(plan: SignalPlan, raeumt: string, faehrtEin: string): number {
77 const schluessel = intergreenKey(gruppe(plan, raeumt).groupId, gruppe(plan, faehrtEin).groupId);
78 const eintrag = plan.intergreens.get(schluessel);
79 expect(eintrag, `keine Zwischenzeit: ${raeumt} raeumt, ${faehrtEin} faehrt ein`).toBeDefined();
80 return eintrag!.value;
81 }
82
83 describe('Fussgaengerschutzanlage mit dem Bewertungsverfahren HCM', () => {
84 /*
85 * ZWEI SCHRANKEN TREFFEN AUFEINANDER, und die vier Faelle hier trennen sie:
86 * die Anlagenart, die an einer Fussgaengerschutzanlage die Kfz-Leistungs-
87 * faehigkeit ausser Betracht laesst, und das Verfahren HCM, das fuer
88 * Fussgaenger und Radverkehr keine Stufentafel kennt. Jede fuer sich ist
89 * bewacht; was sie ZUSAMMEN ergeben, stand nirgends.
90 *
91 * Aufgebaut als Vierfeldertafel - Knotenpunkt gegen Fussgaengerschutzanlage,
92 * HBS gegen HCM -, weil sich nur so zeigen laesst, welche Schranke welchen
93 * Befund wegnimmt. Ein einzelner Fall koennte beide Wirkungen nicht
94 * auseinanderhalten.
95 *
96 * DIE ANLAGE IST BEWUSST UEBERLASTET. Die Vorgabe des gefuehrten Einstiegs
97 * traegt gar keine Verkehrsstaerke - ohne sie gaebe es an der Fahrbahn
98 * ueberhaupt nichts zu bewerten, und ein Fall, der dann "es bleibt nichts"
99 * feststellt, haette nur die fehlende Eingabe gemessen und keine der beiden
100 * Schranken. Erst 6000 Fz/h auf den zwei Fahrstreifen bringen den
101 * Auslastungsgrad auf 1,79 und beide Bewertungen ueberhaupt in Reichweite.
102 */
103 const belastet = ausDemAssistenten('fussgaengerschutzanlage', { verkehrsstaerke: 6000 });
104
105 /** Dieselbe Planung, nur als Knotenpunkt beurteilt. */
106 const alsKnotenpunkt: Project = { ...belastet, anlagenart: 'knotenpunkt' };
107
108 it('meldet am Knotenpunkt beides: die ueberlastete Fahrbahn und beide Stufen', () => {
109 // Der Vergleichsfall. Ohne ihn waere von den drei Faellen darunter nicht
110 // zu unterscheiden, was die Anlagenart wegnimmt und was ohnehin fehlte.
111 expect(gerechnet(mitVerfahren(alsKnotenpunkt, 'HBS')).bewertung).toEqual([
112 'qualitaetsstufe@F1',
113 'qualitaetsstufe@K1',
114 'ueberlastet@K1',
115 ]);
116 });
117
118 it('nimmt die Anlagenart der Fahrbahn ihre Bewertung', () => {
119 /*
120 * Die erste Schranke, fuer sich. An einer Fussgaengerschutzanlage ist
121 * nicht die Leistungsfaehigkeit der Fahrbahn das Mass, sondern die
122 * Wartezeit der Querenden - der Bericht schweigt dort zum Auslastungsgrad
123 * und zur Kfz-Stufe. GERECHNET wird beides trotzdem: Der Plan fuehrt fuer
124 * K1 denselben Auslastungsgrad und dieselbe Stufe F wie am Knotenpunkt.
125 * Weggenommen ist nur die Meldung.
126 */
127 const { plan, bewertung } = gerechnet(mitVerfahren(belastet, 'HBS'));
128 expect(gruppe(plan, 'K1').delay?.serviceLevel?.grade, 'gerechnet wird sie doch').toBe('F');
129 expect(bewertung).toEqual(['qualitaetsstufe@F1']);
130 });
131
132 it('nimmt das Verfahren HCM der Furt ihre Stufe', () => {
133 /*
134 * Die zweite Schranke, fuer sich - am Knotenpunkt, wo die erste nicht
135 * greift. Das HCM hat fuer Fussgaenger und Radverkehr keine Stufentafel;
136 * die Wartezeit selbst bleibt, was sie war. "Nicht bewertet" ist keine
137 * schlechte Bewertung, und genau das sagt der Bericht auch.
138 */
139 const { plan, bewertung } = gerechnet(mitVerfahren(alsKnotenpunkt, 'HCM'));
140 const furt = gruppe(plan, 'F1');
141 expect(furt.delay?.maximumDelay, 'die Wartezeit selbst aendert sich nicht').toBe(115);
142 expect(furt.delay?.serviceLevel ?? null, 'das HCM hat fuer Furten keine Tafel').toBeNull();
143 expect(bewertung).toEqual([
144 'fuss-rad-nicht-bewertet@Bewertungsverfahren',
145 'qualitaetsstufe@K1',
146 'ueberlastet@K1',
147 ]);
148 });
149
150 it('zusammen bleibt an der Fussgaengerschutzanlage keine Bewertung uebrig', () => {
151 /*
152 * DER FALL, DEN NIEMAND GERECHNET HATTE. Jede der beiden Schranken ist
153 * fuer sich richtig und fuer sich bewacht. Zusammen ergeben sie etwas
154 * Drittes: An einer Anlage, deren Zweck die Querung ist, steht bei einer
155 * zu 179 % ausgelasteten Fahrbahn und 115 s Wartezeit an der Furt keine
156 * einzige Bewertung mehr.
157 *
158 * Bis 5.20.0 stand dort nur der allgemeine Hinweis, die Furt bekomme nach
159 * HCM keine Stufe - und der verschwieg die andere Haelfte. Seither sagt
160 * die Regel `leistungsfaehigkeit.verfahren-ohne-bewertung`, dass gar
161 * nichts mehr bewertet wird, und sie ersetzt den allgemeinen Hinweis:
162 * zwei Meldungen zu derselben Sache waeren eine zu viel.
163 */
164 const { plan, bewertung } = gerechnet(mitVerfahren(belastet, 'HCM'));
165 expect(
166 gruppe(plan, 'K1').capacity?.degreeOfSaturation,
167 'die Fahrbahn ist ueberlastet',
168 ).toBeGreaterThan(1);
169 expect(gruppe(plan, 'F1').delay?.maximumDelay, 'die Furt wartet lange').toBe(115);
170 expect(bewertung).toEqual(['verfahren-ohne-bewertung@Bewertungsverfahren']);
171 });
172
173 it('nennt die Meldung beide Haelften und den Weg heraus', () => {
174 const befund = leistungsbefund(
175 mitVerfahren(belastet, 'HCM'),
176 'leistungsfaehigkeit.verfahren-ohne-bewertung',
177 );
178 expect(befund, 'die Regel schweigt').toBeDefined();
179 expect(befund?.severity, 'eine Unterlage ganz ohne Bewertung ist mehr als ein Hinweis').toBe(
180 'warnung',
181 );
182 expect(befund?.message, 'die Anlagenartschranke fehlt').toContain('Fahrbahn');
183 expect(befund?.message, 'die Verfahrensschranke fehlt').toContain('Furt');
184 expect(befund?.suggestion, 'der Weg heraus fehlt').toContain('HBS');
185 });
186
187 it('schweigt sie, wo eine der beiden Schranken fehlt', () => {
188 // Nach HBS bewertet die Anlage ihre Furt - dann bleibt etwas uebrig.
189 expect(
190 leistungsbefund(
191 mitVerfahren(belastet, 'HBS'),
192 'leistungsfaehigkeit.verfahren-ohne-bewertung',
193 ),
194 'nach HBS meldet sie trotzdem',
195 ).toBeUndefined();
196
197 // Und am Knotenpunkt bleibt unter HCM die Fahrbahn bewertet.
198 expect(
199 leistungsbefund(
200 mitVerfahren(alsKnotenpunkt, 'HCM'),
201 'leistungsfaehigkeit.verfahren-ohne-bewertung',
202 ),
203 'am Knotenpunkt meldet sie',
204 ).toBeUndefined();
205 expect(
206 leistungsbefund(
207 mitVerfahren(alsKnotenpunkt, 'HCM'),
208 'leistungsfaehigkeit.fuss-rad-nicht-bewertet',
209 ),
210 'der allgemeine Hinweis ist am Knotenpunkt verschwunden',
211 ).toBeDefined();
212 });
213 });
214
215 describe('Einstreifige Verkehrsfuehrung mit dem Bewertungsverfahren HCM', () => {
216 /*
217 * Die ergiebigere der beiden Kombinationen: Hier greift die Ausblendung der
218 * Fussgaengerschutzanlage nicht, beide Signalgruppen sind Kfz-Stroeme, und
219 * der Verfahrenswechsel schlaegt bis in die Stufe durch.
220 *
221 * Der Hebel ist die Abflusszeit tA: Nach HBS 2015 ist sie die Freigabezeit
222 * zuzueglich einer Sekunde je Freigabezeitfenster - der Abfluss laeuft in
223 * die Gelbzeit hinein -, nach HCM die Freigabezeit selbst. Die Kapazitaet
224 * C = qS * tA/tU faellt damit unter HCM, und der Auslastungsgrad steigt.
225 */
226 const engstelle = ausDemAssistenten('einstreifig', { verkehrsstaerke: 400 });
227
228 it('rechnet unter HCM eine kleinere Kapazitaet und einen hoeheren Auslastungsgrad', () => {
229 const hbs = gruppe(gerechnet(mitVerfahren(engstelle, 'HBS')).plan, 'K1').capacity;
230 const hcm = gruppe(gerechnet(mitVerfahren(engstelle, 'HCM')).plan, 'K1').capacity;
231 expect(hbs, 'ohne Kapazitaet sagt der Vergleich nichts').not.toBeNull();
232 expect(hcm, 'ohne Kapazitaet sagt der Vergleich nichts').not.toBeNull();
233
234 expect(hbs!.verfahren).toBe('HBS');
235 expect(hcm!.verfahren).toBe('HCM');
236 expect(hcm!.abflusszeit, 'der Abflusszuschlag des HBS steht noch in der Rechnung').toBeLessThan(
237 hbs!.abflusszeit,
238 );
239 expect(hcm!.capacity).toBeLessThan(hbs!.capacity);
240 expect(hcm!.degreeOfSaturation!).toBeGreaterThan(hbs!.degreeOfSaturation!);
241 });
242
243 it('gibt derselben Planung je nach Verfahren eine andere Qualitaetsstufe', () => {
244 /*
245 * DIE ZAHL, DIE DEN FALL TRAEGT. Derselbe Plan, dieselbe Verkehrsstaerke,
246 * dieselbe Engstelle - und die Stufe springt von B auf C, allein weil in
247 * der Projektansicht ein anderes Verfahren steht. Wer eine Planunterlage
248 * nach der Stufe beurteilt, muss wissen, wonach sie gebildet wurde; das
249 * Feld heisst deshalb seit Fassung 5.4.0 "Bewertungsverfahren" und
250 * nicht mehr "Bewertungsskala".
251 */
252 expect(
253 gruppe(gerechnet(mitVerfahren(engstelle, 'HBS')).plan, 'K1').delay?.serviceLevel?.grade,
254 ).toBe('B');
255 expect(
256 gruppe(gerechnet(mitVerfahren(engstelle, 'HCM')).plan, 'K1').delay?.serviceLevel?.grade,
257 ).toBe('C');
258 });
259 });
260
261 describe('OePNV in einer einstreifigen Verkehrsfuehrung', () => {
262 /*
263 * DIE GEFAEHRLICHE RICHTUNG - GEMESSEN UND FESTGEHALTEN, NICHT BEHOBEN.
264 *
265 * Ein Bus faehrt durch die Baustelle. Wer dafuer in der Signalgruppen-
266 * tabelle die Verkehrsart einer Richtung auf "ÖPNV" stellt - die
267 * Schaltflaeche steht dort, nichts hindert daran -, bekommt eine KUERZERE
268 * Zwischenzeit als fuer dieselbe Richtung als Kfz-Strom. Der Grund steht im
269 * Regelwerk und ist fuer sich richtig: Die Raeumgeschwindigkeit des OePNV
270 * ist Vmax/3,6 - bei 50 km/h also 13,89 m/s. Der Kraftfahrzeugverkehr raeumt
271 * an einer Engstelle seit 5.27.0 mit der Staffel des Abschnitts 5.2, bei
272 * 50 km/h also mit 40 km/h = 11,11 m/s (bis 5.26.0 mit festen 10,0 m/s).
273 * Der raeumende OePNV-Strom ist rechnerisch schneller aus der Engstelle
274 * heraus, also darf die Gegenrichtung frueher einfahren.
275 *
276 * Die Voraussetzung dieses Ansatzes prueft das Programm nirgends nach: Er
277 * meint den gesondert signalisierten OePNV, der den Konfliktbereich mit
278 * seiner Hoechstgeschwindigkeit durchfaehrt. In einer einstreifigen
279 * Verkehrsfuehrung faehrt der Bus auf demselben Fahrstreifen wie alles
280 * andere, hinter demselben Baustellenverkehr, und raeumt gerade nicht mit
281 * Vmax. Bei der Vorgabe des Assistenten - 120 m Engstelle, 50 km/h - fallen
282 * beide Verfahren seit 5.27.0 zufaellig auf dieselbe Sekunde; bei 400 m
283 * treten sie um fuenf Sekunden auseinander.
284 *
285 * DIE RECHNUNG BLEIBT, DER BERICHT SAGT ES JETZT. Der Kennwert wird nicht
286 * angetastet - er ist der Regelwert, und ihn zu aendern hiesse, das Regelwerk
287 * zu korrigieren. Was sich geaendert hat, ist das Schweigen daneben: Die
288 * Regel `einstreifig.oepnv-raeumgeschwindigkeit` nennt an dieser Anlagenart
289 * die Voraussetzung des Ansatzes und die beiden Geschwindigkeiten, die
290 * auseinanderfallen. Die Faelle unten halten beides gegeneinander - was
291 * gerechnet wird und was daraufhin im Bericht steht.
292 */
293 const alsKfz = ausDemAssistenten('einstreifig');
294
295 /** Dieselbe Anlage, eine Richtung als OePNV - so, wie es die Tabelle tut. */
296 function mitBus(project: Project): Project {
297 return {
298 ...project,
299 signalGroups: project.signalGroups.map((g) =>
300 g.name === 'K1'
301 ? { ...g, mode: 'oepnv' as const, vehicleClass: defaultVehicleClass('oepnv') }
302 : g,
303 ),
304 };
305 }
306
307 it('verkuerzt die Zwischenzeit, statt sie zu verlaengern', () => {
308 const ohne = gerechnet(alsKfz).plan;
309 const mit = gerechnet(mitBus(alsKfz)).plan;
310
311 /*
312 * NACHGEZOGEN MIT 5.27.0: Bei der Vorgabe des Assistenten - 120 m, 50 km/h
313 * - fallen beide Verfahren auf dieselbe Sekunde. Kfz nach Abschnitt 5.2:
314 * 4 + 120/40 * 3,6 = 14,8 -> 15 s. OePNV nach Abschnitt 2: 5 s
315 * Ueberfahrzeit + 126 m / 13,89 m/s = 14,07 -> 15 s. Bis 5.26.0 stand der
316 * Kfz-Wert bei 16 s und der Unterschied betrug eine Sekunde.
317 */
318 expect(zwischenzeit(ohne, 'K1', 'K2'), 'Kfz raeumt 120 m mit 40 km/h, tue 4 s').toBe(15);
319 expect(zwischenzeit(mit, 'K1', 'K2'), 'der Bus raeumt nach Abschnitt 2').toBe(15);
320
321 // Die Gegenrichtung bleibt, wo sie war - sie raeumt weiter als Kfz. Der
322 // Einfahrweg ist 0, die Verkehrsart des EINFAHRENDEN Stroms also ohne
323 // Wirkung; nur der raeumende zaehlt.
324 expect(zwischenzeit(mit, 'K2', 'K1'), 'die Gegenrichtung hat sich mitverschoben').toBe(
325 zwischenzeit(ohne, 'K2', 'K1'),
326 );
327 });
328
329 it('traegt die Verkuerzung an einer laengeren Engstelle offen zutage', () => {
330 /*
331 * DER FALL, DEN DIE 120 m VERDECKEN. Bei 400 m Engstelle rechnet der
332 * Kraftfahrzeugverkehr nach Abschnitt 5.2 mit 4 + 400/40 * 3,6 = 40 s, der
333 * OePNV nach Abschnitt 2 mit 5 + 406/13,89 = 34,2 -> 35 s. Fuenf Sekunden
334 * kuerzer, allein weil in der Signalgruppentabelle eine Verkehrsart
335 * umgestellt wurde.
336 */
337 const lang = ausDemAssistenten('einstreifig', { engstellenlaenge: 400 });
338 expect(zwischenzeit(gerechnet(lang).plan, 'K1', 'K2')).toBe(40);
339 expect(zwischenzeit(gerechnet(mitBus(lang)).plan, 'K1', 'K2')).toBe(35);
340 });
341
342 it('meldet der Pruefbericht die Voraussetzung des Ansatzes', () => {
343 /*
344 * DER BEFUND, DER DIESE REGEL AUSGELOEST HAT: Bis 5.20.0 war die
345 * Befundliste mit und ohne OePNV-Gruppe ZEICHENGLEICH. Wer die Verkehrsart
346 * umstellte, verkuerzte eine Zwischenzeit und sah am Bericht nichts davon.
347 *
348 * Gemeldet wird nicht der Kennwert - der ist richtig -, sondern seine
349 * Voraussetzung: Der Ansatz vr = Vmax/3,6 gilt dem GESONDERT
350 * signalisierten OePNV. Ob der Bus hier auf eigenem Gleiskoerper faehrt,
351 * kann das Programm nicht wissen; an einer einstreifigen Verkehrsfuehrung
352 * kann es die Frage aber stellen, denn dort gibt es nur einen Fahrstreifen.
353 */
354 const befund = leistungsbefund(mitBus(alsKfz), 'einstreifig.oepnv-raeumgeschwindigkeit');
355 expect(befund, 'die Regel schweigt').toBeDefined();
356 expect(befund?.severity, 'die verkuerzte Zwischenzeit ist mehr als ein Hinweis').toBe(
357 'warnung',
358 );
359 expect(befund?.target?.label, 'der Befund zeigt nicht auf die Signalgruppe').toBe('K1');
360
361 // Beide Geschwindigkeiten stehen darin - sonst muesste der Anwender
362 // nachschlagen, worin der Unterschied besteht.
363 expect(befund?.message, 'die Raeumgeschwindigkeit des OePNV fehlt').toContain('13,9 m/s');
364 expect(befund?.message, 'die des Kfz fehlt').toContain('10,0 m/s');
365 expect(befund?.message, 'die Voraussetzung des Ansatzes fehlt').toContain(
366 'gesondert signalisiert',
367 );
368 });
369
370 it('schweigt sie, wo ihre Voraussetzung nicht in Frage steht', () => {
371 /*
372 * Drei Gegenproben, und jede einzeln noetig - ohne sie waere eine Regel
373 * haltbar, die einfach immer meldet.
374 */
375 expect(
376 leistungsbefund(alsKfz, 'einstreifig.oepnv-raeumgeschwindigkeit'),
377 'ohne OePNV-Gruppe meldet sie trotzdem',
378 ).toBeUndefined();
379
380 // Am Knotenpunkt kann der OePNV sehr wohl gesondert signalisiert sein -
381 // ein eigener Gleiskoerper oder ein Sonderfahrstreifen ist dort der
382 // Regelfall. Die Frage stellt sich nur, wo es einen Fahrstreifen gibt.
383 const knoten: Project = { ...mitBus(alsKfz), anlagenart: 'knotenpunkt' };
384 expect(
385 leistungsbefund(knoten, 'einstreifig.oepnv-raeumgeschwindigkeit'),
386 'am Knotenpunkt meldet sie',
387 ).toBeUndefined();
388
389 /*
390 * Und unterhalb von 36 km/h raeumt der OePNV LANGSAMER als der Kfz-Strom
391 * (10,0 m/s): Die Zwischenzeit wird dann laenger, nicht kuerzer - die
392 * sichere Richtung. Gemessen bei 30 km/h: 8,3 m/s.
393 */
394 const langsam = ausDemAssistenten('einstreifig', { vZul: 30 });
395 expect(
396 leistungsbefund(mitBus(langsam), 'einstreifig.oepnv-raeumgeschwindigkeit'),
397 'auch in der sicheren Richtung meldet sie',
398 ).toBeUndefined();
399 });
400
401 it('nimmt der Richtung das Rot-Gelb und tauscht Gelb gegen das Uebergangssignal', () => {
402 /*
403 * Die zweite Folge desselben Handgriffs, und sie faellt an einer
404 * Baustellenampel mit gewoehnlichen Dreikammersignalen sofort auf: Ein
405 * OePNV-Signal ist ein Sondersignal und hat kein Rot-Gelb.
406 *
407 * NACHGEZOGEN MIT 5.27.0: Bis dahin erwartete dieser Fall auch beim Gelb
408 * eine Null. Ein Sondersignal zeigt kein GELB, wohl aber ein
409 * Uebergangssignal nach Par. 51 BOStrab; die RiLSA 2015 staffelt seine
410 * Dauer in Abschnitt 2.4 nach der betrieblich zugelassenen
411 * Hoechstgeschwindigkeit - bei 50 km/h sind es 6 s. Die Zahl steht in
412 * `times.yellow`, weil dort die Uebergangszeit vor Rot gefuehrt wird.
413 */
414 const vorher = gruppe(gerechnet(alsKfz).plan, 'K1').times;
415 expect([vorher.yellow, vorher.redYellow], 'als Kfz-Strom Gelb und Rot-Gelb').toEqual([3, 1]);
416
417 const bus = gruppe(gerechnet(mitBus(alsKfz)).plan, 'K1').times;
418 expect([bus.yellow, bus.redYellow], 'Uebergangssignal ja, Rot-Gelb nein').toEqual([6, 0]);
419 });
420 });