lsa-planer

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

/ tests export ueberfahrzeitRegelansatz.test.ts

9,6 KB Rohdatei
tests/export/ueberfahrzeitRegelansatz.test.ts — 203 Zeilen
1 import { inflateSync } from 'node:zlib';
2 import { describe, expect, it } from 'vitest';
3 import { ueberfahrzeitAnsatzSatz } from '@/services/export/bewertung';
4 import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf';
5 import { buildSignalPlan } from '@/domain/plan/signalPlan';
6 import { validateProject } from '@/domain/validation';
7 import { createStandardIntersectionProject } from '@/domain/model/factory';
8 import { resolveRilsaSettings } from '@/domain/rilsa/settings';
9 import { UEBERFAHRZEIT_ANSATZ_LABELS } from '@/domain/rilsa/constants';
10 import { toWinAnsi } from '@/render/pdfSurface';
11 import type { Project } from '@/domain/model/project';
12 import type { RilsaOverrides } from '@/domain/rilsa/settings';
13
14 /*
15 * Befunde 6 und 33 zur Fassung 5.9.0.
16 *
17 * Der Absatz unter der Zwischenzeitenmatrix setzte im Zweig 'gelbzeit' die
18 * beiden festen Ueberfahrzeiten aus den WIRKSAMEN Vorgaben ein und haengte
19 * daran eine fest eingeschriebene Aussage: "Behoerdliche Leitfaeden nennen
20 * daneben feste Werte (…); gegen sie faellt dieser Ansatz beim Abbiegen und
21 * bei 60 und 70 km/h laenger aus und ist damit die konservative Seite."
22 *
23 * Beide Vergleichsseiten sind einstellbar: crossingTimeKfzGeradeaus und
24 * crossingTimeKfzAbbiegend von 1 bis 10 s, die Gelbzeitstaffel ueber
25 * yellowKfzUpTo50/UpTo60/Above60. Mit 6 s/6 s liegt die groesste Gelbzeit (5 s)
26 * darunter - der Satz behauptete die konservative Seite gegen zwei Zahlen, die
27 * er selbst nannte, und schrieb eingetragene Vorgabewerte behoerdlichen
28 * Leitfaeden zu. Beim Ansatz 'gelbzeit' geht keiner der beiden Werte in eine
29 * Zwischenzeit ein; es ist kein Fehler der gefaehrlichen Richtung, sondern ein
30 * Wortlautfehler der Planunterlage.
31 *
32 * Fuer den Zweig 'fest' ist derselbe Fehlertyp laengst behoben
33 * (ueberfahrzeitAnsatzRichtungSatz in domain/rilsa/ansaetze.ts, Fassung 5.5.0, Befund B5);
34 * der Regelzweig war dabei uebersehen worden.
35 *
36 * NACHGEBESSERT: Der Vergleichssatz begann mit "Gegen sie" und haengte damit am
37 * Herkunftssatz davor - der im Abweichungsfall zwei Zahlenpaare nennt. Das
38 * Bezugswort steht jetzt ausgeschrieben.
39 *
40 * MUTATIONSNACHWEIS (gemessen an einer Pruefkopie mit den HEAD-Fassungen von
41 * bewertung.ts, csv.ts und pdfSurface.ts): 8 von 9 Faellen fallen; gruen bleibt
42 * allein der letzte, der den unberuehrten Zweig 'fest' festhaelt.
43 */
44
45 const STICHTAG = new Date('2026-01-01T12:00:00Z');
46
47 function satz(overrides: RilsaOverrides): string {
48 const { defaults } = resolveRilsaSettings(overrides);
49 return ueberfahrzeitAnsatzSatz('gelbzeit', defaults);
50 }
51
52 /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */
53 function pdfText(bytes: Uint8Array): string {
54 const roh = new TextDecoder('latin1').decode(bytes);
55 const teile: string[] = [];
56 const suche = /stream\r?\n/g;
57 let treffer: RegExpExecArray | null;
58
59 while ((treffer = suche.exec(roh)) !== null) {
60 const start = treffer.index + treffer[0].length;
61 const ende = roh.indexOf('endstream', start);
62 if (ende < 0) continue;
63 let inhalt: string;
64 try {
65 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
66 } catch {
67 continue;
68 }
69 for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) {
70 teile.push(t[1]!.replace(/\\([()\\])/g, '$1'));
71 }
72 }
73 return teile.join(' ').replace(/\s+/g, ' ');
74 }
75
76 const gedruckt = (text: string): string => toWinAnsi(text).replace(/\s+/g, ' ');
77
78 describe('Befunde 6 und 33 - der Regelansatz nennt Richtung und Herkunft aus den Vorgaben', () => {
79 it('haelt an den Regelwerten die belegte Aussage fest', () => {
80 const text = ueberfahrzeitAnsatzSatz('gelbzeit');
81
82 // Seit 5.27.0 nennt der Satz die RiLSA: Die beiden Zahlen stehen in
83 // Abschnitt 2.5.2 und nicht nur in behoerdlichen Leitfaeden.
84 expect(text).toContain('Die RiLSA 2015 setzt in Abschnitt 2.5.2 feste Werte an');
85 expect(text).toContain('3 s geradeaus und 2 s abbiegend');
86 expect(text).toContain(
87 'Gegen diese Regelwerte fällt dieser Ansatz beim Abbiegen um bis zu 3 s und geradeaus ' +
88 'um bis zu 2 s länger',
89 );
90 expect(text).toContain('ist damit die konservative Seite');
91 // Die Geschwindigkeitsschwellen standen fest im Satz, obwohl die
92 // Gelbzeitstaffel einstellbar ist.
93 expect(text).not.toContain('bei 60 und 70 km/h');
94 });
95
96 it('nennt das Zahlenpaar, gegen das verglichen wird, im Vergleichssatz selbst', () => {
97 /*
98 * Der Vergleichssatz begann mit "Gegen sie" und haengte damit am
99 * Herkunftssatz davor. Weicht der Plan ab, nennt der ZWEI Zahlenpaare - die
100 * eingetragenen und die belegten -, und das Bezugswort zeigte auf das
101 * zuletzt genannte: auf die 3 s/2 s der Leitfaeden. Gegen die ist der
102 * Regelansatz nirgends kuerzer; gerechnet wird gegen die Vorgabenwerte. Der
103 * Satz las sich damit wieder gegen seine eigenen zwei Zahlen.
104 */
105 const text = satz({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 });
106
107 expect(text).toContain('Gegen diese Vorgabewerte fällt dieser Ansatz');
108 expect(text).not.toContain('Gegen sie');
109 });
110
111 it('behauptet keine konservative Seite, wo der Ansatz kürzer ausfällt (Auslöser Befund 6)', () => {
112 const text = satz({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 });
113
114 expect(text).toContain('6 s geradeaus und 6 s abbiegend');
115 expect(text).toContain('beim Abbiegen um bis zu 3 s und geradeaus um bis zu 3 s kürzer');
116 expect(text).toContain('die konservative Seite ist er damit nicht');
117 expect(text).not.toContain('ist damit die konservative Seite');
118 expect(text).not.toContain('länger aus');
119 });
120
121 it('schreibt eingetragene Vorgabewerte nicht behördlichen Leitfäden zu', () => {
122 const text = satz({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 });
123
124 expect(text).not.toContain('Die RiLSA 2015 setzt in Abschnitt 2.5.2 feste Werte an');
125 expect(text).toContain('Die Vorgaben dieses Plans');
126 // Was das Regelwerk wirklich nennt, steht daneben - aus constants.ts.
127 expect(text).toContain('nicht die 3 s geradeaus und 2 s abbiegend der RiLSA 2015');
128 });
129
130 it('rechnet die Größenordnung aus den eingetragenen Werten (Auslöser Befund 33)', () => {
131 const text = satz({ crossingTimeKfzGeradeaus: 8, crossingTimeKfzAbbiegend: 7 });
132
133 expect(text).toContain('8 s geradeaus und 7 s abbiegend');
134 expect(text).toContain('beim Abbiegen um bis zu 4 s und geradeaus um bis zu 5 s kürzer');
135 expect(text).not.toContain('ist damit die konservative Seite');
136 });
137
138 it('nimmt die Schwellen der Gelbzeitstaffel aus den Vorgaben, nicht aus dem Satz', () => {
139 // Alle drei Gelbzeiten auf dem Mindestwert: geradeaus ist der Ansatz dann
140 // nirgends mehr länger, nur beim Abbiegen um 1 s.
141 const text = satz({ yellowKfzUpTo50: 3, yellowKfzUpTo60: 3, yellowKfzAbove60: 3 });
142
143 expect(text).toContain('3 s bis 50 km/h, 3 s bis 60 km/h, 3 s bis 70 km/h');
144 expect(text).toContain('beim Abbiegen um bis zu 1 s länger');
145 expect(text).not.toContain('geradeaus um bis zu');
146 expect(text).not.toContain('bei 60 und 70 km/h');
147 // Nirgends kürzer bleibt er - die konservative Seite gilt hier weiter.
148 expect(text).toContain('ist damit die konservative Seite');
149
150 /*
151 * BEKANNT UND AUSSERHALB DIESER LISTE: Die Beschriftung des Ansatzes traegt
152 * die Staffel weiterhin als feste Zahlen
153 * (UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit in src/domain/rilsa/constants.ts,
154 * Fehlertyp C21) - obwohl sie ueber yellowKfzUpTo50/UpTo60/Above60
155 * einstellbar ist. Mit 3/3/3 widerspricht der Absatz sich deshalb im selben
156 * Atemzug: "(3/4/5 s nach zulässiger Höchstgeschwindigkeit)" gegen
157 * "(3 s bis 50 km/h, 3 s bis 60 km/h, 3 s bis 70 km/h)". constants.ts stand
158 * fuer diese Aenderung nicht zur Verfuegung; der Widerspruch steht hier
159 * festgehalten, damit die Stelle nicht unbemerkt bleibt.
160 */
161 expect(UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit).toContain('3/4/5 s');
162 expect(text).toContain('3/4/5 s nach zulässiger Höchstgeschwindigkeit');
163 });
164
165 it('nennt beide Richtungen, wo der Ansatz teils kürzer und teils länger ausfällt', () => {
166 // Geradeaus 4 s gegen die Staffel 3/4/5: bei 50 km/h ist der Ansatz kürzer,
167 // bei 70 km/h länger.
168 const text = satz({ crossingTimeKfzGeradeaus: 4 });
169
170 expect(text).toContain('geradeaus um bis zu 1 s kürzer');
171 expect(text).toContain('beim Abbiegen um bis zu 3 s und geradeaus um bis zu 1 s länger');
172 expect(text).not.toContain('ist damit die konservative Seite');
173 });
174
175 it('druckt die berichtigte Aussage in der Planunterlage', () => {
176 const basis = createStandardIntersectionProject('Überfahrzeit', STICHTAG);
177 const projekt: Project = {
178 ...basis,
179 settings: {
180 ...basis.settings,
181 rilsa: { crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 },
182 ueberfahrzeitAnsatz: 'gelbzeit',
183 },
184 };
185 const plan = buildSignalPlan(projekt);
186 const bericht = validateProject(projekt, plan, STICHTAG);
187 const text = pdfText(buildProjectPdf(projekt, plan, bericht, DEFAULT_PDF_OPTIONS, STICHTAG));
188
189 expect(text).toContain(gedruckt('Überfahrzeit des Kraftfahrzeugverkehrs: Gerechnet wurde'));
190 expect(text).toContain(gedruckt('die konservative Seite ist er damit nicht'));
191 expect(text).not.toContain(
192 gedruckt('Die RiLSA 2015 setzt in Abschnitt 2.5.2 feste Werte an (6 s'),
193 );
194 });
195
196 it('lässt den Zweig „feste Überfahrzeit" unberührt', () => {
197 const { defaults } = resolveRilsaSettings({});
198 const text = ueberfahrzeitAnsatzSatz('fest', defaults);
199
200 expect(text).toContain('beim Abbiegen um bis zu 3 s');
201 expect(text).toContain('Die Werte sind die Regelwerte der RiLSA 2015');
202 });
203 });