import { describe, expect, it } from 'vitest'; import { ueberfahrzeitHerkunft } from '@/domain/rilsa/ansaetze'; import { RILSA_DEFAULTS, RILSA_ENGSTELLE, UEBERFAHRZEIT_ANSATZ_LABELS, } from '@/domain/rilsa/constants'; import { bezugswerte } from '@/domain/rilsa/intergreen'; import { engstellenRaeumgeschwindigkeitMs } from '@/domain/rilsa/engstellensignalisierung'; /** * Die Bezugswerte richten sich nach dem Verfahren, nicht nach der Gewohnheit. * * ANLASS (Befund aus dem Betrieb, 11.09.2026). Das Zwischenzeitfenster einer * einstreifigen Verkehrsfuehrung nannte unter dem Feld "Raeumgeschwindigkeit" * den Regelwert der KNOTENPUNKTRECHNUNG - 10,0 m/s - und wies zwei Zeilen * tiefer im Rechenweg desselben Fensters 11,11 m/s aus. An einer Engstelle * rechnet der Fachkern nach RiLSA 2015, Abschnitt 5.2.2, also mit der * Raeumgeschwindigkeitsstaffel und nicht mit dem Vorgabenwert; die * Beschriftung holte ihren Wert dagegen aus `regelRaeumgeschwindigkeit`, und * die Funktion kennt die Anlagenart nicht. * * WARUM DAS MEHR IST ALS EIN SCHOENHEITSFEHLER. Der genannte Wert ist die * Groesse, gegen die der Bearbeiter seine Handeingabe abwaegt und im Zweifel * begruendet. 10 m/s sieht strenger aus als die 11,11 m/s des Verfahrens - eine * Handeingabe daneben erschien deshalb harmloser, als sie ist. Das ist die * unsichere Richtung. * * ZWEI NACHBARFELDER HAENGEN MIT DRAN. Die Ueberfahrzeit steht an der Engstelle * mit 4 s fest im Abschnitt und ist nicht die Gelbzeit. Und die * Einfahrgeschwindigkeit geht dort in gar keine Zahl ein, weil der Abschnitt * ausdruecklich ohne Einfahrzeit rechnet - ein Regelwert unter einem Feld ohne * Wirkung laedt dazu ein, daran zu drehen. */ /** Der gemeldete Fall: 50 km/h zulaessig, Raeumgeschwindigkeit der Staffel 40 km/h. */ const KFZ_GERADEAUS = { mode: 'kfz', movement: 'geradeaus', vZul: 50, } as const; describe('Am Knotenpunkt bleibt alles beim Regelwert der Knotenpunktrechnung', () => { it('nennt die Raeumgeschwindigkeit des Kennwertsatzes', () => { const werte = bezugswerte('knotenpunkt', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); expect(werte.raeumgeschwindigkeit).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight); }); it('nennt eine Einfahrgeschwindigkeit, denn dort wirkt sie', () => { const werte = bezugswerte('knotenpunkt', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); expect(werte.einfahrgeschwindigkeit).toBe(RILSA_DEFAULTS.enteringSpeed.kfz); }); it('nennt die Ueberfahrzeit der Fahrbeziehung', () => { const werte = bezugswerte('knotenpunkt', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); expect(werte.ueberfahrzeit).toBeGreaterThan(0); expect(werte.ueberfahrzeit).not.toBe(RILSA_ENGSTELLE.ueberfahrzeit); }); }); describe('An der Engstelle gelten die Werte des Abschnitts 5.2', () => { const werte = bezugswerte('engstelle', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); it('nennt die Raeumgeschwindigkeit der Staffel und nicht den Vorgabenwert', () => { // Genau der gemeldete Fall: 50 km/h zulaessig ergeben 40 km/h = 11,11 m/s, // nicht die 10 m/s der Knotenpunktrechnung. expect(werte.raeumgeschwindigkeit).toBeCloseTo(40 / 3.6, 6); expect(werte.raeumgeschwindigkeit).not.toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight); }); it('holt diese Zahl aus derselben Stelle wie die Rechnung', () => { // Keine zweite Fassung der Staffel: Beide Wege muessen dieselbe Zahl geben. expect(werte.raeumgeschwindigkeit).toBe(engstellenRaeumgeschwindigkeitMs(50)); }); it('nennt die feste Ueberfahrzeit von 4 s', () => { expect(werte.ueberfahrzeit).toBe(RILSA_ENGSTELLE.ueberfahrzeit); }); it('nennt gar keine Einfahrgeschwindigkeit, weil dort keine wirkt', () => { // null und nicht 0: 0 waere eine Zahl, gegen die sich abwaegen liesse. expect(werte.einfahrgeschwindigkeit).toBeNull(); }); it('gibt das Verfahren zurueck, nach dem es gerechnet hat', () => { expect(werte.verfahren).toBe('engstelle'); }); }); describe('Die Staffel folgt der zulaessigen Hoechstgeschwindigkeit', () => { it.each([ // Die Staffel liegt eine Stufe unter der zulaessigen Hoechstgeschwindigkeit, // und unterhalb ihrer kleinsten Stufe gilt diese. [30, 30], [40, 30], [50, 40], [60, 50], ])('bei %s km/h zulaessig steht der Bezugswert bei %s km/h', (vZul, erwartetKmh) => { // Die Bezugsgroesse darf nicht an einer Zahl kleben: Sie haengt an der // Oertlichkeit, und das Fenster muss die des jeweiligen Stroms nennen. const werte = bezugswerte('engstelle', { ...KFZ_GERADEAUS, vZul }, 'kfz', RILSA_DEFAULTS); expect(werte.raeumgeschwindigkeit).toBeCloseTo(erwartetKmh / 3.6, 6); }); }); /* * NACHTRAG (Fassung 5.43.0): Ein Feld desselben Fensters blieb bei * der Gewohnheit. * * Die Zeile "Ueberfahrzeit tue" des Rechenwegs holt ihre Erlaeuterung aus * `ueberfahrzeitHerkunft`, und die Funktion kannte nur die Verkehrsart und den * gewaehlten Kfz-Ansatz. An einer Engstelle stand deshalb neben den 4 s des * Abschnitts 5.2.2 die Beschriftung des KNOTENPUNKT-Ansatzes - bei der Vorgabe * "Überfahrzeit = Gelbzeit" also "3/4/5 s nach zulässiger * Hoechstgeschwindigkeit" und beim festen Ansatz "3 s geradeaus / 2 s * abbiegend", beides neben einer Zahl, die keiner der beiden hergibt. Dieselbe * Spalte druckt die Tabellenausgabe (services/export/csv.ts). * * Die Funktion bekommt das Verfahren jetzt als ERSTES Argument - wie * `bezugswerte` darueber und aus demselben Grund: So muss jede Aufrufstelle es * mitgeben, und eine vergessene bricht beim Uebersetzen statt in der Ausgabe. */ describe('Die Herkunft der Ueberfahrzeit kennt das Verfahren ebenso', () => { it('nennt an der Engstelle den Regelwert des Abschnitts 5.2.2 und nicht den Kfz-Ansatz', () => { for (const ansatz of ['gelbzeit', 'fest'] as const) { const text = ueberfahrzeitHerkunft('engstelle', 'kfz', ansatz); expect(text, ansatz).toBe( `Regelwert der Engstelle: tü = ${RILSA_ENGSTELLE.ueberfahrzeit} s ` + '(RiLSA 2015, Abschnitt 5.2.2)', ); expect(text, `${ansatz}: Beschriftung des Kfz-Ansatzes`).not.toContain( UEBERFAHRZEIT_ANSATZ_LABELS[ansatz], ); } }); it('sagt an der Engstelle, dass eine eingetragene Ueberfahrzeit nicht eingeht', () => { /* * `computeEngstellenIntergreen` liest `crossingTimeOverride` gar nicht: Die * Ueberfahrzeit ist dort die feste Zahl des Abschnitts. Bis hierher hiess * eine Eingabe von genau 4 s "an dieser Beziehung von Hand eingetragen" - * eine Herleitung, die nicht stattgefunden hat -, und eine von 6 s * "verworfen", als haette eine Pruefung sie abgelehnt. */ for (const vorgabewert of [4, 6]) { expect( ueberfahrzeitHerkunft('engstelle', 'kfz', 'gelbzeit', { vorgegeben: true, vorgabewert, angesetzt: RILSA_ENGSTELLE.ueberfahrzeit, }), `Vorgabe ${vorgabewert} s`, ).toBe( `Regelwert der Engstelle: tü = ${RILSA_ENGSTELLE.ueberfahrzeit} s ` + `(RiLSA 2015, Abschnitt 5.2.2) – die an dieser Beziehung eingetragenen ${vorgabewert} s ` + 'gehen in dieses Verfahren nicht ein', ); } }); it('laesst am Knotenpunkt jede Antwort, wie sie war', () => { // Die Gegenprobe: Der Zweig darf nur an der Engstelle greifen. for (const ansatz of ['gelbzeit', 'fest'] as const) { expect(ueberfahrzeitHerkunft('knotenpunkt', 'kfz', ansatz)).toBe( UEBERFAHRZEIT_ANSATZ_LABELS[ansatz], ); } expect( ueberfahrzeitHerkunft('knotenpunkt', 'kfz', 'gelbzeit', { vorgegeben: true, vorgabewert: 4, angesetzt: 4, }), ).toBe('an dieser Beziehung von Hand eingetragen'); expect(ueberfahrzeitHerkunft('knotenpunkt', 'fuss', 'fest')).toBe( 'Regelwert der Fußgänger aus den Vorgaben', ); }); it('holt die 4 s aus derselben Stelle wie die Rechnung', () => { // Keine zweite Fassung des Kennwerts: constants.ts ist die einzige Quelle. const text = ueberfahrzeitHerkunft('engstelle', 'kfz', 'gelbzeit'); expect(text).toContain(`${RILSA_ENGSTELLE.ueberfahrzeit} s`); expect(bezugswerte('engstelle', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS).ueberfahrzeit).toBe( RILSA_ENGSTELLE.ueberfahrzeit, ); }); });