lsa-planer

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

/ tests domain sperrzeitenDurchreichen.test.ts

6,6 KB Rohdatei
tests/domain/sperrzeitenDurchreichen.test.ts — 145 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { buildSignalPlan } from '@/domain/plan/signalPlan';
3 import { computeDelay, sperrzeitenAusFenstern } from '@/domain/rilsa/delay';
4 import { createPhase, createStandardIntersectionProject } from '@/domain/model/factory';
5 import type { Project } from '@/domain/model/project';
6
7 /*
8 * Befund 26, im laufenden Programm nicht angekommen (Fassung 5.10.0).
9 *
10 * `computeDelay` bildet den Zaehler der Grundwartezeit seit Fassung
11 * 5.10.0 aus Summe(r_j^2) / tU ueber die Sperrzeiten des Umlaufs;
12 * ohne diese Sperrzeiten faellt es auf die geschlossene Einfensterform
13 * tU * (1 - fA)^2 zurueck. Erhaelt eine Signalgruppe mehrere
14 * Freigabezeitfenster, ist die Einfensterform zu gross - bei zwei gleich langen
15 * Sperrzeiten genau um das Doppelte.
16 *
17 * `computeDelay` hat projektweit genau einen Aufrufer: den Planaufbau in
18 * signalPlan.ts. Der uebergab die bereits gebildete Bindung `sperrzeiten`
19 * nicht, sondern allein deren Groesstwert als `longestRed`. Damit blieb die
20 * Behebung im Fachkern wirkungslos: Signalzeitenplan, Ausdruck und
21 * Tabellenausgabe wiesen tW,G, tW und die Qualitaetsstufe weiter bis zum
22 * Doppelten zu gross aus.
23 *
24 * Alle vorhandenen Wachen zu Befund 26 rufen `computeDelay` unmittelbar auf und
25 * koennen deshalb nicht bemerken, dass der Weg dorthin unterbrochen ist. Diese
26 * Datei geht ueber `buildSignalPlan`.
27 *
28 * GEGEN DEN ALTSTAND schlagen die beiden Faelle zum Zweifensterplan fehl; die
29 * Gegenproben (ein Fenster, Rueckfall ohne brauchbare Sperrzeiten) bestehen
30 * dort ebenso - sie halten fest, dass die Uebergabe den Regelfall nicht
31 * verschiebt.
32 */
33
34 const DATUM = new Date('2026-01-01T12:00:00Z');
35
36 /**
37 * Vierphasiger Umlauf, in dem K1/K2 in Phase 1 und 3 und K3/K4 in Phase 2 und 4
38 * Freigabe haben - derselbe Aufbau wie in tests/export/pdfFreigabefenster.test.ts.
39 *
40 * Vier Phasen und nicht drei: Bei drei Phasen waeren Phase 1 und Phase 3 ueber
41 * das Umlaufende benachbart, und der Planaufbau fasste beide zu einem einzigen
42 * Fenster zusammen.
43 */
44 function zweiFenster(): Project {
45 const roh = createStandardIntersectionProject('Zwei Fenster', DATUM);
46 const id = (name: string) => roh.signalGroups.find((g) => g.name === name)!.id;
47 const nordSued = [id('K1'), id('K2'), id('F1')];
48 const ostWest = [id('K3'), id('K4'), id('F2')];
49 const phasen = [
50 createPhase('Phase 1 - Nord/Süd', nordSued),
51 createPhase('Phase 2 - Ost/West', ostWest),
52 createPhase('Phase 3 - Nord/Süd', nordSued),
53 createPhase('Phase 4 - Ost/West', ostWest),
54 ];
55 return {
56 ...roh,
57 phases: phasen,
58 program: { ...roh.program, phaseOrder: phasen.map((p) => p.id) },
59 // Vier Phasen kosten doppelt so viele Uebergaenge; mit den Verkehrsstaerken
60 // der Vorlage waere der Umlauf uebersaettigt und die Freigabezeiten stuenden
61 // auf einem Ersatzwert.
62 demands: roh.demands.map((d) => ({ ...d, volume: Math.round(d.volume / 2) })),
63 };
64 }
65
66 /**
67 * Die Wartezeit, die `computeDelay` mit denselben Groessen liefert - einmal mit
68 * den Sperrzeiten des Umlaufs, einmal ohne.
69 *
70 * Der Vergleich laeuft ueber den Fachkern selbst und nicht ueber eine im Test
71 * nachgebaute Formel: Geprueft wird die Uebergabe, nicht die Rechnung.
72 */
73 function ausFachkern(projekt: Project, gruppenname: string, mitSperrzeiten: boolean) {
74 const plan = buildSignalPlan(projekt);
75 const gruppe = plan.groups.find((g) => g.name === gruppenname)!;
76 const demand = projekt.demands.find((d) => d.signalGroupId === gruppe.groupId)!;
77 const sperrzeiten = sperrzeitenAusFenstern(gruppe.greens, plan.cycleTime);
78 return computeDelay({
79 cycleTime: plan.cycleTime,
80 greenTime: gruppe.totalGreen,
81 capacity: gruppe.capacity!.capacity,
82 demand: demand.volume,
83 verfahren: projekt.settings.serviceLevelScale,
84 mode: 'kfz',
85 abflusszeit: gruppe.capacity!.abflusszeit,
86 longestRed: sperrzeiten.length > 0 ? Math.max(...sperrzeiten) : 0,
87 ...(mitSperrzeiten ? { sperrzeiten } : {}),
88 instationaritaetsfaktor: plan.defaults.capacity.instationaritaetsfaktor,
89 });
90 }
91
92 describe('Befund 26 im Planaufbau: die Sperrzeiten erreichen die Wartezeitrechnung', () => {
93 it('legt der Pruefung wirklich zwei getrennte Freigabezeitfenster vor', () => {
94 // Vorbedingung: Mit einem Fenster prueft nichts darunter etwas.
95 const plan = buildSignalPlan(zweiFenster());
96 const k1 = plan.groups.find((g) => g.name === 'K1')!;
97 expect(k1.greens).toHaveLength(2);
98 expect(k1.delay, 'ohne Wartezeit prüft der Fall nichts').not.toBeNull();
99 });
100
101 it('rechnet die Grundwartezeit ueber alle Fenster, nicht ueber eines', () => {
102 const projekt = zweiFenster();
103 const plan = buildSignalPlan(projekt);
104 const k1 = plan.groups.find((g) => g.name === 'K1')!;
105
106 const mehrfenster = ausFachkern(projekt, 'K1', true);
107 const einfenster = ausFachkern(projekt, 'K1', false);
108 // Vorbedingung: Ohne Unterschied zwischen beiden Formen prueft der Fall
109 // nichts. Die Einfensterform faellt zu gross aus.
110 expect(einfenster.uniformDelay).toBeGreaterThan(mehrfenster.uniformDelay);
111
112 expect(k1.delay!.uniformDelay).toBeCloseTo(mehrfenster.uniformDelay, 6);
113 expect(k1.delay!.averageDelay).toBeCloseTo(mehrfenster.averageDelay, 6);
114 expect(k1.delay!.uniformDelay).not.toBeCloseTo(einfenster.uniformDelay, 6);
115 });
116
117 it('reicht die Sperrzeiten an jede Kfz-Gruppe mit mehreren Fenstern durch', () => {
118 // Nicht "an K1", sondern "an jede": Eine zweite Uebergabestelle mit einer
119 // anderen Wahl faellt hier auf.
120 const projekt = zweiFenster();
121 const plan = buildSignalPlan(projekt);
122 let geprueft = 0;
123 for (const gruppe of plan.groups) {
124 if (gruppe.delay === null || gruppe.capacity === null) continue;
125 if (gruppe.greens.length < 2) continue;
126 geprueft += 1;
127 expect(gruppe.delay.uniformDelay, gruppe.name).toBeCloseTo(
128 ausFachkern(projekt, gruppe.name, true).uniformDelay,
129 6,
130 );
131 }
132 expect(geprueft, 'die Schleife prüft wirklich etwas').toBeGreaterThan(1);
133 });
134
135 it('laesst bei einem einzigen Fenster die geschlossene Form unveraendert', () => {
136 // Gegenprobe: Bei einem Fenster je Umlauf gehen beide Formen ineinander
137 // ueber - die Uebergabe darf den Regelfall nicht verschieben.
138 const projekt = createStandardIntersectionProject('Ein Fenster', DATUM);
139 const plan = buildSignalPlan(projekt);
140 const k1 = plan.groups.find((g) => g.name === 'K1')!;
141 expect(k1.greens).toHaveLength(1);
142 expect(k1.delay!.uniformDelay).toBeCloseTo(ausFachkern(projekt, 'K1', false).uniformDelay, 6);
143 expect(k1.delay!.uniformDelay).toBeCloseTo(ausFachkern(projekt, 'K1', true).uniformDelay, 6);
144 });
145 });