lsa-planer

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

/ tests domain leerePhaseAmUmlaufende.test.ts

4,2 KB Rohdatei
tests/domain/leerePhaseAmUmlaufende.test.ts — 89 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { buildSignalPlan } from '@/domain/plan/signalPlan';
3 import { createStandardIntersectionProject } from '@/domain/model/factory';
4 import * as actions from '@/app/actions';
5
6 /*
7 * Befund 13, behoben in Fassung 5.10.0 - Freigabefenster am Umlaufende.
8 *
9 * `buildGroupWindows` bildete den Fensterbeginn `rawStart` und mass die Dauer
10 * gegen den UNGEFALTETEN Wert, waehrend die Zeile darunter den Beginn bereits
11 * in den Umlauf zurueckfaltete. Endet der Lauf einer Gruppe genau an der
12 * Umlaufgrenze - alle folgenden Phasen sind leer (0 s Dauer), und der
13 * Uebergang aus der letzten belegten Phase betraegt 0 s -, gilt
14 * rawStart == end == tU: Der Nicht-Ueberlauf-Zweig rechnete dann 0 s statt des
15 * ganzen Umlaufs.
16 *
17 * Folge: Die Phasentabelle wies 30 s aus, die Signalgruppenzeile 0 s; dazu
18 * kamen Scheinbefunde (Mindestfreigabezeit unterschritten, Auslastungsgrad
19 * unendlich), die es fachlich nicht gibt.
20 *
21 * GEGEN DEN ALTSTAND schlagen die ersten drei Faelle fehl (Freigabezeit 0 s
22 * statt des ganzen Umlaufs, dazu die Scheinmeldung). Der vierte Fall ist die
23 * Gegenprobe und besteht auch dort: Bei zwei belegten und zwei leeren Phasen
24 * endet kein Lauf an der Umlaufgrenze, die Faltung aendert dort nichts.
25 */
26
27 const DATUM = new Date('2026-01-01T00:00:00Z');
28
29 /** Standardknotenpunkt, dessen zweite Phase durch eine leere ersetzt ist. */
30 function eineBelegtePhaseUndEineLeere() {
31 const roh = createStandardIntersectionProject('Leere Phase', DATUM);
32 const ohne = actions.removePhase(roh, roh.phases[1]!.id);
33 return actions.addPhase(ohne);
34 }
35
36 describe('Befund 13 - Freigabefenster, dessen Lauf an der Umlaufgrenze endet', () => {
37 it('gibt der Gruppe der einzigen belegten Phase den ganzen Umlauf', () => {
38 const plan = buildSignalPlan(eineBelegtePhaseUndEineLeere());
39 const k1 = plan.groups.find((g) => g.name === 'K1')!;
40
41 // Die leere Phase hat 0 s Dauer und beide Uebergaenge 0 s: Phase 1 traegt
42 // den gesamten Umlauf, ihre Gruppen sind durchgehend freigegeben.
43 expect(plan.phases[0]?.duration).toBe(plan.cycleTime);
44 expect(k1.greens).toHaveLength(1);
45 expect(k1.greens[0]?.start).toBe(0);
46 expect(k1.greens[0]?.duration).toBe(plan.cycleTime);
47 expect(k1.totalGreen).toBe(plan.cycleTime);
48 });
49
50 it('erzeugt dabei keinen Scheinbefund zur Mindestfreigabezeit', () => {
51 const plan = buildSignalPlan(eineBelegtePhaseUndEineLeere());
52
53 // Eine Gruppe mit 30 s Freigabezeit unterschreitet keine
54 // Mindestfreigabezeit - die Meldung entstand allein aus dem Fenster 0 s.
55 const betroffen = plan.notes.filter(
56 (n) => n.code === 'mindestfreigabezeit-unterschritten' && n.signalGroupId !== undefined,
57 );
58 expect(betroffen).toEqual([]);
59 // Und der Auslastungsgrad ist endlich, weil die Kapazitaet nicht 0 ist.
60 const k1 = plan.groups.find((g) => g.name === 'K1')!;
61 expect(k1.capacity?.capacity ?? 0).toBeGreaterThan(0);
62 });
63
64 it('faltet auch einen Lauf ueber mehrere belegte Phasen', () => {
65 // K1 laeuft durch Phase 1 UND Phase 2; erst danach steht die leere Phase.
66 const roh = createStandardIntersectionProject('Zwei Laeufe', DATUM);
67 const k1 = roh.signalGroups.find((g) => g.name === 'K1')!;
68 const mitK1 = actions.updatePhase(roh, roh.phases[1]!.id, { signalGroupIds: [k1.id] });
69 const plan = buildSignalPlan(actions.addPhase(mitK1));
70
71 const geplant = plan.groups.find((g) => g.name === 'K1')!;
72 expect(geplant.totalGreen).toBe(plan.cycleTime);
73 // Die uebrigen Gruppen behalten ihr gewoehnliches Fenster.
74 expect(plan.groups.find((g) => g.name === 'K2')?.totalGreen).toBe(plan.phases[0]?.duration);
75 });
76
77 it('aendert nichts, wenn kein Lauf an der Umlaufgrenze endet', () => {
78 // Gegenprobe: zwei belegte Phasen, danach zwei leere. Der Uebergang aus
79 // Phase 2 wird gegen Phase 1 bemessen und ist groesser als 0; kein Lauf
80 // endet an der Umlaufgrenze.
81 const roh = createStandardIntersectionProject('Gegenprobe', DATUM);
82 const plan = buildSignalPlan(actions.addPhase(actions.addPhase(roh)));
83
84 for (const gruppe of plan.groups) {
85 expect(gruppe.totalGreen).toBeGreaterThan(0);
86 expect(gruppe.totalGreen).toBeLessThan(plan.cycleTime);
87 }
88 });
89 });