import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import { computeIntergreen } from '@/domain/rilsa/intergreen'; import { RILSA_DEFAULTS } from '@/domain/rilsa/constants'; import { QUELLEN } from '@/domain/rilsa/quellen'; import { HILFE } from '@/ui/help/texte'; import { createConflict } from '@/domain/model/factory'; import { aendereKonflikt, hatHandvorgaben } from '@/domain/model/project'; import type { ClearingStream, EnteringStream } from '@/domain/rilsa/types'; /* * Fassung 5.4.0, Fragen E1 und E2 (Zwischenzeitfall 2, "abbiegendes * Kraftfahrzeug raeumt"). * * KEINE RECHENAENDERUNG - dieser Test sichert eine BELEGLAGE. * * Der A2-Vermerk vom 14.08.2026 (Fassung 5.4.0) behauptete als Beifang, das * FGSV-Aenderungsblatt vom 29.07.2015 belege "auch Fall 2 woertlich (tue 2 s * abbiegend; 5 m/s bei Innenradius < 10 m)". Die erneute vollstaendige * Auswertung des Blattes am 15.08.2026 hat das widerlegt: Fall 2 kommt darin * ausschliesslich als Bildunterschrift vor ("Bild 11: Beispiel fuer den * Konfliktfall ..."); weder die Ueberfahrzeit 2 s noch die 5 m/s stehen dort. * * Was oeffentlich belegbar ist, stuetzt den Programmstand nur zum Teil: * - 7,0 m/s fuer Abbieger bestaetigt der Leitfaden fuer Lichtsignalanlagen der * Freien und Hansestadt Hamburg (Anlage A: "Kfz Abbieger 7,0 m/s"). * - Die Ueberfahrzeit koppelt dieses Programm an die Gelbzeit (3/4/5 s), * waehrend die Sekundaerliteratur feste 3 s geradeaus und 2 s abbiegend * nennt. Die Kopplung rechnet LAENGER - konservativ, aber abweichend. * - Ein Sonderwert bei sehr engem Innenradius ist aus keiner oeffentlichen * Quelle belegbar und deshalb nicht als Regel abgebildet; oertlich laesst er * sich je Konfliktbeziehung eintragen. * * Deshalb bleibt gerechnet, was gerechnet wurde - und dieser Test haelt fest, * dass die Unterlagen das auch so sagen. */ function kfzRaeumend(extra: Partial = {}): ClearingStream { return { mode: 'kfz', movement: 'rechts', clearingDistance: 20, vehicleClass: 'pkw', vZul: 50, ...extra, }; } function kfzEinfahrend(): EnteringStream { return { mode: 'kfz', movement: 'geradeaus', enteringDistance: 10 }; } describe('Zwischenzeitfall 2: Programmstand der Abbieger (Fassung 5.4.0, Fragen E1 und E2)', () => { it('raeumt abbiegend mit 7,0 m/s und geradeaus mit 10,0 m/s', () => { // 20 m Raeumweg + 6 m Pkw = 26 m. Abbiegend 26/7 = 3,714 s, // geradeaus 26/10 = 2,6 s. const abbiegend = computeIntergreen(kfzRaeumend(), kfzEinfahrend()); const geradeaus = computeIntergreen(kfzRaeumend({ movement: 'geradeaus' }), kfzEinfahrend()); expect(abbiegend.clearingSpeed).toBe(7.0); expect(abbiegend.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzTurning); expect(abbiegend.clearingTime).toBe(3.714); expect(geradeaus.clearingSpeed).toBe(10.0); }); it('setzt die Ueberfahrzeit auch beim Abbiegen gleich der Gelbzeit (3/4/5 s)', () => { // Der Kern der Frage E2 (Fassung 5.4.0): Die Sekundaerliteratur nennt fuer Abbieger // 2 s. Dieses Programm zeigt hier 3, 4 und 5 s - jede Stufe laenger. for (const [vZul, gelb] of [ [50, 3], [60, 4], [70, 5], ] as const) { const result = computeIntergreen(kfzRaeumend({ vZul }), kfzEinfahrend()); expect(result.crossingTime, `vZul ${vZul}`).toBe(gelb); } }); it('rechnet die Kopplung damit laenger als die Literatur - nicht kuerzer', () => { // 50 km/h, 20 m Raeumweg, 10 m Einfahrweg: // Programm 3 + 26/7 - 10/11,1 = 3 + 3,714 - 0,901 = 5,813 -> 6 s // Literatur 2 + 26/7 - 10/11,1 = 4,813 -> 5 s // Die Abweichung liegt auf der sicheren Seite; ein Umbau auf 2 s wuerde // die Zwischenzeit VERKUERZEN und ist ohne Druckwerk nicht zu vertreten. const result = computeIntergreen(kfzRaeumend(), kfzEinfahrend()); expect(result.raw).toBe(5.813); expect(result.intergreen).toBe(6); }); }); describe('Enger Innenradius: Ueberschreibung je Beziehung (Fassung 5.4.0, Frage E1)', () => { it('wirkt mit 5,0 m/s und verlaengert die Zwischenzeit', () => { // Die 5 m/s bei Innenradius < 10 m sind oeffentlich nicht belegbar und // deshalb keine Regel des Programms. Wer sie oertlich braucht, traegt sie // an der Beziehung ein: 2 + 26/5 - 0,901 -> 3 + 5,2 - 0,901 = 7,299 -> 8 s. const result = computeIntergreen(kfzRaeumend({ clearingSpeedOverride: 5.0 }), kfzEinfahrend()); expect(result.clearingSpeed).toBe(5.0); expect(result.intergreen).toBe(8); // Eine Verschaerfung ist zulaessig: kein Fehler, keine Kappung. expect(result.notes.some((n) => n.severity === 'fehler')).toBe(false); }); it('erscheint als Abweichung: Handvorgabe und entwerteter Wegnachweis', () => { const vorher = createConflict('sg-1', 'sg-2'); expect(hatHandvorgaben(vorher)).toBe(false); const nachher = aendereKonflikt(vorher, { clearingSpeedOverride: 5.0 }); expect(hatHandvorgaben(nachher)).toBe(true); expect(nachher.wegnachweis.herkunft).toBe('handeingabe'); }); }); describe('Beleglage in Quelltext und Unterlagen', () => { it('nennt das Fundstellenverzeichnis den Fall 2 samt beider Raeumgeschwindigkeiten', () => { /* * UMGESTELLT MIT 5.27.0. Bis dahin verlangte dieser Fall den Nachweis der * LUECKE: die Hamburger Stuetze fuer die 7,0 m/s, die Abweichung bei der * Ueberfahrzeit und den Vermerk, Fall 2 stehe im FGSV-Aenderungsblatt nur * als Bildunterschrift. Alles drei war richtig, solange das Druckwerk * fehlte. Es liegt vor; die Luecke gibt es nicht mehr, und ein Verzeichnis, * das sie weiter behauptete, waere falsch. */ const quelle = QUELLEN['rilsa-zwischenzeiten']; const hinweis = quelle.hinweis ?? ''; expect(quelle.fundstelle).toContain('2.5.2'); // (a) Beide Raeumgeschwindigkeiten des Falles 2 mit ihrer Bedingung. expect(hinweis).toContain('7 m/s'); expect(hinweis).toContain('5 m/s bei einem Radius des Fahrstreifeninnenrands unter 10 m'); // (b) Die feste Ueberfahrzeit des Falles 2. expect(hinweis).toContain('tü = 2 s abbiegend'); // (c) Kein Rest der alten Luecke. expect(hinweis).not.toContain('Bildunterschrift'); expect(hinweis).not.toContain('nicht belegbar'); }); it('traegt der Kennwert kfzTurning die Einordnung im Quelltext', () => { const quelltext = readFileSync('src/domain/rilsa/constants.ts', 'utf8'); expect(quelltext).toContain('Abschnitt 2.5.2, Fall 2'); expect(quelltext).toContain('Innenradius'); }); it('nennt die Hilfe den engen Innenradius als oertliche Verschaerfung', () => { const hilfe = readFileSync('src/ui/help/texte.ts', 'utf8'); expect(hilfe).toContain('Innenradius'); expect(hilfe).toContain('örtliche Verschärfung'); }); /* * BEFUND (Fassung 5.4.0): Die Hilfe gab die 5 m/s ohne jeden Vorbehalt als * Praxiswert aus - "(in der Praxis 5 m/s)" -, waehrend quellen.ts und die * Unterlagen im selben Arbeitsstand sagen, die Zahl sei aus keiner * oeffentlichen Quelle belegbar. Genau diese Art Behauptung sollte * abgeschafft sein. */ it('gibt die Hilfe die 5 m/s nicht als belegten Praxiswert aus', () => { const raeumgeschwindigkeit = HILFE.raeumgeschwindigkeit.woher; expect(raeumgeschwindigkeit).not.toContain('in der Praxis 5 m/s'); // Steht die Zahl da, steht der Vorbehalt daneben. if (raeumgeschwindigkeit.includes('5 m/s')) { expect(raeumgeschwindigkeit).toContain('nicht belegbar'); expect(raeumgeschwindigkeit).toContain('Fundstellenverzeichnis'); } }); it('sagt die Hilfe dasselbe wie das Fundstellenverzeichnis', () => { /* * Beide Texte gehen an denselben Anwender; widersprechen sie sich, ist der * vorsichtigere wertlos. Bis 5.26.0 war der gemeinsame Nenner "nicht * belegbar"; seit 5.27.0 ist es die Fundstelle - und keiner der beiden darf * die alte Aussage noch fuehren. */ const hinweis = QUELLEN['rilsa-zwischenzeiten'].hinweis!; expect(hinweis).not.toContain('nicht belegbar'); expect(HILFE.engerRadius.wasIstDas).toContain('Abschnitt 2.5.2'); expect(HILFE.engerRadius.wasIstDas).not.toContain('PRAXISANSATZ, KEIN REGELWERKSWERT'); expect(HILFE.raeumgeschwindigkeit.woher).not.toMatch(/\(in der Praxis [\d,]+ m\/s\)/); expect(HILFE.raeumgeschwindigkeit.woher).not.toContain('nicht nachweisbar'); }); });