lsa-planer

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

/ tests domain leerePhase.test.ts

4,1 KB Rohdatei
tests/domain/leerePhase.test.ts — 104 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { buildSignalPlan, intergreenBetween, type GreenInterval } from '@/domain/plan/signalPlan';
3 import { createPhase, createStandardIntersectionProject } from '@/domain/model/factory';
4 import type { Project } from '@/domain/model/project';
5
6 /*
7 * Befund F6: Eine Phase ohne Signalgruppen setzte die Zwischenzeit des
8 * Phasenwechsels auf 0 s.
9 *
10 * Wer in der Phasenansicht auf "Phase hinzufügen" klickt und der neuen Phase
11 * noch keine Signalgruppe zuordnet, erhielt einen Signalzeitenplan, in dem die
12 * Freigabe der Folgephase in derselben Sekunde begann, in der die Freigabe der
13 * Vorphase endete - die gerechnete Zwischenzeit von mehreren Sekunden fiel
14 * ersatzlos weg, weil beide angrenzenden Uebergaenge auf 0 s standen.
15 */
16
17 const FESTES_DATUM = new Date('2026-01-01T00:00:00Z');
18
19 interface Prueffall {
20 readonly project: Project;
21 readonly leerePhaseId: string;
22 readonly vorphaseId: string;
23 }
24
25 /** Standardknotenpunkt mit einer zusaetzlich angehaengten Phase ohne Signalgruppe. */
26 function mitLeererPhase(): Prueffall {
27 const basis = createStandardIntersectionProject('Prueffall', FESTES_DATUM);
28 const leer = createPhase('Phase 3 - ohne Zuordnung');
29 return {
30 project: {
31 ...basis,
32 phases: [...basis.phases, leer],
33 program: { ...basis.program, phaseOrder: [...basis.program.phaseOrder, leer.id] },
34 },
35 leerePhaseId: leer.id,
36 vorphaseId: basis.phases[1]!.id,
37 };
38 }
39
40 /** Kleinster Abstand vom Freigabeende der einen zum Freigabebeginn der anderen Gruppe. */
41 function kleinsterAbstand(
42 raeumend: readonly GreenInterval[],
43 einfahrend: readonly GreenInterval[],
44 umlauf: number,
45 ): number {
46 let kleinster = Number.POSITIVE_INFINITY;
47 for (const a of raeumend) {
48 const ende = a.start + a.duration;
49 for (const b of einfahrend) {
50 const luecke = (((b.start - ende) % umlauf) + umlauf) % umlauf;
51 if (luecke < kleinster) kleinster = luecke;
52 }
53 }
54 return kleinster;
55 }
56
57 describe('Phase ohne Signalgruppen im Signalzeitenplan', () => {
58 it('haelt jede geforderte Zwischenzeit ein, obwohl eine leere Phase dazwischen steht', () => {
59 const { project } = mitLeererPhase();
60 const plan = buildSignalPlan(project);
61
62 for (const conflict of project.conflicts) {
63 const gefordert = intergreenBetween(plan.intergreens, conflict.fromId, conflict.toId);
64 if (gefordert <= 0) continue;
65
66 const raeumend = plan.groups.find((g) => g.groupId === conflict.fromId)!;
67 const einfahrend = plan.groups.find((g) => g.groupId === conflict.toId)!;
68 if (raeumend.greens.length === 0 || einfahrend.greens.length === 0) continue;
69
70 const ist = kleinsterAbstand(raeumend.greens, einfahrend.greens, plan.cycleTime);
71 expect(
72 ist + 1e-6,
73 `${raeumend.name} räumt, ${einfahrend.name} fährt ein`,
74 ).toBeGreaterThanOrEqual(gefordert);
75 }
76 });
77
78 it('bemisst den Uebergang in die leere Phase nach der naechsten belegten Phase', () => {
79 const { project, leerePhaseId, vorphaseId } = mitLeererPhase();
80 const plan = buildSignalPlan(project);
81
82 const hinein = plan.transitions.find((t) => t.toPhaseId === leerePhaseId)!;
83 const heraus = plan.transitions.find((t) => t.fromPhaseId === leerePhaseId)!;
84
85 // Massgebend ist die groesste Zwischenzeit zwischen den in der Vorphase
86 // endenden und den in der naechsten belegten Phase beginnenden Gruppen.
87 const vorphase = project.phases.find((p) => p.id === vorphaseId)!;
88 const folgephase = project.phases[0]!;
89 let erwartet = 0;
90 for (const fromId of vorphase.signalGroupIds) {
91 if (folgephase.signalGroupIds.includes(fromId)) continue;
92 for (const toId of folgephase.signalGroupIds) {
93 if (vorphase.signalGroupIds.includes(toId)) continue;
94 erwartet = Math.max(erwartet, intergreenBetween(plan.intergreens, fromId, toId));
95 }
96 }
97
98 expect(erwartet).toBeGreaterThan(0);
99 expect(hinein.duration).toBe(erwartet);
100 expect(hinein.critical?.intergreen).toBe(erwartet);
101 // Aus der leeren Phase heraus endet keine Freigabe - dort ist nichts zu sichern.
102 expect(heraus.duration).toBe(0);
103 });
104 });