lsa-planer

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

/ src services export bewertung.ts

55,3 KB Rohdatei
src/services/export/bewertung.ts — 1173 Zeilen
1 import {
2 BEWERTUNGSVERFAHREN_LABELS,
3 ENGER_RADIUS_LABEL,
4 ENGSTELLE_PRAXIS,
5 HBS_GEOMETRIE,
6 HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG,
7 HBS_PKW_GLEICHWERTE,
8 HBS_STANDARDBEDINGUNGEN,
9 KRITERIUM_LABELS,
10 RILSA_DEFAULTS,
11 RSA_EINSTREIFIG,
12 SAFETY_FLOORS,
13 SERVICE_LEVEL_SCALES,
14 STAURAUMBEDARF_JE_FAHRZEUG,
15 UEBERFAHRZEIT_ANSATZ_LABELS,
16 VZUL_OBERGRENZE_LSA,
17 type RilsaDefaults,
18 type ServiceLevelStep,
19 } from '@/domain/rilsa/constants';
20 import { QUELLEN, type Quelle, type QuellenSchluessel } from '@/domain/rilsa/quellen';
21 import { ueberfahrzeitAnsatzRichtungSatz, ueberfahrzeitVerkuerzung } from '@/domain/rilsa/ansaetze';
22 import {
23 aufteilungAusLastzuganteil,
24 istLastzuganteil,
25 lastzuganteilSpanne,
26 schwerverkehrsfaktor,
27 wirksamerSchwerverkehrsanteil,
28 } from '@/domain/rilsa/capacity';
29 import { hatSaettigungsverkehrsstaerke } from '@/domain/plan/signalPlan';
30 import {
31 resolveRilsaSettings,
32 settingFields,
33 wirkungslosigkeit,
34 type RilsaOverrides,
35 type SettingDeviation,
36 type WirkungsKontext,
37 } from '@/domain/rilsa/settings';
38 import type {
39 Anlagenart,
40 Bewertungsverfahren,
41 ServiceLevel,
42 UeberfahrzeitAnsatz,
43 } from '@/domain/rilsa/types';
44 import type { Project, TrafficDemand } from '@/domain/model/project';
45 import { roundTo, type Ratio } from '@/domain/units';
46 import * as fmt from '@/ui/format';
47
48 /**
49 * Benennung von Bewertungsverfahren, Kriterium und Stufe fuer Tabelle und
50 * Ausdruck.
51 *
52 * Steht hier und nicht in pdf.ts, weil beide Ausgabewege dieselben Worte
53 * fuehren muessen (Vorbild: wegherkunft.ts). Vor Fassung 5.4.0
54 * (Befunde B1 bis B4) schrieb der Ausdruck "Bewertung nach HBS 2015", die
55 * Tabelle nur "HBS" - und beides fuer eine Rechnung, die in Wahrheit die
56 * HCM-Formel war. Jetzt gibt es zwei Verfahren, die auch rechnen, was sie
57 * behaupten; die Benennung darf deshalb nirgends mehr frei formuliert werden.
58 *
59 * Die Woerter selbst stehen im Fachkern (constants.ts:
60 * BEWERTUNGSVERFAHREN_LABELS, KRITERIUM_LABELS) und werden hier nur
61 * durchgereicht: Vor der Fassung 5.4.0 fuehrte diese Datei eine zweite, eigene
62 * Tafel derselben Woerter - zwei Quellen fuer dieselbe Benennung sind genau die
63 * Stelle, an der Bildschirm und Ausdruck unbemerkt auseinanderlaufen.
64 */
65
66 export { KRITERIUM_LABELS };
67
68 /** Name des Bewertungsverfahrens, wie er in Ausdruck und Tabelle steht. */
69 export function verfahrenLabel(verfahren: Bewertungsverfahren): string {
70 return BEWERTUNGSVERFAHREN_LABELS[verfahren];
71 }
72
73 /**
74 * Stufe fuer eine Tabellenzelle. Bei Ueberlastung steht der Grund dabei: Nach
75 * HBS 2015 ist F fuer Kfz keine Wartezeitstufe, sondern gilt genau dann, wenn
76 * q > C - ohne den Zusatz liesse sich das "F" neben einer Wartezeit von 40 s
77 * nicht mit der gedruckten Stufentafel in Einklang bringen.
78 */
79 export function stufeZelle(level: ServiceLevel | null, leer = '–'): string {
80 if (level === null) return leer;
81 return level.kriterium === 'ueberlastung' ? `${level.grade} (überlastet)` : level.grade;
82 }
83
84 /**
85 * Stufentafel als Satzteil, aus denselben Konstanten, mit denen bewertet
86 * wird: "A bis 20 s, B bis 35 s, C bis 50 s, D bis 70 s, E darüber".
87 *
88 * Nicht ausgeschrieben, weil ausgeschriebene Zahlen die Stelle sind, an der
89 * ein Ausdruck unbemerkt veraltet: Vor Fassung 5.4.0 (Befund B1)
90 * stand die Kfz-Tafel des HBS 2001 unter der Ueberschrift "HBS 2015".
91 */
92 export function stufentafelSatz(tafel: readonly ServiceLevelStep[]): string {
93 return tafel
94 .map((stufe) =>
95 Number.isFinite(stufe.maxDelay)
96 ? `${stufe.grade} bis ${fmt.seconds(stufe.maxDelay)}`
97 : `${stufe.grade} darüber`,
98 )
99 .join(', ');
100 }
101
102 /** Die Tafeln des Verfahrens, benannt fuer den Ausdruck. */
103 export function stufentafeln(verfahren: Bewertungsverfahren): {
104 readonly kfz: string;
105 readonly oepnv: string;
106 readonly fussRad: string | null;
107 } {
108 if (verfahren === 'HBS') {
109 return {
110 kfz: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.kfz),
111 oepnv: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.oepnv),
112 fussRad: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.fussRad),
113 };
114 }
115 const kfz = stufentafelSatz(SERVICE_LEVEL_SCALES.HCM.kfz);
116 // Das HCM-Verfahren dieses Programms hat fuer Fussgaenger und Radverkehr
117 // keine Tafel (delay.ts, serviceLevelFor liefert null).
118 return { kfz, oepnv: kfz, fussRad: null };
119 }
120
121 /**
122 * Herkunft, an der kein Vorgabenfeld haengt.
123 *
124 * Das Fundstellenverzeichnis (Ausdruck und Vorgabenverwaltung) entsteht aus
125 * settingFieldsByQuelle - es kennt also nur Quellen, zu denen ein anpassbarer
126 * Kennwert gehoert. Die Stufentafeln der Qualitaetsstufen sind kein solcher
127 * Kennwert: Sie sind fest hinterlegt und nicht einstellbar. Die Quelle
128 * 'hbs-qualitaetsstufen' erschien deshalb vor der Fassung 5.4.0 NIRGENDS: Der
129 * Ausdruck druckte die Tafeln im Leistungsabschnitt, aber im Nachweis der
130 * Kennwerte hatten sie weder Herkunft noch Pruefstand - und gerade die OePNV-
131 * und Fuss/Rad-Tafeln sind nur ueber Sekundaerquellen belegt und am Original
132 * abzugleichen.
133 *
134 * Diese Liste ergaenzt das Verzeichnis um solche Quellen. Sie steht hier und
135 * nicht in settings.ts, weil sie keine Vorgaben beschreibt, sondern Kennwerte,
136 * die nur ausgegeben werden; kommt eine weitere feldlose Quelle hinzu, bekommt
137 * sie hier einen Eintrag und erscheint damit in beiden Verzeichnissen.
138 */
139 export interface QuelleOhneFeld {
140 readonly schluessel: QuellenSchluessel;
141 readonly quelle: Quelle;
142 /** Was die Quelle beisteuert - an Stelle der Kennwertliste einer Vorgabengruppe. */
143 readonly bezeichnung: string;
144 /**
145 * Die Werte, mit denen TATSAECHLICH gerechnet wird, aus den Konstanten des
146 * Fachkerns - nicht ausgeschrieben, aus demselben Grund wie in stufentafelSatz.
147 */
148 readonly kennwerte: readonly { readonly label: string; readonly wert: string }[];
149 }
150
151 export function quellenOhneFeld(
152 verfahren: Bewertungsverfahren = 'HBS',
153 anlagenart: Anlagenart = 'knotenpunkt',
154 // Die Kennwerte, mit denen gerechnet wird - nicht die Regelwerte. Siehe
155 // vwvStvoEintrag (Befund C21 im gedruckten Nachweis).
156 defaults: RilsaDefaults = RILSA_DEFAULTS,
157 /**
158 * Wird der Verfahrensvergleich in DIESER Unterlage gedruckt?
159 *
160 * Nur dann gehoert seine Regel in das Verzeichnis. Dieselbe Ueberlegung wie
161 * bei der RSA 21: Was in keine gedruckte Zahl eingeht, gehoert nicht in den
162 * Nachweis dieses Projekts - ein Verzeichnis, das alles auffuehrt, sagt
163 * nichts darueber, worauf diese Unterlage beruht.
164 *
165 * Die Vorgabenansicht ruft ohne dieses Kennzeichen: Dort steht die Herkunft
166 * der VORGABEN, und die Vergleichsregel ist keine.
167 */
168 mitVerfahrensvergleich = false,
169 ): readonly QuelleOhneFeld[] {
170 const eintraege: QuelleOhneFeld[] = [stufentafelnEintrag(verfahren)];
171
172 /*
173 * DIE REGEL DES VERFAHRENSVERGLEICHS.
174 *
175 * Sie stand frueher unbelegt im Quelltext, und das fiel nicht auf, weil
176 * niemand sie sah: Der Vergleich hatte keinen Aufrufer. Seit sein Ergebnis
177 * in der Planunterlage steht, gilt derselbe Satz wie fuer jede andere Zahl
178 * darin - die Unterlage sagt, woher sie kommt.
179 */
180 if (mitVerfahrensvergleich) eintraege.push(verfahrensvergleichEintrag());
181
182 /*
183 * VwV-StVO und RSA 21 (Befunde C2, C3, C5, C6): Beide bekamen einen Eintrag
184 * in QUELLEN, aber zunaechst keinen Weg ins Verzeichnis - das kennt nur
185 * Vorgabenfelder und diese Liste, und an beiden haengt kein Vorgabenfeld. Die
186 * Unterlagen behaupteten "neuer Eintrag VwV-StVO im Fundstellenverzeichnis",
187 * und im Ausdruck stand er nicht. Die VwV-StVO gilt fuer jede Anlagenart; die
188 * RSA nur bei einstreifiger Verkehrsfuehrung - an einem Knotenpunkt geht
189 * keiner ihrer Anhaltswerte in eine Meldung ein und gehoert deshalb nicht in
190 * den Nachweis dieses Projekts.
191 */
192 eintraege.push(vwvStvoEintrag(defaults));
193 if (anlagenart === 'einstreifig') eintraege.push(rsaEintrag());
194
195 /*
196 * DIE GEOMETRISCHEN ANPASSUNGSFAKTOREN. Sie stehen zuletzt, weil sie als
197 * einzige der Liste jede Anlagenart und jedes Bewertungsverfahren betreffen:
198 * Sie wirken auf jede Saettigungsverkehrsstaerke - auch dort, wo keine
199 * Zufahrt vermessen ist, denn dann steht der Faktor 1 fuer eine ANGENOMMENE
200 * Standardbedingung und nicht fuer eine gemessene. Genau das gehoert in den
201 * Nachweis: Wer die Unterlage prueft, muss erfahren, dass die 2000 Kfz/h eine
202 * Voraussetzung haben.
203 */
204 eintraege.push(geometriefaktorenEintrag());
205
206 /*
207 * DER STAURAUMBEDARF JE FAHRZEUG. Seit der Fassung 5.31.0 druckt die
208 * Unterlage eine Rueckstaulaenge, und die entsteht aus einem Praxiswert, den
209 * kein Regelwerk nennt. Eine Laenge ohne Herkunft in einer
210 * Anordnungsunterlage ist genau das, was dieses Verzeichnis verhindern soll.
211 */
212 eintraege.push(stauraumbedarfEintrag());
213 return eintraege;
214 }
215
216 /** Die Regel, nach der der Verfahrensvergleich einen Wert benennt. */
217 function verfahrensvergleichEintrag(): QuelleOhneFeld {
218 return {
219 schluessel: 'programm-umlaufzeitvergleich',
220 quelle: QUELLEN['programm-umlaufzeitvergleich'],
221 bezeichnung: 'Regel des Verfahrensvergleichs (keine Empfehlung, kein Regelwerkswert)',
222 kennwerte: [
223 {
224 label: 'Maßgebender Wert',
225 wert:
226 'der größere aus kapazitätsorientiertem Bedarf (HBS bzw. HCM, der größere) und ' +
227 'Wartezeitoptimum (Webster bzw. Akçelik, der kleinere)',
228 },
229 {
230 label: 'Wirkung auf den Signalzeitenplan',
231 wert: 'keine – gerechnet wird ausschließlich das unter „Phasen" gewählte Verfahren',
232 },
233 ],
234 };
235 }
236
237 /** Die Laenge, die ein wartendes Fahrzeug im Rueckstau belegt. */
238 function stauraumbedarfEintrag(): QuelleOhneFeld {
239 return {
240 schluessel: 'praxis-stauraum-je-fahrzeug',
241 quelle: QUELLEN['praxis-stauraum-je-fahrzeug'],
242 bezeichnung: 'Rückstaulänge aus dem mittleren Rückstau bei Freigabezeitende',
243 kennwerte: [
244 {
245 label: 'Länge je wartendem Fahrzeug',
246 wert: `${fmt.meters(STAURAUMBEDARF_JE_FAHRZEUG, 0)} (Fahrzeug und Abstand)`,
247 },
248 {
249 label: 'Verglichene Größe',
250 wert:
251 'der MITTLERE Rückstau NGE über die Betrachtungsstunde – in etwa der Hälfte der ' +
252 'Umläufe steht mehr; ein 95-Prozent-Wert wird nicht gebildet',
253 },
254 ],
255 };
256 }
257
258 /** Die geometrischen Anpassungsfaktoren des Zeitbedarfswerts. */
259 function geometriefaktorenEintrag(): QuelleOhneFeld {
260 const { fahrstreifenbreiteAb, kurvenradiusAb, laengsneigungBis } = HBS_STANDARDBEDINGUNGEN;
261 return {
262 schluessel: 'hbs-geometriefaktoren',
263 quelle: QUELLEN['hbs-geometriefaktoren'],
264 bezeichnung: 'Geometrie der Zufahrt (Fahrstreifenbreite, Kurvenradius, Längsneigung)',
265 kennwerte: [
266 {
267 label: 'Standardbedingungen (Faktor 1)',
268 wert:
269 `Breite ab ${fmt.numShort(fahrstreifenbreiteAb, 2)} m, Radius ab ` +
270 `${fmt.numShort(kurvenradiusAb, 0)} m, Längsneigung zwischen ` +
271 `−${fmt.numShort(laengsneigungBis, 0)} % und ${fmt.numShort(laengsneigungBis, 0)} %`,
272 },
273 {
274 label: 'Anpassungsfaktoren',
275 wert:
276 `fb = ${fmt.numShort(HBS_GEOMETRIE.breite.steigung, 3)} · b + ` +
277 `${fmt.numShort(HBS_GEOMETRIE.breite.achsenabschnitt, 3)}; ` +
278 `fR = ${fmt.numShort(HBS_GEOMETRIE.radius.steigung, 3)} · R + ` +
279 `${fmt.numShort(HBS_GEOMETRIE.radius.achsenabschnitt, 1)}; ` +
280 `fs = ${fmt.numShort(HBS_GEOMETRIE.neigung.steigung, 2)} · s + 1; ` +
281 'angesetzt wird max(1; max(fb, fR, fs) · min(1; fs))',
282 },
283 ],
284 };
285 }
286
287 /** Stufentafeln der Qualitaetsstufen des gewaehlten Verfahrens. */
288 function stufentafelnEintrag(verfahren: Bewertungsverfahren): QuelleOhneFeld {
289 const mittlere = KRITERIUM_LABELS['mittlere-wartezeit'];
290 const maximale = KRITERIUM_LABELS['maximale-wartezeit'];
291 // Nach HBS 2015 ist F fuer Kfz keine Wartezeitstufe (E ist nach oben offen),
292 // fuer OePNV eine Wartezeitstufe UND die Folge der Ueberlastung; die Tafel
293 // allein liesse das F der Leistungstabelle unerklaert.
294 const fUeberlastung = `F bei ${KRITERIUM_LABELS.ueberlastung}`;
295 if (verfahren === 'HCM') {
296 // Im HCM-Verfahren wird nach der HCM-Tafel bewertet; die HBS-Tafeln
297 // gehen in keine Zahl ein und gehoeren deshalb nicht in den Nachweis
298 // dieses Projekts.
299 return {
300 schluessel: 'hcm-verfahren',
301 quelle: QUELLEN['hcm-verfahren'],
302 bezeichnung: `Level of Service (Kfz/ÖPNV: ${mittlere}; Fuß/Rad: keine Tafel)`,
303 kennwerte: [
304 {
305 label: `Stufentafel Kfz und ÖPNV (${mittlere})`,
306 wert: `${stufentafelSatz(SERVICE_LEVEL_SCALES.HCM.kfz)}; ${fUeberlastung}`,
307 },
308 ],
309 };
310 }
311 return {
312 schluessel: 'hbs-qualitaetsstufen',
313 quelle: QUELLEN['hbs-qualitaetsstufen'],
314 bezeichnung: `Stufentafeln der Qualitätsstufen (Kfz/ÖPNV: ${mittlere}; Fuß/Rad: ${maximale})`,
315 kennwerte: [
316 {
317 label: `Stufentafel Kfz (${mittlere})`,
318 wert: `${stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.kfz)}; ${fUeberlastung}`,
319 },
320 {
321 label: `Stufentafel ÖPNV (${mittlere})`,
322 wert: `${stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.oepnv)}; ${fUeberlastung}`,
323 },
324 {
325 label: `Stufentafel Fußgänger und Radverkehr (${maximale})`,
326 wert: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.fussRad),
327 },
328 ],
329 };
330 }
331
332 /**
333 * Gelbzeitstaffel als Satzteil aus den Vorgaben, mit denen gerechnet wird:
334 * "3 s bis 50 km/h, 4 s bis 60 km/h, 5 s bis 70 km/h". Die letzte Stufe der
335 * RiLSA ist nach oben offen; hier endet sie bei der Hoechstgeschwindigkeit,
336 * bis zu der Lichtsignalanlagen eingerichtet werden sollen - darueber gilt im
337 * Programm dieselbe Gelbzeit, aber die VwV-StVO sieht dort keine Anlage vor.
338 *
339 * KORREKTUR (Befund C21): Genau dieser Kommentar stand schon hier, der
340 * Quelltext nahm aber RILSA_DEFAULTS - der Kommentar sagte das Gegenteil des
341 * Codes. Die Felder `yellowKfzUpTo50/UpTo60/Above60` sind einstellbar; wer sie
342 * aendert, bekam im Fundstellenverzeichnis der Planunterlage weiter die
343 * Regelstaffel gedruckt.
344 *
345 * WARUM EXPORTIERT: Derselbe Satz steht als Feldhinweis am vZul-Feld
346 * (projectView). Zwei Stellen, die dieselbe Staffel unabhaengig voneinander
347 * zusammensetzen, laufen genau dann auseinander, wenn niemand daran denkt.
348 */
349 export function gelbzeitstaffelSatz(defaults: RilsaDefaults = RILSA_DEFAULTS): string {
350 return defaults.yellowBySpeed
351 .map((stufe) =>
352 Number.isFinite(stufe.upToKmh)
353 ? `${fmt.seconds(stufe.yellow)} bis ${fmt.numShort(stufe.upToKmh)} km/h`
354 : `${fmt.seconds(stufe.yellow)} bis ${fmt.numShort(VZUL_OBERGRENZE_LSA)} km/h`,
355 )
356 .join(', ');
357 }
358
359 /**
360 * Welcher Ueberfahrzeit-Ansatz gerechnet wurde - und was daran haengt
361 * (Fassung 5.5.0, Frage E2).
362 *
363 * Der Ansatz aendert JEDE Zwischenzeit eines Kfz-Raeumstroms. Eine
364 * Zwischenzeitenmatrix ohne diese Angabe laesst sich nicht nachrechnen: Bei
365 * 60 km/h und abbiegendem Strom stehen je nach Ansatz 4 s oder 2 s
366 * Ueberfahrzeit hinter derselben Zahl.
367 *
368 * WARUM DER SATZ IN BEIDEN ZWEIGEN DIE RICHTUNG NENNT: Die feste Ueberfahrzeit
369 * VERKUERZT die Zwischenzeit gegenueber dem Regelansatz dieses Programms - bei
370 * den Regelwerten. Eine Verkuerzung, die in der Unterlage nur als Name eines
371 * Ansatzes erscheint, ist vor der Behoerde keine ausgewiesene Abweichung - sie
372 * sieht aus wie eine Einstellung. Deshalb stehen Richtung, Groessenordnung und
373 * Herkunft der Werte im selben Satz, und die NICHT eingebaute Untergrenze steht
374 * dabei: Wer sie braucht, muss wissen, dass dieses Programm sie nicht setzt.
375 *
376 * Die beiden Zahlen kommen aus den aufgeloesten Vorgaben und nicht aus dem
377 * Satz (Befund C21): Sie sind einstellbar, und ein Ausdruck, der 3 s und 2 s
378 * behauptet, waehrend die Rechnung mit anderen Werten lief, ist schlimmer als
379 * gar keine Angabe.
380 *
381 * NACHGEZOGEN (Fassung 5.10.0): Dieser Docblock begruendete nur den Zweig
382 * 'fest', und nur dort kamen Richtung und Groessenordnung aus dem Fachkern. Der
383 * Regelzweig - der in fast jeder Planunterlage steht - trug beides fest
384 * eingeschrieben im Text, obwohl die festen Ueberfahrzeiten auch bei ihm
385 * einstellbar sind und die Gelbzeitstaffel ebenso. Beide Zweige bilden Richtung
386 * und Herkunft jetzt aus den wirksamen Vorgaben (regelansatzVergleichSatz,
387 * festeWerteHerkunftSatz).
388 */
389 export function ueberfahrzeitAnsatzSatz(
390 ansatz: UeberfahrzeitAnsatz,
391 defaults: RilsaDefaults = RILSA_DEFAULTS,
392 ): string {
393 const geradeaus = fmt.seconds(defaults.crossingTime.kfzGeradeaus);
394 const abbiegend = fmt.seconds(defaults.crossingTime.kfzAbbiegend);
395 const feste = `${geradeaus} geradeaus und ${abbiegend} abbiegend`;
396
397 if (ansatz === 'gelbzeit') {
398 // EINE Antwort fuer beide Saetze: Der Herkunftssatz entscheidet, ob die
399 // genannten Zahlen Leitfaden- oder Vorgabewerte sind, der Vergleichssatz
400 // daneben nennt sie beim selben Namen.
401 const nachLeitfaden = festeWerteNachLeitfaden(defaults);
402 return (
403 'Überfahrzeit des Kraftfahrzeugverkehrs: Gerechnet wurde mit dem Ansatz ' +
404 `„${UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit}" – dem Regelansatz dieses Programms. Angesetzt ` +
405 `ist damit die Gelbzeit dieser Signalgruppe (${gelbzeitstaffelSatz(defaults)}). ` +
406 // Herkunft UND Richtung aus den wirksamen Vorgaben (Fassung 5.10.0) - wie
407 // im Zweig 'fest' eine Zeile weiter unten. Hier stand beides fest
408 // eingeschrieben im Satz.
409 `${festeWerteHerkunftSatz(feste, nachLeitfaden)} ` +
410 `${regelansatzVergleichSatz(defaults, nachLeitfaden)}`
411 );
412 }
413
414 /*
415 * "nie kürzer" und nicht mehr "länger" (Fassung 5.43.0): Bei 50 km/h
416 * geradeaus ergeben beide Ansaetze 3 s. Wahr bei jeder zulaessigen
417 * Gelbzeitstaffel ist nur "nie kürzer", und auch das nur gegen die Regelwerte
418 * der RiLSA: SAFETY_FLOORS.yellowKfz liegt nicht unter ihnen, wohl aber unter
419 * jeder festen Vorgabe ueber 3 s.
420 *
421 * NACHGEZOGEN (Fassung 5.43.0): Hier stand der Satz unbedingt -
422 * "Die Werte sind die Regelwerte der RiLSA 2015 ...; ... rechnet nie kürzer
423 * als diese" auch bei 4 s abbiegend, wo die Gelbzeit bei 50 km/h 3 s
424 * betraegt, und bei 6 s / 6 s unmittelbar hinter dem Satz, die Werte wichen
425 * von den Regelwerten ab. Beide Aussagen haengen jetzt an derselben Antwort
426 * wie im Zweig 'gelbzeit' (festeWerteNachLeitfaden).
427 */
428 const nachLeitfaden = festeWerteNachLeitfaden(defaults);
429 const zuschreibung = nachLeitfaden
430 ? 'Die Werte sind die Regelwerte der RiLSA 2015 (Abschnitt 2.5.2, Fälle 1 und 2); der ' +
431 'Regelansatz dieses Programms – die Kopplung an die Gelbzeit – rechnet nie kürzer als diese.'
432 : `Die Werte sind Vorgaben dieses Plans, nicht die ${rilsaFesteWerte()} der RiLSA 2015 ` +
433 '(Abschnitt 2.5.2, Fälle 1 und 2); der Regelansatz dieses Programms – die Kopplung an die ' +
434 'Gelbzeit – rechnet nie kürzer als die Regelwerte der RiLSA 2015.';
435 return (
436 'Überfahrzeit des Kraftfahrzeugverkehrs: Gerechnet wurde mit dem Ansatz ' +
437 `„${UEBERFAHRZEIT_ANSATZ_LABELS.fest}" – angesetzt sind ${feste}, unabhängig von der ` +
438 // Richtung UND Groessenordnung aus den wirksamen Vorgaben: Beide festen
439 // Ueberfahrzeiten sind einstellbar, "bis zu 3 s" galt nur fuer ihre
440 // Regelwerte.
441 `zulässigen Höchstgeschwindigkeit. ${ueberfahrzeitAnsatzRichtungSatz(defaults)} ` +
442 `${zuschreibung} Die Untergrenze der Überfahr- und Räumzeit (tü + tr mindestens ` +
443 'Gelbzeit + 1 s, RiLSA 2015, Abschnitt 2.5.2) ' +
444 'gilt bei beiden Ansätzen und ist eingebaut; wo sie greift, nennt der Rechenweg sie.'
445 );
446 }
447
448 /** "3 s geradeaus und 2 s abbiegend" - die festen Ueberfahrzeiten aus constants.ts. */
449 function rilsaFesteWerte(): string {
450 return (
451 `${fmt.seconds(RILSA_DEFAULTS.crossingTime.kfzGeradeaus)} geradeaus und ` +
452 `${fmt.seconds(RILSA_DEFAULTS.crossingTime.kfzAbbiegend)} abbiegend`
453 );
454 }
455
456 /**
457 * Entsprechen die beiden festen Ueberfahrzeiten dieses Plans den Regelwerten
458 * aus constants.ts?
459 *
460 * Zwei Saetze haengen daran: die Zuschreibung an die RiLSA und das Bezugswort
461 * des Vergleichssatzes daneben. Sie muessen dieselbe Antwort bekommen - sonst
462 * nennt der eine "Vorgabewerte", was der andere gerade dem Regelwerk
463 * zugeschrieben hat.
464 *
465 * NACHGEZOGEN (5.27.0): Der Parametername `nachLeitfaden` und die Saetze
466 * sprachen von behoerdlichen Leitfaeden, weil die beiden Zahlen bis dahin nur
467 * dort belegt waren. Sie stehen in der RiLSA 2015 (Abschnitt 2.5.2, Faelle 1
468 * und 2); der Leitfaden bleibt eine zweite, uebereinstimmende Quelle.
469 */
470 function festeWerteNachLeitfaden(defaults: RilsaDefaults): boolean {
471 return (
472 defaults.crossingTime.kfzGeradeaus === RILSA_DEFAULTS.crossingTime.kfzGeradeaus &&
473 defaults.crossingTime.kfzAbbiegend === RILSA_DEFAULTS.crossingTime.kfzAbbiegend
474 );
475 }
476
477 /**
478 * Woher die daneben genannten festen Ueberfahrzeiten stammen.
479 *
480 * KORREKTUR (Fassung 5.10.0): Der Regelzweig schrieb die eingesetzten Zahlen
481 * unbedingt behoerdlichen Leitfaeden zu (seit 5.27.0: der RiLSA). Belegt sind
482 * ausschliesslich die Regelwerte aus constants.ts (RiLSA 2015, Abschnitt
483 * 2.5.2); die Felder crossingTimeKfzGeradeaus und crossingTimeKfzAbbiegend sind
484 * von 1 bis 10 s einstellbar und bleiben es auch beim Ansatz 'gelbzeit' - die
485 * Vorgabenverwaltung vermerkt dort nur die Wirkungslosigkeit. Ein Pruefer las
486 * damit "6 s geradeaus" als Leitfadenwert: eine falsche Tatsachenbehauptung
487 * ueber ein Regelwerk in der Planunterlage.
488 *
489 * Weicht der Plan ab, stehen beide Zahlenpaare da - das eingetragene und das
490 * belegte. Der Pruefer soll die Abweichung sehen, nicht nur ihr Ergebnis.
491 */
492 function festeWerteHerkunftSatz(feste: string, nachLeitfaden: boolean): string {
493 if (nachLeitfaden) {
494 return `Die RiLSA 2015 setzt in Abschnitt 2.5.2 feste Werte an (${feste}).`;
495 }
496
497 return (
498 `Die Vorgaben dieses Plans setzen für den Ansatz „feste Überfahrzeit" daneben ${feste} an – ` +
499 `nicht die ${rilsaFesteWerte()} der RiLSA 2015 (Abschnitt 2.5.2).`
500 );
501 }
502
503 /**
504 * Wie der Regelansatz gegen die daneben genannten festen Ueberfahrzeiten
505 * ausfaellt - aus den wirksamen Vorgaben gebildet, nicht aus dem Satz.
506 *
507 * KORREKTUR (Fassung 5.10.0): Hier stand "gegen sie faellt dieser Ansatz beim
508 * Abbiegen und bei 60 und 70 km/h laenger aus und ist damit die konservative
509 * Seite" - fest eingeschrieben, obwohl BEIDE Vergleichsseiten einstellbar sind:
510 * die festen Ueberfahrzeiten von 1 bis 10 s, die Gelbzeitstaffel ueber
511 * yellowKfzUpTo50/UpTo60/Above60. Mit 6 s geradeaus und 6 s abbiegend liegt die
512 * groesste Gelbzeit (5 s) darunter; der Satz behauptete die konservative Seite
513 * gegen zwei Zahlen, die er selbst nannte. Auch die Schwellen "60 und 70 km/h"
514 * galten nur fuer die Regelstaffel. Derselbe Fehlertyp ist fuer den Zweig
515 * 'fest' laengst behoben (ansaetze.ts, ueberfahrzeitAnsatzRichtungSatz); der
516 * Regelzweig war dabei uebersehen worden.
517 *
518 * Bezugsgroessen sind die KLEINSTE und die GROESSTE Gelbzeit der Staffel: Der
519 * Ansatz faellt dort am laengsten aus, wo die Gelbzeit am laengsten ist, und
520 * dort am kuerzesten, wo sie am kuerzesten ist. "Die konservative Seite" steht
521 * nur, wenn er NIRGENDS kuerzer ausfaellt - eine laengere Ueberfahrzeit heisst
522 * eine laengere Zwischenzeit (tz = tue + tr - te).
523 *
524 * DAS BEZUGSWORT STEHT AUSGESCHRIEBEN: Hier begann der Satz mit "Gegen sie" und
525 * haengte damit am vorangehenden Herkunftssatz. Weicht der Plan ab, nennt der
526 * zwei Zahlenpaare - die eingetragenen und die belegten -, und "sie" zeigte auf
527 * das zuletzt genannte, also auf die Leitfadenwerte, gegen die hier gar nicht
528 * gerechnet wird. Der Satz sagt jetzt allein stehend, welches Zahlenpaar der
529 * Massstab ist.
530 */
531 function regelansatzVergleichSatz(defaults: RilsaDefaults, nachLeitfaden: boolean): string {
532 // Die laengere Haelfte kommt aus dem Fachkern: `ueberfahrzeitVerkuerzung`
533 // rechnet groesste Gelbzeit minus feste Ueberfahrzeit - dieselbe Groesse, die
534 // der Zweig 'fest' als Verkuerzung ausweist, hier aus der Gegenrichtung
535 // gelesen. Sie stand hier ein zweites Mal, samt eigenem Rueckfall bei leerer
536 // Gelbzeitstaffel.
537 const { abbiegend, geradeaus } = ueberfahrzeitVerkuerzung(defaults);
538 const laenger = vergleichsspanne(abbiegend, geradeaus);
539
540 /*
541 * VORLAEUFIG AN DIESER STELLE: Die gespiegelte Groesse (feste Ueberfahrzeit
542 * minus KLEINSTE Gelbzeit) und der Satzbau dieses Zweigs gehoeren nach
543 * src/domain/rilsa/ansaetze.ts neben `ueberfahrzeitAnsatzRichtungSatz` -
544 * dort steht die Richtung der beiden Ansaetze an EINER Stelle, und der
545 * Bildschirm (projectView.ts) kann eine private Funktion dieser Datei nicht
546 * erreichen. ansaetze.ts stand fuer diese Aenderung nicht zur Verfuegung;
547 * solange die Verlegung aussteht, ist dies die zweite Fassung derselben
548 * Aussage - genau die Lage, gegen die der Docblock von ansaetze.ts steht.
549 */
550 const kleinste = kleinsteGelbzeit(defaults);
551 const kuerzer = vergleichsspanne(
552 defaults.crossingTime.kfzAbbiegend - kleinste,
553 defaults.crossingTime.kfzGeradeaus - kleinste,
554 );
555
556 const bezug = nachLeitfaden ? 'Gegen diese Regelwerte' : 'Gegen diese Vorgabewerte';
557 if (kuerzer === '') {
558 if (laenger === '') return `${bezug} fällt dieser Ansatz weder länger noch kürzer aus.`;
559 return (
560 `${bezug} fällt dieser Ansatz ${laenger} länger aus und nirgends kürzer – er ist damit ` +
561 'die konservative Seite.'
562 );
563 }
564 return (
565 `${bezug} fällt dieser Ansatz ${kuerzer} kürzer aus` +
566 (laenger === '' ? '' : ` und ${laenger} länger`) +
567 '; die konservative Seite ist er damit nicht.'
568 );
569 }
570
571 /**
572 * Kleinste Gelbzeit der Staffel - der Wert, gegen den der Regelansatz am
573 * kuerzesten ausfaellt.
574 *
575 * Gespiegelt zu `hoechsteGelbzeit` in ansaetze.ts, samt dessen Rueckfall auf
576 * die Regelstaffel: Eine leere Staffel darf hier keine Verkuerzung "um bis zu
577 * 6 s" behaupten. Gehoert mit der Funktion darueber in den Fachkern.
578 */
579 function kleinsteGelbzeit(defaults: RilsaDefaults): number {
580 const werte = defaults.yellowBySpeed.map((stufe) => stufe.yellow);
581 if (werte.length > 0) return Math.min(...werte);
582 return Math.min(...RILSA_DEFAULTS.yellowBySpeed.map((stufe) => stufe.yellow));
583 }
584
585 /**
586 * "beim Abbiegen um bis zu 3 s und geradeaus um bis zu 2 s" - leer, wo in
587 * dieser Richtung nichts bleibt. Gerundet wird vor dem Vergleich, damit eine
588 * Differenz von 1e-15 keine Richtung behauptet.
589 *
590 * Zweite Fassung des Satzteils aus `ueberfahrzeitAnsatzRichtungSatz`
591 * (ansaetze.ts) und mit `fmt.numShort` statt dessen `formatZahl` gebildet -
592 * bei Ueberfahrzeiten von 1 bis 10 s liefern beide dieselbe Zeichenfolge. Auch
593 * sie gehoert in den Fachkern, siehe die Funktion darueber.
594 */
595 function vergleichsspanne(abbiegend: number, geradeaus: number): string {
596 const teile: string[] = [];
597 if (roundTo(abbiegend, 2) > 0) {
598 teile.push(`beim Abbiegen um bis zu ${fmt.numShort(abbiegend, 2)} s`);
599 }
600 if (roundTo(geradeaus, 2) > 0) {
601 teile.push(`geradeaus um bis zu ${fmt.numShort(geradeaus, 2)} s`);
602 }
603 return teile.join(' und ');
604 }
605
606 /**
607 * Wie das Merkmal "enger Innenradius" in einer Tabellenzelle steht - und
608 * warum es ueberhaupt eine eigene Spalte bekommt (Fassung 5.5.0, Frage E1).
609 *
610 * Das Merkmal steht an der einzelnen Konfliktbeziehung und senkt dort die
611 * Raeumgeschwindigkeit des abbiegenden Kraftfahrzeugs. In der gedruckten
612 * Zeile steht sonst nur die Zahl - 5,0 m/s neben einem Regelwert von 7,0 m/s,
613 * ohne erkennbaren Grund. Genau diese Luecke war der Befund hinter der Spalte
614 * "Schwerverkehr" der Leistungstabelle.
615 *
616 * ES STEHT DA, WAS ERFASST IST - NICHT, WAS GEWIRKT HAT: Die Zelle gibt das
617 * Merkmal der Beziehung wieder. Ob es die Rechnung geaendert hat, haengt am
618 * raeumenden Strom (nur abbiegende Kraftfahrzeuge); ist es ohne Wirkung
619 * gesetzt, meldet das der Pruefbericht als eigene Warnung
620 * ('zwischenzeiten.enger-radius-ohne-wirkung'). Die Ausgabe hier die Wirkung
621 * ein zweites Mal herleiten zu lassen, hiesse eine Regel des Fachkerns zu
622 * verdoppeln.
623 */
624 export function engerRadiusZelle(gesetzt: boolean | undefined, leer = '–'): string {
625 return gesetzt === true ? ENGER_RADIUS_LABEL : leer;
626 }
627
628 /**
629 * Was Ausdruck und Tabellenausgabe ueber den Schwerverkehr EINES Stroms sagen:
630 * der Faktor fSV, die Gleichung, die ihn gebildet hat, und die Datenlage, die
631 * ueber diese Gleichung entschieden hat.
632 *
633 * NEU (Schema 13). Bis dahin gab es hier nichts, weil es nichts zu entscheiden
634 * gab: Das Datenmodell kannte keine Aufteilung des Schwerverkehrs, jeder Strom
635 * lief ueber Gl. 2-6, und beide Ausgabewege bildeten den Faktor mit einem
636 * eigenen Aufruf `schwerverkehrsfaktor(anteil)`. Seit die Aufteilung erfassbar
637 * ist, ist genau das falsch: Der Plan rechnet mit Gl. 2-5, waehrend Ausdruck
638 * und Tabelle weiter den Pauschalwert drucken - bei 20 % Schwerverkehr aus
639 * lauter Lastzuegen stand dann fSV = 1,180 neben einer
640 * Saettigungsverkehrsstaerke von 1538 Fz/h, die zu 1,300 gehoert. Der
641 * Anpassungsfaktor wird deshalb hier gebildet, aus derselben Funktion und mit
642 * denselben Eingangsgroessen wie in plan/signalPlan.ts (groupSaturationFlow) -
643 * eine Stelle, zwei Ausgaben.
644 *
645 * DIE GLEICHUNG STEHT DABEI, NICHT NUR DER FAKTOR: Zwei Stroeme mit demselben
646 * Schwerverkehrsanteil koennen jetzt verschiedene Faktoren tragen, und das ist
647 * kein Fehler, sondern der Unterschied zwischen gezaehlt und nicht gezaehlt.
648 * Ohne die Angabe der Gleichung liesse sich die Unterlage nicht nachrechnen -
649 * der Pruefer kaeme mit dem Pauschalwert auf eine andere Zahl und wuesste
650 * nicht, warum.
651 */
652 export interface Schwerverkehrsangabe {
653 /** Anpassungsfaktor fSV, mit dem der Plan diesen Strom rechnet. */
654 readonly fsv: Ratio;
655 /** Rechnet dieser Strom nach Gl. 2-5 (erfasste Aufteilung)? */
656 readonly mitAufteilung: boolean;
657 /** Kurzform fuer eine Tabellenzelle: "Gl. 2-5" bzw. "Gl. 2-6". */
658 readonly gleichungKurz: string;
659 /** Die Datenlage in einer Zeile - fuer die Zelle des Ausdrucks. */
660 readonly datenlage: string;
661 /** Gleichung und Datenlage fuer die eigene Spalte der Tabellenausgabe. */
662 readonly gleichungSpalte: string;
663 /**
664 * Lastzuganteil am Schwerverkehr als Prozentzahl OHNE Einheit, fuer eine
665 * auswertbare Tabellenspalte - der leere Text, wo keiner erfasst ist. Leer
666 * und "0" sind verschiedene Angaben: "0" heisst gezaehlt und ohne Lastzuege,
667 * leer heisst nicht gezaehlt.
668 */
669 readonly lastzuganteilProzent: string;
670 /**
671 * Schwerverkehrsanteil als Prozentzahl OHNE Einheit - der Anteil, mit dem
672 * GERECHNET wurde.
673 *
674 * NEU (Fassung 5.6.0): Ausdruck und Tabellenausgabe bildeten diese Zahl je
675 * selbst aus `demand.heavyVehicleShare`, waehrend `fsv` daneben aus dem auf 0
676 * bis 1 begrenzten Anteil entsteht (rilsa/capacity.ts,
677 * wirksamerSchwerverkehrsanteil). Ein Projekt mit 150 % Schwerverkehr - ueber
678 * Datei und Oberflaeche nicht erreichbar, ueber Fremdwerkzeuge sehr wohl -
679 * druckte damit "150,0 %" neben "fSV = 1,900", also neben dem Faktor fuer
680 * 100 %. Der Pruefbericht hat denselben Befund und dieselbe Loesung; dass der
681 * eingetragene Wert begrenzt wurde, meldet die Plannotiz
682 * `schwerverkehrsanteil-ausserhalb`.
683 */
684 readonly anteilProzent: string;
685 }
686
687 export function schwerverkehrsangabe(
688 demand: Pick<TrafficDemand, 'heavyVehicleShare' | 'lastzugAnteil'>,
689 ): Schwerverkehrsangabe {
690 const lastzugAnteil = demand.lastzugAnteil;
691 const pSV = wirksamerSchwerverkehrsanteil(demand.heavyVehicleShare);
692 const fsv = schwerverkehrsfaktor(pSV, aufteilungAusLastzuganteil(pSV, lastzugAnteil));
693 const anteilProzent = fmt.numShort(pSV * 100, 1);
694 const annahme =
695 `Annahme ${fmt.percent(HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG)} Lastzüge am ` +
696 'Schwerverkehr';
697
698 if (istLastzuganteil(lastzugAnteil)) {
699 return {
700 fsv,
701 mitAufteilung: true,
702 gleichungKurz: 'Gl. 2-5',
703 // "des Schwerverkehrs" ausgeschrieben, obwohl die Zelle damit umbricht:
704 // Die Bezugsgroesse ist der Schwerverkehr und nicht die
705 // Gesamtverkehrsstaerke, und "Lastzüge 40 %" neben "12,0 %" liesse genau
706 // diese Verwechslung zu.
707 datenlage: `Lastzüge ${fmt.numShort(lastzugAnteil * 100, 1)} % des Schwerverkehrs`,
708 gleichungSpalte: '2-5 (Aufteilung erfasst)',
709 lastzuganteilProzent: fmt.numShort(lastzugAnteil * 100, 1),
710 anteilProzent,
711 };
712 }
713
714 /*
715 * Eingetragen, aber unbrauchbar (ausserhalb 0 bis 1 oder keine Zahl): Der
716 * Fachkern VERWIRFT den Wert und rechnet nach Gl. 2-6 (capacity.ts,
717 * istLastzuganteil). In der Unterlage darf daraus nicht "nicht erfasst"
718 * werden - der Bearbeiter hat etwas eingetragen, und ein Ausdruck, der das
719 * verschweigt, macht aus dem Verwerfen eine stille Ersetzung. Der
720 * Pruefbericht meldet den Fall zusaetzlich als Warnung
721 * ('lastzuganteil-ausserhalb').
722 *
723 * Der Linter meldet die Abfrage als immer falsch: `istLastzuganteil` traegt
724 * das Praedikat `wert is number`, also verengt der Uebersetzer den
725 * Nein-Zweig auf `undefined`. Das Praedikat ist zu grosszuegig - es weist
726 * auch -0,3, 1,5 und NaN ab, und die sind Zahlen. Genau diese Eintraege
727 * sollen hier als verworfen erscheinen; bewacht in
728 * tests/export/schwerverkehrsaufteilung.test.ts.
729 */
730 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- das Praedikat ist zu grosszuegig; siehe darueber
731 const verworfen = lastzugAnteil !== undefined;
732 return {
733 fsv,
734 mitAufteilung: false,
735 gleichungKurz: 'Gl. 2-6',
736 datenlage: verworfen ? 'Aufteilung unbrauchbar, verworfen' : 'Aufteilung nicht erfasst',
737 gleichungSpalte: verworfen
738 ? `2-6 (Eintrag zum Lastzuganteil unbrauchbar und verworfen; ${annahme})`
739 : `2-6 (keine Aufteilung erfasst; ${annahme})`,
740 lastzuganteilProzent: '',
741 anteilProzent,
742 };
743 }
744
745 /**
746 * Wie viele Stroeme dieses Plans nach welcher Gleichung rechnen.
747 *
748 * WARUM DER AUSDRUCK DAS ZAEHLEN MUSS (Fassung 5.6.0): Seit beide Gleichungen
749 * im Einsatz sind, ist "wie gerechnet wurde" keine Programmeigenschaft mehr,
750 * sondern eine Eigenschaft DIESES Plans. Ein Erlaeuterungsabsatz, der beide
751 * Gleichungen nennt, ohne zu sagen, welche hier gegriffen hat, laesst den
752 * Pruefer im Unklaren; einer, der pauschal die Fehlerrichtung der fehlenden
753 * Aufteilung beschreibt, obwohl jeder Strom gezaehlt ist, behauptet einen
754 * Vorbehalt, den dieser Plan nicht hat.
755 *
756 * GEZAEHLT WIRD, WOFUER EIN FAKTOR GEDRUCKT WIRD: dieselbe Bedingung wie in
757 * der Zelle des Ausdrucks (Kfz und OePNV, Verkehrsstaerke groesser als 0).
758 * Sonst zaehlte der Absatz Stroeme mit, die in der Tabelle einen Strich tragen.
759 */
760 export interface Aufteilungslage {
761 /** Stroeme mit erfasster Aufteilung - sie rechnen nach Gl. 2-5. */
762 readonly mitAufteilung: number;
763 /** Stroeme ohne - sie rechnen nach Gl. 2-6. */
764 readonly ohneAufteilung: number;
765 }
766
767 export function aufteilungslage(project: Project): Aufteilungslage {
768 let mit = 0;
769 let ohne = 0;
770 for (const gruppe of project.signalGroups) {
771 if (!hatSaettigungsverkehrsstaerke(gruppe.mode)) continue;
772 const demand = project.demands.find((d) => d.signalGroupId === gruppe.id);
773 if (!demand || !Number.isFinite(demand.volume) || demand.volume <= 0) continue;
774 if (istLastzuganteil(demand.lastzugAnteil)) mit += 1;
775 else ohne += 1;
776 }
777 return { mitAufteilung: mit, ohneAufteilung: ohne };
778 }
779
780 /**
781 * Wie der Schwerverkehr in die Saettigungsverkehrsstaerke eingeht
782 * (Fassung 5.5.0, Frage E6).
783 *
784 * KORREKTUR: Hier stand "Der Schwerverkehrsanteil mindert die
785 * Saettigungsverkehrsstaerke (Pkw-Aequivalent 2,0)". Beides war falsch
786 * beschriftet - die 2,0 sind der Wert des HCM 2010, gedruckt unter der
787 * Ueberschrift HBS 2015, und ein Pruefer konnte aus dem Satz weder den
788 * Zeitbedarfswert noch die gedruckte Saettigungsverkehrsstaerke herleiten.
789 * Jetzt steht die Kette da, mit der das HBS rechnet: fSV, tB, qS.
790 *
791 * Der Zeitbedarfswert kommt aus der angesetzten Saettigungsverkehrsstaerke
792 * (tB = 3600/qS) und nicht als ausgeschriebene 1,8 s: qS ist ein Vorgabenfeld.
793 * Wer es aendert, bekaeme sonst einen Satz gedruckt, der zu seinen eigenen
794 * Zahlen nicht passt (Befund C21).
795 *
796 * WAS NICHT ABGEBILDET IST, STEHT DABEI: Die Anpassungsfaktoren f1 und f2 des
797 * HBS fehlen. Ihre Fehlerrichtung ist die unsichere - ohne sie faellt die
798 * Saettigungsverkehrsstaerke einer schmalen, engen oder steigenden Zufahrt zu
799 * gross aus.
800 *
801 * NACHGETRAGEN (Fassung 5.5.0, Befund F2): Zur fehlenden AUFTEILUNG des
802 * Schwerverkehrs stand hier nur die Tatsache ("erfasst keine Aufteilung"),
803 * nicht die Fehlerrichtung - dabei ist sie der groesste unsichere Posten der
804 * ganzen Umstellung. Gl. 2-6 rechnet jedes Schwerfahrzeug mit dem
805 * Pkw-Gleichwert 1,9; ein Lastzug traegt nach Gl. 2-5 aber 2,5. Bei 20 %
806 * Schwerverkehr aus lauter Lastzuegen rechnet dieses Programm 2000/1,180 =
807 * 1695 Kfz/h statt 2000/1,300 = 1538 Kfz/h, also 10,2 % zu gross - und der
808 * Fehler waechst mit dem Anteil bis auf 31,6 % bei reinem Lastzugverkehr. Diese
809 * Zahl stand in der Hilfe und in den Unterlagen, aber nicht in der Unterlage,
810 * die zur Behoerde geht.
811 *
812 * NACHGEFUEHRT (Schema 13): Der Satz "Dieses Programm erfasst keine Aufteilung
813 * des Schwerverkehrs und rechnet deshalb mit der Gleichung fuer unbekannte
814 * Aufteilung" ist seit Schema 13 falsch - die Aufteilung ist erfassbar, und beide
815 * Gleichungen sind im Einsatz, je Strom nach dessen Datenlage. Der Absatz sagt
816 * jetzt beides, nennt die Annahme hinter dem Pauschalwert samt Nachrechnung
817 * (BASt V 400, S. 10: "Bei einem Anteil der Lkw mit Anhaenger und Sattel-Kfz
818 * (Fahrzeugklasse LkwK) am Schwerverkehr von 20 % ergibt sich der
819 * Funktionsverlauf der Gleichung (2-6)"; 1,75 * 0,8 + 2,50 * 0,2 = 1,90) und
820 * begrenzt die Fehlerrichtung auf die Stroeme, fuer die sie noch gilt.
821 *
822 * KORREKTUR (Fassung 5.10.0): Der Absatz nannte die Kette nur bis fSV und sagte
823 * daneben zu, in der Spalte "Saettigungsverkehrsstaerke" stuenden bei reinem
824 * Pkw-Verkehr die 2.000 Fz/h je Fahrstreifen. Die Spalte fuehrt aber
825 * saturationFlow(movement, lanes, defaults) / fSV - also auch die
826 * Fahrstreifenzahl und, bei abbiegenden Stroemen, die Abminderung fA aus den
827 * Vorgaben. Ein einstreifiger Abbieger ohne Schwerverkehr steht mit 1.800 Fz/h
828 * da, und der Absatz behauptete daneben 2.000. Er schloss zugleich mit dem
829 * Anspruch, an dem er scheiterte ("ohne ihn liesse sich die
830 * Saettigungsverkehrsstaerke ... nicht nachvollziehen"). Jetzt steht die
831 * vollstaendige Kette da, mit fA aus denselben Vorgaben.
832 *
833 * EIN ZEICHEN, EINE GROESSE: Die erste Berichtigung fuehrte
834 * qS in einem Satz fuer zwei verschiedene Groessen - einmal als 3600/tB, also
835 * je Fahrstreifen und einschliesslich fSV, einmal als Wert des ganzen Stroms.
836 * Woertlich genommen stimmten beide nur fuer n = 1 und fA = 1, und wer die eine
837 * in die andere einsetzte, teilte ein zweites Mal durch fSV. Der Grundwert je
838 * Fahrstreifen heisst jetzt qS0; die Spalte fuehrt qS0 · n · fA / fSV.
839 *
840 * `lage` ist die Datenlage DIESES Plans (aufteilungslage). Wer sie nicht
841 * durchreicht, bekommt die vollstaendige Fassung mit der Fehlerrichtung: Der
842 * Vorbehalt darf nur entfallen, wenn NACHGEWIESEN ist, dass jeder Strom eine
843 * erfasste Aufteilung hat - nicht schon dann, wenn der Aufrufer nichts weiss.
844 */
845 export function schwerverkehrSatz(
846 defaults: RilsaDefaults = RILSA_DEFAULTS,
847 lage?: Aufteilungslage,
848 ): string {
849 const qS = defaults.capacity.saturationFlow;
850 // Der Zeitbedarfswert ist der Kehrwert der Saettigungsverkehrsstaerke; ohne
851 // gueltige qS gibt es keinen, und dann bleibt die Kette ungenannt, statt eine
852 // Zahl aus einer Division durch 0 zu drucken.
853 const tB = qS > 0 ? `${fmt.numShort(3600 / qS, 2)} s` : 'dem Zeitbedarfswert des Regelwerts';
854 const pauschal = fmt.numShort(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2);
855 const zuschlag = fmt.numShort(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal - 1, 2);
856 const lkwUndBus = fmt.numShort(HBS_PKW_GLEICHWERTE.lkwUndBus, 2);
857 const lastzug = fmt.numShort(HBS_PKW_GLEICHWERTE.lkwMitAnhaenger, 2);
858 const lastzuganteil = HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG;
859
860 // Die VOLLSTAENDIGE Kette der gedruckten Spalte (Fassung 5.10.0): Sie fuehrt
861 // saturationFlow(movement, lanes, defaults) / fSV, also auch die
862 // Fahrstreifenzahl und - bei 'links' und 'rechts' - die Abminderung fuer
863 // abbiegende Stroeme. Der Absatz nannte nur fSV und sagte daneben die 2.000
864 // Fz/h je Fahrstreifen als Spalteninhalt zu; bei jedem abbiegenden Strom
865 // stehen dort 1.800 Fz/h. Der Grundwert je Fahrstreifen traegt in der Kette
866 // ein eigenes Zeichen (qS0), damit qS nicht zugleich fuer den Wert des ganzen
867 // Stroms steht.
868 const fA = defaults.capacity.turningFactor;
869
870 return (
871 'Der Schwerverkehr geht nach HBS 2015 über den Anpassungsfaktor fSV in den Zeitbedarfswert ' +
872 `ein: tB = fSV · ${tB} und qS = 3600 / tB; bei reinem Pkw-Verkehr (fSV = 1) ist das der ` +
873 `Grundwert qS0 = ${fmt.vehiclesPerHour(qS)} je Fahrstreifen. Die Spalte ` +
874 '"Sättigungsverkehrsstärke" nennt den Wert des ganzen Stroms und damit die vollständige ' +
875 'Kette qS0 · n · fA / fSV: n ist die Fahrstreifenzahl, fA die Abminderung für abbiegende ' +
876 `Ströme (fA = ${fmt.numShort(fA, 2)} für links und rechts abbiegende, fA = 1 für alle ` +
877 'übrigen). Ein einstreifiger abbiegender Strom ohne Schwerverkehr steht deshalb mit ' +
878 `${fmt.vehiclesPerHour(qS * fA)} in der Spalte und nicht mit ${fmt.vehiclesPerHour(qS)}. ` +
879 'Welche der ' +
880 'beiden Gleichungen des HBS gilt, entscheidet die Datenlage DES EINZELNEN STROMS – so sieht ' +
881 'es das Regelwerk vor („in Abhängigkeit von der Datenverfügbarkeit zur Aufteilung des ' +
882 'Schwerverkehrs"): Ist an seiner Verkehrsstärke der Anteil der Lastzüge (Lkw mit Anhänger ' +
883 `und Sattel-Kfz) am Schwerverkehr erfasst, gilt fSV = (qLV + ${lkwUndBus} · qLkw+Bus + ` +
884 `${lastzug} · qLkwK) / qKfz (Gl. 2-5); fehlt er, gilt fSV = (qLV + ${pauschal} · qSV) / qKfz, ` +
885 `also fSV = 1 + ${zuschlag} · pSV (Gl. 2-6). Die Spalte "Schwerverkehr" nennt für jeden Strom ` +
886 'den Anteil pSV, die Aufteilung – soweit erfasst – und den daraus gebildeten Faktor mit ' +
887 'seiner Gleichung; ohne ihn ließe sich die Sättigungsverkehrsstärke aus dieser Kette nicht ' +
888 `nachvollziehen. ${lageSatz(lage)}DIESELBE RECHNUNG, ZWEI DATENLAGEN: ` +
889 `Hinter dem Pauschalwert ${fmt.num(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2)} steht die ` +
890 `Annahme, dass ${fmt.percent(lastzuganteil)} des Schwerverkehrs Lastzüge sind ` +
891 `(${fmt.num(HBS_PKW_GLEICHWERTE.lkwUndBus, 2)} · ${fmt.num(1 - lastzuganteil, 2)} + ` +
892 `${fmt.num(HBS_PKW_GLEICHWERTE.lkwMitAnhaenger, 2)} · ${fmt.num(lastzuganteil, 2)} = ` +
893 `${fmt.num(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2)}, ohne Rundung); Gl. 2-6 ist damit ` +
894 `Gl. 2-5 mit ${fmt.percent(lastzuganteil)} Lastzügen. Wer diesen Anteil einträgt, ändert an ` +
895 'seinen Zahlen nichts: Die Erfassung der Aufteilung ist kein anderes Verfahren, sondern ' +
896 `dieselbe Rechnung mit einer Angabe mehr. ${fehlerrichtungSatz(qS, lage)}` +
897 'Die geometrischen Anpassungsfaktoren des HBS für Fahrstreifenbreite, Kurvenradius und ' +
898 'Längsneigung sind seit 5.30.0 abgebildet. Sie greifen nur, wo die Zufahrt vermessen und ' +
899 'eingetragen ist; ohne Eintrag gilt die Standardbedingung ' +
900 `(Längsneigung höchstens ${fmt.numShort(HBS_STANDARDBEDINGUNGEN.laengsneigungBis)} %, ` +
901 `Kurvenradius mindestens ${fmt.meters(HBS_STANDARDBEDINGUNGEN.kurvenradiusAb, 0)}, ` +
902 `Fahrstreifenbreite mindestens ${fmt.meters(HBS_STANDARDBEDINGUNGEN.fahrstreifenbreiteAb, 0)}), ` +
903 'und die Sättigungsverkehrsstärke fällt für eine davon abweichende Zufahrt zu groß aus. ' +
904 'Die Spalte „Geometrie" der Leistungstabelle sagt je Strom, ob eine Vermessung vorliegt.'
905 );
906 }
907
908 /**
909 * Was in DIESEM Plan gerechnet wurde - mit abschliessendem Leerzeichen, damit
910 * der Absatz ohne Datenlage keine doppelte Luecke bekommt.
911 *
912 * Die Zahlen stehen da, nicht die Namen: Welcher Strom nach welcher Gleichung
913 * rechnet, sagt die Tabelle Zeile fuer Zeile; hier geht es um die Frage, ob der
914 * Pruefer im Folgenden ueberhaupt nach Stroemen ohne Aufteilung suchen muss.
915 */
916 function lageSatz(lage: Aufteilungslage | undefined): string {
917 if (lage === undefined) return '';
918 const { mitAufteilung, ohneAufteilung } = lage;
919 if (mitAufteilung + ohneAufteilung === 0) return '';
920 if (ohneAufteilung === 0) {
921 return (
922 `In diesem Plan ist die Aufteilung für ${stroeme(mitAufteilung)} erfasst; gerechnet wurde ` +
923 'durchgehend nach Gl. 2-5. '
924 );
925 }
926 if (mitAufteilung === 0) {
927 return (
928 `In diesem Plan ist für keinen der ${stroeme(ohneAufteilung)} eine Aufteilung erfasst; ` +
929 'gerechnet wurde durchgehend nach Gl. 2-6. '
930 );
931 }
932 return (
933 `In diesem Plan rechnen ${stroeme(mitAufteilung)} nach Gl. 2-5 und ${stroeme(ohneAufteilung)} ` +
934 'nach Gl. 2-6. '
935 );
936 }
937
938 /** "1 Strom" oder "3 Ströme" - eine Unterlage schreibt keine "1 Ströme". */
939 function stroeme(anzahl: number): string {
940 return anzahl === 1 ? '1 Strom' : `${fmt.numShort(anzahl, 0)} Ströme`;
941 }
942
943 /**
944 * Die Fehlerrichtung der fehlenden Aufteilung - an einem gerechneten Beispiel
945 * und mit ihrer Obergrenze (Fassung 5.5.0, Befund F2), begrenzt auf die Stroeme, fuer die sie
946 * gilt.
947 *
948 * DIE ZAHLEN KOMMEN AUS DER RECHENFUNKTION und nicht aus dem Satz: Die
949 * Pkw-Gleichwerte stehen in HBS_PKW_GLEICHWERTE, die Spanne bildet
950 * `lastzuganteilSpanne` - dieselbe Funktion, mit der der Pruefbericht die
951 * Spanne je Strom beziffert. Ein Ausdruck, der eine Prozentzahl behauptet, die
952 * zur eigenen Rechnung nicht passt, ist genau der Befund, um den es hier geht.
953 *
954 * DAS BEISPIEL BLEIBT BEI 20 % SCHWERVERKEHR und wird nicht auf den groessten
955 * Anteil dieses Plans umgestellt: Der Absatz erklaert die Gleichung, nicht den
956 * einzelnen Strom. Fuer den einzelnen Strom rechnet der Pruefbericht die
957 * Spanne aus seinem eigenen Anteil aus - darauf verweist der letzte Satz.
958 */
959 function fehlerrichtungSatz(qS: number, lage: Aufteilungslage | undefined): string {
960 if (lage !== undefined && lage.ohneAufteilung === 0) {
961 return (
962 'Der Vorbehalt, der sonst an Gl. 2-6 hängt – der Pauschalwert unterschätzt einen ' +
963 'lastzuglastigen Strom –, geht damit in keine Zahl dieses Plans ein. '
964 );
965 }
966 const beispiel = 0.2;
967 const spanne = lastzuganteilSpanne(beispiel);
968 const pauschal = fmt.numShort(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2);
969 const lastzug = fmt.numShort(HBS_PKW_GLEICHWERTE.lkwMitAnhaenger, 2);
970 return (
971 'FEHLERRICHTUNG, WO DIE AUFTEILUNG FEHLT: Gl. 2-6 rechnet jedes Schwerfahrzeug mit dem ' +
972 `Pkw-Gleichwert ${pauschal}; ein Lastzug trägt nach Gl. 2-5 aber ${lastzug}. Besteht der ` +
973 'Schwerverkehr eines solchen Stroms überwiegend aus Lastzügen, fällt seine ' +
974 `Sättigungsverkehrsstärke ZU GROSS aus – bei ${fmt.percent(beispiel)} Schwerverkehr aus ` +
975 `lauter Lastzügen um ${fmt.percent(spanne.hoechstensZuGross, 1)} ` +
976 `(${fmt.vehiclesPerHour(qS / spanne.pauschal)} statt ` +
977 `${fmt.vehiclesPerHour(qS / spanne.nurLastzuege)}), mit steigendem Anteil bis zu ` +
978 `${fmt.percent(lastzuganteilSpanne(1).hoechstensZuGross, 1)}; enthält er keine Lastzüge, ` +
979 `rechnet das Programm ${fmt.percent(spanne.hoechstensZuKlein, 1)} zu klein, also auf der ` +
980 'sicheren Seite. Kapazität, Auslastungsgrad und Qualitätsstufe eines lastzuglastigen Stroms ' +
981 'ohne erfasste Aufteilung fallen entsprechend günstiger aus, als sie sind; wo der ' +
982 // KEINE MELDESCHWELLE IM SATZ (SCHWERVERKEHR_AUFTEILUNG_MELDESCHWELLE in
983 // validation/rules.ts): Ob der Bericht einen Strom anspricht, entscheidet
984 // der Fachkern. "Der Prüfbericht beziffert es je Strom" waere fuer einen
985 // Strom mit 5 % Schwerverkehr schlicht falsch - dort schweigt er.
986 'Schwerverkehrsanteil ins Gewicht fällt, beziffert der Prüfbericht beide Richtungen für den ' +
987 'Anteil des jeweiligen Stroms. Für Ströme MIT erfasster Aufteilung entfällt dieser Posten. '
988 );
989 }
990
991 /**
992 * Wirksamer Wert und - falls er davon abweicht - der Regelwert dahinter.
993 *
994 * Ein Fundstellenverzeichnis, das nur den Regelwert nennt, behauptet vor der
995 * Behoerde eine Uebereinstimmung, die der Plan nicht hat; eines, das nur den
996 * wirksamen Wert nennt, verschweigt die Abweichung. Es stehen beide da.
997 */
998 function wirksamMitRegelwert(wirksam: string, regelwert: string): string {
999 return wirksam === regelwert ? wirksam : `${wirksam} – abweichend vom Regelwert ${regelwert}`;
1000 }
1001
1002 /**
1003 * VwV-StVO zu Par. 37: Einsatzgrenze und Signalfolge - fuer jede Anlagenart.
1004 *
1005 * `defaults` sind die Kennwerte, mit denen gerechnet wird (Befund C21). Die
1006 * Ueberschrift behauptete frueher "keine einstellbaren Vorgaben"; das stimmt
1007 * fuer die Einsatzgrenze und die Rot-Gelb-Obergrenze, nicht aber fuer die
1008 * Rot-Gelb-Zeit und die Gelbzeitstaffel - beide sind Vorgabenfelder.
1009 */
1010 function vwvStvoEintrag(defaults: RilsaDefaults): QuelleOhneFeld {
1011 return {
1012 schluessel: 'vwv-stvo',
1013 quelle: QUELLEN['vwv-stvo'],
1014 bezeichnung:
1015 'Einsatzgrenze der Lichtsignalanlage und Signalfolge (Einsatzgrenze und Rot-Gelb-Obergrenze ' +
1016 'sind harte Schranken; Rot-Gelb-Zeit und Gelbzeitstaffel sind einstellbar – hier steht der ' +
1017 'wirksame Wert)',
1018 kennwerte: [
1019 {
1020 label: 'Zulässige Höchstgeschwindigkeit für den Betrieb einer LSA',
1021 wert: `bis ${fmt.numShort(VZUL_OBERGRENZE_LSA)} km/h (Rn. 10)`,
1022 },
1023 {
1024 label: 'Rot-Gelb-Zeit',
1025 wert:
1026 `${wirksamMitRegelwert(fmt.seconds(defaults.redYellow), fmt.seconds(RILSA_DEFAULTS.redYellow))}, ` +
1027 `höchstens ${fmt.seconds(SAFETY_FLOORS.maxRedYellow)} (Rn. 17)`,
1028 },
1029 {
1030 label: 'Gelbzeit nach zulässiger Höchstgeschwindigkeit',
1031 wert: `${wirksamMitRegelwert(gelbzeitstaffelSatz(defaults), gelbzeitstaffelSatz())} (Rn. 17)`,
1032 },
1033 ],
1034 };
1035 }
1036
1037 /**
1038 * RSA 21: Anhaltswerte der einstreifigen Verkehrsfuehrung - nur dort. Die
1039 * Zeile zu 400/600 m steht dabei, obwohl sie KEIN Wert der RSA ist: Der
1040 * Pruefbericht nennt diese Laengen im selben Atemzug wie die RSA-Kriterien,
1041 * und der Nachweis muss sagen, welche der Zahlen aus dem Regelwerk stammt und
1042 * welche aus der Behoerdenpraxis (Befund C5).
1043 */
1044 function rsaEintrag(): QuelleOhneFeld {
1045 return {
1046 schluessel: 'rsa-arbeitsstellen',
1047 quelle: QUELLEN['rsa-arbeitsstellen'],
1048 bezeichnung:
1049 'Anhaltswerte für die Hinweise des Prüfberichts bei einstreifiger Verkehrsführung (keine ' +
1050 'Rechengröße; die Umlaufzeit- und Wartezeitschranken der Anlagenart stehen im Abschnitt ' +
1051 '„Schranken der Anlagenart")',
1052 kennwerte: [
1053 {
1054 label: 'Kurze Engstelle, für die eine Regelung ohne Lichtsignalanlage genügen kann',
1055 wert:
1056 `bis ${fmt.meters(RSA_EINSTREIFIG.kurzeEngstelleBis, 0)} (Regelung nach § 6 StVO bzw. mit den ` +
1057 'Zeichen 208/308; Teil B)',
1058 },
1059 {
1060 label: 'Richtwert der Verkehrsstärke für die Anordnung (beide Richtungen zusammen)',
1061 wert: `über ${fmt.numShort(RSA_EINSTREIFIG.verkehrsstaerkeRichtwert)} Kfz/h (Teil B)`,
1062 },
1063 {
1064 label: 'Zulässige Höchstgeschwindigkeit an Arbeitsstellen längerer Dauer',
1065 wert: `in der Regel ${fmt.numShort(RSA_EINSTREIFIG.vZulRegel)} km/h (Teil C 2.3.2)`,
1066 },
1067 {
1068 label:
1069 `Engstellenlänge ${fmt.meters(ENGSTELLE_PRAXIS.hinweisAb, 0)} / ` +
1070 `${fmt.meters(ENGSTELLE_PRAXIS.warnungAb, 0)} (Hinweis / Warnung)`,
1071 wert:
1072 'Behördenpraxis (z. B. Hessen Mobil), KEIN Wert der RSA - Baustellenampeln werden ' +
1073 `üblicherweise bis ${fmt.meters(ENGSTELLE_PRAXIS.hinweisAb, 0)} aufgestellt, ausnahmsweise ` +
1074 `und mit Zustimmung bis ${fmt.meters(ENGSTELLE_PRAXIS.warnungAb, 0)}`,
1075 },
1076 ],
1077 };
1078 }
1079
1080 /**
1081 * Was das Fundstellenverzeichnis je Kennwert nennt: den Regelwert - und, wo
1082 * dieser Plan davon abweicht, den angesetzten Wert.
1083 */
1084 export interface Fundstellenkennwert {
1085 /** Der Wert, gegen den die Abweichungsliste misst. */
1086 readonly regelwert: number;
1087 /** Angesetzter Wert, wo diese Planung abweicht; sonst null. */
1088 readonly angesetzt: number | null;
1089 }
1090
1091 /**
1092 * Regelwert und angesetzter Wert je Kennwert - beides aus dem Fachkern, keiner
1093 * davon hier noch einmal entschieden.
1094 *
1095 * Der Regelwert ist das Feld `regelwert` der Vorgabenzeile (settings.ts,
1096 * `regelwertVon`) - die eine Stelle, aus der auch die Abweichungsliste ihren
1097 * Bezug nimmt und aus der die Vorgabenansicht liest. Er haengt allein an der
1098 * Anlagenart und nie am Eintrag des Anwenders: Eine Spalte "Regelwert", die
1099 * zwischen 40 und 60 s wechselt, je nachdem ob jemand etwas eingetragen hat,
1100 * waere ein Widerspruch in einer Zeile. WAS ANGESETZT WIRD, sagt dagegen die
1101 * Abweichungsliste (`resolveRilsaSettings`), also dieselbe Funktion, aus der
1102 * die gedruckte Aufstellung "Abweichungen von den Regelwerten" entsteht.
1103 *
1104 * KORREKTUR (Fassung 5.12.0): Das Verzeichnis bildete beides selbst - aus
1105 * `settingFields(...).standard`, also der Zahl des Regelwerkssatzes, und einem
1106 * Wertvergleich daneben. Bei den beiden Regelbereichsfeldern der Umlaufzeit ist
1107 * der Bezug aber der Wert, der OHNE Eintrag fuer die ANLAGENART gilt; an einer
1108 * Fussgaengerschutzanlage nannte dieselbe Unterlage damit zwei Regelwerte fuer
1109 * denselben Kennwert (40 gegen 60 s).
1110 *
1111 * EINE QUELLE, KEIN UMWEG MEHR (Fassung 5.13.0): Bis dahin
1112 * holte diese Funktion den Regelwert ueber eine zweite Abweichungsliste - eine
1113 * Vorlage, in der jeder Kennwert auf `standard` stand, durch
1114 * `resolveRilsaSettings` gefuehrt, und der Bezug aus deren `standardValue`
1115 * gelesen. Seit der Fassung 5.12.0 fuehrt die Vorgabenzeile
1116 * denselben Wert unmittelbar; gemessen ueber alle 45 Kennwerte und alle drei
1117 * Anlagenarten liefern beide Wege dieselbe Zahl. Dass die Vorgabenzeile und die
1118 * Abweichungsliste zusammenbleiben, haelt
1119 * tests/domain/regelwertBezugJeFeld.test.ts fest - fuer jedes Feld und jede
1120 * Anlagenart, nicht nur fuer die beiden Regelbereichsfelder.
1121 */
1122 export function fundstellenkennwerte(
1123 overrides: RilsaOverrides | undefined,
1124 anlagenart: Anlagenart = 'knotenpunkt',
1125 ): ReadonlyMap<keyof RilsaOverrides, Fundstellenkennwert> {
1126 const abweichung = new Map(
1127 resolveRilsaSettings(overrides, RILSA_DEFAULTS, anlagenart).deviations.map(
1128 (d): readonly [keyof RilsaOverrides, SettingDeviation] => [d.key, d],
1129 ),
1130 );
1131
1132 return new Map(
1133 settingFields(RILSA_DEFAULTS, anlagenart).map(
1134 (f): readonly [keyof RilsaOverrides, Fundstellenkennwert] => [
1135 f.key,
1136 {
1137 regelwert: f.regelwert,
1138 angesetzt: abweichung.get(f.key)?.appliedValue ?? null,
1139 },
1140 ],
1141 ),
1142 );
1143 }
1144
1145 /**
1146 * Zusatz zur Kennwertbeschriftung im Fundstellenverzeichnis, wenn ein Kennwert
1147 * in dieser Planung in keine Zahl eingeht - in Klammern und mit dem Grund:
1148 * " (ohne Wirkung: Bewertungsverfahren HCM)" bzw. " (ohne Wirkung in dieser
1149 * Fassung)". Sonst der leere Text.
1150 *
1151 * WELCHE Kennwerte das sind, entscheidet der Fachkern
1152 * (settings.ts, `wirkungslosigkeit`) - hier steht nur noch die Klammer.
1153 * Vor Fassung 5.4.0 (Befund C20) entschied diese Funktion es
1154 * selbst und kannte dabei nur den Instationaritaetsfaktor; die
1155 * Koordinierungs-Kennwerte standen im Nachweis wie angewandte Werte, obwohl
1156 * coordination.ts von keiner Rechnung aufgerufen wird.
1157 *
1158 * Der `kontext` traegt die projektabhaengigen Faelle (Fassung 5.5.0, Fragen E1 und E2):
1159 * die beiden festen Ueberfahrzeiten, die nur beim Ansatz 'fest' wirken, und die
1160 * Raeumgeschwindigkeit bei engem Innenradius, die nur wirkt, wenn wenigstens
1161 * eine Beziehung das Merkmal traegt. Jedes Feld ist optional wie im Fachkern -
1162 * wer es nicht durchreicht, bekommt "der Wert wirkt" und damit eine
1163 * Begruendungspflicht zu viel statt einer Wirkungsbehauptung zu wenig. Jeder
1164 * Aufrufer, der das Projekt kennt, reicht den Kontext durch.
1165 */
1166 export function wirkungsvermerk(
1167 quellenSchluessel: QuellenSchluessel,
1168 verfahren: Bewertungsverfahren,
1169 kontext: WirkungsKontext = {},
1170 ): string {
1171 const ohneWirkung = wirkungslosigkeit(quellenSchluessel, verfahren, kontext);
1172 return ohneWirkung === null ? '' : ` (${ohneWirkung.vermerk})`;
1173 }