lsa-planer

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

/ tests export saettigungskette.test.ts

6,3 KB Rohdatei
tests/export/saettigungskette.test.ts — 133 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { schwerverkehrSatz } from '@/services/export/bewertung';
3 import { signalGroupsCsv } from '@/services/export/csv';
4 import { buildSignalPlan } from '@/domain/plan/signalPlan';
5 import { createStandardIntersectionProject } from '@/domain/model/factory';
6 import { saturationFlow } from '@/domain/rilsa/capacity';
7 import { RILSA_DEFAULTS } from '@/domain/rilsa/constants';
8 import { resolveRilsaSettings } from '@/domain/rilsa/settings';
9 import type { Project } from '@/domain/model/project';
10 import * as fmt from '@/ui/format';
11
12 /*
13 * Befund 34 zur Fassung 5.9.0.
14 *
15 * Der Erlaeuterungsabsatz zum Schwerverkehr beschrieb die Kette als
16 * tB = fSV * tB0 und qS = 3600 / tB und behauptete daneben, bei reinem
17 * Pkw-Verkehr stuenden in der Spalte "Saettigungsverkehrsstaerke" die 2000 Fz/h
18 * je Fahrstreifen. Die Spalte fuehrt aber saturationFlow(movement, lanes,
19 * defaults) / fSV, also zusaetzlich die Abminderung fuer abbiegende Stroeme
20 * (capacity.ts: qS0 * n, bei 'links' und 'rechts' mal turningFactor). Bei einer
21 * einstreifigen abbiegenden Kfz-Gruppe ohne Schwerverkehr stehen dort 1800 Fz/h
22 * - der Absatz sagte eine Zahl zu, die die Tabelle daneben widerlegt, und
23 * schloss zugleich mit dem Anspruch, an dem er scheiterte ("ohne ihn liesse
24 * sich die Saettigungsverkehrsstaerke aus dem Regelwert je Fahrstreifen nicht
25 * nachvollziehen").
26 *
27 * Keine Zahl der Rechnung ist betroffen; die Abminderung liegt auf der sicheren
28 * Seite. Falsch war der Nachweis in der Unterlage, die zur Behoerde geht.
29 *
30 * NACHGEBESSERT: Die berichtigte Kette fuehrte das Zeichen qS zweimal - einmal
31 * als 3600/tB (je Fahrstreifen, einschliesslich fSV), einmal als Wert des
32 * ganzen Stroms. Der Grundwert je Fahrstreifen heisst jetzt qS0.
33 *
34 * MUTATIONSNACHWEIS (gemessen an einer Pruefkopie mit den HEAD-Fassungen von
35 * bewertung.ts und csv.ts): Gegen den Altstand fallen die vier Satzfaelle; die
36 * beiden Belegzahlfaelle halten den Ausloeser fest - die 1.800 Fz/h in Rechnung
37 * und Tabellenzelle - und sind in beiden Staenden gruen. 4 von 6.
38 */
39
40 const STICHTAG = new Date('2026-01-01T12:00:00Z');
41 const QS0 = RILSA_DEFAULTS.capacity.saturationFlow;
42 const FA = RILSA_DEFAULTS.capacity.turningFactor;
43
44 /** Knotenpunkt mit einstreifiger, links abbiegender erster Kfz-Gruppe ohne Schwerverkehr. */
45 function abbiegerOhneSchwerverkehr(): Project {
46 const basis = createStandardIntersectionProject('Sättigungskette', STICHTAG);
47 return {
48 ...basis,
49 signalGroups: basis.signalGroups.map((g, index) =>
50 index === 0 ? { ...g, movement: 'links' as const, lanes: 1 } : g,
51 ),
52 demands: basis.demands.map((d, index) => (index === 0 ? { ...d, heavyVehicleShare: 0 } : d)),
53 };
54 }
55
56 describe('Befund 34 - der Leistungsabsatz nennt die vollständige Kette der Spalte', () => {
57 it('rechnet die Belegzahl vor: ein einstreifiger Abbieger steht mit 1800 Fz/h in der Spalte', () => {
58 const projekt = abbiegerOhneSchwerverkehr();
59 const plan = buildSignalPlan(projekt);
60 const gruppe = plan.groups.find((g) => g.groupId === projekt.signalGroups[0]!.id)!;
61
62 expect(saturationFlow('links', 1, plan.defaults)).toBeCloseTo(QS0 * FA, 9);
63 expect(gruppe.capacity?.saturationFlow).toBeCloseTo(QS0 * FA, 6);
64 expect(fmt.vehiclesPerHour(gruppe.capacity!.saturationFlow)).toBe('1.800 Fz/h');
65 });
66
67 it('behauptet nicht mehr, in der Spalte stünde der Regelwert je Fahrstreifen', () => {
68 const satz = schwerverkehrSatz();
69
70 expect(satz).not.toContain('je Fahrstreifen der Spalte');
71 expect(satz).not.toContain('aus dem Regelwert je Fahrstreifen nicht nachvollziehen');
72 });
73
74 it('nennt Fahrstreifenzahl und Abminderung in derselben Kette', () => {
75 const satz = schwerverkehrSatz();
76
77 expect(satz).toContain(`qS0 = ${fmt.vehiclesPerHour(QS0)} je Fahrstreifen`);
78 expect(satz).toContain('qS0 · n · fA / fSV');
79 expect(satz).toContain('Abminderung für abbiegende Ströme');
80 expect(satz).toContain(`fA = ${fmt.numShort(FA, 2)}`);
81 expect(satz).toContain('fA = 1');
82 // Und die Zahl, die daneben in der Tabelle steht.
83 expect(satz).toContain(fmt.vehiclesPerHour(QS0 * FA));
84 });
85
86 it('führt qS nicht zugleich für den Wert des ganzen Stroms', () => {
87 /*
88 * Der Absatz nennt zwei Groessen: qS = 3600 / tB ist der Wert JE
89 * FAHRSTREIFEN einschliesslich fSV, die Spalte der ganze Strom. Stuenden
90 * beide unter dem Zeichen qS, waere die Kette nur fuer n = 1 und fA = 1
91 * richtig, und wer die erste Gleichung in die zweite einsetzt, teilt ein
92 * zweites Mal durch fSV - in einem Absatz, dessen Zweck die
93 * Nachrechenbarkeit ist.
94 */
95 const satz = schwerverkehrSatz();
96
97 // Der Wert je Fahrstreifen - aus dem Zeitbedarfswert.
98 expect(satz).toContain('tB = fSV · 1,8 s und qS = 3600 / tB');
99 // Der Wert des ganzen Stroms - eigene Größe, eigenes Zeichen.
100 expect(satz).toContain('nennt den Wert des ganzen Stroms');
101 expect(satz).not.toContain(`qS = ${fmt.vehiclesPerHour(QS0)}`);
102 expect(satz).not.toContain('qS = qS0');
103 });
104
105 it('nimmt die Abminderung aus den wirksamen Vorgaben, nicht aus dem Satz', () => {
106 const { defaults } = resolveRilsaSettings({ turningFactor: 0.8, saturationFlow: 1900 });
107 const satz = schwerverkehrSatz(defaults);
108
109 expect(satz).toContain('fA = 0,8');
110 expect(satz).toContain(`qS0 = ${fmt.vehiclesPerHour(1900)} je Fahrstreifen`);
111 expect(satz).toContain('qS0 · n · fA / fSV');
112 expect(satz).toContain(fmt.vehiclesPerHour(1900 * 0.8));
113 expect(satz).not.toContain('fA = 0,9');
114 });
115
116 it('Belegzahl der Tabellenausgabe: dieselbe Spalte trägt 1.800 Fz/h', () => {
117 const projekt = abbiegerOhneSchwerverkehr();
118 const csv = signalGroupsCsv(projekt, buildSignalPlan(projekt));
119 const zerlege = (zeile: string): string[] =>
120 (zeile.match(/("([^"]|"")*"|[^;]*)(;|$)/g) ?? []).map((feld) =>
121 feld.replace(/;$/, '').replace(/^"|"$/g, '').replace(/""/g, '"'),
122 );
123 const alle = csv.split('\r\n');
124 const kopf = zerlege((alle[0] ?? '').replace(/^/, ''));
125 const spalte = kopf.indexOf('Sättigungsverkehrsstärke [Fz/h]');
126 const zeile = zerlege(alle.find((z) => z.startsWith(`${projekt.signalGroups[0]!.name};`))!);
127
128 expect(spalte).toBeGreaterThanOrEqual(0);
129 // Die Zelle traegt die Abminderung; der Kommentar ueber der Spalte fSV
130 // sagte, mit fSV allein liesse sie sich nachrechnen.
131 expect(zeile[spalte]).toBe('1.800');
132 });
133 });