lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain auslastungsgradquelle.test.ts
| 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 | }); |