lsa-planer

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

/ tests domain quellen.test.ts

7,4 KB Rohdatei
tests/domain/quellen.test.ts — 161 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import {
3 PRUEFSTAND_ERLAEUTERUNG,
4 PRUEFSTAND_LABELS,
5 QUELLEN,
6 RECHTSGRUNDLAGEN,
7 } from '@/domain/rilsa/quellen';
8 import { settingFields, settingFieldsByQuelle } from '@/domain/rilsa/settings';
9 import { RILSA_DEFAULTS } from '@/domain/rilsa/constants';
10 import { validateProject } from '@/domain/validation';
11 import { createEmptyProject, createStandardIntersectionProject } from '@/domain/model/factory';
12
13 const FIXED_DATE = new Date('2026-01-01T00:00:00Z');
14
15 describe('Herkunft der Kennwerte', () => {
16 it('weist jedem Kennwert eine Herkunft zu', () => {
17 const fields = settingFields();
18 expect(fields.length).toBeGreaterThan(30);
19 for (const field of fields) {
20 expect(field.quelle, field.label).toBeDefined();
21 expect(field.quelle.regelwerk).not.toBe('');
22 expect(field.quelle.herausgeber).not.toBe('');
23 expect(field.quelle.sachgebiet).not.toBe('');
24 expect(field.quelle.verwendung).not.toBe('');
25 expect(PRUEFSTAND_LABELS[field.quelle.pruefstand]).toBeDefined();
26 }
27 });
28
29 it('nennt eine Gliederungsnummer nur, wo das Original vorliegt', () => {
30 /*
31 * UMGESTELLT MIT 5.27.0. Hier stand: nirgends eine Nummer. Das war
32 * richtig, solange kein Regelwerk im Original vorlag - eine ungeprueffte
33 * Nummer taeuscht eine Genauigkeit vor, die es nicht gibt. Die gekaufte
34 * RiLSA 2015 liegt vor; HBS, RSA 21 und VwV-StVO nicht. Die Wache prueft
35 * jetzt genau diese Trennung.
36 */
37 const muster = /\b(Abschnitt|Kapitel|Tabelle|Bild|Seite)\s+\d/i;
38 for (const [schluessel, quelle] of Object.entries(QUELLEN)) {
39 // Die Kopfspalten bleiben frei von Nummern: Dort steht, WELCHES Werk
40 // gemeint ist, nicht welche Stelle darin.
41 for (const feld of [quelle.regelwerk, quelle.herausgeber, quelle.sachgebiet]) {
42 expect(feld, schluessel).not.toMatch(muster);
43 }
44 const ausRilsa = quelle.regelwerk.includes('RiLSA 2015');
45 if (!ausRilsa) {
46 expect(quelle.fundstelle, schluessel).toBeUndefined();
47 const text = `${quelle.verwendung} ${quelle.hinweis ?? ''}`;
48 // Die RSA-Fundstellen (Teil A 3.2 usw.) sind keine Nummern dieses
49 // Musters; eine "Abschnitt 5" waere eine.
50 expect(text, schluessel).not.toMatch(muster);
51 }
52 }
53 });
54
55 it('haengt an jede RiLSA-Quelle mit Vorgabenfeld eine Fundstelle', () => {
56 // Wo das Original vorliegt, ist eine fehlende Nummer eine Luecke - und
57 // eine Nummer ohne "Abschnitt" davor keine Fundstelle.
58 for (const [schluessel, quelle] of Object.entries(QUELLEN)) {
59 if (!quelle.regelwerk.includes('RiLSA 2015')) continue;
60 expect(quelle.fundstelle, schluessel).toBeDefined();
61 expect(quelle.fundstelle, schluessel).toMatch(/Abschnitt\s+\d/);
62 }
63 });
64
65 it('nennt auch im Pruefbericht keine ungepruefte Gliederungsnummer', () => {
66 const muster = /\b(Abschnitt|Kapitel|Tabelle|Bild|Seite)\s+\d/i;
67 const berichte = [
68 validateProject(createEmptyProject('Leer', FIXED_DATE), undefined, FIXED_DATE),
69 validateProject(createStandardIntersectionProject('Voll', FIXED_DATE), undefined, FIXED_DATE),
70 ];
71 for (const bericht of berichte) {
72 for (const finding of bericht.findings) {
73 expect(finding.reference, finding.rule).not.toMatch(muster);
74 expect(finding.reference).not.toBe('');
75 }
76 }
77 });
78
79 it('weist Rechenverfahren aus der Fachliteratur getrennt aus', () => {
80 // Webster und Akcelik stammen nicht aus der RiLSA; das muss erkennbar sein.
81 expect(QUELLEN['verfahren-webster'].pruefstand).toBe('fachliteratur');
82 expect(QUELLEN['verfahren-akcelik'].pruefstand).toBe('fachliteratur');
83 expect(QUELLEN['verfahren-webster'].regelwerk).not.toContain('RiLSA');
84 expect(QUELLEN['verfahren-akcelik'].regelwerk).not.toContain('RiLSA');
85 });
86
87 it('ordnet sicherheitsrelevante Signalzeiten der RiLSA zu', () => {
88 const fields = settingFields();
89 const byKey = new Map(fields.map((f) => [f.key, f]));
90 for (const key of ['yellowKfzUpTo50', 'redYellow', 'minGreenKfz'] as const) {
91 const field = byKey.get(key)!;
92 expect(field.quelle.regelwerk).toContain('RiLSA 2015');
93 expect(field.quelle.pruefstand).toBe('gesichert');
94 }
95 });
96
97 it('ordnet Kapazitaetskennwerte dem HBS zu - je nach Beleglage gesichert oder abzugleichen', () => {
98 const byKey = new Map(settingFields().map((f) => [f.key, f]));
99 // Saettigungsverkehrsstaerke 2000 Kfz/h: HBS-Wert, durch BASt V 400
100 // bestaetigt (Befund B3).
101 expect(byKey.get('saturationFlow')!.quelle.regelwerk).toContain('HBS 2015');
102 expect(byKey.get('saturationFlow')!.quelle.pruefstand).toBe('gesichert');
103 // Pauschaler Abbiegerfaktor: das HBS differenziert nach Radius und Gegenverkehr.
104 expect(byKey.get('turningFactor')!.quelle.regelwerk).toContain('HBS 2015');
105 expect(byKey.get('turningFactor')!.quelle.pruefstand).toBe('abzugleichen');
106 // Wartezeit-Formelkette: in der Fachliteratur dokumentiert, Druckwerkabgleich offen.
107 expect(byKey.get('instationaritaetsfaktor')!.quelle.regelwerk).toContain('HBS 2015');
108 expect(byKey.get('instationaritaetsfaktor')!.quelle.pruefstand).toBe('abzugleichen');
109 /*
110 * Ziel- und Hoechstwert des Auslastungsgrades: keine HBS-Werte - das HBS
111 * kennt als Ueberlastungskriterium allein q > C. Seit 5.27.0 aber auch
112 * keine reine Planungspraxis mehr: Die RiLSA 2015 nennt in Abschnitt 2.6
113 * ein Band von 0,80 bis 0,90 fuer x, und beide Werte liegen darin. Der
114 * Pruefstand bleibt 'abzugleichen', weil die Wahl INNERHALB des Bandes
115 * dieses Programm trifft.
116 */
117 for (const key of ['targetDegreeOfSaturation', 'maxDegreeOfSaturation'] as const) {
118 const quelle = byKey.get(key)!.quelle;
119 expect(quelle.kurz).toBe('RiLSA 2015');
120 expect(quelle.pruefstand).toBe('abzugleichen');
121 expect(quelle.regelwerk).not.toContain('HBS');
122 }
123 expect(RILSA_DEFAULTS.capacity.targetDegreeOfSaturation).toBeGreaterThanOrEqual(0.8);
124 expect(RILSA_DEFAULTS.capacity.maxDegreeOfSaturation).toBeLessThanOrEqual(0.9);
125 });
126
127 it('gruppiert die Kennwerte lueckenlos nach Herkunft', () => {
128 const alle = settingFields();
129 const gruppiert = settingFieldsByQuelle();
130 const summe = gruppiert.reduce((a, g) => a + g.felder.length, 0);
131 expect(summe).toBe(alle.length);
132
133 const keys = new Set(gruppiert.flatMap((g) => g.felder.map((f) => f.key)));
134 expect(keys.size).toBe(alle.length);
135 });
136
137 it('erlaeutert jeden Pruefstand', () => {
138 for (const stand of ['gesichert', 'abzugleichen', 'fachliteratur'] as const) {
139 expect(PRUEFSTAND_LABELS[stand]).not.toBe('');
140 expect(PRUEFSTAND_ERLAEUTERUNG[stand].length).toBeGreaterThan(30);
141 }
142 });
143
144 it('beschreibt die Rechtskette von der StVO bis zum Regelwerk', () => {
145 const bezeichnungen = RECHTSGRUNDLAGEN.map((r) => r.bezeichnung).join(' | ');
146 expect(bezeichnungen).toContain('§ 37 StVO');
147 expect(bezeichnungen).toContain('§ 45 StVO');
148 expect(bezeichnungen).toContain('VwV-StVO');
149 expect(bezeichnungen).toContain('RiLSA 2015');
150 expect(bezeichnungen).toContain('HBS 2015');
151
152 for (const grundlage of RECHTSGRUNDLAGEN) {
153 expect(grundlage.inhalt.length).toBeGreaterThan(40);
154 }
155 });
156
157 it('haelt fest, dass die StVO selbst keine Zahlenwerte vorgibt', () => {
158 const stvo = RECHTSGRUNDLAGEN.find((r) => r.bezeichnung.includes('§ 37 StVO'))!;
159 expect(stvo.inhalt.toLowerCase()).toContain('zahlenwerte');
160 });
161 });