import { describe, expect, it } from 'vitest'; import { ANLAGENART_GRENZEN, RILSA_DEFAULTS } from '@/domain/rilsa/constants'; import { resolveRilsaSettings, settingFields } from '@/domain/rilsa/settings'; import type { RilsaOverrides, SettingField } from '@/domain/rilsa/settings'; import type { Anlagenart } from '@/domain/rilsa/types'; /* * DER BEZUGSWERT STEHT AN EINER STELLE - fuer jedes Feld und jede Anlagenart. * * Die Zahl, gegen die eine Vorgabe als Abweichung gemessen wird, faellt in * settings.ts an einer einzigen Stelle (`regelwertVon`). Zwei Ausgaben lesen * sie: die Abweichungsliste (`resolveRilsaSettings(...).deviations`, aus der * die gedruckte Aufstellung "Abweichungen von den Regelwerten" und der * Pruefbericht entstehen) und das Feld `regelwert` der Vorgabenzeile * (`settingFields`), aus dem Vorgabenansicht und Fundstellenverzeichnis lesen. * * DASS BEIDE DIESELBE ZAHL FUEHREN, IST DIE BEHAUPTUNG, AUF DER DAS * FUNDSTELLENVERZEICHNIS RUHT. `fundstellenkennwerte` * (services/export/bewertung.ts) hat den Bezug bis Fassung 5.12.0 ueber einen * Umweg geholt: eine Vorlage, in der jeder Kennwert auf `standard` steht, durch * `resolveRilsaSettings` gefuehrt, und aus den gemeldeten Abweichungen deren * `standardValue` gelesen. Seither nimmt es `regelwert` unmittelbar. Der Umweg * war gegen genau eine Gefahr gebaut - dass die beiden Seiten auseinanderlaufen * und die Anordnungsunterlage zwei Regelwerte fuer denselben Kennwert nennt; * dreimal ist das schon geschehen (tests/domain/regelbereichBezug.test.ts, * tests/export/regelbereichFundstelle.test.ts, * tests/ui/vorgabenRegelwert.test.ts). Diese Wache haelt sie stattdessen * zusammen, und zwar breiter als der Umweg es konnte: nicht nur an den beiden * Regelbereichsfeldern der Umlaufzeit, sondern an JEDEM Kennwert und JEDER * Anlagenart. * * FUER DIE VEREINFACHUNG SELBST GIBT ES KEINEN ROTEN FALL, und das ist keine * Nachlaessigkeit: Sie ist wirkungsgleich, gemessen ueber alle Kennwerte und * alle Anlagenarten (Kopfkommentar von `fundstellenkennwerte`). Rot wird diese * Wache gegen die Gefahr, die dahintersteht - gemessen an zwei Staenden, die es * beide einmal gegeben hat: Fuehrt die Vorgabenzeile ihren Regelwert wieder aus * `standard` (der Stand vor Fassung 5.12.0), schlagen 3 der 5 Faelle fehl - die * Vorbedingung und die beiden Anlagenarten mit eigenem Regelbereich. Misst die * Abweichungsliste wieder gegen `standard` (der Stand vor Fassung 5.11.0), sind * es 2 von 5: Dort bleibt die Vorbedingung gruen, weil die Vorgabenzeile die * Verschiebung weiterhin fuehrt. */ const ARTEN = Object.keys(ANLAGENART_GRENZEN) as readonly Anlagenart[]; /** * Ein Eintrag, der innerhalb der Schranken des Feldes liegt und vom Regelwert * abweicht - damit die Abweichungsliste den Bezug ueberhaupt nennt. * * Bevorzugt nach UNTEN, denn nach oben kappen die zusammenhaengenden Schranken * (Abbieger gegen Geradeausstrom, enger Innenradius, erhoehter Zeitbedarf) den * Wert auf den Nachbarn und koennen die Zeile dadurch verschwinden lassen. Wo * der Regelwert selbst die Untergrenze ist - bei den Einfahrgeschwindigkeiten * und der kleinsten Umlaufzeit -, geht es nach oben. * * DICHT AM REGELWERT, nicht in die Mitte bis zur Schranke: Eine kleinste * Umlaufzeit von 165 s (die Mitte zwischen 30 und den 300 s der einstreifigen * Fuehrung) ist groesser als die groesste Umlaufzeit von 120 s, und dieser * Widerspruch setzt zu Recht ALLE Vorgaben auf die Regelwerte zurueck - * gemessen: Die Abweichungsliste ist dann leer und nennt gar keinen Bezug * mehr. Ein Schritt von hoechstens einer Einheit bleibt innerhalb der * Schranken beider Umlaufzeitfelder. */ function eintragNebenDemRegelwert(feld: SettingField): number { if (Number.isFinite(feld.min) && feld.min < feld.regelwert) { return feld.regelwert - Math.min(1, (feld.regelwert - feld.min) / 2); } if (Number.isFinite(feld.max) && feld.max > feld.regelwert) { return feld.regelwert + Math.min(1, (feld.max - feld.regelwert) / 2); } return feld.regelwert + 1; } describe('Der Bezugswert der Abweichungsliste ist der Regelwert der Vorgabenzeile', () => { it('haelt die Vorbedingung: die Anlagenart verschiebt wenigstens einen Regelwert', () => { /* * Ohne ein Feld, bei dem `regelwert` und `standard` auseinandertreten, * pruefte alles Weitere nichts: Dann waere jede Verwechslung der beiden * folgenlos. Die beiden Regelbereichsfelder der Umlaufzeit sind dieses * Feld - an der Fussgaengerschutzanlage und bei einstreifiger Fuehrung. */ const verschoben = ARTEN.flatMap((art) => settingFields(RILSA_DEFAULTS, art) .filter((f) => f.regelwert !== f.standard) .map((f) => `${art}/${f.key}`), ); /* * Seit 5.27.0 steht der Vorgabewert der Untergrenze bei 30 s (RiLSA 2015, * Abschnitt 2.6) statt bei 60 s. Damit tritt auch die einstreifige * Verkehrsfuehrung mit ihren 60 s auseinander - vorher fiel sie mit dem * Vorgabewert zusammen und stand deshalb nicht in dieser Liste. */ expect(verschoben).toEqual([ 'fussgaengerschutzanlage/cycleTimeRecommendedMin', 'fussgaengerschutzanlage/cycleTimeRecommendedMax', 'einstreifig/cycleTimeRecommendedMin', 'einstreifig/cycleTimeRecommendedMax', ]); }); it.each(ARTEN)('misst bei %s jeden Kennwert gegen den Regelwert seiner Zeile', (art) => { const felder = settingFields(RILSA_DEFAULTS, art); expect(felder.length, 'Vorbedingung: die Kennwerte stehen alle in der Liste').toBeGreaterThan( 40, ); for (const feld of felder) { const eintrag = eintragNebenDemRegelwert(feld); // Weit genug weg: `resolveRilsaSettings` rundet den Vergleich auf sechs // Nachkommastellen, ein noch kleinerer Abstand waere keine Abweichung. expect( Math.abs(eintrag - feld.regelwert), `${feld.key}: der Probeeintrag weicht ab`, ).toBeGreaterThan(1e-6); const overrides: RilsaOverrides = { [feld.key]: eintrag }; const { deviations } = resolveRilsaSettings(overrides, RILSA_DEFAULTS, art); const gemeldet = deviations.find((d) => d.key === feld.key); expect(gemeldet, `${feld.key}: die Abweichungsliste fuehrt die Zeile`).toBeDefined(); expect(gemeldet?.standardValue, `${feld.key}: Bezugswert der Abweichungsliste`).toBe( feld.regelwert, ); /* * Auch die Zeilen, die der Eintrag NEBENBEI ausloest: Wer den * Geradeausstrom absenkt, kappt den Abbieger mit, und die Abweichung * dieses Nachbarn entsteht erst dabei (settings.ts, * `berichtigeAbweichung`). Sie muss denselben Bezug nennen. */ for (const d of deviations) { const nachbar = felder.find((f) => f.key === d.key); expect(d.standardValue, `${feld.key} loest ${d.key} aus: Bezugswert`).toBe( nachbar?.regelwert, ); } } }); it('nennt ohne Eintrag keine Abweichung und keinen abweichenden Bezug', () => { // Die Gegenprobe: Der Regelwertsatz selbst ist keine Abweichung - auch // dort nicht, wo die Anlagenart einen anderen Regelwert fuehrt als die // Zahl des Regelwerkssatzes. for (const art of ARTEN) { expect(resolveRilsaSettings(undefined, RILSA_DEFAULTS, art).deviations).toEqual([]); expect(resolveRilsaSettings({}, RILSA_DEFAULTS, art).deviations).toEqual([]); } }); });