lsa-planer

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

/ tests domain umlaufzeitRasterung.test.ts

9,1 KB Rohdatei
tests/domain/umlaufzeitRasterung.test.ts — 199 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { computeCycleTime } from '@/domain/rilsa/cycle';
3 import { RILSA_DEFAULTS } from '@/domain/rilsa/constants';
4
5 /**
6 * Die Rasterung entscheidet nicht mehr ueber eine Beanstandung.
7 *
8 * Vor Fassung 5.13.0 prueft `computeCycleTime` den GERASTERTEN Wert
9 * gegen den Hoechstwert. Damit entschied die Rasterung - ein in der
10 * Vorgabenverwaltung von 1 bis 10 s frei einstellbarer Praxiswert ohne
11 * Regelwerksbezug - darueber, ob der Pruefbericht "Die Anlage ist mit dieser
12 * Phaseneinteilung nicht leistungsfaehig" fuehrt. Gemessen mit demselben
13 * rechnerischen Wert 119,48 s: bei den Rasterungen 1 bis 6, 8 und 10 s die
14 * Umlaufzeit 120 s ohne Beanstandung, bei 7 und 9 s dieselben 120 s mit dem
15 * Fehler 'umlaufzeit-ueber-hoechstwert'.
16 *
17 * Beurteilt wird jetzt der ERFORDERLICHE Umlauf vor der Rasterung: das Maximum
18 * aus dem rechnerischen Wert, der Mindestumlaufzeit aus Mindestfreigabe- und
19 * Zwischenzeiten und der kleinsten zulaessigen Umlaufzeit.
20 *
21 * GEGEN DEN ALTSTAND (gemessen: `cycle.ts` und `signalPlan.ts` auf den Stand
22 * vor Fassung 5.13.0 zurueckgespielt) sind fuenf der elf Faelle rot: die
23 * Messreihe ueber alle zehn Rasterungen, der Regelbereichshinweis, die
24 * Meldung mit dem erforderlichen statt dem gerasterten Wert und die beiden
25 * Faelle zum dichten Zahlenpaar. Die uebrigen sechs sind Gegenproben und
26 * vorher wie nachher gruen - darunter ein erforderlicher Umlauf ueber dem
27 * Hoechstwert, eine Mindestumlaufzeit ueber dem Hoechstwert und der
28 * uebersaettigte Knoten; sie halten fest, was sich NICHT aendern darf.
29 */
30
31 const HOECHSTWERT = RILSA_DEFAULTS.cycleTime.max;
32 const RASTERUNGEN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as const;
33
34 /**
35 * Eingang, dessen rechnerischer Wert nach Webster genau `raw` betraegt:
36 * tU = (1,5 * L + 5) / (1 - Y) nach L aufgeloest, mit Y = 0,5.
37 */
38 function eingang(raw: number, step: number, minimumCycle = 30) {
39 const Y = 0.5;
40 return {
41 lostTime: (raw * (1 - Y) - 5) / 1.5,
42 criticalFlowRatios: [Y],
43 minimumCycle,
44 step,
45 };
46 }
47
48 function codes(raw: number, step: number, minimumCycle?: number): string[] {
49 return computeCycleTime('webster', eingang(raw, step, minimumCycle)).notes.map((n) => n.code);
50 }
51
52 describe('Die Rasterung der Umlaufzeit entscheidet nicht ueber die Beanstandung', () => {
53 it('haelt der rechnerische Wert 119,48 s den Hoechstwert bei jeder Rasterung ein', () => {
54 for (const step of RASTERUNGEN) {
55 const ergebnis = computeCycleTime('webster', eingang(119.48, step));
56 expect(ergebnis.raw, `Rasterung ${step} s: rechnerischer Wert`).toBeCloseTo(119.48, 2);
57 expect(ergebnis.cycleTime, `Rasterung ${step} s: Umlaufzeit`).toBe(HOECHSTWERT);
58 expect(ergebnis.bounded, `Rasterung ${step} s: Zustand`).toBe('keine');
59 expect(
60 ergebnis.notes.map((n) => n.code),
61 `Rasterung ${step} s: Meldungen`,
62 ).not.toContain('umlaufzeit-ueber-hoechstwert');
63 }
64 });
65
66 it('steht bei jeder Rasterung derselbe Hinweis zum Regelbereich', () => {
67 // Bis zur Aenderung gab es zwei Ausgaenge: 'keine' mit dem
68 // Regelbereichshinweis (Rasterung 5 s) und 'maximum' mit dem Fehler
69 // (Rasterung 7 s) - beide bei derselben Umlaufzeit von 120 s.
70 for (const step of RASTERUNGEN) {
71 expect(codes(119.48, step), `Rasterung ${step} s`).toEqual([
72 'umlaufzeit-ausserhalb-regelbereich',
73 ]);
74 }
75 });
76
77 it('meldet ein erforderlicher Umlauf ueber dem Hoechstwert bei jeder Rasterung', () => {
78 // Gegenprobe: 121 s sind wirklich mehr als 120 s. Hier darf nichts
79 // verschwinden - und die Umlaufzeit bleibt der gekappte Hoechstwert.
80 for (const step of RASTERUNGEN) {
81 const ergebnis = computeCycleTime('webster', eingang(121, step));
82 expect(
83 ergebnis.notes.map((n) => n.code),
84 `Rasterung ${step} s`,
85 ).toContain('umlaufzeit-ueber-hoechstwert');
86 expect(ergebnis.bounded, `Rasterung ${step} s`).toBe('maximum');
87 expect(ergebnis.cycleTime, `Rasterung ${step} s`).toBe(HOECHSTWERT);
88 }
89 });
90
91 it('bleibt die Meldung, wenn die Mindestumlaufzeit ueber dem Hoechstwert liegt', () => {
92 /*
93 * Die wichtigste Gegenprobe: `minimumCycle` folgt zwingend aus
94 * Mindestfreigabe- und Zwischenzeiten. Liegt SIE ueber dem Hoechstwert,
95 * ist die Anlage nicht schaltbar - der rechnerische Wert allein (hier
96 * 60 s) sagt davon nichts. Waere nur `raw` beurteilt worden, fiele diese
97 * Meldung fort.
98 */
99 for (const step of RASTERUNGEN) {
100 const ergebnis = computeCycleTime('webster', eingang(60, step, 125));
101 expect(
102 ergebnis.notes.map((n) => n.code),
103 `Rasterung ${step} s`,
104 ).toContain('umlaufzeit-ueber-hoechstwert');
105 expect(ergebnis.bounded, `Rasterung ${step} s`).toBe('maximum');
106 }
107 });
108
109 it('bleibt eine Mindestumlaufzeit dicht unter dem Hoechstwert unbeanstandet', () => {
110 // 119 s sind schaltbar. Bei Rasterung 7 s trifft die Aufrundung sie genau,
111 // bei 5 s hebt sie auf 120 s - beides ohne Beanstandung.
112 expect(codes(60, 7, 119)).toEqual(['mindestumlauf-massgebend']);
113 expect(codes(60, 5, 119)).toEqual(['mindestumlauf-massgebend']);
114 expect(computeCycleTime('webster', eingang(60, 7, 119)).cycleTime).toBe(119);
115 expect(computeCycleTime('webster', eingang(60, 5, 119)).cycleTime).toBe(120);
116 });
117
118 it('nennt die Meldung den erforderlichen und nicht den gerasterten Wert', () => {
119 /*
120 * "Die erforderliche Umlaufzeit beträgt {value} s" stand bis zur Aenderung
121 * mit dem GERASTERTEN Wert im Satz: bei 121 s Bedarf und Rasterung 7 s
122 * las der Bearbeiter "126,0 s" - eine Zahl, die in keiner Rechnung
123 * erforderlich ist.
124 */
125 const meldung = computeCycleTime('webster', eingang(121, 7)).notes.find(
126 (n) => n.code === 'umlaufzeit-ueber-hoechstwert',
127 );
128 expect(meldung, 'Meldung zum Hoechstwert').toBeDefined();
129 expect(meldung!.message).toContain('Die erforderliche Umlaufzeit beträgt 121,0 s');
130 expect(meldung!.message, 'der gerasterte Wert gehoert nicht in diesen Satz').not.toContain(
131 '126',
132 );
133 });
134
135 it('nennt die Meldung die Mindestumlaufzeit, wo diese massgebend ist', () => {
136 const meldung = computeCycleTime('webster', eingang(60, 5, 125)).notes.find(
137 (n) => n.code === 'umlaufzeit-ueber-hoechstwert',
138 );
139 expect(meldung!.message).toContain('Die erforderliche Umlaufzeit beträgt 125,0 s');
140 });
141
142 it('schreibt die Meldung die beiden Zahlen unterscheidbar, wenn sie dicht beieinanderliegen', () => {
143 /*
144 * Mit dem gerasterten Wert konnte das nicht vorkommen - er lag stets
145 * mindestens eine Rasterstufe ueber dem Hoechstwert. Der erforderliche
146 * Umlauf kann ihn um Hundertstelsekunden ueberschreiten; mit einer
147 * Nachkommastelle stuende dann "beträgt 120,0 s und überschreitet den
148 * Höchstwert von 120,0 s".
149 */
150 const meldung = computeCycleTime('webster', eingang(120.01, 7)).notes.find(
151 (n) => n.code === 'umlaufzeit-ueber-hoechstwert',
152 );
153 expect(meldung, 'auch 0,01 s ueber dem Hoechstwert werden gemeldet').toBeDefined();
154 expect(meldung!.message).toContain('beträgt 120,01 s');
155 expect(meldung!.message).toContain('Höchstwert von 120,00 s');
156 });
157
158 it('sagt sie "mehr als", wo auch drei Stellen den Unterschied nicht mehr zeigen', () => {
159 /*
160 * Der Rueckfall des Helfers `unterscheidbar`. Er ist erreichbar, weil der
161 * erforderliche Umlauf aus dem rechnerischen Wert stammt und stetig ist:
162 * 120,0001 s runden auf drei Stellen wie der Hoechstwert selbst. Statt
163 * eine Zahl zu erfinden, die dann beide Male gleich lautete, sagt der Satz
164 * "mehr als". Die Zusage steht im Kopfkommentar des Helfers; ohne diesen
165 * Fall bewacht sie nichts.
166 */
167 const meldung = computeCycleTime('webster', eingang(120.0001, 7)).notes.find(
168 (n) => n.code === 'umlaufzeit-ueber-hoechstwert',
169 );
170 expect(meldung, 'auch ein Zehntausendstel ueber dem Hoechstwert wird gemeldet').toBeDefined();
171 expect(meldung!.message).toContain('beträgt mehr als 120,0 s');
172 expect(meldung!.message).toContain('Höchstwert von 120,0 s');
173 expect(meldung!.message, 'keine erfundene Zahl').not.toContain('beträgt 120,0 s');
174 });
175
176 it('geht der Hoechstwert der Rasterung vor - die ausgegebene Umlaufzeit steht dann nicht im Raster', () => {
177 /*
178 * Die Rasterung ist keine harte Nebenbedingung des Ergebnisses: Die
179 * Kappung bricht sie, und zwar auch schon vor dieser Aenderung (120 ist
180 * kein Vielfaches von 7). Genau deshalb darf sie der Beurteilung nicht
181 * vorangehen.
182 */
183 const ergebnis = computeCycleTime('webster', eingang(119.48, 7));
184 expect(ergebnis.cycleTime).toBe(120);
185 expect(ergebnis.cycleTime % 7, '120 ist kein Vielfaches von 7').not.toBe(0);
186 });
187
188 it('bleibt der Zustand "uebersaettigt" unberuehrt', () => {
189 // Gegenprobe an der anderen Ausnahme: Y >= 1 kehrt vor der Rasterung
190 // zurueck und traegt weiterhin den eigenen Zustand.
191 const ergebnis = computeCycleTime('webster', {
192 lostTime: 20,
193 criticalFlowRatios: [0.6, 0.5],
194 step: 7,
195 });
196 expect(ergebnis.bounded).toBe('uebersaettigt');
197 expect(ergebnis.notes.map((n) => n.code)).toContain('uebersaettigt');
198 });
199 });