lsa-planer

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

/ tests domain massgebenderStrom.test.ts

21,5 KB Rohdatei
tests/domain/massgebenderStrom.test.ts — 505 Zeilen
1 import { readFileSync } from 'node:fs';
2 import { describe, expect, it } from 'vitest';
3 import { setzeAlleWege } from '@/app/actions';
4 import { leiteAb, uebernimmWege, wegSchluessel } from '@/domain/geometrie/ableitung';
5 import { loadProject } from '@/domain/model/migrate';
6 import { parseProject } from '@/domain/model/schema';
7 import { aendereKonflikt, type Conflict, type Project } from '@/domain/model/project';
8 import {
9 buildSignalPlan,
10 intergreenKey,
11 massgebendeRaeumbeziehung,
12 } from '@/domain/plan/signalPlan';
13 import { RILSA_DEFAULTS } from '@/domain/rilsa/constants';
14 import { clearingSpeedFor } from '@/domain/rilsa/intergreen';
15
16 /*
17 * Der massgebende Strom (Fassung 5.11.0).
18 *
19 * `uebernimmWege` verdichtet die vermessenen Fahrlinienpaare auf
20 * Signalgruppenpaare und waehlt je Paar das unguenstigste Strompaar. Gerechnet
21 * wird dabei mit der Fahrbeziehung der FAHRLINIE (ableitung.ts, `movement:
22 * von.movement`), gespeichert wurde bis zu diesem Befund aber nur die
23 * Geometrie. Der Planaufbau rechnete die Beziehung danach mit der
24 * Fahrbeziehung der SIGNALGRUPPE neu (signalPlan.ts, `movement:
25 * from.movement`).
26 *
27 * Bei einer Sammelsignalgruppe "geradeaus UND rechts" - dem Regelfall des
28 * gemeinsamen Fahrstreifens, den das Erzeugungswerkzeug selbst baut - fielen
29 * beide Rechnungen auseinander: Der Raeumweg des Rechtsabbiegers wurde mit
30 * 10,0 m/s statt mit 7,0 m/s geraeumt. In der Planunterlage stand die
31 * Zwischenzeit neben ihrem eigenen Widerspruch ("... (9 s)" bei gedruckten
32 * 7 s), und zwei Sekunden fehlten in der gefaehrlichen Richtung.
33 *
34 * Diese Datei bewacht beide Seiten: dass die Uebernahme die Fahrbeziehung des
35 * massgebenden Stroms mitschreibt und dass der Plan mit ihr rechnet - und
36 * ebenso, dass sie es NICHT tut, wo der massgebende Strom die Fahrbeziehung
37 * seiner Gruppe hat (sonst raeumte ein Geradeausweg mit Abbiegegeschwindigkeit
38 * und die Zwischenzeit fiele unbegruendet zu lang aus).
39 */
40
41 const DATEI = 'beispiele/vierarmiger-knotenpunkt.lsap';
42 const DATUM = new Date('2026-01-01T00:00:00Z');
43
44 /** Die mitgelieferte Altdatei, eingelesen wie beim Oeffnen. */
45 function beispiel(): Project {
46 const ergebnis = parseProject(JSON.parse(readFileSync(DATEI, 'utf8')), DATUM);
47 expect(ergebnis.issues.filter((i) => i.severity === 'fehler')).toEqual([]);
48 return ergebnis.project;
49 }
50
51 /** Dieselbe Datei nach "Ausgewählte übernehmen" ueber alle Beziehungen. */
52 function nachUebernahme(): Project {
53 const basis = beispiel();
54 const ableitung = leiteAb(basis, basis.lageplan);
55 return uebernimmWege(basis, basis.lageplan, ableitung, ableitung.wege.map(wegSchluessel), DATUM)
56 .project;
57 }
58
59 function konflikt(project: Project, fromId: string, toId: string): Conflict {
60 const gefunden = project.conflicts.find((c) => c.fromId === fromId && c.toId === toId);
61 expect(gefunden, `Beziehung ${fromId} -> ${toId}`).toBeDefined();
62 return gefunden!;
63 }
64
65 /** Die Sekundenzahl, die die Uebernahme als massgebend in die Bemerkung schrieb. */
66 function vermerkteZwischenzeit(note: string): number | null {
67 const treffer = /\((\d+) s\)\.$/.exec(note);
68 return treffer === null ? null : Number(treffer[1]);
69 }
70
71 describe('A1/1: Die Uebernahme fuehrt die Fahrbeziehung des massgebenden Stroms mit', () => {
72 it('schreibt "rechts" an die vier Beziehungen, an denen der Rechtsabbieger den Ausschlag gibt', () => {
73 const project = nachUebernahme();
74
75 // Die vier Kfz-Sammelgruppen tragen selbst 'geradeaus' - ihr Fahrstreifen
76 // fuehrt geradeaus UND rechts. Massgebend ist an diesen vier Beziehungen
77 // der Rechtsabbieger (Bemerkung: '"Nord rechts" raeumt vor ...').
78 for (const [von, nach] of [
79 ['sg-k-nord', 'sg-k-ost'],
80 ['sg-k-ost', 'sg-k-sued'],
81 ['sg-k-sued', 'sg-k-west'],
82 ['sg-k-west', 'sg-k-nord'],
83 ] as const) {
84 const gruppe = project.signalGroups.find((g) => g.id === von);
85 expect(gruppe?.movement, `Fahrbeziehung der Gruppe ${von}`).toBe('geradeaus');
86 expect(konflikt(project, von, nach).raeumbeziehung, `${von} -> ${nach}`).toBe('rechts');
87 }
88 });
89
90 it('laesst das Feld weg, wo der massgebende Strom die Fahrbeziehung seiner Gruppe hat', () => {
91 const project = nachUebernahme();
92
93 // "K Nord" -> "K West": massgebend ist "Nord geradeaus" (s0 = 52 m). Das
94 // Feld darf hier NICHT stehen - sonst wuerde eine spaetere Berichtigung
95 // der Fahrbeziehung an der Signalgruppe wirkungslos, und ein pauschales
96 // 'rechts' raeumte einen Geradeausweg mit 7,0 m/s (12 s statt 9 s).
97 const west = konflikt(project, 'sg-k-nord', 'sg-k-west');
98 expect(west.note).toContain('"Nord geradeaus"');
99 expect(west.raeumbeziehung).toBeUndefined();
100 expect('raeumbeziehung' in west).toBe(false);
101
102 /*
103 * Zwoelf der 72 Beziehungen tragen das Feld - an allen zwoelf gibt der
104 * Rechtsabbieger der Sammelgruppe den Ausschlag. Acht davon aendern die
105 * gedruckte Zwischenzeit: vier Kfz-Beziehungen (7 s -> 9 s) und seit
106 * Fassung 5.43.0 vier Furtbeziehungen (5 s -> 6 s; bis dahin blieben sie
107 * bei 0 s, weil der Einfahrweg des Fussgaengers ab der entfernten
108 * Bordkante gemessen wurde). Die vier eigenen Furten stehen mit 5 s
109 * vorher wie nachher da.
110 *
111 * NACHGEZOGEN AM 10.09.2026 (Fassung 5.24.0): Hier standen acht. Die
112 * vier eigenen Furten - "K Nord" vor "F Nord" und die drei gleichartigen -
113 * sind hinzugekommen, weil dort beide Stroeme auf dieselben 5 s
114 * aufrunden: geradeaus 4,013 s (seit 5.43.0 mit te = 0 s: 4,100 s),
115 * rechts 4,591 s. Bis dahin gewann der zuerst gezeichnete, also die
116 * Reihenfolge der Linien im Lageplan; jetzt
117 * entscheidet bei gleicher gerundeter Zwischenzeit der ungerundete Wert,
118 * und das ist der Rechtsabbieger. Die gedruckte Zwischenzeit bleibt 5 s;
119 * gespeichert wird die anspruchsvollere Geometrie, und die bleibt es
120 * auch, wenn sich Gelbzeit oder zulaessige Geschwindigkeit spaeter
121 * aendern.
122 */
123 expect(
124 project.conflicts.filter((c) => 'raeumbeziehung' in c).map((c) => `${c.fromId}->${c.toId}`),
125 ).toEqual([
126 'sg-k-nord->sg-f-nord',
127 'sg-k-nord->sg-k-ost',
128 'sg-k-nord->sg-f-west',
129 'sg-k-ost->sg-f-ost',
130 'sg-k-ost->sg-k-sued',
131 'sg-k-ost->sg-f-nord',
132 'sg-k-sued->sg-f-sued',
133 'sg-k-sued->sg-k-west',
134 'sg-k-sued->sg-f-ost',
135 'sg-k-west->sg-k-nord',
136 'sg-k-west->sg-f-west',
137 'sg-k-west->sg-f-sued',
138 ]);
139 // Die eigene Furt: Das Feld steht jetzt da, die Zahl daneben nicht anders.
140 expect(
141 buildSignalPlan(project).intergreens.get(intergreenKey('sg-k-nord', 'sg-f-nord'))?.value,
142 'die eigene Furt bleibt bei 5 s',
143 ).toBe(5);
144 expect(project.conflicts.every((c) => c.raeumbeziehung !== 'geradeaus')).toBe(true);
145 expect(project.conflicts).toHaveLength(72);
146 });
147
148 it('entfernt eine ueberholte Angabe bei der naechsten Uebernahme', () => {
149 // Eine Beziehung, deren Siegerpaar diesmal die Fahrbeziehung seiner Gruppe
150 // hat, darf die Angabe der vorigen Uebernahme nicht behalten: Der Plan
151 // raeumte den neuen Weg sonst mit der Geschwindigkeit des alten Siegers.
152 const basis = beispiel();
153 const veraltet: Project = {
154 ...basis,
155 conflicts: basis.conflicts.map((c) =>
156 c.fromId === 'sg-k-nord' && c.toId === 'sg-k-west' ? { ...c, raeumbeziehung: 'links' } : c,
157 ),
158 };
159 expect(konflikt(veraltet, 'sg-k-nord', 'sg-k-west').raeumbeziehung).toBe('links');
160
161 const ableitung = leiteAb(veraltet, veraltet.lageplan);
162 const { project } = uebernimmWege(
163 veraltet,
164 veraltet.lageplan,
165 ableitung,
166 ableitung.wege.map(wegSchluessel),
167 DATUM,
168 );
169
170 const west = konflikt(project, 'sg-k-nord', 'sg-k-west');
171 expect('raeumbeziehung' in west).toBe(false);
172 const aufgeloest = buildSignalPlan(project).intergreens.get(
173 intergreenKey('sg-k-nord', 'sg-k-west'),
174 );
175 expect(aufgeloest?.calculation?.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight);
176 expect(aufgeloest?.value).toBe(9);
177 });
178 });
179
180 describe('A1/2: Der Plan raeumt mit der Geschwindigkeit des massgebenden Stroms', () => {
181 it('rechnet "K Nord" -> "K Ost" mit 7,0 m/s und 9 s statt mit 10,0 m/s und 7 s', () => {
182 const project = nachUebernahme();
183 const plan = buildSignalPlan(project);
184 const beziehung = konflikt(project, 'sg-k-nord', 'sg-k-ost');
185 const aufgeloest = plan.intergreens.get(intergreenKey('sg-k-nord', 'sg-k-ost'));
186
187 expect(beziehung.clearingDistance).toBeCloseTo(38.91, 2);
188 expect(beziehung.enteringDistance).toBeCloseTo(13.89, 2);
189 expect(aufgeloest?.calculation?.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzTurning);
190 // tue 3 s + tr (38,91 + 6) / 7,0 = 6,416 s - te 13,89 / 11,1 = 1,251 s
191 expect(aufgeloest?.calculation?.raw).toBeCloseTo(8.164, 3);
192 expect(aufgeloest?.value).toBe(9);
193 });
194
195 it('laesst "K Nord" -> "K West" bei 9 s - der Geradeausweg raeumt weiter mit 10,0 m/s', () => {
196 const project = nachUebernahme();
197 const plan = buildSignalPlan(project);
198 const aufgeloest = plan.intergreens.get(intergreenKey('sg-k-nord', 'sg-k-west'));
199
200 expect(konflikt(project, 'sg-k-nord', 'sg-k-west').clearingDistance).toBeCloseTo(52, 2);
201 expect(aufgeloest?.calculation?.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight);
202 expect(aufgeloest?.value).toBe(9);
203 });
204
205 it('bringt Bemerkung und gedruckte Zwischenzeit an allen 72 Beziehungen zur Deckung', () => {
206 const project = nachUebernahme();
207 const plan = buildSignalPlan(project);
208
209 const abweichend = project.conflicts
210 .map((c) => ({
211 beziehung: `${c.fromId} -> ${c.toId}`,
212 vermerk: vermerkteZwischenzeit(c.note),
213 plan: plan.intergreens.get(intergreenKey(c.fromId, c.toId))?.value,
214 }))
215 .filter((z) => z.vermerk !== z.plan);
216
217 expect(abweichend).toEqual([]);
218 });
219 });
220
221 describe('A1/3: Die Fahrbeziehung uebersteht Speichern und Laden', () => {
222 it('traegt "rechts" durch die Datei und rechnet danach dieselben 9 s', () => {
223 const project = nachUebernahme();
224 const geladen = loadProject(JSON.parse(JSON.stringify(project)), DATUM);
225
226 expect(geladen.issues.filter((i) => i.severity === 'fehler')).toEqual([]);
227 expect(konflikt(geladen.project, 'sg-k-nord', 'sg-k-ost').raeumbeziehung).toBe('rechts');
228 // Zwoelf seit 5.24.0 (siehe A1/1); durch die Datei gehen alle zwoelf.
229 expect(geladen.project.conflicts.filter((c) => 'raeumbeziehung' in c)).toHaveLength(12);
230 expect(
231 buildSignalPlan(geladen.project).intergreens.get(intergreenKey('sg-k-nord', 'sg-k-ost'))
232 ?.value,
233 ).toBe(9);
234 });
235
236 it('meldet eine unlesbare Angabe und faellt auf die Fahrbeziehung der Gruppe zurueck', () => {
237 const project = nachUebernahme();
238 const roh = JSON.parse(JSON.stringify(project)) as {
239 conflicts: { fromId: string; toId: string; raeumbeziehung?: unknown }[];
240 };
241 const eintrag = roh.conflicts.find((c) => c.fromId === 'sg-k-nord' && c.toId === 'sg-k-ost');
242 eintrag!.raeumbeziehung = 'seitwaerts';
243
244 const gelesen = parseProject(roh, DATUM);
245 expect(
246 gelesen.issues.some((i) => i.path.endsWith('.raeumbeziehung') && i.severity !== 'hinweis'),
247 ).toBe(true);
248 expect(konflikt(gelesen.project, 'sg-k-nord', 'sg-k-ost').raeumbeziehung).toBeUndefined();
249 });
250
251 it('rechnet eine Beziehung ohne das Feld unveraendert mit der Fahrbeziehung der Gruppe', () => {
252 // Rueckfall fuer jede Datei aelterer Fassung: fehlt die Angabe, gilt die
253 // Fahrbeziehung der Signalgruppe - das Verhalten bis zu diesem Befund.
254 const project = beispiel();
255 expect(project.conflicts.every((c) => !('raeumbeziehung' in c))).toBe(true);
256
257 const plan = buildSignalPlan(project);
258 const aufgeloest = plan.intergreens.get(intergreenKey('sg-k-nord', 'sg-k-ost'));
259 expect(aufgeloest?.calculation?.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight);
260 expect(aufgeloest?.value).toBe(5);
261 });
262 });
263
264 describe('A1/4: Die Entscheidung faellt genau einmal', () => {
265 it('raeumt an allen 72 Beziehungen mit der Fahrbeziehung, die massgebendeRaeumbeziehung nennt', () => {
266 // Die Wache gegen einen Rueckfall auf `from.movement` im Planaufbau: Wer
267 // die Entscheidung dort ein zweites Mal nachbaut, faellt hier auf.
268 const project = nachUebernahme();
269 const plan = buildSignalPlan(project);
270
271 const abweichend = project.conflicts
272 .map((c) => {
273 const from = project.signalGroups.find((g) => g.id === c.fromId)!;
274 return {
275 beziehung: `${c.fromId} -> ${c.toId}`,
276 plan: plan.intergreens.get(intergreenKey(c.fromId, c.toId))?.calculation?.clearingSpeed,
277 erwartet: clearingSpeedFor(
278 {
279 mode: from.mode,
280 movement: massgebendeRaeumbeziehung(from, c),
281 vZul: from.vZul,
282 ...(from.reducedMobility ? { reducedMobility: true } : {}),
283 ...(c.engerRadius === true ? { engerRadius: true } : {}),
284 },
285 plan.defaults,
286 ),
287 };
288 })
289 .filter((z) => z.plan !== z.erwartet);
290
291 expect(abweichend).toEqual([]);
292 // Und an den zwoelf Beziehungen mit Feld ist es eine ANDERE Fahrbeziehung
293 // als die der Gruppe - sonst pruefte der Vergleich oben nichts.
294 const mitFeld = project.conflicts.filter((c) => 'raeumbeziehung' in c);
295 expect(mitFeld).toHaveLength(12);
296 for (const c of mitFeld) {
297 const from = project.signalGroups.find((g) => g.id === c.fromId)!;
298 expect(massgebendeRaeumbeziehung(from, c)).not.toBe(from.movement);
299 }
300 });
301
302 it('faellt ohne Feld auf die Fahrbeziehung der Signalgruppe zurueck', () => {
303 const project = beispiel();
304 const gruppe = project.signalGroups.find((g) => g.id === 'sg-k-nord')!;
305 const ohne = konflikt(project, 'sg-k-nord', 'sg-k-ost');
306
307 expect('raeumbeziehung' in ohne).toBe(false);
308 expect(massgebendeRaeumbeziehung(gruppe, ohne)).toBe(gruppe.movement);
309 expect(massgebendeRaeumbeziehung(gruppe, { ...ohne, raeumbeziehung: 'rechts' })).toBe('rechts');
310 });
311 });
312
313 describe('A1/5: Bleibt das Feld nach einer Handeingabe stehen, ist das nicht immer die laengere Seite', () => {
314 /*
315 * Der Spiegelfall zur Beispielplanung: Traegt die Sammelgruppe 'rechts' und
316 * gibt der gebuendelte GERADEAUSstrom den Ausschlag, speichert die
317 * Uebernahme `raeumbeziehung: 'geradeaus'`. Bleibt das Feld danach stehen,
318 * raeumt der Plan mit 10,0 m/s, waehrend die Gruppe allein 7,0 m/s ergaebe -
319 * die KUERZERE Seite.
320 *
321 * Diese Faelle belegen den Wortlaut an `Conflict.raeumbeziehung`; der
322 * Kommentar sagte vor der Fassung 5.11.0 unbedingt "die laengere und damit
323 * sichere Seite" zu.
324 */
325 function spiegelfall(): Project {
326 const basis = beispiel();
327 const gedreht: Project = {
328 ...basis,
329 signalGroups: basis.signalGroups.map((g) =>
330 g.id === 'sg-k-nord' ? { ...g, movement: 'rechts' as const } : g,
331 ),
332 };
333 const ableitung = leiteAb(gedreht, gedreht.lageplan);
334 return uebernimmWege(
335 gedreht,
336 gedreht.lageplan,
337 ableitung,
338 ableitung.wege.map(wegSchluessel),
339 DATUM,
340 ).project;
341 }
342
343 it('speichert "geradeaus", wo der Geradeausstrom einer Rechtsabbiegergruppe gewinnt', () => {
344 const project = spiegelfall();
345 const west = konflikt(project, 'sg-k-nord', 'sg-k-west');
346
347 expect(project.signalGroups.find((g) => g.id === 'sg-k-nord')?.movement).toBe('rechts');
348 expect(west.note).toContain('"Nord geradeaus"');
349 expect(west.raeumbeziehung).toBe('geradeaus');
350 const aufgeloest = buildSignalPlan(project).intergreens.get(
351 intergreenKey('sg-k-nord', 'sg-k-west'),
352 );
353 expect(aufgeloest?.calculation?.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight);
354 expect(aufgeloest?.value).toBe(9);
355
356 // Ohne das Feld raeumte derselbe Weg mit der Fahrbeziehung der Gruppe,
357 // also mit 7,0 m/s: 12 s. Die gespeicherte Fahrbeziehung ist hier die
358 // KUERZERE - und zugleich die fachlich richtige, denn den Weg faehrt der
359 // Geradeausstrom.
360 const ohneFeld: Project = {
361 ...project,
362 conflicts: project.conflicts.map((c) => {
363 const { raeumbeziehung: _weg, ...ohne } = c;
364 return ohne;
365 }),
366 };
367 expect(
368 buildSignalPlan(ohneFeld).intergreens.get(intergreenKey('sg-k-nord', 'sg-k-west'))?.value,
369 ).toBe(12);
370 });
371
372 it('rechnet nach einer Handeingabe am Raeumweg kuerzer als ohne das Feld', () => {
373 const project = spiegelfall();
374 const vonHand = (p: Project, mitFeld: boolean): Project => ({
375 ...p,
376 conflicts: p.conflicts.map((c) => {
377 if (!(c.fromId === 'sg-k-nord' && c.toId === 'sg-k-west')) return c;
378 const { raeumbeziehung: _weg, ...ohne } = c;
379 return aendereKonflikt(mitFeld ? c : ohne, { clearingDistance: 60 });
380 }),
381 });
382
383 const mit = buildSignalPlan(vonHand(project, true)).intergreens.get(
384 intergreenKey('sg-k-nord', 'sg-k-west'),
385 );
386 const ohne = buildSignalPlan(vonHand(project, false)).intergreens.get(
387 intergreenKey('sg-k-nord', 'sg-k-west'),
388 );
389
390 expect(mit?.calculation?.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight);
391 expect(ohne?.calculation?.clearingSpeed).toBe(RILSA_DEFAULTS.clearingSpeed.kfzTurning);
392 expect(mit!.value).toBeLessThan(ohne!.value);
393 expect([mit?.value, ohne?.value]).toEqual([10, 13]);
394 });
395
396 it('laesst das Feld auch bei den Sammelwegen stehen (setzeAlleWege)', () => {
397 // `setzeAlleWege` ueberschreibt die Wege ALLER Beziehungen ueber
398 // `aendereKonflikt`; die Fahrbeziehung bleibt dabei ebenso stehen.
399 const project = setzeAlleWege(spiegelfall(), 60, 12, false);
400 const west = konflikt(project, 'sg-k-nord', 'sg-k-west');
401
402 expect(west.clearingDistance).toBe(60);
403 expect(west.raeumbeziehung).toBe('geradeaus');
404 expect(west.wegnachweis.herkunft).toBe('handeingabe');
405 expect(
406 buildSignalPlan(project).intergreens.get(intergreenKey('sg-k-nord', 'sg-k-west'))?.calculation
407 ?.clearingSpeed,
408 ).toBe(RILSA_DEFAULTS.clearingSpeed.kfzStraight);
409 });
410 });
411
412 describe('A1/6: Verwirft ein aelterer Programmstand das Feld, bleibt der Vermerk', () => {
413 /*
414 * Beleg fuer den Absatz "KEIN SCHEMAAUFSTIEG" an `Conflict.raeumbeziehung`:
415 * Der Verlust ist weder unwiederbringlich noch spurlos. Nachgestellt wird
416 * ein aelterer Programmstand, indem der Schluessel aus der Datei entfernt
417 * wird - genau das tut er beim Einlesen und beim naechsten Speichern.
418 */
419 function ohneFeld(project: Project): Project {
420 return {
421 ...project,
422 conflicts: project.conflicts.map((c) => {
423 const { raeumbeziehung: _weg, ...ohne } = c;
424 return ohne;
425 }),
426 };
427 }
428
429 it('nennt die Bemerkung weiter 9 s, wo die verkuerzte Zwischenzeit 7 s druckt', () => {
430 const alt = ohneFeld(nachUebernahme());
431 const plan = buildSignalPlan(alt);
432 const ost = konflikt(alt, 'sg-k-nord', 'sg-k-ost');
433
434 expect(vermerkteZwischenzeit(ost.note)).toBe(9);
435 expect(plan.intergreens.get(intergreenKey('sg-k-nord', 'sg-k-ost'))?.value).toBe(7);
436 // Der Widerspruch steht an genau den Beziehungen, an denen der
437 // Rechtsabbieger den Ausschlag gibt und die Zahl aendert - sichtbar in
438 // derselben gedruckten Zeile, in der die Bemerkung neben der Zwischenzeit
439 // steht.
440 //
441 // NACHGEZOGEN MIT FASSUNG 5.43.0 (Gruppe Furt): Hier standen vier. Die
442 // vier Beziehungen "Kfz raeumt vor der Furt, die der Rechtsabbieger beim
443 // Ausfahren quert" (K Nord -> F West und die drei gleichartigen) blieben
444 // bis dahin mit und ohne Feld bei 0 s - der Einfahrweg des Fussgaengers
445 // wurde ab der entfernten Bordkante gemessen (11 m, te 7,3 s). Seit er
446 // vom Fahrbahnrand aus mit 0 m gilt, raeumt der Rechtsabbieger dort mit
447 // 6 s (3 + (9,95 + 6)/7,0 = 5,28 s), geradeaus gerechnet mit 5 s
448 // (3 + 15,95/10,0 = 4,60 s) - der Widerspruch steht jetzt auch dort.
449 expect(
450 alt.conflicts
451 .filter(
452 (c) =>
453 vermerkteZwischenzeit(c.note) !==
454 plan.intergreens.get(intergreenKey(c.fromId, c.toId))?.value,
455 )
456 .map((c) => `${c.fromId}->${c.toId}`),
457 ).toEqual([
458 'sg-k-nord->sg-k-ost',
459 'sg-k-nord->sg-f-west',
460 'sg-k-ost->sg-k-sued',
461 'sg-k-ost->sg-f-nord',
462 'sg-k-sued->sg-k-west',
463 'sg-k-sued->sg-f-ost',
464 'sg-k-west->sg-k-nord',
465 'sg-k-west->sg-f-sued',
466 ]);
467 });
468
469 it('setzt die naechste Uebernahme aus demselben Lageplan wieder her', () => {
470 const alt = ohneFeld(nachUebernahme());
471 expect(alt.conflicts.some((c) => 'raeumbeziehung' in c)).toBe(false);
472
473 const ableitung = leiteAb(alt, alt.lageplan);
474 const { project } = uebernimmWege(
475 alt,
476 alt.lageplan,
477 ableitung,
478 ableitung.wege.map(wegSchluessel),
479 DATUM,
480 );
481
482 expect(project.conflicts.filter((c) => 'raeumbeziehung' in c)).toHaveLength(12);
483 expect(konflikt(project, 'sg-k-nord', 'sg-k-ost').raeumbeziehung).toBe('rechts');
484 expect(
485 buildSignalPlan(project).intergreens.get(intergreenKey('sg-k-nord', 'sg-k-ost'))?.value,
486 ).toBe(9);
487 });
488 });
489
490 describe('A1/S1: Die Beispieldatei bleibt das Pruefstueck fuer den Altdatei-Pfad', () => {
491 it('traegt weiterhin schemaVersion 7 und laeuft durch die ganze Migrationskette', () => {
492 const roh = JSON.parse(readFileSync(DATEI, 'utf8')) as { schemaVersion?: number };
493 expect(roh.schemaVersion).toBe(7);
494
495 const geladen = loadProject(roh, DATUM);
496 expect(geladen.migrated).toBe(false);
497 expect(geladen.sourceVersion).toBe('7');
498 expect(geladen.issues.filter((i) => i.severity === 'fehler')).toEqual([]);
499 // Der Aufstieg 7 -> aktuell meldet den fehlenden Wegnachweis als Hinweis.
500 expect(geladen.issues.some((i) => i.path === 'conflicts' && i.severity === 'hinweis')).toBe(
501 true,
502 );
503 expect(geladen.project.conflicts).toHaveLength(72);
504 });
505 });