lsa-planer

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

/ tests domain zwischenzeitSchranken.test.ts

5,8 KB Rohdatei
tests/domain/zwischenzeitSchranken.test.ts — 129 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { validateProject } from '@/domain/validation/engine';
3 import { buildSignalPlan, intergreenKey } from '@/domain/plan/signalPlan';
4 import { createStandardIntersectionProject } from '@/domain/model/factory';
5 import type { Conflict, Project } from '@/domain/model/project';
6
7 const STICHTAG = new Date('2026-01-01T00:00:00Z');
8
9 /** Regelknotenpunkt, in dem genau eine Kfz-Beziehung abgewandelt ist. */
10 function mitKonflikt(aenderung: Partial<Conflict>): { projekt: Project; konflikt: Conflict } {
11 const roh = createStandardIntersectionProject('Schranken', STICHTAG);
12 const [k1, , k3] = roh.signalGroups;
13 const ziel = roh.conflicts.find((c) => c.fromId === k1!.id && c.toId === k3!.id)!;
14 const geaendert = { ...ziel, ...aenderung };
15 return {
16 konflikt: geaendert,
17 projekt: {
18 ...roh,
19 conflicts: roh.conflicts.map((c) => (c.id === ziel.id ? geaendert : c)),
20 meta: {
21 ...roh.meta,
22 projectNumber: 'VP-2026-001',
23 client: 'Stadt Musterhausen',
24 planner: 'O. Willerding',
25 location: 'Schranken',
26 },
27 },
28 };
29 }
30
31 describe('Schranken der Zwischenzeit lassen sich nicht ueber ein Nachbarfeld umgehen', () => {
32 /*
33 * Die vorgegebene Zwischenzeit ersetzt die Rechnung vollstaendig -
34 * computeIntergreen wurde gar nicht erst gerufen. Damit liefen alle Schranken
35 * ins Leere, die fuer die Einzelwerte gelten. In der Zwischenzeitenansicht
36 * stehen beide Felder nebeneinander: Wer kuerzen wollte, nahm das Feld ohne
37 * Schranke.
38 */
39 it('meldet eine vorgegebene Zwischenzeit unter dem Rechenwert', () => {
40 const { projekt: ohne, konflikt: ungeaendert } = mitKonflikt({});
41 const gerechnet =
42 buildSignalPlan(ohne).intergreens.get(intergreenKey(ungeaendert.fromId, ungeaendert.toId))
43 ?.value ?? 0;
44 expect(gerechnet).toBeGreaterThan(0);
45
46 const { projekt, konflikt } = mitKonflikt({ manualIntergreen: gerechnet - 3 });
47 const plan = buildSignalPlan(projekt);
48
49 expect(plan.intergreens.get(intergreenKey(konflikt.fromId, konflikt.toId))?.value).toBe(
50 gerechnet - 3,
51 );
52 const meldung = plan.notes.find((n) => n.code === 'zwischenzeit-vorgabe-unter-rechenwert');
53 expect(meldung, 'Die Unterschreitung darf nicht stillschweigend bleiben').toBeDefined();
54 expect(meldung!.severity).toBe('warnung');
55 // Beide Zahlen muessen im Text stehen, sonst ist die Abweichung nicht zu
56 // beziffern.
57 expect(meldung!.message).toContain(`${gerechnet - 3} s`);
58 expect(meldung!.message).toContain(`${gerechnet} s`);
59 });
60
61 it('schweigt, wenn die Vorgabe den Rechenwert erreicht oder ueberschreitet', () => {
62 const { projekt: ohne, konflikt: ungeaendert } = mitKonflikt({});
63 const gerechnet =
64 buildSignalPlan(ohne).intergreens.get(intergreenKey(ungeaendert.fromId, ungeaendert.toId))
65 ?.value ?? 0;
66
67 for (const vorgabe of [gerechnet, gerechnet + 2]) {
68 const { projekt } = mitKonflikt({ manualIntergreen: vorgabe });
69 expect(
70 buildSignalPlan(projekt).notes.some(
71 (n) => n.code === 'zwischenzeit-vorgabe-unter-rechenwert',
72 ),
73 `Vorgabe ${vorgabe}`,
74 ).toBe(false);
75 }
76 });
77
78 it('vergleicht nicht, solange kein Raeumweg erfasst ist - meldet den Fall aber', () => {
79 /*
80 * BERICHTIGUNG (Befund 19, Fassung 5.10.0): Hier stand
81 * "Die fehlende Vermassung meldet bereits konflikte.raeumweg-fehlt". Das
82 * war nachweislich falsch - jene Regel greift nur bei
83 * `manualIntergreen === null` (engine.ts), also gerade nicht in diesem
84 * Fall. Der Testfall hielt damit fest, dass zu "Handvorgabe gesetzt und
85 * Raeumweg 0 m" ueberhaupt nichts gemeldet wird, und begruendete es mit
86 * einer Meldung, die es dort nie gab: Der Eintrag im Nachbarfeld hob die
87 * Ausgabesperre auf - unter der Ueberschrift dieses describe, die das
88 * Gegenteil zusagt.
89 *
90 * Ohne Vergleichsgrundlage NICHT ZU VERGLEICHEN bleibt richtig; gerechnet
91 * wird ohne Raeumweg nichts, was sich gegenhalten liesse. Gemeldet werden
92 * muss der Zustand trotzdem, und zwar als Fehler - das leistet jetzt
93 * 'konflikte.vorgabe-ohne-raeumweg'.
94 */
95 const { projekt, konflikt } = mitKonflikt({ manualIntergreen: 1, clearingDistance: 0 });
96 expect(
97 buildSignalPlan(projekt).notes.some(
98 (n) => n.code === 'zwischenzeit-vorgabe-unter-rechenwert',
99 ),
100 ).toBe(false);
101
102 const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG);
103 const befund = bericht.findings.find((f) => f.rule === 'konflikte.vorgabe-ohne-raeumweg');
104 expect(befund, 'Die Vorgabe ohne Vergleichsgrundlage gehoert in den Bericht').toBeDefined();
105 expect(befund!.severity).toBe('fehler');
106 expect(befund!.target?.id).toBe(konflikt.id);
107 expect(bericht.exportBlocked).toBe(true);
108 });
109
110 it('fuehrt Meldungen der Zwischenzeitrechnung auf ihre Konfliktbeziehung zurueck', () => {
111 // Zuvor zeigten alle Meldungen aus dem Planaufbau pauschal auf das
112 // Programm; der Sprungknopf im Pruefbericht fuehrte damit in die
113 // Phasenansicht, wo sich an einem Raeumweg nichts aendern laesst.
114 const { projekt, konflikt } = mitKonflikt({ crossingTimeOverride: 0 });
115 const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG);
116
117 const befund = bericht.findings.find(
118 (f) => f.rule === 'signalplan.ueberfahrzeit-unter-gelbzeitschranke',
119 );
120 expect(befund, 'Die gekappte Ueberfahrzeit muss im Bericht stehen').toBeDefined();
121 expect(befund!.severity).toBe('fehler');
122 expect(befund!.category).toBe('zwischenzeiten');
123 expect(befund!.target?.kind).toBe('konflikt');
124 expect(befund!.target?.id).toBe(konflikt.id);
125 expect(befund!.target?.label).toBe('K1 nach K3');
126 // Eigene Ueberschrift statt des Sammeltitels "Hinweis aus dem Planaufbau".
127 expect(befund!.title).not.toContain('Hinweis aus dem Planaufbau');
128 });
129 });