lsa-planer

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

/ tests domain paarschluessel.test.ts

4,6 KB Rohdatei
tests/domain/paarschluessel.test.ts — 113 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { validateProject } from '@/domain/validation/engine';
3 import { buildSignalPlan } from '@/domain/plan/signalPlan';
4 import { createPhase, createStandardIntersectionProject } from '@/domain/model/factory';
5 import type { Project } from '@/domain/model/project';
6
7 /*
8 * Befund 16 (Fassung 5.11.0): Der Pruefbericht verschwieg ein zweites
9 * feindliches Paar mit gleichzeitiger Freigabe.
10 *
11 * `checkSimultaneousGreen` und `pruefeFeindlichInPhase` teilen sich eine Menge
12 * bereits gemeldeter Paare. Der Schluessel dieser Menge entstand aus
13 * `[a, b].sort().join('|')` - genau der zusammengeklebte Schluessel, den
14 * Befund 61 in `intergreenKey` und in `parseConflicts` beseitigt hat.
15 *
16 * Eine Signalgruppenkennung kommt ungeprueft aus der Projektdatei: Das
17 * Einlesen beanstandet nur die leere und die doppelt vergebene Kennung, kein
18 * Zeichenrepertoire. Die Paare ("a|b", "c") und ("a", "b|c") ergaben damit
19 * beide den Schluessel "a|b|c"; das zuerst gemeldete Paar sperrte das zweite
20 * ueber `if (reported.has(key)) continue;`. Weil beide Teilpruefungen dieselbe
21 * Menge benutzen, stand das zweite Paar danach ueberhaupt nicht mehr im
22 * Bericht - weder als 'phasen.feindlich-in-phase' noch als
23 * 'signalplan.feindliche-freigabe' -, und die Fehlerzahl im Kopf des Berichts
24 * nannte eine Ursache zu wenig.
25 *
26 * Das ist die gefaehrliche Richtung: Die unterdrueckte Meldung ist die, deren
27 * Wortlaut lautet "Dieser Plan darf nicht geschaltet werden".
28 *
29 * GEGEN DEN ALTSTAND schlagen die beiden Faelle mit "|" in der Kennung fehl
30 * (zwei statt drei Paare, eine Fehlerzahl zu wenig); die Gegenprobe mit
31 * gewoehnlichen Kennungen besteht dort ebenso.
32 */
33
34 const STICHTAG = new Date('2026-01-01T00:00:00Z');
35
36 /**
37 * Vier Signalgruppen des Regelknotenpunkts in EINER Phase, die Kennungen frei
38 * gewaehlt. Erhalten bleiben die drei Konfliktpaare K1/K3, K1/F2 und K3/F1 -
39 * jedes in beiden Richtungen erfasst.
40 */
41 function vierGruppenEinePhase(kennungen: Readonly<Record<string, string>>): Project {
42 const roh = createStandardIntersectionProject('Paarschluessel', STICHTAG);
43 const behalten = ['K1', 'K3', 'F1', 'F2'];
44 const gruppen = roh.signalGroups.filter((g) => behalten.includes(g.name));
45 const neueId = new Map(gruppen.map((g) => [g.id, kennungen[g.name] ?? g.id]));
46
47 const signalGroups = gruppen.map((g) => ({ ...g, id: neueId.get(g.id)! }));
48 const conflicts = roh.conflicts
49 .filter((c) => neueId.has(c.fromId) && neueId.has(c.toId))
50 .map((c) => ({
51 ...c,
52 fromId: neueId.get(c.fromId)!,
53 toId: neueId.get(c.toId)!,
54 }));
55 const phase = createPhase(
56 'Phase 1',
57 signalGroups.map((g) => g.id),
58 );
59
60 return {
61 ...roh,
62 signalGroups,
63 conflicts,
64 phases: [phase],
65 program: { ...roh.program, phaseOrder: [phase.id] },
66 demands: [],
67 };
68 }
69
70 /** Titel aller Meldungen, die ein feindliches Paar mit Freigabe beanstanden. */
71 function feindlicheMeldungen(projekt: Project): string[] {
72 const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG);
73 return bericht.findings
74 .filter(
75 (f) => f.rule === 'phasen.feindlich-in-phase' || f.rule === 'signalplan.feindliche-freigabe',
76 )
77 .map((f) => f.title);
78 }
79
80 /*
81 * "a|b" (Kfz), "c" (Furt), "a" (Kfz), "b|c" (Furt): Die Paare ("a|b", "c") und
82 * ("a", "b|c") fielen auf denselben zusammengeklebten Schluessel "a|b|c".
83 */
84 const MIT_STRICH = { K1: 'a|b', F2: 'c', K3: 'a', F1: 'b|c' } as const;
85
86 describe('Paarschluessel des Pruefberichts bei einer Kennung mit "|"', () => {
87 it('meldet jedes feindliche Paar einer gemeinsamen Phase, auch bei Kennungen mit "|"', () => {
88 const gewoehnlich = feindlicheMeldungen(vierGruppenEinePhase({}));
89 // Voraussetzung des Aufbaus: drei feindliche Paare in einer Phase.
90 expect(gewoehnlich).toHaveLength(3);
91
92 const mitStrich = feindlicheMeldungen(vierGruppenEinePhase(MIT_STRICH));
93 expect(
94 mitStrich,
95 'Das Paar ("a", "b|c") fiel auf denselben Schluessel wie ("a|b", "c") und fehlte ganz',
96 ).toHaveLength(3);
97 });
98
99 it('zaehlt die verschwiegene Ursache auch im Kopf des Berichts mit', () => {
100 const projekt = vierGruppenEinePhase(MIT_STRICH);
101 const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG);
102 const gewoehnlichesProjekt = vierGruppenEinePhase({});
103 const vergleich = validateProject(
104 gewoehnlichesProjekt,
105 buildSignalPlan(gewoehnlichesProjekt),
106 STICHTAG,
107 );
108
109 // Dieselbe Anlage, nur andere Kennungen: Die Fehlerzahl darf nicht davon
110 // abhaengen, welche Zeichen der Bearbeiter in einer Kennung verwendet.
111 expect(bericht.errorCount).toBe(vergleich.errorCount);
112 });
113 });