lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain zwischenzeitfall2.test.ts
| 1 | import { readFileSync } from 'node:fs'; |
| 2 | import { describe, expect, it } from 'vitest'; |
| 3 | import { computeIntergreen } from '@/domain/rilsa/intergreen'; |
| 4 | import { RILSA_DEFAULTS } from '@/domain/rilsa/constants'; |
| 5 | import { QUELLEN } from '@/domain/rilsa/quellen'; |
| 6 | import { HILFE } from '@/ui/help/texte'; |
| 7 | import { createConflict } from '@/domain/model/factory'; |
| 8 | import { aendereKonflikt, hatHandvorgaben } from '@/domain/model/project'; |
| 9 | import type { ClearingStream, EnteringStream } from '@/domain/rilsa/types'; |
| 10 | |
| 11 | /* |
| 12 | * Fassung 5.4.0, Fragen E1 und E2 (Zwischenzeitfall 2, "abbiegendes |
| 13 | * Kraftfahrzeug raeumt"). |
| 14 | * |
| 15 | * KEINE RECHENAENDERUNG - dieser Test sichert eine BELEGLAGE. |
| 16 | * |
| 17 | * Der A2-Vermerk vom 14.08.2026 (Fassung 5.4.0) behauptete als Beifang, das |
| 18 | * FGSV-Aenderungsblatt vom 29.07.2015 belege "auch Fall 2 woertlich (tue 2 s |
| 19 | * abbiegend; 5 m/s bei Innenradius < 10 m)". Die erneute vollstaendige |
| 20 | * Auswertung des Blattes am 15.08.2026 hat das widerlegt: Fall 2 kommt darin |
| 21 | * ausschliesslich als Bildunterschrift vor ("Bild 11: Beispiel fuer den |
| 22 | * Konfliktfall ..."); weder die Ueberfahrzeit 2 s noch die 5 m/s stehen dort. |
| 23 | * |
| 24 | * Was oeffentlich belegbar ist, stuetzt den Programmstand nur zum Teil: |
| 25 | * - 7,0 m/s fuer Abbieger bestaetigt der Leitfaden fuer Lichtsignalanlagen der |
| 26 | * Freien und Hansestadt Hamburg (Anlage A: "Kfz Abbieger 7,0 m/s"). |
| 27 | * - Die Ueberfahrzeit koppelt dieses Programm an die Gelbzeit (3/4/5 s), |
| 28 | * waehrend die Sekundaerliteratur feste 3 s geradeaus und 2 s abbiegend |
| 29 | * nennt. Die Kopplung rechnet LAENGER - konservativ, aber abweichend. |
| 30 | * - Ein Sonderwert bei sehr engem Innenradius ist aus keiner oeffentlichen |
| 31 | * Quelle belegbar und deshalb nicht als Regel abgebildet; oertlich laesst er |
| 32 | * sich je Konfliktbeziehung eintragen. |
| 33 | * |
| 34 | * Deshalb bleibt gerechnet, was gerechnet wurde - und dieser Test haelt fest, |
| 35 | * dass die Unterlagen das auch so sagen. |
| 36 | */ |
| 37 | |
| 38 | function kfzRaeumend(extra: Partial<ClearingStream> = {}): ClearingStream { |
| 39 | return { |
| 40 | mode: 'kfz', |
| 41 | movement: 'rechts', |
| 42 | clearingDistance: 20, |
| 43 | vehicleClass: 'pkw', |
| 44 | vZul: 50, |
| 45 | ...extra, |
| 46 | }; |
| 47 | } |
| 48 | |
| 49 | function kfzEinfahrend(): EnteringStream { |
| 50 | return { mode: 'kfz', movement: 'geradeaus', enteringDistance: 10 }; |
| 51 | } |
| 52 | |
| 53 | describe('Zwischenzeitfall 2: Programmstand der Abbieger (Fassung 5.4.0, Fragen E1 und E2)', () => { |
| 54 | it('raeumt abbiegend mit 7,0 m/s und geradeaus mit 10,0 m/s', () => { |
| 55 | // 20 m Raeumweg + 6 m Pkw = 26 m. Abbiegend 26/7 = 3,714 s, |
| 56 | // geradeaus 26/10 = 2,6 s. |
| 57 | const abbiegend = computeIntergreen(kfzRaeumend(), kfzEinfahrend()); |
| 58 | const geradeaus = computeIntergreen(kfzRaeumend({ movement: 'geradeaus' }), kfzEinfahrend()); |
| 59 | |
| 60 | expect(abbiegend.clearingSpeed).toBe(7.0); |
| 61 | expect(abbiegend.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzTurning); |
| 62 | expect(abbiegend.clearingTime).toBe(3.714); |
| 63 | expect(geradeaus.clearingSpeed).toBe(10.0); |
| 64 | }); |
| 65 | |
| 66 | it('setzt die Ueberfahrzeit auch beim Abbiegen gleich der Gelbzeit (3/4/5 s)', () => { |
| 67 | // Der Kern der Frage E2 (Fassung 5.4.0): Die Sekundaerliteratur nennt fuer Abbieger |
| 68 | // 2 s. Dieses Programm zeigt hier 3, 4 und 5 s - jede Stufe laenger. |
| 69 | for (const [vZul, gelb] of [ |
| 70 | [50, 3], |
| 71 | [60, 4], |
| 72 | [70, 5], |
| 73 | ] as const) { |
| 74 | const result = computeIntergreen(kfzRaeumend({ vZul }), kfzEinfahrend()); |
| 75 | expect(result.crossingTime, `vZul ${vZul}`).toBe(gelb); |
| 76 | } |
| 77 | }); |
| 78 | |
| 79 | it('rechnet die Kopplung damit laenger als die Literatur - nicht kuerzer', () => { |
| 80 | // 50 km/h, 20 m Raeumweg, 10 m Einfahrweg: |
| 81 | // Programm 3 + 26/7 - 10/11,1 = 3 + 3,714 - 0,901 = 5,813 -> 6 s |
| 82 | // Literatur 2 + 26/7 - 10/11,1 = 4,813 -> 5 s |
| 83 | // Die Abweichung liegt auf der sicheren Seite; ein Umbau auf 2 s wuerde |
| 84 | // die Zwischenzeit VERKUERZEN und ist ohne Druckwerk nicht zu vertreten. |
| 85 | const result = computeIntergreen(kfzRaeumend(), kfzEinfahrend()); |
| 86 | |
| 87 | expect(result.raw).toBe(5.813); |
| 88 | expect(result.intergreen).toBe(6); |
| 89 | }); |
| 90 | }); |
| 91 | |
| 92 | describe('Enger Innenradius: Ueberschreibung je Beziehung (Fassung 5.4.0, Frage E1)', () => { |
| 93 | it('wirkt mit 5,0 m/s und verlaengert die Zwischenzeit', () => { |
| 94 | // Die 5 m/s bei Innenradius < 10 m sind oeffentlich nicht belegbar und |
| 95 | // deshalb keine Regel des Programms. Wer sie oertlich braucht, traegt sie |
| 96 | // an der Beziehung ein: 2 + 26/5 - 0,901 -> 3 + 5,2 - 0,901 = 7,299 -> 8 s. |
| 97 | const result = computeIntergreen(kfzRaeumend({ clearingSpeedOverride: 5.0 }), kfzEinfahrend()); |
| 98 | |
| 99 | expect(result.clearingSpeed).toBe(5.0); |
| 100 | expect(result.intergreen).toBe(8); |
| 101 | // Eine Verschaerfung ist zulaessig: kein Fehler, keine Kappung. |
| 102 | expect(result.notes.some((n) => n.severity === 'fehler')).toBe(false); |
| 103 | }); |
| 104 | |
| 105 | it('erscheint als Abweichung: Handvorgabe und entwerteter Wegnachweis', () => { |
| 106 | const vorher = createConflict('sg-1', 'sg-2'); |
| 107 | expect(hatHandvorgaben(vorher)).toBe(false); |
| 108 | |
| 109 | const nachher = aendereKonflikt(vorher, { clearingSpeedOverride: 5.0 }); |
| 110 | |
| 111 | expect(hatHandvorgaben(nachher)).toBe(true); |
| 112 | expect(nachher.wegnachweis.herkunft).toBe('handeingabe'); |
| 113 | }); |
| 114 | }); |
| 115 | |
| 116 | describe('Beleglage in Quelltext und Unterlagen', () => { |
| 117 | it('nennt das Fundstellenverzeichnis den Fall 2 samt beider Raeumgeschwindigkeiten', () => { |
| 118 | /* |
| 119 | * UMGESTELLT MIT 5.27.0. Bis dahin verlangte dieser Fall den Nachweis der |
| 120 | * LUECKE: die Hamburger Stuetze fuer die 7,0 m/s, die Abweichung bei der |
| 121 | * Ueberfahrzeit und den Vermerk, Fall 2 stehe im FGSV-Aenderungsblatt nur |
| 122 | * als Bildunterschrift. Alles drei war richtig, solange das Druckwerk |
| 123 | * fehlte. Es liegt vor; die Luecke gibt es nicht mehr, und ein Verzeichnis, |
| 124 | * das sie weiter behauptete, waere falsch. |
| 125 | */ |
| 126 | const quelle = QUELLEN['rilsa-zwischenzeiten']; |
| 127 | const hinweis = quelle.hinweis ?? ''; |
| 128 | |
| 129 | expect(quelle.fundstelle).toContain('2.5.2'); |
| 130 | // (a) Beide Raeumgeschwindigkeiten des Falles 2 mit ihrer Bedingung. |
| 131 | expect(hinweis).toContain('7 m/s'); |
| 132 | expect(hinweis).toContain('5 m/s bei einem Radius des Fahrstreifeninnenrands unter 10 m'); |
| 133 | // (b) Die feste Ueberfahrzeit des Falles 2. |
| 134 | expect(hinweis).toContain('tü = 2 s abbiegend'); |
| 135 | // (c) Kein Rest der alten Luecke. |
| 136 | expect(hinweis).not.toContain('Bildunterschrift'); |
| 137 | expect(hinweis).not.toContain('nicht belegbar'); |
| 138 | }); |
| 139 | |
| 140 | it('traegt der Kennwert kfzTurning die Einordnung im Quelltext', () => { |
| 141 | const quelltext = readFileSync('src/domain/rilsa/constants.ts', 'utf8'); |
| 142 | |
| 143 | expect(quelltext).toContain('Abschnitt 2.5.2, Fall 2'); |
| 144 | expect(quelltext).toContain('Innenradius'); |
| 145 | }); |
| 146 | |
| 147 | it('nennt die Hilfe den engen Innenradius als oertliche Verschaerfung', () => { |
| 148 | const hilfe = readFileSync('src/ui/help/texte.ts', 'utf8'); |
| 149 | |
| 150 | expect(hilfe).toContain('Innenradius'); |
| 151 | expect(hilfe).toContain('örtliche Verschärfung'); |
| 152 | }); |
| 153 | |
| 154 | /* |
| 155 | * BEFUND (Fassung 5.4.0): Die Hilfe gab die 5 m/s ohne jeden Vorbehalt als |
| 156 | * Praxiswert aus - "(in der Praxis 5 m/s)" -, waehrend quellen.ts und die |
| 157 | * Unterlagen im selben Arbeitsstand sagen, die Zahl sei aus keiner |
| 158 | * oeffentlichen Quelle belegbar. Genau diese Art Behauptung sollte |
| 159 | * abgeschafft sein. |
| 160 | */ |
| 161 | it('gibt die Hilfe die 5 m/s nicht als belegten Praxiswert aus', () => { |
| 162 | const raeumgeschwindigkeit = HILFE.raeumgeschwindigkeit.woher; |
| 163 | |
| 164 | expect(raeumgeschwindigkeit).not.toContain('in der Praxis 5 m/s'); |
| 165 | // Steht die Zahl da, steht der Vorbehalt daneben. |
| 166 | if (raeumgeschwindigkeit.includes('5 m/s')) { |
| 167 | expect(raeumgeschwindigkeit).toContain('nicht belegbar'); |
| 168 | expect(raeumgeschwindigkeit).toContain('Fundstellenverzeichnis'); |
| 169 | } |
| 170 | }); |
| 171 | |
| 172 | it('sagt die Hilfe dasselbe wie das Fundstellenverzeichnis', () => { |
| 173 | /* |
| 174 | * Beide Texte gehen an denselben Anwender; widersprechen sie sich, ist der |
| 175 | * vorsichtigere wertlos. Bis 5.26.0 war der gemeinsame Nenner "nicht |
| 176 | * belegbar"; seit 5.27.0 ist es die Fundstelle - und keiner der beiden darf |
| 177 | * die alte Aussage noch fuehren. |
| 178 | */ |
| 179 | const hinweis = QUELLEN['rilsa-zwischenzeiten'].hinweis!; |
| 180 | expect(hinweis).not.toContain('nicht belegbar'); |
| 181 | expect(HILFE.engerRadius.wasIstDas).toContain('Abschnitt 2.5.2'); |
| 182 | expect(HILFE.engerRadius.wasIstDas).not.toContain('PRAXISANSATZ, KEIN REGELWERKSWERT'); |
| 183 | expect(HILFE.raeumgeschwindigkeit.woher).not.toMatch(/\(in der Praxis [\d,]+ m\/s\)/); |
| 184 | expect(HILFE.raeumgeschwindigkeit.woher).not.toContain('nicht nachweisbar'); |
| 185 | }); |
| 186 | }); |