lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests domain dokumentationZahlen.test.ts

35,3 KB Rohdatei
tests/domain/dokumentationZahlen.test.ts — 802 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { computeCapacity } from '@/domain/rilsa/capacity';
3 import { computeDelay, computeFussRadDelay, serviceLevelFor } from '@/domain/rilsa/delay';
4 import { computeIntergreen } from '@/domain/rilsa/intergreen';
5 import { computeCycleTime } from '@/domain/rilsa/cycle';
6 import { resolveRilsaSettings } from '@/domain/rilsa/settings';
7 import {
8 ANLAGENART_GRENZEN,
9 ENGSTELLE_PRAXIS,
10 OEPNV_ANFAHRBESCHLEUNIGUNG,
11 RILSA_DEFAULTS,
12 RSA_EINSTREIFIG,
13 SAFETY_FLOORS,
14 VZUL_OBERGRENZE_LSA,
15 } from '@/domain/rilsa/constants';
16 import { engstellenFolgen, engstellenPraxisHinweis } from '@/domain/rilsa/engstelle';
17 import { furtFreigabebedarf } from '@/domain/rilsa/signalTimes';
18 import {
19 UMLAUFZEIT_ERSATZWERT_VERMERK,
20 buildSignalPlan,
21 mindestfreigabezeitDerGruppe,
22 umlaufzeitIstErsatzwert,
23 } from '@/domain/plan/signalPlan';
24 import { einstreifigBefunde, hoechstgeschwindigkeitBefunde } from '@/domain/validation/rules';
25 import {
26 createConflict,
27 createEmptyProject,
28 createPhase,
29 createSignalGroup,
30 createStandardIntersectionProject,
31 } from '@/domain/model/factory';
32 import { CURRENT_SCHEMA_VERSION, type Project, type SignalGroup } from '@/domain/model/project';
33 import type { ClearingStream, EnteringStream, VehicleClass } from '@/domain/rilsa/types';
34
35 /*
36 * Die Zahlenbeispiele zu den Befunden: Kapazitaet, Wartezeit und
37 * Qualitaetsstufe, Zwischenzeiten, Furt, Schranken und ersetzte Eingaben.
38 *
39 * Grundsatz: Was gedruckt wird, muss sich aus den gedruckten Groessen
40 * nachrechnen lassen. Das gilt auch fuer die Zahlenbeispiele der
41 * Dokumentation - eine Zahl in einer Beschreibung, die sich aus keiner
42 * Rechnung des Programms ergibt, ist als Begruendung wertlos (siehe
43 * engstelle.test.ts: 125 s Zwischenzeit, die es nirgends gab). Dieser Test
44 * haelt die Beispiele an die Fachlogik.
45 *
46 * Erster Block (B1-B4): Kfz-Strom, ein Fahrstreifen geradeaus, tU = 90 s,
47 * tF = 30 s. Zweiter Block (A1-A4): die Zwischenzeitbeispiele zur Fassung
48 * 5.4.0.
49 */
50
51 const TU = 90;
52 const TF = 30;
53 const QS = RILSA_DEFAULTS.capacity.saturationFlow;
54
55 function hbs(q: number) {
56 const kap = computeCapacity(TF, TU, QS, q, { verfahren: 'HBS' });
57 const delay = computeDelay({
58 cycleTime: TU,
59 greenTime: TF,
60 capacity: kap.capacity,
61 demand: q,
62 verfahren: 'HBS',
63 mode: 'kfz',
64 abflusszeit: kap.abflusszeit,
65 });
66 return { kap, delay };
67 }
68
69 /** Der Altstand 5.3.5: 1800 Fz/h, HCM-Formel mit g = tF - unter dem Etikett HBS. */
70 function altstand(q: number) {
71 const kap = computeCapacity(TF, TU, 1800, q, { verfahren: 'HCM' });
72 const delay = computeDelay({
73 cycleTime: TU,
74 greenTime: TF,
75 capacity: kap.capacity,
76 demand: q,
77 verfahren: 'HCM',
78 mode: 'kfz',
79 abflusszeit: kap.abflusszeit,
80 });
81 return { kap, delay };
82 }
83
84 describe('Zahlenbeispiele zu Kapazitaet, Wartezeit und Qualitaetsstufe (B1-B4)', () => {
85 it('Kapazitaet: 1800 * 30 / 90 = 600 bisher, 2000 * 31 / 90 = 689 jetzt (+15 %)', () => {
86 expect(QS).toBe(2000);
87 expect(altstand(550).kap.capacity).toBe(600);
88 const { kap } = hbs(550);
89 expect(kap.abflusszeit).toBe(31);
90 expect(kap.capacity).toBe(689);
91 expect(Math.round(((689 - 600) / 600) * 100)).toBe(15);
92 });
93
94 it('q = 550: bisher x 0,92 -> 49,9 s, jetzt x 0,80 -> 43,3 s, beide Stufe C', () => {
95 const alt = altstand(550);
96 expect(alt.kap.degreeOfSaturation).toBeCloseTo(0.92, 2);
97 expect(alt.delay.averageDelay).toBeCloseTo(49.9, 1);
98 const neu = hbs(550);
99 expect(neu.kap.degreeOfSaturation).toBeCloseTo(0.8, 2);
100 expect(neu.delay.averageDelay).toBeCloseTo(43.3, 1);
101 expect(neu.delay.serviceLevel?.grade).toBe('C');
102 });
103
104 it('q = 655: bisher x 1,09 -> 94,2 s; jetzt x 0,95 -> 28,8 + 82,3 = 111,1 s, Stufe E', () => {
105 const alt = altstand(655);
106 expect(alt.kap.degreeOfSaturation).toBeCloseTo(1.09, 2);
107 expect(alt.delay.averageDelay).toBeCloseTo(94.2, 1);
108 const neu = hbs(655);
109 expect(neu.kap.degreeOfSaturation).toBeCloseTo(0.95, 2);
110 expect(neu.delay.uniformDelay).toBeCloseTo(28.8, 1);
111 expect(neu.delay.incrementalDelay).toBeCloseTo(82.3, 1);
112 expect(neu.delay.averageDelay).toBeCloseTo(111.1, 1);
113 expect(neu.delay.serviceLevel?.grade).toBe('E');
114 expect(neu.delay.serviceLevel?.kriterium).toBe('mittlere-wartezeit');
115 });
116
117 it('q = 662: bisher 98,3 s; jetzt 120,0 s bei x 0,96 - Stufe E, nicht F', () => {
118 expect(altstand(662).delay.averageDelay).toBeCloseTo(98.3, 1);
119 const neu = hbs(662);
120 expect(neu.kap.degreeOfSaturation).toBeCloseTo(0.96, 2);
121 expect(neu.delay.averageDelay).toBeCloseTo(120.0, 1);
122 expect(neu.delay.serviceLevel?.grade).toBe('E');
123 });
124
125 it('q = 700: bisher 122,1 s; jetzt x 1,02 -> 173,4 s, Stufe F wegen Ueberlastung', () => {
126 expect(altstand(700).delay.averageDelay).toBeCloseTo(122.1, 1);
127 const neu = hbs(700);
128 expect(neu.kap.degreeOfSaturation).toBeCloseTo(1.02, 2);
129 expect(neu.delay.averageDelay).toBeCloseTo(173.4, 1);
130 expect(neu.delay.serviceLevel?.grade).toBe('F');
131 expect(neu.delay.serviceLevel?.kriterium).toBe('ueberlastung');
132 });
133
134 it('gleiche Kapazitaet und gleicher Auslastungsgrad: HCM-Formel rund 53 s, HBS 111 s', () => {
135 // C = 689, q = 655 -> x = 0,95; nur die Formel wechselt.
136 const gemeinsam = {
137 cycleTime: TU,
138 greenTime: TF,
139 capacity: 689,
140 demand: 655,
141 mode: 'kfz' as const,
142 };
143 const hcm = computeDelay({ ...gemeinsam, verfahren: 'HCM' });
144 const hbsErgebnis = computeDelay({ ...gemeinsam, verfahren: 'HBS', abflusszeit: 31 });
145 expect(hcm.averageDelay).toBeGreaterThanOrEqual(52.5);
146 expect(hcm.averageDelay).toBeLessThanOrEqual(53.5);
147 expect(hbsErgebnis.averageDelay).toBeCloseTo(111.1, 1);
148 // "etwa das Doppelte" bzw. umgekehrt "etwa die Haelfte"
149 expect(hbsErgebnis.averageDelay / hcm.averageDelay).toBeGreaterThan(1.9);
150 expect(hbsErgebnis.averageDelay / hcm.averageDelay).toBeLessThan(2.3);
151 });
152
153 it('Fussgaengerfurt tU 90 s, 12 s Freigabe: Sperrzeit 78 s -> Stufe E; HBS-S4-Beispiel 67 s -> D', () => {
154 const furt = computeFussRadDelay({
155 cycleTime: 90,
156 sperrzeiten: [78],
157 verfahren: 'HBS',
158 mode: 'fuss',
159 });
160 expect(furt.maximumDelay).toBe(78);
161 expect(furt.serviceLevel?.grade).toBe('E');
162 expect(furt.serviceLevel?.kriterium).toBe('maximale-wartezeit');
163 const beispiel = computeFussRadDelay({
164 cycleTime: 110,
165 sperrzeiten: [67],
166 verfahren: 'HBS',
167 mode: 'fuss',
168 });
169 expect(beispiel.serviceLevel?.grade).toBe('D');
170 });
171
172 it('OePNV mit 25 s: Kfz-Tafel B, OePNV-Tafel C', () => {
173 expect(serviceLevelFor(25, 'HBS', { mode: 'kfz' })?.grade).toBe('B');
174 expect(serviceLevelFor(25, 'HBS', { mode: 'oepnv' })?.grade).toBe('C');
175 });
176
177 it('LISA+ K5: tW,G 37,9 s, tW,R 21,1 s, tW 59,0 s, Stufe D', () => {
178 const kap = computeCapacity(10, 90, 1992, 167, { verfahren: 'HBS' });
179 expect(kap.capacity).toBe(243);
180 const delay = computeDelay({
181 cycleTime: 90,
182 greenTime: 10,
183 capacity: 243,
184 demand: 167,
185 verfahren: 'HBS',
186 mode: 'kfz',
187 abflusszeit: 11,
188 });
189 expect(delay.uniformDelay).toBeCloseTo(37.9, 1);
190 expect(delay.incrementalDelay).toBeCloseTo(21.1, 1);
191 expect(delay.averageDelay).toBeCloseTo(59.0, 1);
192 expect(delay.rechenweg.reststau).toBeCloseTo(1.43, 2);
193 expect(delay.serviceLevel?.grade).toBe('D');
194 // tW,R ist die Differenz auf den ausgegebenen Ziffern, damit "37,9 + 21,1 =
195 // 59,0" im Ausdruck aufgeht. Unabhaengig gerundet ergaebe 37,85 + 21,17 =
196 // 59,02 die Zeile "37,9 + 21,2 = 59,0", die sich selbst widerlegt.
197 expect(delay.uniformDelay + delay.incrementalDelay).toBeCloseTo(delay.averageDelay, 6);
198 });
199
200 it('ohne Kapazitaet UND ohne Nachfrage keine Stufe; ohne Kapazitaet mit Nachfrage F', () => {
201 const leer = computeDelay({
202 cycleTime: TU,
203 greenTime: TF,
204 capacity: 0,
205 demand: 0,
206 verfahren: 'HBS',
207 mode: 'kfz',
208 });
209 expect(leer.serviceLevel).toBeNull();
210 const ueberlastet = computeDelay({
211 cycleTime: TU,
212 greenTime: TF,
213 capacity: 0,
214 demand: 100,
215 verfahren: 'HBS',
216 mode: 'kfz',
217 });
218 expect(ueberlastet.serviceLevel?.grade).toBe('F');
219 expect(ueberlastet.serviceLevel?.kriterium).toBe('ueberlastung');
220 });
221 });
222
223 /*
224 * Zahlenbeispiele zu den kritischen Befunden A1 bis A4 (Fassung 5.4.0). Die
225 * Fachtests dazu stehen in raeumgeschwindigkeitSchranken.test.ts,
226 * messgrundlage.test.ts und oepnvZwischenzeit.test.ts; hier geht es allein
227 * darum, dass die gedruckten Zahlen aus der Rechnung des Programms folgen.
228 */
229
230 const KFZ_EINFAHREND_10M: EnteringStream = {
231 mode: 'kfz',
232 movement: 'geradeaus',
233 enteringDistance: 10,
234 };
235
236 function radRaeumend(clearingDistance: number): ClearingStream {
237 return { mode: 'rad', movement: 'geradeaus', clearingDistance, vehicleClass: 'rad', vZul: 0 };
238 }
239
240 function bahnOhneHalt(extra: Partial<ClearingStream> = {}): ClearingStream {
241 // 25 m Grundraeumweg + 15 m Strassenbahn = 40 m Raeumweg.
242 return {
243 mode: 'oepnv',
244 movement: 'geradeaus',
245 clearingDistance: 25,
246 vehicleClass: 'strassenbahn',
247 vZul: 50,
248 ...extra,
249 };
250 }
251
252 describe('Zahlenbeispiele zu Raeum- und Einfahrzeiten (A1-A4)', () => {
253 it('A1: Rad raeumt 20 m, Kfz faehrt 10 m ein - bisher 4,1 -> 5 s, RiLSA 5,1 -> 6 s', () => {
254 // Der Altstand (5,0 m/s) laesst sich mit dem heutigen Programm nicht mehr
255 // rechnen - die Rad-Schranke kappt ihn. Die alte Zeile wird deshalb mit
256 // derselben Formel tz = tue + sr/vr - se/ve nachgestellt, die auch das
257 // Programm rechnet; der neue Wert kommt aus dem Programm selbst.
258 const te = 10 / RILSA_DEFAULTS.enteringSpeed.kfz;
259 const alt = 1 + 20 / 5 - te;
260 expect(alt).toBeCloseTo(4.1, 1);
261 expect(Math.ceil(alt)).toBe(5);
262
263 const neu = computeIntergreen(radRaeumend(20), KFZ_EINFAHREND_10M);
264 expect(neu.clearingSpeed).toBe(4.0);
265 expect(neu.raw).toBeCloseTo(5.1, 1);
266 expect(neu.intergreen).toBe(6);
267 // "1 s zu frueh Freigabe"
268 expect(neu.intergreen - Math.ceil(alt)).toBe(1);
269 });
270
271 it('A1: bei 40 m Raeumweg waechst das Defizit auf 2 s (9 statt 11 s)', () => {
272 const te = 10 / RILSA_DEFAULTS.enteringSpeed.kfz;
273 expect(Math.ceil(1 + 40 / 5 - te)).toBe(9);
274 const neu = computeIntergreen(radRaeumend(40), KFZ_EINFAHREND_10M);
275 expect(neu.intergreen).toBe(11);
276 });
277
278 it('A1: 8 m/s als Rad-Ueberschreibung wird jetzt mit Fehler auf 4 m/s gekappt', () => {
279 const result = computeIntergreen(
280 { ...radRaeumend(20), clearingSpeedOverride: 8 },
281 KFZ_EINFAHREND_10M,
282 );
283 expect(SAFETY_FLOORS.maxClearingSpeedRad).toBe(4.0);
284 expect(result.clearingSpeed).toBe(4.0);
285 expect(result.notes.some((n) => n.severity === 'fehler')).toBe(true);
286 });
287
288 it('A3: 15 m/s aus den Vorgaben werden auf die 10-m/s-Schranke gekappt (Raeumzeit sonst um ein Drittel kuerzer)', () => {
289 const { defaults, notes } = resolveRilsaSettings({ clearingSpeedOepnv: 15 });
290 expect(defaults.clearingSpeed.oepnv).toBe(SAFETY_FLOORS.maxClearingSpeedKfz);
291 expect(SAFETY_FLOORS.maxClearingSpeedKfz).toBe(10);
292 expect(notes.some((n) => n.severity === 'fehler')).toBe(true);
293 // 40 m: 40/15 = 2,67 s statt 40/10 = 4,0 s - ein Drittel weniger.
294 expect((40 / 10 - 40 / 15) / (40 / 10)).toBeCloseTo(1 / 3, 6);
295 });
296
297 it('A4: derselbe Wegfehler wirkt an der Furt fast zehnfach (1,2 statt 10 m/s)', () => {
298 expect(RILSA_DEFAULTS.clearingSpeed.fuss).toBe(1.2);
299 expect(RILSA_DEFAULTS.clearingSpeed.kfzStraight).toBe(10);
300 const faktor = RILSA_DEFAULTS.clearingSpeed.kfzStraight / RILSA_DEFAULTS.clearingSpeed.fuss;
301 expect(faktor).toBeGreaterThan(8);
302 expect(faktor).toBeLessThan(10);
303 });
304
305 it('A2: Bahn ohne Halt, Vmax 50, 40 m Raeumweg - bisher 0 + 40/10 = 4,0 s, RiLSA 5 + 40/13,9 = 7,9 s', () => {
306 // Vor Einfahrzeitabzug, deshalb Einfahrweg 0.
307 const neu = computeIntergreen(bahnOhneHalt(), {
308 mode: 'kfz',
309 movement: 'geradeaus',
310 enteringDistance: 0,
311 });
312 expect(neu.clearingPath).toBe(40);
313 expect(neu.crossingTime).toBe(5);
314 expect(neu.clearingSpeed).toBeCloseTo(50 / 3.6, 6);
315 expect(neu.raw).toBeCloseTo(7.9, 1);
316 expect(neu.intergreen).toBe(8);
317 // Altstand: Ueberfahrzeit 0 s, feste 10 m/s.
318 const alt = 0 + 40 / 10;
319 expect(alt).toBe(4.0);
320 // "rund 4 s zu wenig"
321 expect(Math.round(neu.raw - alt)).toBe(4);
322 });
323
324 it('A2: dieselbe Bahn mit Halt vor dem Knotenpunkt - 0 + Wurzel(2 * 40 / 1,0) = 8,9 s', () => {
325 const halt = computeIntergreen(bahnOhneHalt({ haltVorKnoten: true }), {
326 mode: 'kfz',
327 movement: 'geradeaus',
328 enteringDistance: 0,
329 });
330 expect(OEPNV_ANFAHRBESCHLEUNIGUNG.strassenbahn).toBe(1.0);
331 expect(halt.crossingTime).toBe(0);
332 expect(halt.raeumansatz).toBe('anfahren');
333 expect(halt.raw).toBeCloseTo(8.9, 1);
334 });
335 });
336
337 /*
338 * Zahlenbeispiele zu den Befunden C1 bis C6: Pruefungen, die fehlten - zur
339 * Furt, zu den Schranken je Anlagenart, zu den Signalzeiten und zu den
340 * Randbedingungen der Anordnung. Die Fachtests stehen in
341 * fehlendePruefungen.test.ts; hier geht es allein darum, dass jede gedruckte
342 * Zahl aus der Rechnung des Programms folgt - und dass Regelwerkswert und
343 * Praxiswert je als solcher gefuehrt sind.
344 */
345
346 const DATUM_C = new Date('2026-01-01T00:00:00Z');
347
348 /** Ein Kfz-Strom und eine Furt, die ihn quert; Furtlaenge = Raeumweg der Furt. */
349 function furtProjekt(furtlaenge: number, furt: Partial<SignalGroup> = {}): Project {
350 const basis = createEmptyProject('Furt', DATUM_C);
351 const k = createSignalGroup({ name: 'K1', mode: 'kfz', index: 0 });
352 const f: SignalGroup = { ...createSignalGroup({ name: 'F1', mode: 'fuss', index: 1 }), ...furt };
353 const phaseK = createPhase('Kfz', [k.id]);
354 const phaseF = createPhase('Fuss', [f.id]);
355 return {
356 ...basis,
357 signalGroups: [k, f],
358 conflicts: [
359 { ...createConflict(f.id, k.id), clearingDistance: furtlaenge, enteringDistance: 3 },
360 { ...createConflict(k.id, f.id), clearingDistance: 15, enteringDistance: 5 },
361 ],
362 phases: [phaseK, phaseF],
363 program: { ...basis.program, phaseOrder: [phaseK.id, phaseF.id] },
364 };
365 }
366
367 /** Einstreifige Verkehrsfuehrung, zwei Richtungen; die Engstelle ist der Raeumweg. */
368 function engstellenProjekt(laenge: number, vZul = 50): Project {
369 const basis = createEmptyProject('Arbeitsstelle', DATUM_C, 'einstreifig');
370 const a1 = createSignalGroup({ name: 'A1', mode: 'kfz', index: 0, vZul });
371 const a2 = createSignalGroup({ name: 'A2', mode: 'kfz', index: 1, vZul });
372 const p1 = createPhase('Richtung 1', [a1.id]);
373 const p2 = createPhase('Richtung 2', [a2.id]);
374 return {
375 ...basis,
376 intersection: { ...basis.intersection, vZul },
377 signalGroups: [a1, a2],
378 conflicts: [
379 { ...createConflict(a1.id, a2.id), clearingDistance: laenge, enteringDistance: 0 },
380 { ...createConflict(a2.id, a1.id), clearingDistance: laenge, enteringDistance: 0 },
381 ],
382 phases: [p1, p2],
383 program: { ...basis.program, phaseOrder: [p1.id, p2.id] },
384 };
385 }
386
387 function furtGruppe(project: Project): SignalGroup {
388 return project.signalGroups.find((g) => g.mode === 'fuss')!;
389 }
390
391 describe('Zahlenbeispiele zu Furt, Geschwindigkeit, Engstelle und Umlaufzeit (C1-C6)', () => {
392 it('C1: Furt 14 m, vr 1,2 m/s - halbe Furt 7 m / 1,2 m/s = 5,83 s, aufgerundet 6 s (bisher 5 s)', () => {
393 expect(RILSA_DEFAULTS.minGreen.fuss).toBe(5);
394 expect(RILSA_DEFAULTS.clearingSpeed.fuss).toBe(1.2);
395 const halb = furtFreigabebedarf(14, 1.2, false);
396 expect(halb.furtweg).toBe(7);
397 expect(halb.zeitbedarf).toBeCloseTo(5.83, 2);
398
399 const projekt = furtProjekt(14);
400 const mfz = mindestfreigabezeitDerGruppe(projekt, furtGruppe(projekt), RILSA_DEFAULTS);
401 expect(mfz.regelwert).toBe(5);
402 expect(mfz.wert).toBe(6);
403 expect(mfz.massgebend).toBe('furt');
404 // Der Hinweis nennt den Rechenweg, wie er in den Unterlagen steht - Furtweg
405 // mit zwei, Zeitbedarf mit drei Nachkommastellen.
406 const notiz = buildSignalPlan(projekt).notes.find(
407 (n) => n.code === 'mindestfreigabezeit-aus-furtlaenge',
408 );
409 expect(notiz?.message).toContain('7,00 m / 1,2 m/s = 5,833 s, aufgerundet 6 s');
410 });
411
412 it('C1: Furt 12,01 m - 6,01 m / 1,2 m/s = 5,004 s -> 6 s; der gedruckte Rechenweg gibt sein Ergebnis her', () => {
413 const projekt = furtProjekt(12.01);
414 const mfz = mindestfreigabezeitDerGruppe(projekt, furtGruppe(projekt), RILSA_DEFAULTS);
415 expect(mfz.furt?.zeitbedarf).toBeCloseTo(5.004, 3);
416 expect(mfz.furt?.zeitbedarfGerundet).toBe(6);
417 expect(mfz.wert).toBe(6);
418 const notiz = buildSignalPlan(projekt).notes.find(
419 (n) => n.code === 'mindestfreigabezeit-aus-furtlaenge',
420 );
421 expect(notiz?.message).toContain('6,01 m / 1,2 m/s = 5,004 s, aufgerundet 6 s');
422 });
423
424 it('C1: dieselbe Furt mit Zusatzeinrichtung fuer Blinde und Sehbehinderte - ganze Furt 14 m / 1,2 m/s = 11,67 s -> 12 s', () => {
425 const projekt = furtProjekt(14, { blindenzusatz: true });
426 const mfz = mindestfreigabezeitDerGruppe(projekt, furtGruppe(projekt), RILSA_DEFAULTS);
427 expect(mfz.furt?.ganzeFurt).toBe(true);
428 expect(mfz.furt?.furtweg).toBe(14);
429 expect(mfz.furt?.zeitbedarf).toBeCloseTo(11.67, 2);
430 expect(mfz.wert).toBe(12);
431 });
432
433 it('C1: erhoehter Zeitbedarf rechnet mit 1,0 m/s - 7 m / 1,0 m/s = 7 s', () => {
434 expect(RILSA_DEFAULTS.clearingSpeed.fussReducedMobility).toBe(1.0);
435 const projekt = furtProjekt(14, { reducedMobility: true });
436 const mfz = mindestfreigabezeitDerGruppe(projekt, furtGruppe(projekt), RILSA_DEFAULTS);
437 expect(mfz.furt?.vr).toBe(1.0);
438 expect(mfz.wert).toBe(7);
439 });
440
441 it('C1: Furt 20 m - halbe Furt 10 m / 1,2 m/s = 8,33 s -> 9 s; die Fussgaengerphase erhaelt mindestens 9 s', () => {
442 const projekt = furtProjekt(20);
443 const mfz = mindestfreigabezeitDerGruppe(projekt, furtGruppe(projekt), RILSA_DEFAULTS);
444 expect(mfz.furt?.zeitbedarf).toBeCloseTo(8.33, 2);
445 expect(mfz.wert).toBe(9);
446 // "Umlauf kann wachsen": Verteilung und Pruefung ziehen dieselbe Untergrenze;
447 // die Phase der Furt erhaelt mindestens die 9 s, nicht mehr 5 s.
448 const plan = buildSignalPlan(projekt);
449 expect(plan.phases.find((p) => p.name === 'Fuss')!.duration).toBeGreaterThanOrEqual(9);
450 expect(plan.groups.find((g) => g.mode === 'fuss')!.times.minGreen).toBe(9);
451 // Eine von Hand auf 5 s gesetzte Fussgaengerphase - bis dahin stillschweigend
452 // zulaessig - wird auf die 9 s angehoben und als zu kurz gemeldet.
453 const vonHand: Project = {
454 ...projekt,
455 phases: projekt.phases.map((p) => (p.name === 'Fuss' ? { ...p, manualGreen: 5 } : p)),
456 };
457 const planVonHand = buildSignalPlan(vonHand);
458 expect(planVonHand.phases.find((p) => p.name === 'Fuss')!.duration).toBe(9);
459 expect(planVonHand.notes.some((n) => n.code === 'phase-freigabezeit-zu-kurz')).toBe(true);
460 // Zum Vergleich: dieselbe Furt mit 12 m bleibt bei 5 s.
461 expect(
462 buildSignalPlan(furtProjekt(12)).groups.find((g) => g.mode === 'fuss')!.times.minGreen,
463 ).toBe(5);
464 });
465
466 it('C1: Furt 12 m - halbe Furt 6 m / 1,2 m/s = 5,0 s, der Regelwert bleibt; Beispielknotenpunkt unveraendert', () => {
467 const projekt = furtProjekt(12);
468 const mfz = mindestfreigabezeitDerGruppe(projekt, furtGruppe(projekt), RILSA_DEFAULTS);
469 expect(mfz.wert).toBe(5);
470 expect(mfz.massgebend).toBe('regelwert');
471 const plan = buildSignalPlan(createStandardIntersectionProject('Vorlage', DATUM_C));
472 for (const g of plan.groups.filter((g) => g.mode === 'fuss')) expect(g.times.minGreen).toBe(5);
473 });
474
475 it('C1: Schemastand 10 auf 11; ein fehlendes Feld heisst "ohne Zusatz" (halbe Furt)', () => {
476 // Der Schemastand ist seither weiter gestiegen (12 seit Fassung 5.5.0 mit
477 // den Fragen E1/E2, 13 seit Fassung 5.6.0 mit dem Lastzuganteil); die
478 // Aussage von C1 bleibt davon unberuehrt - das Feld blindenzusatz kam mit
479 // der 11.
480 expect(CURRENT_SCHEMA_VERSION).toBe(19);
481 const projekt = furtProjekt(14);
482 const ohneFeld: SignalGroup = { ...furtGruppe(projekt) };
483 delete (ohneFeld as { blindenzusatz?: boolean }).blindenzusatz;
484 const mfz = mindestfreigabezeitDerGruppe(
485 {
486 ...projekt,
487 signalGroups: projekt.signalGroups.map((g) => (g.mode === 'fuss' ? ohneFeld : g)),
488 },
489 ohneFeld,
490 RILSA_DEFAULTS,
491 );
492 expect(mfz.furt?.ganzeFurt).toBe(false);
493 expect(mfz.wert).toBe(6);
494 });
495
496 it('C2/C6: 70 km/h nach VwV-StVO, 50 km/h nach RSA 21 - 80 km/h am Knotenpunkt: Warnung, bisher keine', () => {
497 expect(VZUL_OBERGRENZE_LSA).toBe(70);
498 expect(RSA_EINSTREIFIG.vZulRegel).toBe(50);
499 const knoten = createStandardIntersectionProject('vZul', DATUM_C);
500 const mit80: Project = {
501 ...knoten,
502 signalGroups: knoten.signalGroups.map((g) => (g.name === 'K1' ? { ...g, vZul: 80 } : g)),
503 };
504 const befunde = hoechstgeschwindigkeitBefunde(mit80);
505 expect(
506 befunde.filter((f) => f.rule === 'signalgruppen.vzul-ueber-70' && f.severity === 'warnung'),
507 ).toHaveLength(1);
508 // Am Knotenpunkt keine RSA-Regel; 70 km/h selbst ist noch kein Befund.
509 expect(befunde.filter((f) => f.rule === 'einstreifig.vzul')).toEqual([]);
510 expect(hoechstgeschwindigkeitBefunde(knoten)).toEqual([]);
511 });
512
513 it('C6: einstreifig 60 km/h - Hinweis je Kfz-Gruppe und fuer den Anlagenbereich; 80 km/h - Warnung, dazu C2', () => {
514 const hinweise = hoechstgeschwindigkeitBefunde(engstellenProjekt(200, 60));
515 const rsa60 = hinweise.filter((f) => f.rule === 'einstreifig.vzul');
516 // zwei Kfz-Gruppen + Anlagenbereich
517 expect(rsa60).toHaveLength(3);
518 expect(rsa60.every((f) => f.severity === 'hinweis')).toBe(true);
519 expect(hinweise.filter((f) => f.rule.endsWith('vzul-ueber-70'))).toEqual([]);
520
521 const warnungen = hoechstgeschwindigkeitBefunde(engstellenProjekt(200, 80));
522 expect(
523 warnungen.filter((f) => f.rule === 'einstreifig.vzul' && f.severity === 'warnung'),
524 ).toHaveLength(3);
525 expect(warnungen.filter((f) => f.rule === 'signalgruppen.vzul-ueber-70')).toHaveLength(2);
526 expect(warnungen.filter((f) => f.rule === 'projekt.vzul-ueber-70')).toHaveLength(1);
527 });
528
529 it('C3: 50 m und 500 Kfz/h - Anordnungskriterien einmal je Projekt, kurze Engstelle bis 50 m, Typklasse', () => {
530 expect(RSA_EINSTREIFIG.kurzeEngstelleBis).toBe(50);
531 expect(RSA_EINSTREIFIG.verkehrsstaerkeRichtwert).toBe(500);
532 const kurz = einstreifigBefunde(engstellenProjekt(50));
533 expect(kurz.filter((f) => f.rule === 'einstreifig.rsa-anordnungskriterien')).toHaveLength(1);
534 expect(kurz.filter((f) => f.rule === 'einstreifig.kurze-engstelle')).toHaveLength(1);
535 expect(kurz.filter((f) => f.rule === 'einstreifig.ztv-typklasse')).toHaveLength(1);
536 expect(kurz.every((f) => f.severity === 'hinweis')).toBe(true);
537 expect(
538 einstreifigBefunde(engstellenProjekt(51)).some(
539 (f) => f.rule === 'einstreifig.kurze-engstelle',
540 ),
541 ).toBe(false);
542 // Am Knotenpunkt keine dieser Regeln.
543 expect(einstreifigBefunde(createStandardIntersectionProject('Knoten', DATUM_C))).toEqual([]);
544 });
545
546 it('C4: 120 s von Hand am Knotenpunkt - "ausserhalb des ueblichen Bereichs von 30 bis 90 s"', () => {
547 // Seit 5.27.0 der Richtwert der RiLSA (Abschnitt 2.6) statt der 60 s,
548 // die dieses Programm gesetzt hatte.
549 expect(ANLAGENART_GRENZEN.knotenpunkt.cycleTime.recommendedMin).toBe(30);
550 expect(ANLAGENART_GRENZEN.knotenpunkt.cycleTime.recommendedMax).toBe(90);
551 const roh = createStandardIntersectionProject('Manuell', DATUM_C);
552 const manuell: Project = {
553 ...roh,
554 program: { ...roh.program, method: 'manuell', manualCycleTime: 120 },
555 };
556 const treffer = buildSignalPlan(manuell).notes.filter(
557 (n) => n.code === 'umlaufzeit-ausserhalb-regelbereich',
558 );
559 expect(treffer).toHaveLength(1);
560 expect(treffer[0]!.severity).toBe('hinweis');
561 expect(treffer[0]!.message).toBe(
562 'Die Umlaufzeit 120 s liegt außerhalb des üblichen Bereichs von 30 bis 90 s.',
563 );
564 // 150 s bei einstreifiger Fuehrung liegen im Regelbereich (60-180 s).
565 expect(ANLAGENART_GRENZEN.einstreifig.cycleTime.recommendedMax).toBe(180);
566 const eng = engstellenProjekt(300);
567 const eng150: Project = {
568 ...eng,
569 program: { ...eng.program, method: 'manuell', manualCycleTime: 150 },
570 };
571 expect(
572 buildSignalPlan(eng150).notes.some((n) => n.code === 'umlaufzeit-ausserhalb-regelbereich'),
573 ).toBe(false);
574 });
575
576 it('C5: 400 m Hinweis, 600 m Warnung (Behoerdenpraxis); die Wartezeitschranke schlug bisher erst ab 835 m an', () => {
577 expect(ENGSTELLE_PRAXIS).toEqual({ hinweisAb: 400, warnungAb: 600 });
578 expect(engstellenPraxisHinweis(399)).toBeNull();
579 expect(engstellenPraxisHinweis(400)?.stufe).toBe('hinweis');
580 expect(engstellenPraxisHinweis(600)?.stufe).toBe('warnung');
581 /*
582 * Vor C5 gab es zwischen 400 m und der Wartezeitschranke keine Meldung.
583 * Wo diese Schranke greift, hat sich mit 5.27.0 verschoben: Das
584 * Engstellenverfahren des Abschnitts 5.2 raeumt bei zulaessig 50 km/h mit
585 * 40 km/h statt mit 10 m/s, und der Raeumweg traegt keine Fahrzeuglaenge
586 * mehr. Aus rund 835 m sind rund 923 m geworden - 5 s Freigabe + 2 x
587 * Zwischenzeit; bei 922 m ist tz = 87 s (5 + 174 = 179 s), bei 923 m
588 * tz = 88 s (5 + 176 = 181 s). Der Befund C5 traegt dadurch weiter: Ohne
589 * die Behoerdenpraxis waere die erste Meldung noch spaeter gekommen.
590 */
591 expect(ANLAGENART_GRENZEN.einstreifig.warteZeitWarnung).toBe(180);
592 expect(engstellenFolgen(922, 50).zwischenzeit).toBe(87);
593 expect(engstellenFolgen(922, 50).wartezeit).toBe(179);
594 expect(engstellenFolgen(922, 50).stufe).toBe('hinweis');
595 expect(engstellenFolgen(923, 50).zwischenzeit).toBe(88);
596 expect(engstellenFolgen(923, 50).stufe).toBe('warnung');
597 // Und das Beispiel mit 400 m: 40 s Zwischenzeit, 120 s Umlauf bei 20 s Freigabe.
598 expect(engstellenFolgen(400, 50).zwischenzeit).toBe(40);
599 expect(engstellenFolgen(400, 50).praxisHinweis?.stufe).toBe('hinweis');
600 expect(engstellenFolgen(700, 50).praxisHinweis?.stufe).toBe('warnung');
601 // Im Bericht: Referenz nennt die Einordnung.
602 const [befund] = einstreifigBefunde(engstellenProjekt(600)).filter(
603 (f) => f.rule === 'einstreifig.laenge-behoerdenpraxis',
604 );
605 expect(befund?.severity).toBe('warnung');
606 expect(befund?.reference).toBe('Behördenpraxis (z. B. Hessen Mobil), kein Regelwerkswert');
607 });
608 });
609
610 /*
611 * Zahlenbeispiele zu den Befunden C7 bis C12: Exportsperre und stille
612 * Ersetzungen - an Zwischenzeit, Signalzeiten, Umlaufzeit, Vorgabewerten und
613 * Laengenzuschlag. Die Fachtests stehen in stilleErsetzungen.test.ts; hier geht
614 * es allein darum, dass jede gedruckte Zahl und jeder zitierte Wortlaut aus der
615 * Rechnung des Programms folgt. C7 und C12 (Ausgabe-Ansicht, Assistent) haben
616 * keine Zahl im Fachkern und stehen deshalb nicht hier.
617 */
618
619 /** Zwei Kfz-Gruppen in zwei Phasen; die zweite Gruppe laesst sich anpassen. */
620 function zweiKfzGruppen(
621 zweite: Partial<SignalGroup> = {},
622 anlagenart: 'knotenpunkt' | 'einstreifig' = 'knotenpunkt',
623 rilsa: Project['settings']['rilsa'] = {},
624 ): Project {
625 const basis = createEmptyProject('Zwei Gruppen', DATUM_C, anlagenart);
626 const a = createSignalGroup({ name: 'K1', mode: 'kfz', index: 0 });
627 const b: SignalGroup = { ...createSignalGroup({ name: 'K2', mode: 'kfz', index: 1 }), ...zweite };
628 const phase1 = createPhase('Phase 1', [a.id]);
629 const phase2 = createPhase('Phase 2', [b.id]);
630 return {
631 ...basis,
632 signalGroups: [a, b],
633 conflicts: [
634 { ...createConflict(a.id, b.id), clearingDistance: 22, enteringDistance: 8 },
635 { ...createConflict(b.id, a.id), clearingDistance: 22, enteringDistance: 8 },
636 ],
637 phases: [phase1, phase2],
638 program: { ...basis.program, phaseOrder: [phase1.id, phase2.id] },
639 settings: { ...basis.settings, rilsa },
640 };
641 }
642
643 describe('Zahlenbeispiele zu gekappten und ersetzten Eingaben (C7-C12)', () => {
644 it('C8: Kfz raeumt 22 m, faehrt 8 m ein - 0 m: 3 + 2,20 - 0,72 = 4,48 -> 5 s; 6 m: 3 + 2,80 - 0,72 = 5,08 -> 6 s wie beim Pkw', () => {
645 const raeumend: ClearingStream = {
646 mode: 'kfz',
647 movement: 'geradeaus',
648 clearingDistance: 22,
649 vehicleClass: 'traktor' as VehicleClass,
650 vZul: 50,
651 };
652 const einfahrend: EnteringStream = { mode: 'kfz', movement: 'geradeaus', enteringDistance: 8 };
653
654 // Der Altstand (0 m) laesst sich mit dem heutigen Programm nicht mehr
655 // rechnen; die alte Zeile wird mit derselben Formel nachgestellt.
656 const te = 8 / RILSA_DEFAULTS.enteringSpeed.kfz;
657 expect(te).toBeCloseTo(0.72, 2);
658 const alt = 3 + 22 / 10 - te;
659 expect(alt).toBeCloseTo(4.48, 2);
660 expect(Math.ceil(alt)).toBe(5);
661
662 const neu = computeIntergreen(raeumend, einfahrend);
663 expect(neu.vehicleLength).toBe(SAFETY_FLOORS.minVehicleLengthKfz);
664 expect(neu.vehicleLength).toBe(6);
665 expect(neu.clearingPath).toBe(28);
666 expect(neu.crossingTime).toBe(3);
667 expect(neu.raw).toBeCloseTo(5.08, 2);
668 expect(neu.intergreen).toBe(6);
669 expect(
670 neu.notes.some((n) => n.code === 'fahrzeugart-unbekannt' && n.severity === 'fehler'),
671 ).toBe(true);
672 // "wie beim Pkw"
673 const pkw = computeIntergreen({ ...raeumend, vehicleClass: 'pkw' }, einfahrend);
674 expect(pkw.intergreen).toBe(6);
675 expect(pkw.raw).toBeCloseTo(neu.raw, 6);
676 // Fussgaenger: 0 m ohne Meldung.
677 const fuss = computeIntergreen(
678 {
679 mode: 'fuss',
680 movement: 'querung',
681 clearingDistance: 12,
682 vehicleClass: 'traktor' as VehicleClass,
683 vZul: 0,
684 },
685 einfahrend,
686 );
687 expect(fuss.vehicleLength).toBe(0);
688 expect(fuss.notes.some((n) => n.code === 'fahrzeugart-unbekannt')).toBe(false);
689 });
690
691 it('C9: 3 s eingetragen - "sie unterschreitet den Regelwert der Verkehrsart von 5 s. Massgebend bleibt 5 s; die Vorgabe hat keine Wirkung."', () => {
692 const projekt = zweiKfzGruppen({ minGreenOverride: 3 });
693 const plan = buildSignalPlan(projekt);
694 const k2 = projekt.signalGroups.find((g) => g.name === 'K2')!;
695 expect(plan.groups.find((g) => g.groupId === k2.id)!.times.minGreen).toBe(5);
696 const [notiz] = plan.notes.filter(
697 (n) => n.code === 'mindestfreigabezeit-vorgabe-unterschritten',
698 );
699 expect(notiz?.severity).toBe('warnung');
700 expect(notiz?.message).toBe(
701 'Für die Signalgruppe "K2" ist eine Mindestfreigabezeit von 3 s eingetragen; sie ' +
702 'unterschreitet den Regelwert der Verkehrsart von 5 s. Maßgebend bleibt 5 s; die Vorgabe ' +
703 'hat keine Wirkung.',
704 );
705 // 5,5 s an einer 14-m-Furt (Furtbedarf 6 s): "unterschreitet den Furtbedarf von 6 s".
706 const furt = furtProjekt(14, { minGreenOverride: 5.5 });
707 expect(mindestfreigabezeitDerGruppe(furt, furtGruppe(furt), RILSA_DEFAULTS).wert).toBe(6);
708 const [furtNotiz] = buildSignalPlan(furt).notes.filter(
709 (n) => n.code === 'mindestfreigabezeit-vorgabe-unterschritten',
710 );
711 expect(furtNotiz?.message).toContain('5,5 s eingetragen');
712 expect(furtNotiz?.message).toContain('unterschreitet den Furtbedarf von 6 s');
713 expect(furtNotiz?.message).toContain('Maßgebend bleibt 6 s');
714 });
715
716 it('C10: 250 s am Knotenpunkt -> 120 s mit Fehler ("nach RiLSA nicht vorzusehen"); einstreifig 250 s ohne Befund, 400 s -> 300 s; 5 s -> 30 s ("nicht schaltbar")', () => {
717 expect(ANLAGENART_GRENZEN.knotenpunkt.cycleTime.max).toBe(120);
718 expect(ANLAGENART_GRENZEN.fussgaengerschutzanlage.cycleTime.max).toBe(120);
719 expect(ANLAGENART_GRENZEN.einstreifig.cycleTime.max).toBe(300);
720 expect(SAFETY_FLOORS.minCycleTime).toBe(30);
721
722 const knoten = resolveRilsaSettings({ cycleTimeMax: 250 }, undefined, 'knotenpunkt');
723 expect(knoten.defaults.cycleTime.max).toBe(120);
724 const kn = knoten.notes.find((n) => n.code === 'vorgabe-ueberschritten:cycleTimeMax');
725 expect(kn?.severity).toBe('fehler');
726 expect(kn?.message).toContain('höchstens 120 s');
727 expect(kn?.message).toContain('Umlaufzeiten über 120 s sind nach RiLSA nicht vorzusehen.');
728
729 const eng = resolveRilsaSettings({ cycleTimeMax: 250 }, undefined, 'einstreifig');
730 expect(eng.defaults.cycleTime.max).toBe(250);
731 expect(eng.notes).toHaveLength(0);
732
733 const zuLang = resolveRilsaSettings({ cycleTimeMax: 400 }, undefined, 'einstreifig');
734 expect(zuLang.defaults.cycleTime.max).toBe(300);
735 expect(
736 zuLang.notes.find((n) => n.code === 'vorgabe-ueberschritten:cycleTimeMax')?.message,
737 ).toContain('RSA');
738
739 const zuKurz = resolveRilsaSettings({ cycleTimeMin: 5 }, undefined, 'knotenpunkt');
740 expect(zuKurz.defaults.cycleTime.min).toBe(30);
741 expect(
742 zuKurz.notes.find((n) => n.code === 'vorgabe-unterschritten:cycleTimeMin')?.message,
743 ).toContain('Umlaufzeiten unter 30 s sind nicht schaltbar.');
744
745 // Im Plan und im Bericht: der einstreifige Plan mit 250 s bleibt ungekappt.
746 const engPlan = buildSignalPlan(zweiKfzGruppen({}, 'einstreifig', { cycleTimeMax: 250 }));
747 expect(engPlan.grenzen.cycleTime.max).toBe(250);
748 expect(engPlan.notes.some((n) => n.code.startsWith('vorgabe-'))).toBe(false);
749 const knotenPlan = buildSignalPlan(zweiKfzGruppen({}, 'knotenpunkt', { cycleTimeMax: 250 }));
750 expect(knotenPlan.grenzen.cycleTime.max).toBe(120);
751 expect(knotenPlan.notes.some((n) => n.code === 'vorgabe-ueberschritten:cycleTimeMax')).toBe(
752 true,
753 );
754 });
755
756 it('C11: Webster L = 20 s, Y = 0,70 + 0,35 = 1,05 -> uebersaettigt, Ersatzwert 120 s; L = 40 s, Y = 0,85 -> 433 s, gekappt ("maximum"); HBS Y = 0,90 >= 0,85 -> uebersaettigt', () => {
757 expect(RILSA_DEFAULTS.cycleTime.max).toBe(120);
758 const ueber = computeCycleTime('webster', { lostTime: 20, criticalFlowRatios: [0.7, 0.35] });
759 expect(ueber.totalFlowRatio).toBeCloseTo(1.05, 6);
760 expect(ueber.bounded).toBe('uebersaettigt');
761 expect(ueber.cycleTime).toBe(120);
762 expect(ueber.raw).toBe(Number.POSITIVE_INFINITY);
763 const notiz = ueber.notes.find((n) => n.code === 'uebersaettigt');
764 expect(notiz?.severity).toBe('fehler');
765 expect(notiz?.message).toContain(
766 'Der Plan führt ersatzweise die Obergrenze von 120 s - das ist kein Bemessungsergebnis (nicht bemessbar, Ersatzwert).',
767 );
768
769 const lang = computeCycleTime('webster', { lostTime: 40, criticalFlowRatios: [0.55, 0.3] });
770 // raw wird in cycle.ts auf zwei Nachkommastellen gefuehrt: 433,33 s.
771 expect(lang.raw).toBeCloseTo((1.5 * 40 + 5) / 0.15, 2);
772 expect(Math.round(lang.raw)).toBe(433);
773 expect(lang.cycleTime).toBe(120);
774 expect(lang.bounded).toBe('maximum');
775 expect(lang.notes.some((n) => n.code === 'uebersaettigt')).toBe(false);
776
777 expect(RILSA_DEFAULTS.capacity.targetDegreeOfSaturation).toBe(0.85);
778 // Seit 5.27.0 das obere Ende des Bandes der RiLSA (Abschnitt 2.6).
779 expect(RILSA_DEFAULTS.capacity.maxDegreeOfSaturation).toBe(0.9);
780 expect(computeCycleTime('hbs', { lostTime: 20, criticalFlowRatios: [0.5, 0.4] }).bounded).toBe(
781 'uebersaettigt',
782 );
783 expect(
784 computeCycleTime('webster', { lostTime: 20, criticalFlowRatios: [0.5, 0.4] }).bounded,
785 ).not.toBe('uebersaettigt');
786
787 // Der Vermerk, den jede Ausgabe neben den Ersatzwert setzt - mit
788 // Gedankenstrich wie in Hilfe und Unterlagen (Bindestrich in der
789 // Konstante, Gedankenstrich in den Unterlagen ergab "120 s – … - Ersatzwert").
790 expect(UMLAUFZEIT_ERSATZWERT_VERMERK).toBe('nicht bemessbar (übersättigt) – Ersatzwert');
791 const roh = createStandardIntersectionProject('Uebersaettigt', DATUM_C);
792 const plan = buildSignalPlan({
793 ...roh,
794 demands: roh.signalGroups
795 .filter((g) => g.mode === 'kfz')
796 .map((g) => ({ signalGroupId: g.id, volume: 1800, heavyVehicleShare: 0.1 })),
797 });
798 expect(plan.cycleResult?.bounded).toBe('uebersaettigt');
799 expect(umlaufzeitIstErsatzwert(plan)).toBe(true);
800 expect(umlaufzeitIstErsatzwert(buildSignalPlan(roh))).toBe(false);
801 });
802 });