lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain bezugswerteVerfahren.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { ueberfahrzeitHerkunft } from '@/domain/rilsa/ansaetze'; |
| 3 | import { |
| 4 | RILSA_DEFAULTS, |
| 5 | RILSA_ENGSTELLE, |
| 6 | UEBERFAHRZEIT_ANSATZ_LABELS, |
| 7 | } from '@/domain/rilsa/constants'; |
| 8 | import { bezugswerte } from '@/domain/rilsa/intergreen'; |
| 9 | import { engstellenRaeumgeschwindigkeitMs } from '@/domain/rilsa/engstellensignalisierung'; |
| 10 | |
| 11 | /** |
| 12 | * Die Bezugswerte richten sich nach dem Verfahren, nicht nach der Gewohnheit. |
| 13 | * |
| 14 | * ANLASS (Befund aus dem Betrieb, 11.09.2026). Das Zwischenzeitfenster einer |
| 15 | * einstreifigen Verkehrsfuehrung nannte unter dem Feld "Raeumgeschwindigkeit" |
| 16 | * den Regelwert der KNOTENPUNKTRECHNUNG - 10,0 m/s - und wies zwei Zeilen |
| 17 | * tiefer im Rechenweg desselben Fensters 11,11 m/s aus. An einer Engstelle |
| 18 | * rechnet der Fachkern nach RiLSA 2015, Abschnitt 5.2.2, also mit der |
| 19 | * Raeumgeschwindigkeitsstaffel und nicht mit dem Vorgabenwert; die |
| 20 | * Beschriftung holte ihren Wert dagegen aus `regelRaeumgeschwindigkeit`, und |
| 21 | * die Funktion kennt die Anlagenart nicht. |
| 22 | * |
| 23 | * WARUM DAS MEHR IST ALS EIN SCHOENHEITSFEHLER. Der genannte Wert ist die |
| 24 | * Groesse, gegen die der Bearbeiter seine Handeingabe abwaegt und im Zweifel |
| 25 | * begruendet. 10 m/s sieht strenger aus als die 11,11 m/s des Verfahrens - eine |
| 26 | * Handeingabe daneben erschien deshalb harmloser, als sie ist. Das ist die |
| 27 | * unsichere Richtung. |
| 28 | * |
| 29 | * ZWEI NACHBARFELDER HAENGEN MIT DRAN. Die Ueberfahrzeit steht an der Engstelle |
| 30 | * mit 4 s fest im Abschnitt und ist nicht die Gelbzeit. Und die |
| 31 | * Einfahrgeschwindigkeit geht dort in gar keine Zahl ein, weil der Abschnitt |
| 32 | * ausdruecklich ohne Einfahrzeit rechnet - ein Regelwert unter einem Feld ohne |
| 33 | * Wirkung laedt dazu ein, daran zu drehen. |
| 34 | */ |
| 35 | |
| 36 | /** Der gemeldete Fall: 50 km/h zulaessig, Raeumgeschwindigkeit der Staffel 40 km/h. */ |
| 37 | const KFZ_GERADEAUS = { |
| 38 | mode: 'kfz', |
| 39 | movement: 'geradeaus', |
| 40 | vZul: 50, |
| 41 | } as const; |
| 42 | |
| 43 | describe('Am Knotenpunkt bleibt alles beim Regelwert der Knotenpunktrechnung', () => { |
| 44 | it('nennt die Raeumgeschwindigkeit des Kennwertsatzes', () => { |
| 45 | const werte = bezugswerte('knotenpunkt', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); |
| 46 | expect(werte.raeumgeschwindigkeit).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight); |
| 47 | }); |
| 48 | |
| 49 | it('nennt eine Einfahrgeschwindigkeit, denn dort wirkt sie', () => { |
| 50 | const werte = bezugswerte('knotenpunkt', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); |
| 51 | expect(werte.einfahrgeschwindigkeit).toBe(RILSA_DEFAULTS.enteringSpeed.kfz); |
| 52 | }); |
| 53 | |
| 54 | it('nennt die Ueberfahrzeit der Fahrbeziehung', () => { |
| 55 | const werte = bezugswerte('knotenpunkt', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); |
| 56 | expect(werte.ueberfahrzeit).toBeGreaterThan(0); |
| 57 | expect(werte.ueberfahrzeit).not.toBe(RILSA_ENGSTELLE.ueberfahrzeit); |
| 58 | }); |
| 59 | }); |
| 60 | |
| 61 | describe('An der Engstelle gelten die Werte des Abschnitts 5.2', () => { |
| 62 | const werte = bezugswerte('engstelle', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS); |
| 63 | |
| 64 | it('nennt die Raeumgeschwindigkeit der Staffel und nicht den Vorgabenwert', () => { |
| 65 | // Genau der gemeldete Fall: 50 km/h zulaessig ergeben 40 km/h = 11,11 m/s, |
| 66 | // nicht die 10 m/s der Knotenpunktrechnung. |
| 67 | expect(werte.raeumgeschwindigkeit).toBeCloseTo(40 / 3.6, 6); |
| 68 | expect(werte.raeumgeschwindigkeit).not.toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight); |
| 69 | }); |
| 70 | |
| 71 | it('holt diese Zahl aus derselben Stelle wie die Rechnung', () => { |
| 72 | // Keine zweite Fassung der Staffel: Beide Wege muessen dieselbe Zahl geben. |
| 73 | expect(werte.raeumgeschwindigkeit).toBe(engstellenRaeumgeschwindigkeitMs(50)); |
| 74 | }); |
| 75 | |
| 76 | it('nennt die feste Ueberfahrzeit von 4 s', () => { |
| 77 | expect(werte.ueberfahrzeit).toBe(RILSA_ENGSTELLE.ueberfahrzeit); |
| 78 | }); |
| 79 | |
| 80 | it('nennt gar keine Einfahrgeschwindigkeit, weil dort keine wirkt', () => { |
| 81 | // null und nicht 0: 0 waere eine Zahl, gegen die sich abwaegen liesse. |
| 82 | expect(werte.einfahrgeschwindigkeit).toBeNull(); |
| 83 | }); |
| 84 | |
| 85 | it('gibt das Verfahren zurueck, nach dem es gerechnet hat', () => { |
| 86 | expect(werte.verfahren).toBe('engstelle'); |
| 87 | }); |
| 88 | }); |
| 89 | |
| 90 | describe('Die Staffel folgt der zulaessigen Hoechstgeschwindigkeit', () => { |
| 91 | it.each([ |
| 92 | // Die Staffel liegt eine Stufe unter der zulaessigen Hoechstgeschwindigkeit, |
| 93 | // und unterhalb ihrer kleinsten Stufe gilt diese. |
| 94 | [30, 30], |
| 95 | [40, 30], |
| 96 | [50, 40], |
| 97 | [60, 50], |
| 98 | ])('bei %s km/h zulaessig steht der Bezugswert bei %s km/h', (vZul, erwartetKmh) => { |
| 99 | // Die Bezugsgroesse darf nicht an einer Zahl kleben: Sie haengt an der |
| 100 | // Oertlichkeit, und das Fenster muss die des jeweiligen Stroms nennen. |
| 101 | const werte = bezugswerte('engstelle', { ...KFZ_GERADEAUS, vZul }, 'kfz', RILSA_DEFAULTS); |
| 102 | expect(werte.raeumgeschwindigkeit).toBeCloseTo(erwartetKmh / 3.6, 6); |
| 103 | }); |
| 104 | }); |
| 105 | |
| 106 | /* |
| 107 | * NACHTRAG (Fassung 5.43.0): Ein Feld desselben Fensters blieb bei |
| 108 | * der Gewohnheit. |
| 109 | * |
| 110 | * Die Zeile "Ueberfahrzeit tue" des Rechenwegs holt ihre Erlaeuterung aus |
| 111 | * `ueberfahrzeitHerkunft`, und die Funktion kannte nur die Verkehrsart und den |
| 112 | * gewaehlten Kfz-Ansatz. An einer Engstelle stand deshalb neben den 4 s des |
| 113 | * Abschnitts 5.2.2 die Beschriftung des KNOTENPUNKT-Ansatzes - bei der Vorgabe |
| 114 | * "Überfahrzeit = Gelbzeit" also "3/4/5 s nach zulässiger |
| 115 | * Hoechstgeschwindigkeit" und beim festen Ansatz "3 s geradeaus / 2 s |
| 116 | * abbiegend", beides neben einer Zahl, die keiner der beiden hergibt. Dieselbe |
| 117 | * Spalte druckt die Tabellenausgabe (services/export/csv.ts). |
| 118 | * |
| 119 | * Die Funktion bekommt das Verfahren jetzt als ERSTES Argument - wie |
| 120 | * `bezugswerte` darueber und aus demselben Grund: So muss jede Aufrufstelle es |
| 121 | * mitgeben, und eine vergessene bricht beim Uebersetzen statt in der Ausgabe. |
| 122 | */ |
| 123 | describe('Die Herkunft der Ueberfahrzeit kennt das Verfahren ebenso', () => { |
| 124 | it('nennt an der Engstelle den Regelwert des Abschnitts 5.2.2 und nicht den Kfz-Ansatz', () => { |
| 125 | for (const ansatz of ['gelbzeit', 'fest'] as const) { |
| 126 | const text = ueberfahrzeitHerkunft('engstelle', 'kfz', ansatz); |
| 127 | expect(text, ansatz).toBe( |
| 128 | `Regelwert der Engstelle: tü = ${RILSA_ENGSTELLE.ueberfahrzeit} s ` + |
| 129 | '(RiLSA 2015, Abschnitt 5.2.2)', |
| 130 | ); |
| 131 | expect(text, `${ansatz}: Beschriftung des Kfz-Ansatzes`).not.toContain( |
| 132 | UEBERFAHRZEIT_ANSATZ_LABELS[ansatz], |
| 133 | ); |
| 134 | } |
| 135 | }); |
| 136 | |
| 137 | it('sagt an der Engstelle, dass eine eingetragene Ueberfahrzeit nicht eingeht', () => { |
| 138 | /* |
| 139 | * `computeEngstellenIntergreen` liest `crossingTimeOverride` gar nicht: Die |
| 140 | * Ueberfahrzeit ist dort die feste Zahl des Abschnitts. Bis hierher hiess |
| 141 | * eine Eingabe von genau 4 s "an dieser Beziehung von Hand eingetragen" - |
| 142 | * eine Herleitung, die nicht stattgefunden hat -, und eine von 6 s |
| 143 | * "verworfen", als haette eine Pruefung sie abgelehnt. |
| 144 | */ |
| 145 | for (const vorgabewert of [4, 6]) { |
| 146 | expect( |
| 147 | ueberfahrzeitHerkunft('engstelle', 'kfz', 'gelbzeit', { |
| 148 | vorgegeben: true, |
| 149 | vorgabewert, |
| 150 | angesetzt: RILSA_ENGSTELLE.ueberfahrzeit, |
| 151 | }), |
| 152 | `Vorgabe ${vorgabewert} s`, |
| 153 | ).toBe( |
| 154 | `Regelwert der Engstelle: tü = ${RILSA_ENGSTELLE.ueberfahrzeit} s ` + |
| 155 | `(RiLSA 2015, Abschnitt 5.2.2) – die an dieser Beziehung eingetragenen ${vorgabewert} s ` + |
| 156 | 'gehen in dieses Verfahren nicht ein', |
| 157 | ); |
| 158 | } |
| 159 | }); |
| 160 | |
| 161 | it('laesst am Knotenpunkt jede Antwort, wie sie war', () => { |
| 162 | // Die Gegenprobe: Der Zweig darf nur an der Engstelle greifen. |
| 163 | for (const ansatz of ['gelbzeit', 'fest'] as const) { |
| 164 | expect(ueberfahrzeitHerkunft('knotenpunkt', 'kfz', ansatz)).toBe( |
| 165 | UEBERFAHRZEIT_ANSATZ_LABELS[ansatz], |
| 166 | ); |
| 167 | } |
| 168 | expect( |
| 169 | ueberfahrzeitHerkunft('knotenpunkt', 'kfz', 'gelbzeit', { |
| 170 | vorgegeben: true, |
| 171 | vorgabewert: 4, |
| 172 | angesetzt: 4, |
| 173 | }), |
| 174 | ).toBe('an dieser Beziehung von Hand eingetragen'); |
| 175 | expect(ueberfahrzeitHerkunft('knotenpunkt', 'fuss', 'fest')).toBe( |
| 176 | 'Regelwert der Fußgänger aus den Vorgaben', |
| 177 | ); |
| 178 | }); |
| 179 | |
| 180 | it('holt die 4 s aus derselben Stelle wie die Rechnung', () => { |
| 181 | // Keine zweite Fassung des Kennwerts: constants.ts ist die einzige Quelle. |
| 182 | const text = ueberfahrzeitHerkunft('engstelle', 'kfz', 'gelbzeit'); |
| 183 | expect(text).toContain(`${RILSA_ENGSTELLE.ueberfahrzeit} s`); |
| 184 | expect(bezugswerte('engstelle', KFZ_GERADEAUS, 'kfz', RILSA_DEFAULTS).ueberfahrzeit).toBe( |
| 185 | RILSA_ENGSTELLE.ueberfahrzeit, |
| 186 | ); |
| 187 | }); |
| 188 | }); |