lsa-planer

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

/ tests export pdfBewertung.test.ts

21,4 KB Rohdatei
tests/export/pdfBewertung.test.ts — 454 Zeilen
1 import { inflateSync } from 'node:zlib';
2 import { describe, expect, it } from 'vitest';
3 import { buildProjectPdf, DEFAULT_PDF_OPTIONS, type PdfOptions } from '@/services/export/pdf';
4 import { buildSignalPlan } from '@/domain/plan/signalPlan';
5 import { validateProject } from '@/domain/validation';
6 import { createSignalGroup, createStandardIntersectionProject } from '@/domain/model/factory';
7 import { SERVICE_LEVEL_SCALES } from '@/domain/rilsa/constants';
8 import { PRUEFSTAND_LABELS, QUELLEN } from '@/domain/rilsa/quellen';
9 import { quellenOhneFeld, stufentafelSatz } from '@/services/export/bewertung';
10 import type { Project } from '@/domain/model/project';
11 import * as fmt from '@/ui/format';
12
13 /*
14 * Befunde B1 bis B4: Der Leistungsabschnitt des Ausdrucks behauptete "Bewertung
15 * nach HBS 2015", waehrend fuer beide Einstellungen dieselbe HCM-Formel
16 * gerechnet und nach der Kfz-Tafel des HBS 2001 bewertet wurde; Fussgaenger
17 * fehlten mit der Begruendung, ihre Leistungsfaehigkeit sei "nicht
18 * massgebend" - das HBS 2015 bewertet sie nach der maximalen Wartezeit. Diese
19 * Tests sichern, dass der Ausdruck nur noch sagt, was gerechnet wurde.
20 */
21
22 const STICHTAG = new Date('2026-01-01T12:00:00Z');
23
24 /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */
25 function pdfText(bytes: Uint8Array): string {
26 const roh = new TextDecoder('latin1').decode(bytes);
27 const teile: string[] = [];
28 const suche = /stream\r?\n/g;
29 let treffer: RegExpExecArray | null;
30
31 while ((treffer = suche.exec(roh)) !== null) {
32 const start = treffer.index + treffer[0].length;
33 const ende = roh.indexOf('endstream', start);
34 if (ende < 0) continue;
35 let inhalt: string;
36 try {
37 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
38 } catch {
39 continue;
40 }
41 for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) {
42 teile.push(t[1]!.replace(/\\([()\\])/g, '$1'));
43 }
44 }
45 return teile.join('\n');
46 }
47
48 /**
49 * Nur der Leistungsabschnitt, ohne das Fundstellenverzeichnis: Dort steht
50 * "HBS 2015" zu Recht als Herkunft der Saettigungsverkehrsstaerke - auch wenn
51 * nach HCM bewertet wird. Die Behauptung, um die es hier geht, ist die
52 * "Bewertung nach ...".
53 */
54 const NUR_LEISTUNG: PdfOptions = {
55 ...DEFAULT_PDF_OPTIONS,
56 includeSignalGroups: false,
57 includeIntergreenMatrix: false,
58 includePhases: false,
59 includePlan: false,
60 includeReport: false,
61 includeSources: false,
62 };
63
64 /** Fliesstext ohne Zeilenumbrueche - der Ausdruck bricht Saetze fuer den Satzspiegel um. */
65 function ausdruck(project: Project, options: PdfOptions = NUR_LEISTUNG): string {
66 const plan = buildSignalPlan(project);
67 return pdfText(
68 buildProjectPdf(project, plan, validateProject(project, plan, STICHTAG), options),
69 ).replace(/\s+/g, ' ');
70 }
71
72 function vollstaendig(verfahren: 'HBS' | 'HCM' = 'HBS'): Project {
73 const base = createStandardIntersectionProject('Bewertung', STICHTAG);
74 return {
75 ...base,
76 meta: { ...base.meta, planner: 'O. Willerding' },
77 settings: { ...base.settings, serviceLevelScale: verfahren },
78 };
79 }
80
81 describe('Ausdruck nennt das Bewertungsverfahren, das gerechnet wurde', () => {
82 it('sagt "Bewertung nach HBS 2015" nur beim HBS-Verfahren, sonst "Bewertung nach HCM"', () => {
83 const hbs = ausdruck(vollstaendig('HBS'));
84 expect(hbs).toContain('Bewertung nach HBS 2015');
85 expect(hbs).not.toContain('Bewertung nach HCM');
86 // Die Formelkette, die tatsaechlich gerechnet wird - nicht die HCM-Formel
87 // unter fremder Ueberschrift (Befund B2).
88 expect(hbs).toContain('tA = tF + 1 s');
89 expect(hbs).toContain('tW = tW,G + tW,R');
90 expect(hbs).toContain('Instationaritätsfaktor fin = 1,1');
91 expect(hbs).not.toContain('d1 + d2');
92
93 const hcm = ausdruck(vollstaendig('HCM'));
94 expect(hcm).toContain('Bewertung nach HCM');
95 expect(hcm).not.toContain('Bewertung nach HBS 2015');
96 /*
97 * Ohne Fundstellenverzeichnis darf "HBS 2015" im HCM-Ausdruck nur dort
98 * vorkommen, wo es die HERKUNFT einer Eingangsgroesse ist - nie als
99 * Bewertungsverfahren. Zwei solche Stellen gibt es:
100 *
101 * - die ehrliche Anmerkung zur Fuss/Rad-Bewertung (Befund B4),
102 * - der Rechenweg der Saettigungsverkehrsstaerke (Fassung 5.5.0, Frage E6): Der
103 * Anpassungsfaktor fSV stammt auch in einem HCM-Projekt aus dem
104 * HBS 2015, weil die Saettigungsverkehrsstaerke es tut. Bis zum
105 * 15.08.2026 stand an dieser Stelle "Pkw-Aequivalent 2,0" - der Wert
106 * des HCM 2010, gedruckt unter der Ueberschrift HBS. Die Nennung des
107 * Regelwerks ist hier also gerade die Behebung des Befunds und nicht
108 * sein Rueckfall.
109 */
110 const ohneHerkunftsangaben = hcm
111 .replace(/nach der das HBS 2015 bewerten würde/g, '')
112 .replace(/Der Schwerverkehr geht nach HBS 2015 über den Anpassungsfaktor fSV/g, '');
113 expect(ohneHerkunftsangaben).not.toContain('HBS 2015');
114 expect(hcm).toContain('d = d1 + d2');
115 expect(hcm).toContain('T = 0,25 h');
116 expect(hcm).not.toContain('tW,R');
117 });
118
119 it('druckt die Kfz-Stufentafel des HBS 2015 aus den Konstanten - E ohne Obergrenze, F bei q > C', () => {
120 // Befund B1: Bis dahin stand die Tafel des HBS 2001 (E bis 100 s, F
121 // darueber) unter der Ueberschrift "HBS 2015". Die gedruckte Tafel kommt
122 // jetzt aus denselben Konstanten wie die Bewertung.
123 const text = ausdruck(vollstaendig('HBS'));
124 expect(text).toContain('A bis 20 s, B bis 35 s, C bis 50 s, D bis 70 s, E darüber');
125 expect(text).toContain('Stufe F bei Überlastung (q > C)');
126 expect(text).not.toContain('E bis 100 s');
127 // Gegenprobe gegen die Konstante selbst.
128 const e = SERVICE_LEVEL_SCALES.HBS.kfz.find((s) => s.grade === 'E')!;
129 expect(Number.isFinite(e.maxDelay)).toBe(false);
130 });
131
132 it('fuehrt Fussgaenger mit der maximalen Wartezeit auf und streicht den falschen Satz', () => {
133 // Befund B4: Der alte Satz "Fussgaengersignalgruppen sind nicht
134 // aufgefuehrt, da ihre Leistungsfaehigkeit ... nicht massgebend ist" war
135 // falsch - das HBS 2015 bewertet sie nach der laengsten Sperrzeit.
136 const projekt = vollstaendig('HBS');
137 const plan = buildSignalPlan(projekt);
138 const text = ausdruck(projekt);
139
140 expect(text).not.toContain('nicht aufgeführt');
141 expect(text).not.toContain('nicht maßgebend');
142 expect(text).toContain('Fußgänger und Radverkehr');
143 expect(text).toContain('Längste Sperrzeit = maximale Wartezeit');
144 expect(text).toContain('nach der maximalen Wartezeit bewertet');
145 expect(text).toContain('A bis 30 s, B bis 40 s, C bis 55 s, D bis 70 s, E bis 85 s, F darüber');
146
147 // Die gedruckte maximale Wartezeit ist die laengste Sperrzeit der Gruppe -
148 // und die Stufe dazu ist die des Fachkerns.
149 const fuss = plan.groups.filter((g) => g.mode === 'fuss');
150 expect(fuss.length).toBeGreaterThan(0);
151 for (const gruppe of fuss) {
152 expect(gruppe.delay, gruppe.name).not.toBeNull();
153 const maximal = gruppe.delay!.maximumDelay;
154 expect(text, gruppe.name).toContain(`${maximal.toFixed(1).replace('.', ',')} s`);
155 expect(gruppe.delay!.serviceLevel, gruppe.name).not.toBeNull();
156 expect(gruppe.delay!.serviceLevel!.kriterium).toBe('maximale-wartezeit');
157 }
158 });
159
160 it('vergibt nach HCM fuer Fussgaenger keine Stufe und sagt das', () => {
161 const projekt = vollstaendig('HCM');
162 const plan = buildSignalPlan(projekt);
163 for (const gruppe of plan.groups.filter((g) => g.mode === 'fuss')) {
164 expect(gruppe.delay?.serviceLevel ?? null, gruppe.name).toBeNull();
165 }
166 const text = ausdruck(projekt);
167 expect(text).toContain('Fußgänger und Radverkehr');
168 expect(text).toContain('Nach HCM wird für Fußgänger und Radverkehr keine Stufe vergeben');
169 // Auch ohne Stufe steht die maximale Wartezeit da.
170 expect(text).toContain('Längste Sperrzeit = maximale Wartezeit');
171 });
172
173 it('kennzeichnet die Stufe F bei Ueberlastung als "überlastet"', () => {
174 // Nach HBS 2015 haengt F fuer Kfz an q > C, nicht an der Wartezeit. Ein
175 // blosses "F" neben einer Wartezeit liesse sich mit der gedruckten Tafel
176 // nicht in Einklang bringen.
177 const roh = vollstaendig('HBS');
178 const projekt: Project = {
179 ...roh,
180 demands: roh.demands.map((d, i) => (i === 0 ? { ...d, volume: 3000 } : d)),
181 };
182 const plan = buildSignalPlan(projekt);
183 const ueberlastet = plan.groups.filter(
184 (g) => g.delay?.serviceLevel?.kriterium === 'ueberlastung',
185 );
186 expect(ueberlastet.length, 'die Vorbereitung muss eine Ueberlastung erzeugen').toBeGreaterThan(
187 0,
188 );
189
190 const text = ausdruck(projekt);
191 expect(text).toContain('F (überlastet)');
192 });
193
194 it('druckt auf Wunsch den Rechenweg der Wartezeit mit den Zwischenwerten', () => {
195 // Grundsatz: tW muss sich aus den gedruckten Groessen
196 // nachrechnen lassen. Der Rechenweg wird wie bei den Zwischenzeiten ueber
197 // includeCalculationDetails zugeschaltet.
198 const projekt = vollstaendig('HBS');
199 const plan = buildSignalPlan(projekt);
200 // Seit Befund C15 ist der Rechenweg voreingestellt; die Abwahl muss hier
201 // ausdruecklich gesetzt werden. Die Zeile stand bis dahin ohne Zusatz da -
202 // sie las die Voreinstellung mit.
203 const ohne = ausdruck(projekt, { ...NUR_LEISTUNG, includeCalculationDetails: false });
204 expect(ohne).not.toContain('Rechenweg Wartezeit');
205
206 const mit = ausdruck(projekt, { ...NUR_LEISTUNG, includeCalculationDetails: true });
207 expect(mit).toContain('Rechenweg Wartezeit');
208 expect(mit).toContain('NGE');
209 expect(mit).toContain('tW,G');
210 expect(mit).toContain('tW,R');
211 expect(mit).toContain('Wurzel aus');
212
213 // Die Zwischenwerte des Fachkerns stehen im Ausdruck.
214 const k1 = plan.groups.find((g) => g.mode === 'kfz' && g.delay !== null)!;
215 const rw = k1.delay!.rechenweg;
216 expect(rw.reststau).not.toBeNull();
217 expect(mit).toContain(`${k1.delay!.uniformDelay.toFixed(1).replace('.', ',')} s`);
218 expect(mit).toContain(rw.reststau!.toFixed(2).replace('.', ','));
219
220 // Nach HCM stehen die HCM-Beiwerte im Rechenweg, nicht NGE.
221 const hcm = ausdruck(vollstaendig('HCM'), { ...NUR_LEISTUNG, includeCalculationDetails: true });
222 expect(hcm).toContain('Rechenweg Wartezeit');
223 expect(hcm).toContain('d1');
224 expect(hcm).toContain('k = 0,5');
225 expect(hcm).not.toContain('NGE');
226 });
227
228 it('druckt die Abflusszeit, aus der sich die Kapazitaet nachrechnen laesst', () => {
229 // C = qS · tA/tU - ohne tA in der Tabelle ginge die Kapazitaet neben der
230 // Freigabezeit nicht auf (Befund B2, tA = tF + 1 s).
231 const projekt = vollstaendig('HBS');
232 const plan = buildSignalPlan(projekt);
233 const text = ausdruck(projekt);
234 expect(text).toContain('Abflusszeit tA');
235 const k1 = plan.groups.find((g) => g.mode === 'kfz' && g.capacity !== null)!;
236 expect(k1.capacity!.abflusszeit).toBeCloseTo(k1.totalGreen + 1, 3);
237 expect(text).toContain(`${k1.capacity!.abflusszeit.toFixed(1).replace('.', ',')} s`);
238 });
239
240 it('druckt den Auslastungsgrad mit drei Nachkommastellen', () => {
241 // Mit zwei Stellen stand bei q = 244 und C = 243 "1,00" neben "F
242 // (ueberlastet)" - die gedruckte Zahl widersprach der gedruckten Stufe.
243 // Drei Stellen wie im Rechenweg und im Pruefbericht.
244 const projekt = vollstaendig('HBS');
245 const plan = buildSignalPlan(projekt);
246 const text = ausdruck(projekt);
247 const kfz = plan.groups.filter((g) => g.mode === 'kfz' && g.capacity !== null);
248 expect(kfz.length).toBeGreaterThan(0);
249 for (const gruppe of kfz) {
250 expect(gruppe.capacity!.degreeOfSaturation, gruppe.name).toBeDefined();
251 const drei = fmt.ratio(gruppe.capacity!.degreeOfSaturation!, 3);
252 expect(drei, gruppe.name).toMatch(/^\d,\d{3}$/);
253 expect(text, gruppe.name).toContain(drei);
254 }
255 // Der Fall des Befunds: q knapp ueber C darf nicht als "1,00" erscheinen.
256 // Der Umlauf haengt an der Nachfrage (Webster); deshalb wird die
257 // Verkehrsstaerke aus dem Plan abgeleitet und der Plan neu gebaut.
258 const k1 = kfz[0]!;
259 const knapp: Project = {
260 ...projekt,
261 demands: projekt.demands.map((d) =>
262 d.signalGroupId === k1.groupId ? { ...d, volume: k1.capacity!.capacity + 1 } : d,
263 ),
264 };
265 const neu = buildSignalPlan(knapp).groups.find((g) => g.groupId === k1.groupId)!;
266 const x = neu.capacity!.degreeOfSaturation!;
267 const gedruckt = ausdruck(knapp);
268 if (x > 1 && x < 1.005) {
269 expect(fmt.ratio(x, 2), 'Vorbedingung des Befunds').toBe('1,00');
270 expect(gedruckt).toContain(fmt.ratio(x, 3));
271 expect(gedruckt).toContain('F (überlastet)');
272 } else {
273 // Der Neuaufbau hat den Umlauf verschoben; dann gilt nur die Stellenzahl.
274 expect(gedruckt).toContain(fmt.ratio(x, 3));
275 }
276 });
277 });
278
279 describe('Signalgruppentabelle: Verkehrsstaerke nur fuer Kfz und OePNV', () => {
280 it('druckt fuer eine Radgruppe mit gespeicherter Verkehrsstaerke "–"', () => {
281 // Altdatei aus der Zeit vor Fassung 5.4.0 (Befund B4): Fuer
282 // eine Radgruppe ist eine Verkehrsstaerke gespeichert. Sie geht in keine
283 // Rechnung ein, und der Pruefbericht sagt das - im Ausdruck darf sie
284 // deshalb nicht als Eingangsgroesse erscheinen.
285 const roh = vollstaendig('HBS');
286 const rad = createSignalGroup({ name: 'R9', mode: 'rad', index: 9 });
287 const projekt: Project = {
288 ...roh,
289 signalGroups: [...roh.signalGroups, rad],
290 demands: [...roh.demands, { signalGroupId: rad.id, volume: 777, heavyVehicleShare: 0 }],
291 };
292 const text = ausdruck(projekt, {
293 ...NUR_LEISTUNG,
294 includeSignalGroups: true,
295 includePerformance: false,
296 });
297 expect(text).toContain('R9');
298 expect(text).not.toContain('777 Fz/h');
299 // Die Kfz-Verkehrsstaerken stehen weiterhin da.
300 expect(text).toContain(`${fmt.vehiclesPerHour(roh.demands[0]!.volume)}`);
301 });
302 });
303
304 const NUR_GRUNDLAGEN: PdfOptions = {
305 ...NUR_LEISTUNG,
306 includePerformance: false,
307 includeSources: true,
308 };
309
310 describe('Grundlagen der Kennwerte', () => {
311 it('sagt bei HCM, dass dieses Projekt nach dem HCM bewertet und aus dem HBS nur Eingangsgroessen nimmt', () => {
312 // Die Rechtsgrundlagentafel nennt das HBS 2015 allgemein als Grundlage
313 // von Kapazitaet, Wartezeit und Qualitaetsstufen. Ohne den Satz zum
314 // Projekt hielte der Leser einer HCM-Bewertung die ganze Bewertung fuer
315 // eine nach HBS 2015.
316 const hcm = ausdruck(vollstaendig('HCM'), NUR_GRUNDLAGEN);
317 expect(hcm).toContain('Dieses Projekt bewertet nach dem HCM (Highway Capacity Manual)');
318 expect(hcm).toContain('Wartezeit d = d1 + d2, Level of Service');
319 expect(hcm).toContain(
320 'Aus dem HBS 2015 stammen hier nur Sättigungsverkehrsstärke und Abbiegerfaktor',
321 );
322 expect(hcm).not.toContain('rechnet und bewertet nach dem HBS 2015');
323
324 const hbs = ausdruck(vollstaendig('HBS'), NUR_GRUNDLAGEN);
325 expect(hbs).toContain('Dieses Projekt rechnet und bewertet nach dem HBS 2015');
326 expect(hbs).not.toContain('Dieses Projekt bewertet nach dem HCM');
327 });
328
329 it('fuehrt die Stufentafeln der Qualitaetsstufen im Fundstellenverzeichnis mit Pruefstand', () => {
330 // Die Quelle 'hbs-qualitaetsstufen' hat kein Vorgabenfeld und fehlte im
331 // Verzeichnis deshalb ganz: Die gedruckten Tafeln hatten weder Herkunft
332 // noch Pruefstand, obwohl OePNV- und Fuss/Rad-Tafel nur ueber
333 // Sekundaerquellen belegt sind.
334 const quelle = QUELLEN['hbs-qualitaetsstufen'];
335 /*
336 * PRUEFSTAND SEIT DEM 15.08.2026 "gesichert" (Fassung 5.5.0, Frage E5): Hier stand
337 * "abzugleichen", weil die OePNV- und die Fuss/Rad-Tafel nur ueber
338 * Sekundaerquellen belegt waren. Inzwischen liegen zwei
339 * HERAUSGEBERSEITIGE Wiedergaben derselben gedruckten HBS-Tabelle vor -
340 * der Vortrag des Verfassers von Kapitel S4 beim FGSV-Einfuehrungs-
341 * kolloquium 2015 und Tab. 2-2 des BASt-Berichts V 400 (2025). Der Test
342 * haelt weiter den Pruefstand der Quelle gegen den gedruckten fest; die
343 * Zusicherung ist also nicht schwaecher geworden, sondern folgt der
344 * Beleglage.
345 */
346 expect(quelle.pruefstand, 'Vorbedingung: die Tafeln sind zweifach belegt').toBe('gesichert');
347 const text = ausdruck(vollstaendig('HBS'), NUR_GRUNDLAGEN);
348
349 expect(text).toContain('Fundstellenverzeichnis');
350 expect(text).toContain(
351 'Stufentafeln der Qualitätsstufen (Kfz/ÖPNV: mittlere Wartezeit; Fuß/Rad: maximale Wartezeit)',
352 );
353 expect(text).toContain(quelle.sachgebiet);
354 // Die Werte selbst, aus den Konstanten - und die Stufe F mit ihrem Grund.
355 expect(text).toContain(
356 `Stufentafel Kfz (mittlere Wartezeit): ${stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.kfz)}`,
357 );
358 expect(text).toContain(
359 `Stufentafel ÖPNV (mittlere Wartezeit): ${stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.oepnv)}`,
360 );
361 expect(text).toContain(
362 `Stufentafel Fußgänger und Radverkehr (maximale Wartezeit): ${stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.fussRad)}`,
363 );
364 expect(text).toContain('F bei Überlastung (q > C)');
365 // Der Hinweis mit der Beleglage steht dabei; der Pruefstand ist derselbe
366 // wie in der Quelle. Der Hinweis nennt jetzt beide Herausgeberquellen und
367 // sagt zugleich, was sie NICHT sind - zwei Wiedergaben eines Originals
368 // sind kein zweiter Zeuge fuer das Original (Fassung 5.5.0, Frage E5).
369 expect(text).toContain('Zu beachten: Alle drei Stufentafeln');
370 expect(text).toContain('zwei Wiedergaben eines Originals');
371 expect(text).toContain(PRUEFSTAND_LABELS[quelle.pruefstand]);
372 // Die Liste der feldlosen Quellen kennt genau diese Quelle.
373 expect(quellenOhneFeld().map((q) => q.schluessel)).toContain('hbs-qualitaetsstufen');
374 });
375 });
376
377 describe('HCM-Projekt: Nachweis der Kennwerte ohne HBS-Bewertungsetikett', () => {
378 it('fuehrt die HCM-Tafel statt der HBS-Tafeln und vermerkt wirkungslose HBS-Wartezeitkennwerte', () => {
379 // Im HCM-Projekt gehen die HBS-Tafeln und der Instationaritaetsfaktor in
380 // keine Zahl ein. Das Verzeichnis druckte sie trotzdem als verwendet
381 // (Befund F6).
382 const projekt: Project = {
383 ...vollstaendig('HCM'),
384 settings: { ...vollstaendig('HCM').settings, rilsa: { instationaritaetsfaktor: 1.2 } },
385 };
386 const text = ausdruck(projekt, NUR_GRUNDLAGEN);
387
388 expect(text).toContain('Level of Service (Kfz/ÖPNV: mittlere Wartezeit; Fuß/Rad: keine Tafel)');
389 expect(text).toContain(
390 `Stufentafel Kfz und ÖPNV (mittlere Wartezeit): ${stufentafelSatz(SERVICE_LEVEL_SCALES.HCM.kfz)}`,
391 );
392 expect(text).not.toContain('Stufentafeln der Qualitätsstufen (Kfz/ÖPNV');
393 expect(text).toContain(
394 'Instationaritätsfaktor (HBS 2015) (ohne Wirkung: Bewertungsverfahren HCM)',
395 );
396 // Die Abweichungstabelle verlangt fuer den wirkungslosen Wert keine Begruendung.
397 expect(text).toContain(
398 'Instationaritätsfaktor (HBS 2015) (ohne Wirkung: Bewertungsverfahren HCM)',
399 );
400 // Die HCM-Tafel statt der HBS-Tafeln; die VwV-StVO steht unabhaengig vom
401 // Verfahren dabei, die RSA nur bei einstreifiger Fuehrung.
402 // Die Geometriefaktoren stehen seit 5.30.0 unabhaengig vom Verfahren
403 // dabei: Sie wirken auf jede Saettigungsverkehrsstaerke, und wo keine
404 // Zufahrt vermessen ist, steht der Faktor 1 fuer eine ANGENOMMENE
405 // Standardbedingung - genau das gehoert in den Nachweis.
406 expect(quellenOhneFeld('HCM').map((q) => q.schluessel)).toEqual([
407 'hcm-verfahren',
408 'vwv-stvo',
409 'hbs-geometriefaktoren',
410 // Seit 5.31.0: die Laenge je wartendem Fahrzeug, aus der die
411 // gedruckte Rueckstaulaenge entsteht.
412 'praxis-stauraum-je-fahrzeug',
413 ]);
414 expect(quellenOhneFeld('HCM').some((q) => q.schluessel === 'hbs-qualitaetsstufen')).toBe(false);
415 expect(QUELLEN['hcm-verfahren'].pruefstand).toBe('fachliteratur');
416 });
417
418 it('vermerkt im HBS-Projekt keinen Kennwert als verfahrensbedingt wirkungslos', () => {
419 const text = ausdruck(vollstaendig('HBS'), NUR_GRUNDLAGEN);
420 // Der verfahrensbedingte Vermerk gilt nur dem HBS-Wartezeitverfahren; im
421 // HBS-Projekt wirkt es, also darf er nirgends stehen.
422 expect(text).not.toContain('ohne Wirkung: Bewertungsverfahren HCM');
423 expect(text).not.toContain('Instationaritätsfaktor (HBS 2015) (ohne Wirkung');
424 // Befund C20: Die Progressionsgeschwindigkeiten sind dagegen UNABHAENGIG
425 // vom Bewertungsverfahren wirkungslos, solange kein Strassenzug erfasst ist
426 // (seit 5.35.0 haengt der Vermerk daran und nicht mehr an der Fassung).
427 // Dieser Vermerk steht deshalb auch hier, und zwar mit seinem eigenen
428 // Grund statt mit dem des Bewertungsverfahrens.
429 expect(text).toContain(
430 'Progressionsgeschwindigkeit (ohne Wirkung: keine Koordinierung erfasst)',
431 );
432 });
433
434 it('nennt in der Abweichungstabelle den eigenen Grund der Koordinierung, nicht das Bewertungsverfahren', () => {
435 // Befund C20, Folgefehler im Ausdruck: Die Bedingung fuer den Zusatz
436 // fragte bereits den Fachkern, der Zusatztext selbst stand fest auf
437 // " (ohne Wirkung: Bewertungsverfahren HCM)". Die Zeile der
438 // Progressionsgeschwindigkeit behauptete damit in einem HBS-Projekt ein
439 // Bewertungsverfahren als Grund, das mit ihr nichts zu tun hat.
440 const roh = vollstaendig('HBS');
441 const projekt: Project = {
442 ...roh,
443 settings: { ...roh.settings, rilsa: { ...roh.settings.rilsa, coordinationDefaultSpeed: 70 } },
444 };
445 const text = ausdruck(projekt, NUR_GRUNDLAGEN);
446
447 expect(text).toContain('Abweichungen von den Regelwerten');
448 // Kennwert | Regelwert | Angesetzt | Einheit - mit dem Grund an der Zeile.
449 expect(text).toMatch(
450 /Progressionsgeschwindigkeit \(ohne Wirkung: keine Koordinierung erfasst\)\s+50\s+70\s+km\/h/,
451 );
452 expect(text).not.toContain('Bewertungsverfahren HCM');
453 });
454 });