lsa-planer

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

/ tests domain auslastungsgradquelle.test.ts

7,9 KB Rohdatei
tests/domain/auslastungsgradquelle.test.ts — 159 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { RILSA_DEFAULTS } from '@/domain/rilsa/constants';
3 import { computeCycleTime } from '@/domain/rilsa/cycle';
4 import { QUELLEN } from '@/domain/rilsa/quellen';
5 import { settingFields } from '@/domain/rilsa/settings';
6
7 /**
8 * Befund 19 (Fassung 5.11.0): Ziel- und Hoechst-Auslastungsgrad standen
9 * unter der Fundstelle 'praxis'.
10 *
11 * Deren Hinweis lautet woertlich "Frei wählbar; wirkt sich nicht auf die
12 * Einhaltung des Regelwerks aus" - und dieser Satz wird GEDRUCKT: Das
13 * Fundstellenverzeichnis der Planunterlage setzt ihn als "Zu beachten" unter
14 * die Zeile (pdf.ts, quellenverzeichnis). Fuer diese beiden Werte ist er
15 * falsch. Der Ziel-Auslastungsgrad steht im Nenner der HBS-Umlaufzeitformel,
16 * der Hoechstwert in dem der HCM-Formel; beide entscheiden ausserdem, ab
17 * welchem Y die Rechnung "uebersaettigt" meldet, und beide sind je die
18 * Schwelle einer Meldung des Pruefberichts. Die Umlaufzeit ist an
19 * Regelwerksgrenzen gebunden - die Einhaltung des Regelwerks haengt also sehr
20 * wohl daran.
21 *
22 * Derselbe Fehlertyp wie bei der Anfahrverlustzeit (Befund 27, Fassung 5.10.0),
23 * die aus demselben Grund eine eigene Fundstelle bekam.
24 */
25
26 const SATZ_DER_WIRKUNGSLOSIGKEIT = 'wirkt sich nicht auf die Einhaltung des Regelwerks aus';
27
28 const AUSLASTUNGSFELDER = ['targetDegreeOfSaturation', 'maxDegreeOfSaturation'] as const;
29
30 function feld(key: (typeof AUSLASTUNGSFELDER)[number]) {
31 const gefunden = settingFields().find((f) => f.key === key);
32 expect(gefunden, `Vorgabenfeld "${key}"`).toBeDefined();
33 return gefunden!;
34 }
35
36 describe('Auslastungsgrade: die Fundstelle darf keine Wirkungslosigkeit behaupten (Befund 19)', () => {
37 it('haengt beide Felder nicht mehr an der Sammelquelle "praxis"', () => {
38 for (const key of AUSLASTUNGSFELDER) {
39 expect(feld(key).quellenSchluessel, key).not.toBe('praxis');
40 }
41 });
42
43 it('druckt zu keinem der beiden Felder den Satz von der Wirkungslosigkeit', () => {
44 for (const key of AUSLASTUNGSFELDER) {
45 expect(feld(key).quelle.hinweis ?? '', key).not.toContain(SATZ_DER_WIRKUNGSLOSIGKEIT);
46 expect(feld(key).quelle.verwendung, key).not.toContain(SATZ_DER_WIRKUNGSLOSIGKEIT);
47 }
48 });
49
50 it('nennt in der Fundstelle statt dessen, worauf die beiden Werte wirken', () => {
51 for (const key of AUSLASTUNGSFELDER) {
52 const quelle = feld(key).quelle;
53 expect(quelle.verwendung, `${key}: Umlaufzeit`).toContain('Umlaufzeit');
54 expect(quelle.verwendung, `${key}: Pruefbericht`).toContain('Prüfbericht');
55 expect(quelle.sachgebiet, `${key}: Sachgebiet`).toContain('Auslastungsgrad');
56 }
57 });
58
59 it('fuehrt beide als selbstgesetzte Schwelle, nie als gesichert', () => {
60 for (const key of AUSLASTUNGSFELDER) {
61 // Grundsatz: selbstgesetzte Schwellen tragen nie "gesichert". Die Wahl
62 // INNERHALB des Bandes trifft dieses Programm; belegt ist nur das Band.
63 expect(feld(key).quelle.pruefstand, key).not.toBe('gesichert');
64 expect(feld(key).quelle.regelwerk, key).not.toContain('HBS');
65 }
66 });
67
68 it('nennt seit 5.27.0 das belegte Band und haelt beide Werte darin', () => {
69 /*
70 * UMGESTELLT MIT 5.27.0. Hier stand die Erwartung "Kurzform Planungspraxis"
71 * und "regelwerk enthaelt nicht RiLSA" - richtig, solange die RiLSA nicht
72 * vorlag: Das HBS 2015 kennt als Ueberlastungskriterium allein q > C, und
73 * ein Ziel- oder Hoechstwert kommt darin nicht vor. Die RiLSA nennt in
74 * Abschnitt 2.6 sehr wohl ein Band (0,80 bis 0,90). Belegt ist damit das
75 * Band, nicht die Wahl darin - deshalb bleibt der Pruefstand
76 * "abzugleichen" und die Fundstelle nennt beides.
77 */
78 for (const key of AUSLASTUNGSFELDER) {
79 const quelle = feld(key).quelle;
80 expect(quelle.kurz, key).toBe('RiLSA 2015');
81 expect(quelle.fundstelle, key).toContain('2.6');
82 expect(quelle.hinweis ?? '', key).toContain('0,80 und 0,90');
83 }
84 expect(RILSA_DEFAULTS.capacity.targetDegreeOfSaturation).toBe(0.85);
85 expect(RILSA_DEFAULTS.capacity.maxDegreeOfSaturation).toBe(0.9);
86 });
87
88 /*
89 * Der Fall sagt genau zwei Dinge zu, und beide misst er: Der Satz steht
90 * weiter an der Sammelquelle, und keines der beiden Auslastungsfelder haengt
91 * noch an ihr. OB der Satz fuer die verbliebenen Felder zutrifft, prueft er
92 * NICHT - eine solche Pauschalaussage war die Ursache von Befund 19, und sie
93 * gehoert nicht als Zusage in einen Testnamen. Nachgemessen ist sie je
94 * Feld einzeln.
95 *
96 * NACHGEZOGEN (Fassung 5.12.0): Die Zusicherung "es haengen ueberhaupt
97 * noch Felder an der Sammelquelle" ist entfallen, denn inzwischen sind die
98 * letzten drei Felder aus ihr herausgeloest
99 * (`kennwertquellen.test.ts`). Die Schleife lief damit ueber eine
100 * leere Liste und pruefte im Regelfall nichts mehr - sie fing nur noch die
101 * Rueckkehr eines Auslastungsfeldes. An ihre Stelle tritt die staerkere Form
102 * derselben Aussage: An der Sammelquelle haengt KEIN Vorgabenfeld mehr,
103 * also erst recht keines der beiden. Der Satz bleibt an der Quelle stehen,
104 * weil sie den Massstab benennt; gedruckt wird er nirgends mehr.
105 *
106 * ENTFERNT: Unter der Null stand die Schleife des alten
107 * Falles - `for (const f of anDerSammelquelle)` mit der Zusicherung, keines
108 * der Felder an der Quelle sei ein Auslastungsfeld. Hinter einer Liste der
109 * Laenge 0 kann sie keinen Durchlauf haben; GEMESSEN mit `expect.fail()`
110 * als erster Zeile ihres Rumpfes: alle acht Faelle der Datei blieben gruen.
111 * Sie faellt weg und wird nicht umgekehrt zur tragenden Zusicherung
112 * gemacht - die Null darueber ist die staerkere Aussage, eine leere Liste
113 * enthaelt auch kein Auslastungsfeld, und sie fuer die Schleife
114 * herzugeben waere ein Abschwaechen. Dass keines der beiden Felder an der
115 * Sammelquelle haengt, misst ohnehin der erste Fall dieser Datei je Feld
116 * einzeln.
117 */
118 it('laesst den Satz an der Sammelquelle stehen, und keines der beiden Auslastungsfelder haengt noch an ihr', () => {
119 expect(QUELLEN.praxis.hinweis ?? '').toContain(SATZ_DER_WIRKUNGSLOSIGKEIT);
120 const anDerSammelquelle = settingFields().filter((f) => f.quellenSchluessel === 'praxis');
121 expect(
122 anDerSammelquelle.map((f) => f.key),
123 'Vorgabenfelder an "praxis"',
124 ).toHaveLength(0);
125 });
126 });
127
128 describe('Auslastungsgrade: die Wirkung, die der alte Satz bestritt', () => {
129 it('verschiebt der Ziel-Auslastungsgrad die HBS-Umlaufzeit', () => {
130 const eingang = { lostTime: 20, criticalFlowRatios: [0.35, 0.35] };
131 const mitVorgabe = computeCycleTime('hbs', eingang);
132 const hoeher = computeCycleTime('hbs', eingang, {
133 ...RILSA_DEFAULTS,
134 capacity: { ...RILSA_DEFAULTS.capacity, targetDegreeOfSaturation: 0.95 },
135 });
136 expect(RILSA_DEFAULTS.capacity.targetDegreeOfSaturation).toBe(0.85);
137 // L * x / (x - Y): 20 * 0,85 / 0,15 = 113,3 s gegen 20 * 0,95 / 0,25 = 76 s.
138 expect(mitVorgabe.raw).toBeCloseTo(113.33, 2);
139 expect(hoeher.raw).toBeCloseTo(76, 2);
140 expect(hoeher.cycleTime).toBeLessThan(mitVorgabe.cycleTime);
141 });
142
143 it('entscheidet der Hoechst-Auslastungsgrad, ab wann die HCM-Rechnung uebersaettigt meldet', () => {
144 // Y = 0,88 - unter dem Hoechstwert 0,90 und ueber einem strengeren 0,85.
145 // Die Zahlen sind mit 5.27.0 nachgezogen: Zuvor stand der Hoechstwert bei
146 // 0,95, und Y = 0,92 lag darunter.
147 const eingang = { lostTime: 20, criticalFlowRatios: [0.44, 0.44] };
148 // Seit 5.27.0 das obere Ende des Bandes, das die RiLSA 2015 in
149 // Abschnitt 2.6 fuer den Auslastungsgrad x nennt (0,80 bis 0,90).
150 expect(RILSA_DEFAULTS.capacity.maxDegreeOfSaturation).toBe(0.9);
151 expect(computeCycleTime('hcm', eingang).bounded).not.toBe('uebersaettigt');
152 const strenger = computeCycleTime('hcm', eingang, {
153 ...RILSA_DEFAULTS,
154 capacity: { ...RILSA_DEFAULTS.capacity, maxDegreeOfSaturation: 0.85 },
155 });
156 expect(strenger.bounded).toBe('uebersaettigt');
157 expect(strenger.notes.some((n) => n.code === 'uebersaettigt')).toBe(true);
158 });
159 });