import { describe, expect, it } from 'vitest'; import { resolveRilsaSettings, settingFields } from '@/domain/rilsa/settings'; import { ANLAGENART_GRENZEN, RILSA_DEFAULTS } from '@/domain/rilsa/constants'; import type { RilsaDefaults } from '@/domain/rilsa/constants'; import type { RilsaOverrides } from '@/domain/rilsa/settings'; /* * Befund 48 (Fassung 5.11.0): An einer Fussgaengerschutzanlage wurde * der Regelbereich der Umlaufzeit gegen den KNOTENPUNKTWERT abgeglichen. * * Die Felder mit `grenzeVonArt` holen ihre Schranken aus der Anlagenart, ihren * Bezugswert fuer die Abweichungsliste aber unveraendert aus RILSA_DEFAULTS. * Ohne Eintrag gilt an der Fussgaengerschutzanlage aber 40 bis 70 s * (ANLAGENART_GRENZEN), nicht 60 bis 90 s. Daraus folgten zwei Fehlverhalten * in derselben Unterlage: * * (a) Wer 40 s eintrug - also genau den Wert, der ohnehin gilt -, bekam eine * Zeile unter "Abweichungen von den Regelwerten" ("Regelwert 60, * Angesetzt 40") und im Pruefbericht die Aufforderung, sie fachlich zu * begruenden. Begruendet werden sollte eine Eingabe, die an keiner Zahl * des Plans etwas aendert. * (b) Wer 60 s eintrug, verschob den wirksamen Regelbereich von 40 bis 70 * auf 60 bis 70 - und weil 60 der Knotenpunktwert ist, entstand KEINE * Abweichungszeile: Die wirksame Aenderung stand in keiner Ausgabe. * * Der Bezugswert ist damit der Wert, der OHNE Eintrag fuer diese Anlagenart * gilt. Am Knotenpunkt sind das dieselben Zahlen wie bisher. * * NUR DIE BEIDEN REGELBEREICHSFELDER: Bei der kleinsten und groessten * Umlaufzeit bleibt der Knotenpunktwert der Bezug, weil er dort ein * Regelwerkswert ist - belegt sind 30 bis 120 s, und die RiLSA staffelt sie * nicht nach Anlagenart. Beim Regelbereich ist keine der beiden Zahlen eine des * Regelwerks: Die 60 s sind "eine Zahl dieses Programms", und den Bereich der * Fussgaengerschutzanlage setzt es ebenfalls selbst. Der Fall steht unten als * Gegenprobe. * * GEGEN DEN ALTSTAND schlagen die fuenf Faelle zur Fussgaengerschutzanlage * fehl; die drei Gegenproben bestehen dort ebenso. */ function abweichungen( overrides: RilsaOverrides, anlagenart: 'knotenpunkt' | 'fussgaengerschutzanlage' | 'einstreifig', ) { return resolveRilsaSettings(overrides, RILSA_DEFAULTS, anlagenart).deviations; } /** * Der Wert, den das Fundstellenverzeichnis als "angesetzt" fuehrt: pdf.ts liest * ihn ueber `settingFields(plan.defaults, plan.anlagenart)` aus dem * aufgeloesten Kennwertsatz, die Vorgabenansicht ebenso ("wirksam N s"). */ function angesetzt(defaults: RilsaDefaults): number | undefined { return settingFields(defaults, 'fussgaengerschutzanlage').find( (f) => f.key === 'cycleTimeRecommendedMin', )?.standard; } describe('Bezugswert der Abweichung bei Feldern mit Schranken der Anlagenart', () => { it('haelt an der Fussgaengerschutzanlage die Zahlen der Anlagenart bereit', () => { // Voraussetzung des Aufbaus: Die Anlagenart fuehrt einen anderen // Regelbereich als der Knotenpunkt. expect(ANLAGENART_GRENZEN.fussgaengerschutzanlage.cycleTime.recommendedMin).toBe(40); // Seit 5.27.0 ist der Vorgabewert der Richtwert der RiLSA (Abschnitt 2.6); // zuvor standen hier 60 s, eine Zahl dieses Programms. expect(RILSA_DEFAULTS.cycleTime.recommendedMin).toBe(30); }); it('macht aus einer wirkungslosen Eingabe keine Abweichung', () => { const deviations = abweichungen({ cycleTimeRecommendedMin: 40 }, 'fussgaengerschutzanlage'); expect( deviations.map((d) => d.key), 'An der Fussgaengerschutzanlage gelten 40 s ohnehin - das ist keine Abweichung', ).not.toContain('cycleTimeRecommendedMin'); }); it('weist eine wirksame Eingabe als Abweichung aus', () => { const deviations = abweichungen({ cycleTimeRecommendedMin: 60 }, 'fussgaengerschutzanlage'); const zeile = deviations.find((d) => d.key === 'cycleTimeRecommendedMin'); expect( zeile, '60 s verschieben den Regelbereich von 40 auf 60 - das gehoert in die Unterlage', ).toBeDefined(); expect(zeile!.standardValue).toBe(40); expect(zeile!.appliedValue).toBe(60); }); it('haelt beide Ausgaben derselben Unterlage gegeneinander', () => { /* * NACHGETRAGEN (Fassung 5.12.0): Der Bezugswert wirkte hier * nur in `resolveRilsaSettings`, also in der Abweichungstabelle und im * Pruefbericht. Das Fundstellenverzeichnis (services/export/pdf.ts) und die * Spalte "Regelwert" der Vorgabenansicht (ui/views/settingsView.ts) lasen * ihren Bezug aus `settingFields().standard`, und der ist der * Knotenpunktwert - dieser Fall hielt fest, was die Unterlage damit an der * Fussgaengerschutzanlage sagte. Inzwischen fuehrt die Vorgabenzeile ihren * Regelwert selbst (`settingFields().regelwert`, gebildet in derselben * Funktion wie der Bezug der Abweichungsliste), und beide Ausgaben lesen * ihn: gemessen in tests/export/regelbereichFundstelle.test.ts und * tests/ui/vorgabenRegelwert.test.ts. * * Der Fall bleibt, wie er ist: Er misst weiter `standard`, also den Wert, * den der uebergebene Kennwertsatz fuehrt, und der ist ohne Eintrag * unveraendert die Knotenpunktzahl. Genau daran haengt die Anzeige * "wirksam N s"; er ist der Grund, warum der Regelwert ein eigenes Feld * bekommen hat und nicht durch Umdeuten von `standard` entstand. */ const verzeichnis = settingFields(RILSA_DEFAULTS, 'fussgaengerschutzanlage').find( (f) => f.key === 'cycleTimeRecommendedMin', ); expect(verzeichnis?.standard, 'Wert des Kennwertsatzes, nicht der Regelwert').toBe(30); expect(verzeichnis?.regelwert, 'Regelwert der Vorgabenzeile - die Zahl der Anlagenart').toBe( 40, ); // Vorgabe 40 s: Die Abweichungstabelle fuehrt sie zu Recht nicht - der // angesetzte Wert ist der Regelwert der Zeile, und vom Knotenpunktwert // weicht er ab, ohne dass das irgendwo eine Abweichung waere. const mit40 = resolveRilsaSettings( { cycleTimeRecommendedMin: 40 }, RILSA_DEFAULTS, 'fussgaengerschutzanlage', ); expect(mit40.deviations.map((d) => d.key)).not.toContain('cycleTimeRecommendedMin'); expect(angesetzt(mit40.defaults)).toBe(verzeichnis?.regelwert); expect(angesetzt(mit40.defaults)).not.toBe(verzeichnis?.standard); // Vorgabe 30 s: Die Abweichungstabelle nennt "Regelwert 40, Angesetzt 30" - // und ihr Bezugswert ist derselbe, den die Vorgabenzeile als Regelwert // fuehrt. Das ist die Klammer, an der Bildschirm und Ausdruck haengen. // Die Zahl war bis 5.26.0 die 60 des Vorgabenwerts; sie steht jetzt bei 30. const mitVorgabewert = resolveRilsaSettings( { cycleTimeRecommendedMin: RILSA_DEFAULTS.cycleTime.recommendedMin }, RILSA_DEFAULTS, 'fussgaengerschutzanlage', ); expect( mitVorgabewert.deviations.find((d) => d.key === 'cycleTimeRecommendedMin')?.standardValue, ).toBe(verzeichnis?.regelwert); expect(angesetzt(mitVorgabewert.defaults)).toBe(verzeichnis?.standard); }); it('misst auch die obere Regelbereichsgrenze gegen die Anlagenart', () => { const ohne = abweichungen({ cycleTimeRecommendedMax: 70 }, 'fussgaengerschutzanlage'); expect(ohne.map((d) => d.key)).not.toContain('cycleTimeRecommendedMax'); const mit = abweichungen({ cycleTimeRecommendedMax: 90 }, 'fussgaengerschutzanlage'); expect(mit.find((d) => d.key === 'cycleTimeRecommendedMax')?.standardValue).toBe(70); }); it('misst die groesste Umlaufzeit weiterhin gegen den Regelwerkswert', () => { /* * GEGENPROBE, in beiden Staenden gruen: Bei der kleinsten und groessten * Umlaufzeit bleibt der Knotenpunktwert der Bezug. Anders als beim * Regelbereich ist er dort ein Regelwerkswert - belegt sind 30 bis 120 s, * und die RiLSA staffelt sie nicht nach Anlagenart. Die 300 s der * einstreifigen Fuehrung setzt dieses Programm; eine Vorgabe von 250 s ist * deshalb eine Abweichung von 120 s, so wie die Vorgabenansicht sie seit * Befund C10 ausweist. */ expect(ANLAGENART_GRENZEN.einstreifig.cycleTime.max).toBe(300); const zeile = abweichungen({ cycleTimeMax: 250 }, 'einstreifig').find( (d) => d.key === 'cycleTimeMax', ); expect(zeile).toBeDefined(); expect(zeile!.standardValue).toBe(120); expect(zeile!.appliedValue).toBe(250); }); it('laesst den Knotenpunkt unveraendert', () => { const zeile = abweichungen({ cycleTimeRecommendedMin: 40 }, 'knotenpunkt').find( (d) => d.key === 'cycleTimeRecommendedMin', ); expect(zeile?.standardValue).toBe(30); expect(abweichungen({ cycleTimeMax: 120 }, 'knotenpunkt').map((d) => d.key)).not.toContain( 'cycleTimeMax', ); }); it('nennt bei einem ungueltigen Wert den Regelwert der Anlagenart', () => { const { notes } = resolveRilsaSettings( { cycleTimeRecommendedMin: Number.NaN }, RILSA_DEFAULTS, 'fussgaengerschutzanlage', ); const note = notes.find((n) => n.code === 'vorgabe-ungueltig:cycleTimeRecommendedMin'); expect(note).toBeDefined(); expect(note!.message).toContain('40 s'); }); });