import { describe, expect, it } from 'vitest'; import { computeIntergreen } from '@/domain/rilsa/intergreen'; import { RILSA_DEFAULTS, SAFETY_FLOORS, type RilsaDefaults } from '@/domain/rilsa/constants'; import { resolveRilsaSettings } from '@/domain/rilsa/settings'; import type { ClearingStream, EnteringStream } from '@/domain/rilsa/types'; /** * Befunde A1 und A3. * * A1: Die Raeumgeschwindigkeit des Radverkehrs stand mit 5,0 m/s ueber dem * RiLSA-Regelwert von 4 m/s (FGSV-Aenderungsblatt vom 29.07.2015, Originalseite * 25: tue + tr = 1 + s0/4). Der zu hohe Wert verkuerzte die Zwischenzeit * zulasten des raeumenden Radverkehrs - bei 20 m Raeumweg um 1 s. Zudem wurden * Rad-Ueberschreibungen nur gegen die Kfz-Schranke von 10 m/s geprueft: 8 m/s * passierten ohne jede Meldung. * * A3: Die harte Obergrenze galt nur fuer stromindividuelle Ueberschreibungen. * Ein ueberhoehter Wert aus den Vorgaben (das Feld erlaubte fuer den OePNV bis * 15 m/s) lief ungeprueft in die Rechnung, waehrend derselbe Wert als * Ueberschreibung mit Fehler gekappt worden waere. */ function radRaeumend(extra: Partial = {}): ClearingStream { return { mode: 'rad', movement: 'geradeaus', clearingDistance: 20, vehicleClass: 'rad', vZul: 0, ...extra, }; } function kfzEinfahrend(): EnteringStream { return { mode: 'kfz', movement: 'geradeaus', enteringDistance: 10 }; } describe('Raeumgeschwindigkeit Radverkehr (Befund A1)', () => { it('rechnet mit dem RiLSA-Regelwert 4 m/s', () => { // RiLSA-Rechenbeispiel: 1 s Ueberfahrzeit + 20 m / 4 m/s - 10 m / 11,1 m/s // = 1 + 5 - 0,9009 = 5,099 -> aufgerundet 6 s. Mit den alten 5,0 m/s kamen // 4,1 -> 5 s heraus: Der einfahrende Kfz-Strom erhielt 1 s zu frueh Freigabe. const result = computeIntergreen(radRaeumend(), kfzEinfahrend()); expect(result.clearingSpeed).toBe(4.0); expect(result.clearingTime).toBe(5); expect(result.intergreen).toBe(6); }); it('kappt eine Rad-Ueberschreibung oberhalb des Regelwerts mit Fehler', () => { // 8 m/s passierten vorher ohne jede Meldung, weil nur gegen die // Kfz-Schranke von 10 m/s geprueft wurde. const result = computeIntergreen(radRaeumend({ clearingSpeedOverride: 8 }), kfzEinfahrend()); expect(result.clearingSpeed).toBe(SAFETY_FLOORS.maxClearingSpeedRad); expect( result.notes.some( (n) => n.code === 'raeumgeschwindigkeit-zu-hoch' && n.severity === 'fehler', ), ).toBe(true); }); it('laesst eine niedrigere Rad-Geschwindigkeit ohne Fehler zu', () => { const result = computeIntergreen(radRaeumend({ clearingSpeedOverride: 3 }), kfzEinfahrend()); expect(result.clearingSpeed).toBe(3); expect(result.notes.some((n) => n.severity === 'fehler')).toBe(false); }); it('begrenzt das Vorgabenfeld auf den Regelwert', () => { const { defaults, notes } = resolveRilsaSettings({ clearingSpeedRad: 7 }); expect(defaults.clearingSpeed.rad).toBe(SAFETY_FLOORS.maxClearingSpeedRad); expect( notes.some( (n) => n.code === 'vorgabe-ueberschritten:clearingSpeedRad' && n.severity === 'fehler', ), ).toBe(true); }); }); describe('Schrankendurchgriff auf Vorgaben-Regelwerte (Befund A3)', () => { /* * ANPASSUNG (Befund A2): Mit vZul am Strom raeumt der OePNV jetzt mit * Vmax/3,6 - der Vorgabenwert ist dort nur noch Rueckfallwert und gelangt * allein bei FEHLENDER Vmax in die Rechnung. Die Aussage des Tests bleibt: * Ein ueberhoehter Wert wird auf dem Vorgabenweg genauso mit Fehler gekappt * wie auf dem Ueberschreibungsweg. Der Testfall traegt deshalb keine Vmax * mehr (vZul 0); ohne Vmax ist die konservative Kfz-Schranke von 10 m/s der * Deckel. */ it('kappt einen ueberhoehten OePNV-Rueckfallwert aus den Vorgaben mit Fehler', () => { // Altprojekte koennen gespeicherte Vorgaben mit 15 m/s tragen; die Schranke // muss auf jedem Weg gelten, auf dem eine Raeumgeschwindigkeit in die // Rechnung gelangt. const alteVorgaben: RilsaDefaults = { ...RILSA_DEFAULTS, clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, oepnv: 15 }, }; const result = computeIntergreen( { mode: 'oepnv', movement: 'geradeaus', clearingDistance: 40, vehicleClass: 'bus', vZul: 0 }, kfzEinfahrend(), alteVorgaben, ); expect(result.clearingSpeed).toBe(SAFETY_FLOORS.maxClearingSpeedKfz); expect( result.notes.some( (n) => n.code === 'raeumgeschwindigkeit-vorgabe-zu-hoch' && n.severity === 'fehler', ), ).toBe(true); // Die fehlende Vmax wird zusaetzlich gemeldet (Befund A2). expect( result.notes.some((n) => n.code === 'oepnv-vmax-fehlt' && n.severity === 'warnung'), ).toBe(true); }); it('liegt eine Vmax vor, zaehlt sie - nicht der Vorgabenwert', () => { // Die 15 m/s aus den Vorgaben duerfen einen Strom mit bekannter Vmax // weder beschleunigen noch ausbremsen: Nach RiLSA-Fall 3 gilt Vmax/3,6. const alteVorgaben: RilsaDefaults = { ...RILSA_DEFAULTS, clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, oepnv: 15 }, }; const result = computeIntergreen( { mode: 'oepnv', movement: 'geradeaus', clearingDistance: 40, vehicleClass: 'bus', vZul: 50 }, kfzEinfahrend(), alteVorgaben, ); expect(result.clearingSpeed).toBeCloseTo(50 / 3.6, 6); expect(result.notes.some((n) => n.severity === 'fehler')).toBe(false); }); it('begrenzt das OePNV-Vorgabenfeld auf die harte Schranke', () => { // Das Feld ist der Rueckfallwert ohne Vmax; ohne bekannte Vmax bleibt die // alte Kfz-Schranke von 10 m/s der Deckel (SAFETY_FLOORS.maxClearingSpeedOepnv // von 19,4 m/s gilt nur, wo eine Vmax das begruendet). const { defaults, notes } = resolveRilsaSettings({ clearingSpeedOepnv: 15 }); expect(defaults.clearingSpeed.oepnv).toBe(SAFETY_FLOORS.maxClearingSpeedKfz); expect( notes.some( (n) => n.code === 'vorgabe-ueberschritten:clearingSpeedOepnv' && n.severity === 'fehler', ), ).toBe(true); }); });