import { BEWERTUNGSVERFAHREN_LABELS, ENGER_RADIUS_LABEL, ENGSTELLE_PRAXIS, HBS_GEOMETRIE, HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG, HBS_PKW_GLEICHWERTE, HBS_STANDARDBEDINGUNGEN, KRITERIUM_LABELS, RILSA_DEFAULTS, RSA_EINSTREIFIG, SAFETY_FLOORS, SERVICE_LEVEL_SCALES, STAURAUMBEDARF_JE_FAHRZEUG, UEBERFAHRZEIT_ANSATZ_LABELS, VZUL_OBERGRENZE_LSA, type RilsaDefaults, type ServiceLevelStep, } from '@/domain/rilsa/constants'; import { QUELLEN, type Quelle, type QuellenSchluessel } from '@/domain/rilsa/quellen'; import { ueberfahrzeitAnsatzRichtungSatz, ueberfahrzeitVerkuerzung } from '@/domain/rilsa/ansaetze'; import { aufteilungAusLastzuganteil, istLastzuganteil, lastzuganteilSpanne, schwerverkehrsfaktor, wirksamerSchwerverkehrsanteil, } from '@/domain/rilsa/capacity'; import { hatSaettigungsverkehrsstaerke } from '@/domain/plan/signalPlan'; import { resolveRilsaSettings, settingFields, wirkungslosigkeit, type RilsaOverrides, type SettingDeviation, type WirkungsKontext, } from '@/domain/rilsa/settings'; import type { Anlagenart, Bewertungsverfahren, ServiceLevel, UeberfahrzeitAnsatz, } from '@/domain/rilsa/types'; import type { Project, TrafficDemand } from '@/domain/model/project'; import { roundTo, type Ratio } from '@/domain/units'; import * as fmt from '@/ui/format'; /** * Benennung von Bewertungsverfahren, Kriterium und Stufe fuer Tabelle und * Ausdruck. * * Steht hier und nicht in pdf.ts, weil beide Ausgabewege dieselben Worte * fuehren muessen (Vorbild: wegherkunft.ts). Vor Fassung 5.4.0 * (Befunde B1 bis B4) schrieb der Ausdruck "Bewertung nach HBS 2015", die * Tabelle nur "HBS" - und beides fuer eine Rechnung, die in Wahrheit die * HCM-Formel war. Jetzt gibt es zwei Verfahren, die auch rechnen, was sie * behaupten; die Benennung darf deshalb nirgends mehr frei formuliert werden. * * Die Woerter selbst stehen im Fachkern (constants.ts: * BEWERTUNGSVERFAHREN_LABELS, KRITERIUM_LABELS) und werden hier nur * durchgereicht: Vor der Fassung 5.4.0 fuehrte diese Datei eine zweite, eigene * Tafel derselben Woerter - zwei Quellen fuer dieselbe Benennung sind genau die * Stelle, an der Bildschirm und Ausdruck unbemerkt auseinanderlaufen. */ export { KRITERIUM_LABELS }; /** Name des Bewertungsverfahrens, wie er in Ausdruck und Tabelle steht. */ export function verfahrenLabel(verfahren: Bewertungsverfahren): string { return BEWERTUNGSVERFAHREN_LABELS[verfahren]; } /** * Stufe fuer eine Tabellenzelle. Bei Ueberlastung steht der Grund dabei: Nach * HBS 2015 ist F fuer Kfz keine Wartezeitstufe, sondern gilt genau dann, wenn * q > C - ohne den Zusatz liesse sich das "F" neben einer Wartezeit von 40 s * nicht mit der gedruckten Stufentafel in Einklang bringen. */ export function stufeZelle(level: ServiceLevel | null, leer = '–'): string { if (level === null) return leer; return level.kriterium === 'ueberlastung' ? `${level.grade} (überlastet)` : level.grade; } /** * Stufentafel als Satzteil, aus denselben Konstanten, mit denen bewertet * wird: "A bis 20 s, B bis 35 s, C bis 50 s, D bis 70 s, E darüber". * * Nicht ausgeschrieben, weil ausgeschriebene Zahlen die Stelle sind, an der * ein Ausdruck unbemerkt veraltet: Vor Fassung 5.4.0 (Befund B1) * stand die Kfz-Tafel des HBS 2001 unter der Ueberschrift "HBS 2015". */ export function stufentafelSatz(tafel: readonly ServiceLevelStep[]): string { return tafel .map((stufe) => Number.isFinite(stufe.maxDelay) ? `${stufe.grade} bis ${fmt.seconds(stufe.maxDelay)}` : `${stufe.grade} darüber`, ) .join(', '); } /** Die Tafeln des Verfahrens, benannt fuer den Ausdruck. */ export function stufentafeln(verfahren: Bewertungsverfahren): { readonly kfz: string; readonly oepnv: string; readonly fussRad: string | null; } { if (verfahren === 'HBS') { return { kfz: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.kfz), oepnv: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.oepnv), fussRad: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.fussRad), }; } const kfz = stufentafelSatz(SERVICE_LEVEL_SCALES.HCM.kfz); // Das HCM-Verfahren dieses Programms hat fuer Fussgaenger und Radverkehr // keine Tafel (delay.ts, serviceLevelFor liefert null). return { kfz, oepnv: kfz, fussRad: null }; } /** * Herkunft, an der kein Vorgabenfeld haengt. * * Das Fundstellenverzeichnis (Ausdruck und Vorgabenverwaltung) entsteht aus * settingFieldsByQuelle - es kennt also nur Quellen, zu denen ein anpassbarer * Kennwert gehoert. Die Stufentafeln der Qualitaetsstufen sind kein solcher * Kennwert: Sie sind fest hinterlegt und nicht einstellbar. Die Quelle * 'hbs-qualitaetsstufen' erschien deshalb vor der Fassung 5.4.0 NIRGENDS: Der * Ausdruck druckte die Tafeln im Leistungsabschnitt, aber im Nachweis der * Kennwerte hatten sie weder Herkunft noch Pruefstand - und gerade die OePNV- * und Fuss/Rad-Tafeln sind nur ueber Sekundaerquellen belegt und am Original * abzugleichen. * * Diese Liste ergaenzt das Verzeichnis um solche Quellen. Sie steht hier und * nicht in settings.ts, weil sie keine Vorgaben beschreibt, sondern Kennwerte, * die nur ausgegeben werden; kommt eine weitere feldlose Quelle hinzu, bekommt * sie hier einen Eintrag und erscheint damit in beiden Verzeichnissen. */ export interface QuelleOhneFeld { readonly schluessel: QuellenSchluessel; readonly quelle: Quelle; /** Was die Quelle beisteuert - an Stelle der Kennwertliste einer Vorgabengruppe. */ readonly bezeichnung: string; /** * Die Werte, mit denen TATSAECHLICH gerechnet wird, aus den Konstanten des * Fachkerns - nicht ausgeschrieben, aus demselben Grund wie in stufentafelSatz. */ readonly kennwerte: readonly { readonly label: string; readonly wert: string }[]; } export function quellenOhneFeld( verfahren: Bewertungsverfahren = 'HBS', anlagenart: Anlagenart = 'knotenpunkt', // Die Kennwerte, mit denen gerechnet wird - nicht die Regelwerte. Siehe // vwvStvoEintrag (Befund C21 im gedruckten Nachweis). defaults: RilsaDefaults = RILSA_DEFAULTS, /** * Wird der Verfahrensvergleich in DIESER Unterlage gedruckt? * * Nur dann gehoert seine Regel in das Verzeichnis. Dieselbe Ueberlegung wie * bei der RSA 21: Was in keine gedruckte Zahl eingeht, gehoert nicht in den * Nachweis dieses Projekts - ein Verzeichnis, das alles auffuehrt, sagt * nichts darueber, worauf diese Unterlage beruht. * * Die Vorgabenansicht ruft ohne dieses Kennzeichen: Dort steht die Herkunft * der VORGABEN, und die Vergleichsregel ist keine. */ mitVerfahrensvergleich = false, ): readonly QuelleOhneFeld[] { const eintraege: QuelleOhneFeld[] = [stufentafelnEintrag(verfahren)]; /* * DIE REGEL DES VERFAHRENSVERGLEICHS. * * Sie stand frueher unbelegt im Quelltext, und das fiel nicht auf, weil * niemand sie sah: Der Vergleich hatte keinen Aufrufer. Seit sein Ergebnis * in der Planunterlage steht, gilt derselbe Satz wie fuer jede andere Zahl * darin - die Unterlage sagt, woher sie kommt. */ if (mitVerfahrensvergleich) eintraege.push(verfahrensvergleichEintrag()); /* * VwV-StVO und RSA 21 (Befunde C2, C3, C5, C6): Beide bekamen einen Eintrag * in QUELLEN, aber zunaechst keinen Weg ins Verzeichnis - das kennt nur * Vorgabenfelder und diese Liste, und an beiden haengt kein Vorgabenfeld. Die * Unterlagen behaupteten "neuer Eintrag VwV-StVO im Fundstellenverzeichnis", * und im Ausdruck stand er nicht. Die VwV-StVO gilt fuer jede Anlagenart; die * RSA nur bei einstreifiger Verkehrsfuehrung - an einem Knotenpunkt geht * keiner ihrer Anhaltswerte in eine Meldung ein und gehoert deshalb nicht in * den Nachweis dieses Projekts. */ eintraege.push(vwvStvoEintrag(defaults)); if (anlagenart === 'einstreifig') eintraege.push(rsaEintrag()); /* * DIE GEOMETRISCHEN ANPASSUNGSFAKTOREN. Sie stehen zuletzt, weil sie als * einzige der Liste jede Anlagenart und jedes Bewertungsverfahren betreffen: * Sie wirken auf jede Saettigungsverkehrsstaerke - auch dort, wo keine * Zufahrt vermessen ist, denn dann steht der Faktor 1 fuer eine ANGENOMMENE * Standardbedingung und nicht fuer eine gemessene. Genau das gehoert in den * Nachweis: Wer die Unterlage prueft, muss erfahren, dass die 2000 Kfz/h eine * Voraussetzung haben. */ eintraege.push(geometriefaktorenEintrag()); /* * DER STAURAUMBEDARF JE FAHRZEUG. Seit der Fassung 5.31.0 druckt die * Unterlage eine Rueckstaulaenge, und die entsteht aus einem Praxiswert, den * kein Regelwerk nennt. Eine Laenge ohne Herkunft in einer * Anordnungsunterlage ist genau das, was dieses Verzeichnis verhindern soll. */ eintraege.push(stauraumbedarfEintrag()); return eintraege; } /** Die Regel, nach der der Verfahrensvergleich einen Wert benennt. */ function verfahrensvergleichEintrag(): QuelleOhneFeld { return { schluessel: 'programm-umlaufzeitvergleich', quelle: QUELLEN['programm-umlaufzeitvergleich'], bezeichnung: 'Regel des Verfahrensvergleichs (keine Empfehlung, kein Regelwerkswert)', kennwerte: [ { label: 'Maßgebender Wert', wert: 'der größere aus kapazitätsorientiertem Bedarf (HBS bzw. HCM, der größere) und ' + 'Wartezeitoptimum (Webster bzw. Akçelik, der kleinere)', }, { label: 'Wirkung auf den Signalzeitenplan', wert: 'keine – gerechnet wird ausschließlich das unter „Phasen" gewählte Verfahren', }, ], }; } /** Die Laenge, die ein wartendes Fahrzeug im Rueckstau belegt. */ function stauraumbedarfEintrag(): QuelleOhneFeld { return { schluessel: 'praxis-stauraum-je-fahrzeug', quelle: QUELLEN['praxis-stauraum-je-fahrzeug'], bezeichnung: 'Rückstaulänge aus dem mittleren Rückstau bei Freigabezeitende', kennwerte: [ { label: 'Länge je wartendem Fahrzeug', wert: `${fmt.meters(STAURAUMBEDARF_JE_FAHRZEUG, 0)} (Fahrzeug und Abstand)`, }, { label: 'Verglichene Größe', wert: 'der MITTLERE Rückstau NGE über die Betrachtungsstunde – in etwa der Hälfte der ' + 'Umläufe steht mehr; ein 95-Prozent-Wert wird nicht gebildet', }, ], }; } /** Die geometrischen Anpassungsfaktoren des Zeitbedarfswerts. */ function geometriefaktorenEintrag(): QuelleOhneFeld { const { fahrstreifenbreiteAb, kurvenradiusAb, laengsneigungBis } = HBS_STANDARDBEDINGUNGEN; return { schluessel: 'hbs-geometriefaktoren', quelle: QUELLEN['hbs-geometriefaktoren'], bezeichnung: 'Geometrie der Zufahrt (Fahrstreifenbreite, Kurvenradius, Längsneigung)', kennwerte: [ { label: 'Standardbedingungen (Faktor 1)', wert: `Breite ab ${fmt.numShort(fahrstreifenbreiteAb, 2)} m, Radius ab ` + `${fmt.numShort(kurvenradiusAb, 0)} m, Längsneigung zwischen ` + `−${fmt.numShort(laengsneigungBis, 0)} % und ${fmt.numShort(laengsneigungBis, 0)} %`, }, { label: 'Anpassungsfaktoren', wert: `fb = ${fmt.numShort(HBS_GEOMETRIE.breite.steigung, 3)} · b + ` + `${fmt.numShort(HBS_GEOMETRIE.breite.achsenabschnitt, 3)}; ` + `fR = ${fmt.numShort(HBS_GEOMETRIE.radius.steigung, 3)} · R + ` + `${fmt.numShort(HBS_GEOMETRIE.radius.achsenabschnitt, 1)}; ` + `fs = ${fmt.numShort(HBS_GEOMETRIE.neigung.steigung, 2)} · s + 1; ` + 'angesetzt wird max(1; max(fb, fR, fs) · min(1; fs))', }, ], }; } /** Stufentafeln der Qualitaetsstufen des gewaehlten Verfahrens. */ function stufentafelnEintrag(verfahren: Bewertungsverfahren): QuelleOhneFeld { const mittlere = KRITERIUM_LABELS['mittlere-wartezeit']; const maximale = KRITERIUM_LABELS['maximale-wartezeit']; // Nach HBS 2015 ist F fuer Kfz keine Wartezeitstufe (E ist nach oben offen), // fuer OePNV eine Wartezeitstufe UND die Folge der Ueberlastung; die Tafel // allein liesse das F der Leistungstabelle unerklaert. const fUeberlastung = `F bei ${KRITERIUM_LABELS.ueberlastung}`; if (verfahren === 'HCM') { // Im HCM-Verfahren wird nach der HCM-Tafel bewertet; die HBS-Tafeln // gehen in keine Zahl ein und gehoeren deshalb nicht in den Nachweis // dieses Projekts. return { schluessel: 'hcm-verfahren', quelle: QUELLEN['hcm-verfahren'], bezeichnung: `Level of Service (Kfz/ÖPNV: ${mittlere}; Fuß/Rad: keine Tafel)`, kennwerte: [ { label: `Stufentafel Kfz und ÖPNV (${mittlere})`, wert: `${stufentafelSatz(SERVICE_LEVEL_SCALES.HCM.kfz)}; ${fUeberlastung}`, }, ], }; } return { schluessel: 'hbs-qualitaetsstufen', quelle: QUELLEN['hbs-qualitaetsstufen'], bezeichnung: `Stufentafeln der Qualitätsstufen (Kfz/ÖPNV: ${mittlere}; Fuß/Rad: ${maximale})`, kennwerte: [ { label: `Stufentafel Kfz (${mittlere})`, wert: `${stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.kfz)}; ${fUeberlastung}`, }, { label: `Stufentafel ÖPNV (${mittlere})`, wert: `${stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.oepnv)}; ${fUeberlastung}`, }, { label: `Stufentafel Fußgänger und Radverkehr (${maximale})`, wert: stufentafelSatz(SERVICE_LEVEL_SCALES.HBS.fussRad), }, ], }; } /** * Gelbzeitstaffel als Satzteil aus den Vorgaben, mit denen gerechnet wird: * "3 s bis 50 km/h, 4 s bis 60 km/h, 5 s bis 70 km/h". Die letzte Stufe der * RiLSA ist nach oben offen; hier endet sie bei der Hoechstgeschwindigkeit, * bis zu der Lichtsignalanlagen eingerichtet werden sollen - darueber gilt im * Programm dieselbe Gelbzeit, aber die VwV-StVO sieht dort keine Anlage vor. * * KORREKTUR (Befund C21): Genau dieser Kommentar stand schon hier, der * Quelltext nahm aber RILSA_DEFAULTS - der Kommentar sagte das Gegenteil des * Codes. Die Felder `yellowKfzUpTo50/UpTo60/Above60` sind einstellbar; wer sie * aendert, bekam im Fundstellenverzeichnis der Planunterlage weiter die * Regelstaffel gedruckt. * * WARUM EXPORTIERT: Derselbe Satz steht als Feldhinweis am vZul-Feld * (projectView). Zwei Stellen, die dieselbe Staffel unabhaengig voneinander * zusammensetzen, laufen genau dann auseinander, wenn niemand daran denkt. */ export function gelbzeitstaffelSatz(defaults: RilsaDefaults = RILSA_DEFAULTS): string { return defaults.yellowBySpeed .map((stufe) => Number.isFinite(stufe.upToKmh) ? `${fmt.seconds(stufe.yellow)} bis ${fmt.numShort(stufe.upToKmh)} km/h` : `${fmt.seconds(stufe.yellow)} bis ${fmt.numShort(VZUL_OBERGRENZE_LSA)} km/h`, ) .join(', '); } /** * Welcher Ueberfahrzeit-Ansatz gerechnet wurde - und was daran haengt * (Fassung 5.5.0, Frage E2). * * Der Ansatz aendert JEDE Zwischenzeit eines Kfz-Raeumstroms. Eine * Zwischenzeitenmatrix ohne diese Angabe laesst sich nicht nachrechnen: Bei * 60 km/h und abbiegendem Strom stehen je nach Ansatz 4 s oder 2 s * Ueberfahrzeit hinter derselben Zahl. * * WARUM DER SATZ IN BEIDEN ZWEIGEN DIE RICHTUNG NENNT: Die feste Ueberfahrzeit * VERKUERZT die Zwischenzeit gegenueber dem Regelansatz dieses Programms - bei * den Regelwerten. Eine Verkuerzung, die in der Unterlage nur als Name eines * Ansatzes erscheint, ist vor der Behoerde keine ausgewiesene Abweichung - sie * sieht aus wie eine Einstellung. Deshalb stehen Richtung, Groessenordnung und * Herkunft der Werte im selben Satz, und die NICHT eingebaute Untergrenze steht * dabei: Wer sie braucht, muss wissen, dass dieses Programm sie nicht setzt. * * Die beiden Zahlen kommen aus den aufgeloesten Vorgaben und nicht aus dem * Satz (Befund C21): Sie sind einstellbar, und ein Ausdruck, der 3 s und 2 s * behauptet, waehrend die Rechnung mit anderen Werten lief, ist schlimmer als * gar keine Angabe. * * NACHGEZOGEN (Fassung 5.10.0): Dieser Docblock begruendete nur den Zweig * 'fest', und nur dort kamen Richtung und Groessenordnung aus dem Fachkern. Der * Regelzweig - der in fast jeder Planunterlage steht - trug beides fest * eingeschrieben im Text, obwohl die festen Ueberfahrzeiten auch bei ihm * einstellbar sind und die Gelbzeitstaffel ebenso. Beide Zweige bilden Richtung * und Herkunft jetzt aus den wirksamen Vorgaben (regelansatzVergleichSatz, * festeWerteHerkunftSatz). */ export function ueberfahrzeitAnsatzSatz( ansatz: UeberfahrzeitAnsatz, defaults: RilsaDefaults = RILSA_DEFAULTS, ): string { const geradeaus = fmt.seconds(defaults.crossingTime.kfzGeradeaus); const abbiegend = fmt.seconds(defaults.crossingTime.kfzAbbiegend); const feste = `${geradeaus} geradeaus und ${abbiegend} abbiegend`; if (ansatz === 'gelbzeit') { // EINE Antwort fuer beide Saetze: Der Herkunftssatz entscheidet, ob die // genannten Zahlen Leitfaden- oder Vorgabewerte sind, der Vergleichssatz // daneben nennt sie beim selben Namen. const nachLeitfaden = festeWerteNachLeitfaden(defaults); return ( 'Überfahrzeit des Kraftfahrzeugverkehrs: Gerechnet wurde mit dem Ansatz ' + `„${UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit}" – dem Regelansatz dieses Programms. Angesetzt ` + `ist damit die Gelbzeit dieser Signalgruppe (${gelbzeitstaffelSatz(defaults)}). ` + // Herkunft UND Richtung aus den wirksamen Vorgaben (Fassung 5.10.0) - wie // im Zweig 'fest' eine Zeile weiter unten. Hier stand beides fest // eingeschrieben im Satz. `${festeWerteHerkunftSatz(feste, nachLeitfaden)} ` + `${regelansatzVergleichSatz(defaults, nachLeitfaden)}` ); } /* * "nie kürzer" und nicht mehr "länger" (Fassung 5.43.0): Bei 50 km/h * geradeaus ergeben beide Ansaetze 3 s. Wahr bei jeder zulaessigen * Gelbzeitstaffel ist nur "nie kürzer", und auch das nur gegen die Regelwerte * der RiLSA: SAFETY_FLOORS.yellowKfz liegt nicht unter ihnen, wohl aber unter * jeder festen Vorgabe ueber 3 s. * * NACHGEZOGEN (Fassung 5.43.0): Hier stand der Satz unbedingt - * "Die Werte sind die Regelwerte der RiLSA 2015 ...; ... rechnet nie kürzer * als diese" auch bei 4 s abbiegend, wo die Gelbzeit bei 50 km/h 3 s * betraegt, und bei 6 s / 6 s unmittelbar hinter dem Satz, die Werte wichen * von den Regelwerten ab. Beide Aussagen haengen jetzt an derselben Antwort * wie im Zweig 'gelbzeit' (festeWerteNachLeitfaden). */ const nachLeitfaden = festeWerteNachLeitfaden(defaults); const zuschreibung = nachLeitfaden ? 'Die Werte sind die Regelwerte der RiLSA 2015 (Abschnitt 2.5.2, Fälle 1 und 2); der ' + 'Regelansatz dieses Programms – die Kopplung an die Gelbzeit – rechnet nie kürzer als diese.' : `Die Werte sind Vorgaben dieses Plans, nicht die ${rilsaFesteWerte()} der RiLSA 2015 ` + '(Abschnitt 2.5.2, Fälle 1 und 2); der Regelansatz dieses Programms – die Kopplung an die ' + 'Gelbzeit – rechnet nie kürzer als die Regelwerte der RiLSA 2015.'; return ( 'Überfahrzeit des Kraftfahrzeugverkehrs: Gerechnet wurde mit dem Ansatz ' + `„${UEBERFAHRZEIT_ANSATZ_LABELS.fest}" – angesetzt sind ${feste}, unabhängig von der ` + // Richtung UND Groessenordnung aus den wirksamen Vorgaben: Beide festen // Ueberfahrzeiten sind einstellbar, "bis zu 3 s" galt nur fuer ihre // Regelwerte. `zulässigen Höchstgeschwindigkeit. ${ueberfahrzeitAnsatzRichtungSatz(defaults)} ` + `${zuschreibung} Die Untergrenze der Überfahr- und Räumzeit (tü + tr mindestens ` + 'Gelbzeit + 1 s, RiLSA 2015, Abschnitt 2.5.2) ' + 'gilt bei beiden Ansätzen und ist eingebaut; wo sie greift, nennt der Rechenweg sie.' ); } /** "3 s geradeaus und 2 s abbiegend" - die festen Ueberfahrzeiten aus constants.ts. */ function rilsaFesteWerte(): string { return ( `${fmt.seconds(RILSA_DEFAULTS.crossingTime.kfzGeradeaus)} geradeaus und ` + `${fmt.seconds(RILSA_DEFAULTS.crossingTime.kfzAbbiegend)} abbiegend` ); } /** * Entsprechen die beiden festen Ueberfahrzeiten dieses Plans den Regelwerten * aus constants.ts? * * Zwei Saetze haengen daran: die Zuschreibung an die RiLSA und das Bezugswort * des Vergleichssatzes daneben. Sie muessen dieselbe Antwort bekommen - sonst * nennt der eine "Vorgabewerte", was der andere gerade dem Regelwerk * zugeschrieben hat. * * NACHGEZOGEN (5.27.0): Der Parametername `nachLeitfaden` und die Saetze * sprachen von behoerdlichen Leitfaeden, weil die beiden Zahlen bis dahin nur * dort belegt waren. Sie stehen in der RiLSA 2015 (Abschnitt 2.5.2, Faelle 1 * und 2); der Leitfaden bleibt eine zweite, uebereinstimmende Quelle. */ function festeWerteNachLeitfaden(defaults: RilsaDefaults): boolean { return ( defaults.crossingTime.kfzGeradeaus === RILSA_DEFAULTS.crossingTime.kfzGeradeaus && defaults.crossingTime.kfzAbbiegend === RILSA_DEFAULTS.crossingTime.kfzAbbiegend ); } /** * Woher die daneben genannten festen Ueberfahrzeiten stammen. * * KORREKTUR (Fassung 5.10.0): Der Regelzweig schrieb die eingesetzten Zahlen * unbedingt behoerdlichen Leitfaeden zu (seit 5.27.0: der RiLSA). Belegt sind * ausschliesslich die Regelwerte aus constants.ts (RiLSA 2015, Abschnitt * 2.5.2); die Felder crossingTimeKfzGeradeaus und crossingTimeKfzAbbiegend sind * von 1 bis 10 s einstellbar und bleiben es auch beim Ansatz 'gelbzeit' - die * Vorgabenverwaltung vermerkt dort nur die Wirkungslosigkeit. Ein Pruefer las * damit "6 s geradeaus" als Leitfadenwert: eine falsche Tatsachenbehauptung * ueber ein Regelwerk in der Planunterlage. * * Weicht der Plan ab, stehen beide Zahlenpaare da - das eingetragene und das * belegte. Der Pruefer soll die Abweichung sehen, nicht nur ihr Ergebnis. */ function festeWerteHerkunftSatz(feste: string, nachLeitfaden: boolean): string { if (nachLeitfaden) { return `Die RiLSA 2015 setzt in Abschnitt 2.5.2 feste Werte an (${feste}).`; } return ( `Die Vorgaben dieses Plans setzen für den Ansatz „feste Überfahrzeit" daneben ${feste} an – ` + `nicht die ${rilsaFesteWerte()} der RiLSA 2015 (Abschnitt 2.5.2).` ); } /** * Wie der Regelansatz gegen die daneben genannten festen Ueberfahrzeiten * ausfaellt - aus den wirksamen Vorgaben gebildet, nicht aus dem Satz. * * KORREKTUR (Fassung 5.10.0): Hier stand "gegen sie faellt dieser Ansatz beim * Abbiegen und bei 60 und 70 km/h laenger aus und ist damit die konservative * Seite" - fest eingeschrieben, obwohl BEIDE Vergleichsseiten einstellbar sind: * die festen Ueberfahrzeiten von 1 bis 10 s, die Gelbzeitstaffel ueber * yellowKfzUpTo50/UpTo60/Above60. Mit 6 s geradeaus und 6 s abbiegend liegt die * groesste Gelbzeit (5 s) darunter; der Satz behauptete die konservative Seite * gegen zwei Zahlen, die er selbst nannte. Auch die Schwellen "60 und 70 km/h" * galten nur fuer die Regelstaffel. Derselbe Fehlertyp ist fuer den Zweig * 'fest' laengst behoben (ansaetze.ts, ueberfahrzeitAnsatzRichtungSatz); der * Regelzweig war dabei uebersehen worden. * * Bezugsgroessen sind die KLEINSTE und die GROESSTE Gelbzeit der Staffel: Der * Ansatz faellt dort am laengsten aus, wo die Gelbzeit am laengsten ist, und * dort am kuerzesten, wo sie am kuerzesten ist. "Die konservative Seite" steht * nur, wenn er NIRGENDS kuerzer ausfaellt - eine laengere Ueberfahrzeit heisst * eine laengere Zwischenzeit (tz = tue + tr - te). * * DAS BEZUGSWORT STEHT AUSGESCHRIEBEN: Hier begann der Satz mit "Gegen sie" und * haengte damit am vorangehenden Herkunftssatz. Weicht der Plan ab, nennt der * zwei Zahlenpaare - die eingetragenen und die belegten -, und "sie" zeigte auf * das zuletzt genannte, also auf die Leitfadenwerte, gegen die hier gar nicht * gerechnet wird. Der Satz sagt jetzt allein stehend, welches Zahlenpaar der * Massstab ist. */ function regelansatzVergleichSatz(defaults: RilsaDefaults, nachLeitfaden: boolean): string { // Die laengere Haelfte kommt aus dem Fachkern: `ueberfahrzeitVerkuerzung` // rechnet groesste Gelbzeit minus feste Ueberfahrzeit - dieselbe Groesse, die // der Zweig 'fest' als Verkuerzung ausweist, hier aus der Gegenrichtung // gelesen. Sie stand hier ein zweites Mal, samt eigenem Rueckfall bei leerer // Gelbzeitstaffel. const { abbiegend, geradeaus } = ueberfahrzeitVerkuerzung(defaults); const laenger = vergleichsspanne(abbiegend, geradeaus); /* * VORLAEUFIG AN DIESER STELLE: Die gespiegelte Groesse (feste Ueberfahrzeit * minus KLEINSTE Gelbzeit) und der Satzbau dieses Zweigs gehoeren nach * src/domain/rilsa/ansaetze.ts neben `ueberfahrzeitAnsatzRichtungSatz` - * dort steht die Richtung der beiden Ansaetze an EINER Stelle, und der * Bildschirm (projectView.ts) kann eine private Funktion dieser Datei nicht * erreichen. ansaetze.ts stand fuer diese Aenderung nicht zur Verfuegung; * solange die Verlegung aussteht, ist dies die zweite Fassung derselben * Aussage - genau die Lage, gegen die der Docblock von ansaetze.ts steht. */ const kleinste = kleinsteGelbzeit(defaults); const kuerzer = vergleichsspanne( defaults.crossingTime.kfzAbbiegend - kleinste, defaults.crossingTime.kfzGeradeaus - kleinste, ); const bezug = nachLeitfaden ? 'Gegen diese Regelwerte' : 'Gegen diese Vorgabewerte'; if (kuerzer === '') { if (laenger === '') return `${bezug} fällt dieser Ansatz weder länger noch kürzer aus.`; return ( `${bezug} fällt dieser Ansatz ${laenger} länger aus und nirgends kürzer – er ist damit ` + 'die konservative Seite.' ); } return ( `${bezug} fällt dieser Ansatz ${kuerzer} kürzer aus` + (laenger === '' ? '' : ` und ${laenger} länger`) + '; die konservative Seite ist er damit nicht.' ); } /** * Kleinste Gelbzeit der Staffel - der Wert, gegen den der Regelansatz am * kuerzesten ausfaellt. * * Gespiegelt zu `hoechsteGelbzeit` in ansaetze.ts, samt dessen Rueckfall auf * die Regelstaffel: Eine leere Staffel darf hier keine Verkuerzung "um bis zu * 6 s" behaupten. Gehoert mit der Funktion darueber in den Fachkern. */ function kleinsteGelbzeit(defaults: RilsaDefaults): number { const werte = defaults.yellowBySpeed.map((stufe) => stufe.yellow); if (werte.length > 0) return Math.min(...werte); return Math.min(...RILSA_DEFAULTS.yellowBySpeed.map((stufe) => stufe.yellow)); } /** * "beim Abbiegen um bis zu 3 s und geradeaus um bis zu 2 s" - leer, wo in * dieser Richtung nichts bleibt. Gerundet wird vor dem Vergleich, damit eine * Differenz von 1e-15 keine Richtung behauptet. * * Zweite Fassung des Satzteils aus `ueberfahrzeitAnsatzRichtungSatz` * (ansaetze.ts) und mit `fmt.numShort` statt dessen `formatZahl` gebildet - * bei Ueberfahrzeiten von 1 bis 10 s liefern beide dieselbe Zeichenfolge. Auch * sie gehoert in den Fachkern, siehe die Funktion darueber. */ function vergleichsspanne(abbiegend: number, geradeaus: number): string { const teile: string[] = []; if (roundTo(abbiegend, 2) > 0) { teile.push(`beim Abbiegen um bis zu ${fmt.numShort(abbiegend, 2)} s`); } if (roundTo(geradeaus, 2) > 0) { teile.push(`geradeaus um bis zu ${fmt.numShort(geradeaus, 2)} s`); } return teile.join(' und '); } /** * Wie das Merkmal "enger Innenradius" in einer Tabellenzelle steht - und * warum es ueberhaupt eine eigene Spalte bekommt (Fassung 5.5.0, Frage E1). * * Das Merkmal steht an der einzelnen Konfliktbeziehung und senkt dort die * Raeumgeschwindigkeit des abbiegenden Kraftfahrzeugs. In der gedruckten * Zeile steht sonst nur die Zahl - 5,0 m/s neben einem Regelwert von 7,0 m/s, * ohne erkennbaren Grund. Genau diese Luecke war der Befund hinter der Spalte * "Schwerverkehr" der Leistungstabelle. * * ES STEHT DA, WAS ERFASST IST - NICHT, WAS GEWIRKT HAT: Die Zelle gibt das * Merkmal der Beziehung wieder. Ob es die Rechnung geaendert hat, haengt am * raeumenden Strom (nur abbiegende Kraftfahrzeuge); ist es ohne Wirkung * gesetzt, meldet das der Pruefbericht als eigene Warnung * ('zwischenzeiten.enger-radius-ohne-wirkung'). Die Ausgabe hier die Wirkung * ein zweites Mal herleiten zu lassen, hiesse eine Regel des Fachkerns zu * verdoppeln. */ export function engerRadiusZelle(gesetzt: boolean | undefined, leer = '–'): string { return gesetzt === true ? ENGER_RADIUS_LABEL : leer; } /** * Was Ausdruck und Tabellenausgabe ueber den Schwerverkehr EINES Stroms sagen: * der Faktor fSV, die Gleichung, die ihn gebildet hat, und die Datenlage, die * ueber diese Gleichung entschieden hat. * * NEU (Schema 13). Bis dahin gab es hier nichts, weil es nichts zu entscheiden * gab: Das Datenmodell kannte keine Aufteilung des Schwerverkehrs, jeder Strom * lief ueber Gl. 2-6, und beide Ausgabewege bildeten den Faktor mit einem * eigenen Aufruf `schwerverkehrsfaktor(anteil)`. Seit die Aufteilung erfassbar * ist, ist genau das falsch: Der Plan rechnet mit Gl. 2-5, waehrend Ausdruck * und Tabelle weiter den Pauschalwert drucken - bei 20 % Schwerverkehr aus * lauter Lastzuegen stand dann fSV = 1,180 neben einer * Saettigungsverkehrsstaerke von 1538 Fz/h, die zu 1,300 gehoert. Der * Anpassungsfaktor wird deshalb hier gebildet, aus derselben Funktion und mit * denselben Eingangsgroessen wie in plan/signalPlan.ts (groupSaturationFlow) - * eine Stelle, zwei Ausgaben. * * DIE GLEICHUNG STEHT DABEI, NICHT NUR DER FAKTOR: Zwei Stroeme mit demselben * Schwerverkehrsanteil koennen jetzt verschiedene Faktoren tragen, und das ist * kein Fehler, sondern der Unterschied zwischen gezaehlt und nicht gezaehlt. * Ohne die Angabe der Gleichung liesse sich die Unterlage nicht nachrechnen - * der Pruefer kaeme mit dem Pauschalwert auf eine andere Zahl und wuesste * nicht, warum. */ export interface Schwerverkehrsangabe { /** Anpassungsfaktor fSV, mit dem der Plan diesen Strom rechnet. */ readonly fsv: Ratio; /** Rechnet dieser Strom nach Gl. 2-5 (erfasste Aufteilung)? */ readonly mitAufteilung: boolean; /** Kurzform fuer eine Tabellenzelle: "Gl. 2-5" bzw. "Gl. 2-6". */ readonly gleichungKurz: string; /** Die Datenlage in einer Zeile - fuer die Zelle des Ausdrucks. */ readonly datenlage: string; /** Gleichung und Datenlage fuer die eigene Spalte der Tabellenausgabe. */ readonly gleichungSpalte: string; /** * Lastzuganteil am Schwerverkehr als Prozentzahl OHNE Einheit, fuer eine * auswertbare Tabellenspalte - der leere Text, wo keiner erfasst ist. Leer * und "0" sind verschiedene Angaben: "0" heisst gezaehlt und ohne Lastzuege, * leer heisst nicht gezaehlt. */ readonly lastzuganteilProzent: string; /** * Schwerverkehrsanteil als Prozentzahl OHNE Einheit - der Anteil, mit dem * GERECHNET wurde. * * NEU (Fassung 5.6.0): Ausdruck und Tabellenausgabe bildeten diese Zahl je * selbst aus `demand.heavyVehicleShare`, waehrend `fsv` daneben aus dem auf 0 * bis 1 begrenzten Anteil entsteht (rilsa/capacity.ts, * wirksamerSchwerverkehrsanteil). Ein Projekt mit 150 % Schwerverkehr - ueber * Datei und Oberflaeche nicht erreichbar, ueber Fremdwerkzeuge sehr wohl - * druckte damit "150,0 %" neben "fSV = 1,900", also neben dem Faktor fuer * 100 %. Der Pruefbericht hat denselben Befund und dieselbe Loesung; dass der * eingetragene Wert begrenzt wurde, meldet die Plannotiz * `schwerverkehrsanteil-ausserhalb`. */ readonly anteilProzent: string; } export function schwerverkehrsangabe( demand: Pick, ): Schwerverkehrsangabe { const lastzugAnteil = demand.lastzugAnteil; const pSV = wirksamerSchwerverkehrsanteil(demand.heavyVehicleShare); const fsv = schwerverkehrsfaktor(pSV, aufteilungAusLastzuganteil(pSV, lastzugAnteil)); const anteilProzent = fmt.numShort(pSV * 100, 1); const annahme = `Annahme ${fmt.percent(HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG)} Lastzüge am ` + 'Schwerverkehr'; if (istLastzuganteil(lastzugAnteil)) { return { fsv, mitAufteilung: true, gleichungKurz: 'Gl. 2-5', // "des Schwerverkehrs" ausgeschrieben, obwohl die Zelle damit umbricht: // Die Bezugsgroesse ist der Schwerverkehr und nicht die // Gesamtverkehrsstaerke, und "Lastzüge 40 %" neben "12,0 %" liesse genau // diese Verwechslung zu. datenlage: `Lastzüge ${fmt.numShort(lastzugAnteil * 100, 1)} % des Schwerverkehrs`, gleichungSpalte: '2-5 (Aufteilung erfasst)', lastzuganteilProzent: fmt.numShort(lastzugAnteil * 100, 1), anteilProzent, }; } /* * Eingetragen, aber unbrauchbar (ausserhalb 0 bis 1 oder keine Zahl): Der * Fachkern VERWIRFT den Wert und rechnet nach Gl. 2-6 (capacity.ts, * istLastzuganteil). In der Unterlage darf daraus nicht "nicht erfasst" * werden - der Bearbeiter hat etwas eingetragen, und ein Ausdruck, der das * verschweigt, macht aus dem Verwerfen eine stille Ersetzung. Der * Pruefbericht meldet den Fall zusaetzlich als Warnung * ('lastzuganteil-ausserhalb'). * * Der Linter meldet die Abfrage als immer falsch: `istLastzuganteil` traegt * das Praedikat `wert is number`, also verengt der Uebersetzer den * Nein-Zweig auf `undefined`. Das Praedikat ist zu grosszuegig - es weist * auch -0,3, 1,5 und NaN ab, und die sind Zahlen. Genau diese Eintraege * sollen hier als verworfen erscheinen; bewacht in * tests/export/schwerverkehrsaufteilung.test.ts. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- das Praedikat ist zu grosszuegig; siehe darueber const verworfen = lastzugAnteil !== undefined; return { fsv, mitAufteilung: false, gleichungKurz: 'Gl. 2-6', datenlage: verworfen ? 'Aufteilung unbrauchbar, verworfen' : 'Aufteilung nicht erfasst', gleichungSpalte: verworfen ? `2-6 (Eintrag zum Lastzuganteil unbrauchbar und verworfen; ${annahme})` : `2-6 (keine Aufteilung erfasst; ${annahme})`, lastzuganteilProzent: '', anteilProzent, }; } /** * Wie viele Stroeme dieses Plans nach welcher Gleichung rechnen. * * WARUM DER AUSDRUCK DAS ZAEHLEN MUSS (Fassung 5.6.0): Seit beide Gleichungen * im Einsatz sind, ist "wie gerechnet wurde" keine Programmeigenschaft mehr, * sondern eine Eigenschaft DIESES Plans. Ein Erlaeuterungsabsatz, der beide * Gleichungen nennt, ohne zu sagen, welche hier gegriffen hat, laesst den * Pruefer im Unklaren; einer, der pauschal die Fehlerrichtung der fehlenden * Aufteilung beschreibt, obwohl jeder Strom gezaehlt ist, behauptet einen * Vorbehalt, den dieser Plan nicht hat. * * GEZAEHLT WIRD, WOFUER EIN FAKTOR GEDRUCKT WIRD: dieselbe Bedingung wie in * der Zelle des Ausdrucks (Kfz und OePNV, Verkehrsstaerke groesser als 0). * Sonst zaehlte der Absatz Stroeme mit, die in der Tabelle einen Strich tragen. */ export interface Aufteilungslage { /** Stroeme mit erfasster Aufteilung - sie rechnen nach Gl. 2-5. */ readonly mitAufteilung: number; /** Stroeme ohne - sie rechnen nach Gl. 2-6. */ readonly ohneAufteilung: number; } export function aufteilungslage(project: Project): Aufteilungslage { let mit = 0; let ohne = 0; for (const gruppe of project.signalGroups) { if (!hatSaettigungsverkehrsstaerke(gruppe.mode)) continue; const demand = project.demands.find((d) => d.signalGroupId === gruppe.id); if (!demand || !Number.isFinite(demand.volume) || demand.volume <= 0) continue; if (istLastzuganteil(demand.lastzugAnteil)) mit += 1; else ohne += 1; } return { mitAufteilung: mit, ohneAufteilung: ohne }; } /** * Wie der Schwerverkehr in die Saettigungsverkehrsstaerke eingeht * (Fassung 5.5.0, Frage E6). * * KORREKTUR: Hier stand "Der Schwerverkehrsanteil mindert die * Saettigungsverkehrsstaerke (Pkw-Aequivalent 2,0)". Beides war falsch * beschriftet - die 2,0 sind der Wert des HCM 2010, gedruckt unter der * Ueberschrift HBS 2015, und ein Pruefer konnte aus dem Satz weder den * Zeitbedarfswert noch die gedruckte Saettigungsverkehrsstaerke herleiten. * Jetzt steht die Kette da, mit der das HBS rechnet: fSV, tB, qS. * * Der Zeitbedarfswert kommt aus der angesetzten Saettigungsverkehrsstaerke * (tB = 3600/qS) und nicht als ausgeschriebene 1,8 s: qS ist ein Vorgabenfeld. * Wer es aendert, bekaeme sonst einen Satz gedruckt, der zu seinen eigenen * Zahlen nicht passt (Befund C21). * * WAS NICHT ABGEBILDET IST, STEHT DABEI: Die Anpassungsfaktoren f1 und f2 des * HBS fehlen. Ihre Fehlerrichtung ist die unsichere - ohne sie faellt die * Saettigungsverkehrsstaerke einer schmalen, engen oder steigenden Zufahrt zu * gross aus. * * NACHGETRAGEN (Fassung 5.5.0, Befund F2): Zur fehlenden AUFTEILUNG des * Schwerverkehrs stand hier nur die Tatsache ("erfasst keine Aufteilung"), * nicht die Fehlerrichtung - dabei ist sie der groesste unsichere Posten der * ganzen Umstellung. Gl. 2-6 rechnet jedes Schwerfahrzeug mit dem * Pkw-Gleichwert 1,9; ein Lastzug traegt nach Gl. 2-5 aber 2,5. Bei 20 % * Schwerverkehr aus lauter Lastzuegen rechnet dieses Programm 2000/1,180 = * 1695 Kfz/h statt 2000/1,300 = 1538 Kfz/h, also 10,2 % zu gross - und der * Fehler waechst mit dem Anteil bis auf 31,6 % bei reinem Lastzugverkehr. Diese * Zahl stand in der Hilfe und in den Unterlagen, aber nicht in der Unterlage, * die zur Behoerde geht. * * NACHGEFUEHRT (Schema 13): Der Satz "Dieses Programm erfasst keine Aufteilung * des Schwerverkehrs und rechnet deshalb mit der Gleichung fuer unbekannte * Aufteilung" ist seit Schema 13 falsch - die Aufteilung ist erfassbar, und beide * Gleichungen sind im Einsatz, je Strom nach dessen Datenlage. Der Absatz sagt * jetzt beides, nennt die Annahme hinter dem Pauschalwert samt Nachrechnung * (BASt V 400, S. 10: "Bei einem Anteil der Lkw mit Anhaenger und Sattel-Kfz * (Fahrzeugklasse LkwK) am Schwerverkehr von 20 % ergibt sich der * Funktionsverlauf der Gleichung (2-6)"; 1,75 * 0,8 + 2,50 * 0,2 = 1,90) und * begrenzt die Fehlerrichtung auf die Stroeme, fuer die sie noch gilt. * * KORREKTUR (Fassung 5.10.0): Der Absatz nannte die Kette nur bis fSV und sagte * daneben zu, in der Spalte "Saettigungsverkehrsstaerke" stuenden bei reinem * Pkw-Verkehr die 2.000 Fz/h je Fahrstreifen. Die Spalte fuehrt aber * saturationFlow(movement, lanes, defaults) / fSV - also auch die * Fahrstreifenzahl und, bei abbiegenden Stroemen, die Abminderung fA aus den * Vorgaben. Ein einstreifiger Abbieger ohne Schwerverkehr steht mit 1.800 Fz/h * da, und der Absatz behauptete daneben 2.000. Er schloss zugleich mit dem * Anspruch, an dem er scheiterte ("ohne ihn liesse sich die * Saettigungsverkehrsstaerke ... nicht nachvollziehen"). Jetzt steht die * vollstaendige Kette da, mit fA aus denselben Vorgaben. * * EIN ZEICHEN, EINE GROESSE: Die erste Berichtigung fuehrte * qS in einem Satz fuer zwei verschiedene Groessen - einmal als 3600/tB, also * je Fahrstreifen und einschliesslich fSV, einmal als Wert des ganzen Stroms. * Woertlich genommen stimmten beide nur fuer n = 1 und fA = 1, und wer die eine * in die andere einsetzte, teilte ein zweites Mal durch fSV. Der Grundwert je * Fahrstreifen heisst jetzt qS0; die Spalte fuehrt qS0 · n · fA / fSV. * * `lage` ist die Datenlage DIESES Plans (aufteilungslage). Wer sie nicht * durchreicht, bekommt die vollstaendige Fassung mit der Fehlerrichtung: Der * Vorbehalt darf nur entfallen, wenn NACHGEWIESEN ist, dass jeder Strom eine * erfasste Aufteilung hat - nicht schon dann, wenn der Aufrufer nichts weiss. */ export function schwerverkehrSatz( defaults: RilsaDefaults = RILSA_DEFAULTS, lage?: Aufteilungslage, ): string { const qS = defaults.capacity.saturationFlow; // Der Zeitbedarfswert ist der Kehrwert der Saettigungsverkehrsstaerke; ohne // gueltige qS gibt es keinen, und dann bleibt die Kette ungenannt, statt eine // Zahl aus einer Division durch 0 zu drucken. const tB = qS > 0 ? `${fmt.numShort(3600 / qS, 2)} s` : 'dem Zeitbedarfswert des Regelwerts'; const pauschal = fmt.numShort(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2); const zuschlag = fmt.numShort(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal - 1, 2); const lkwUndBus = fmt.numShort(HBS_PKW_GLEICHWERTE.lkwUndBus, 2); const lastzug = fmt.numShort(HBS_PKW_GLEICHWERTE.lkwMitAnhaenger, 2); const lastzuganteil = HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG; // Die VOLLSTAENDIGE Kette der gedruckten Spalte (Fassung 5.10.0): Sie fuehrt // saturationFlow(movement, lanes, defaults) / fSV, also auch die // Fahrstreifenzahl und - bei 'links' und 'rechts' - die Abminderung fuer // abbiegende Stroeme. Der Absatz nannte nur fSV und sagte daneben die 2.000 // Fz/h je Fahrstreifen als Spalteninhalt zu; bei jedem abbiegenden Strom // stehen dort 1.800 Fz/h. Der Grundwert je Fahrstreifen traegt in der Kette // ein eigenes Zeichen (qS0), damit qS nicht zugleich fuer den Wert des ganzen // Stroms steht. const fA = defaults.capacity.turningFactor; return ( 'Der Schwerverkehr geht nach HBS 2015 über den Anpassungsfaktor fSV in den Zeitbedarfswert ' + `ein: tB = fSV · ${tB} und qS = 3600 / tB; bei reinem Pkw-Verkehr (fSV = 1) ist das der ` + `Grundwert qS0 = ${fmt.vehiclesPerHour(qS)} je Fahrstreifen. Die Spalte ` + '"Sättigungsverkehrsstärke" nennt den Wert des ganzen Stroms und damit die vollständige ' + 'Kette qS0 · n · fA / fSV: n ist die Fahrstreifenzahl, fA die Abminderung für abbiegende ' + `Ströme (fA = ${fmt.numShort(fA, 2)} für links und rechts abbiegende, fA = 1 für alle ` + 'übrigen). Ein einstreifiger abbiegender Strom ohne Schwerverkehr steht deshalb mit ' + `${fmt.vehiclesPerHour(qS * fA)} in der Spalte und nicht mit ${fmt.vehiclesPerHour(qS)}. ` + 'Welche der ' + 'beiden Gleichungen des HBS gilt, entscheidet die Datenlage DES EINZELNEN STROMS – so sieht ' + 'es das Regelwerk vor („in Abhängigkeit von der Datenverfügbarkeit zur Aufteilung des ' + 'Schwerverkehrs"): Ist an seiner Verkehrsstärke der Anteil der Lastzüge (Lkw mit Anhänger ' + `und Sattel-Kfz) am Schwerverkehr erfasst, gilt fSV = (qLV + ${lkwUndBus} · qLkw+Bus + ` + `${lastzug} · qLkwK) / qKfz (Gl. 2-5); fehlt er, gilt fSV = (qLV + ${pauschal} · qSV) / qKfz, ` + `also fSV = 1 + ${zuschlag} · pSV (Gl. 2-6). Die Spalte "Schwerverkehr" nennt für jeden Strom ` + 'den Anteil pSV, die Aufteilung – soweit erfasst – und den daraus gebildeten Faktor mit ' + 'seiner Gleichung; ohne ihn ließe sich die Sättigungsverkehrsstärke aus dieser Kette nicht ' + `nachvollziehen. ${lageSatz(lage)}DIESELBE RECHNUNG, ZWEI DATENLAGEN: ` + `Hinter dem Pauschalwert ${fmt.num(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2)} steht die ` + `Annahme, dass ${fmt.percent(lastzuganteil)} des Schwerverkehrs Lastzüge sind ` + `(${fmt.num(HBS_PKW_GLEICHWERTE.lkwUndBus, 2)} · ${fmt.num(1 - lastzuganteil, 2)} + ` + `${fmt.num(HBS_PKW_GLEICHWERTE.lkwMitAnhaenger, 2)} · ${fmt.num(lastzuganteil, 2)} = ` + `${fmt.num(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2)}, ohne Rundung); Gl. 2-6 ist damit ` + `Gl. 2-5 mit ${fmt.percent(lastzuganteil)} Lastzügen. Wer diesen Anteil einträgt, ändert an ` + 'seinen Zahlen nichts: Die Erfassung der Aufteilung ist kein anderes Verfahren, sondern ' + `dieselbe Rechnung mit einer Angabe mehr. ${fehlerrichtungSatz(qS, lage)}` + 'Die geometrischen Anpassungsfaktoren des HBS für Fahrstreifenbreite, Kurvenradius und ' + 'Längsneigung sind seit 5.30.0 abgebildet. Sie greifen nur, wo die Zufahrt vermessen und ' + 'eingetragen ist; ohne Eintrag gilt die Standardbedingung ' + `(Längsneigung höchstens ${fmt.numShort(HBS_STANDARDBEDINGUNGEN.laengsneigungBis)} %, ` + `Kurvenradius mindestens ${fmt.meters(HBS_STANDARDBEDINGUNGEN.kurvenradiusAb, 0)}, ` + `Fahrstreifenbreite mindestens ${fmt.meters(HBS_STANDARDBEDINGUNGEN.fahrstreifenbreiteAb, 0)}), ` + 'und die Sättigungsverkehrsstärke fällt für eine davon abweichende Zufahrt zu groß aus. ' + 'Die Spalte „Geometrie" der Leistungstabelle sagt je Strom, ob eine Vermessung vorliegt.' ); } /** * Was in DIESEM Plan gerechnet wurde - mit abschliessendem Leerzeichen, damit * der Absatz ohne Datenlage keine doppelte Luecke bekommt. * * Die Zahlen stehen da, nicht die Namen: Welcher Strom nach welcher Gleichung * rechnet, sagt die Tabelle Zeile fuer Zeile; hier geht es um die Frage, ob der * Pruefer im Folgenden ueberhaupt nach Stroemen ohne Aufteilung suchen muss. */ function lageSatz(lage: Aufteilungslage | undefined): string { if (lage === undefined) return ''; const { mitAufteilung, ohneAufteilung } = lage; if (mitAufteilung + ohneAufteilung === 0) return ''; if (ohneAufteilung === 0) { return ( `In diesem Plan ist die Aufteilung für ${stroeme(mitAufteilung)} erfasst; gerechnet wurde ` + 'durchgehend nach Gl. 2-5. ' ); } if (mitAufteilung === 0) { return ( `In diesem Plan ist für keinen der ${stroeme(ohneAufteilung)} eine Aufteilung erfasst; ` + 'gerechnet wurde durchgehend nach Gl. 2-6. ' ); } return ( `In diesem Plan rechnen ${stroeme(mitAufteilung)} nach Gl. 2-5 und ${stroeme(ohneAufteilung)} ` + 'nach Gl. 2-6. ' ); } /** "1 Strom" oder "3 Ströme" - eine Unterlage schreibt keine "1 Ströme". */ function stroeme(anzahl: number): string { return anzahl === 1 ? '1 Strom' : `${fmt.numShort(anzahl, 0)} Ströme`; } /** * Die Fehlerrichtung der fehlenden Aufteilung - an einem gerechneten Beispiel * und mit ihrer Obergrenze (Fassung 5.5.0, Befund F2), begrenzt auf die Stroeme, fuer die sie * gilt. * * DIE ZAHLEN KOMMEN AUS DER RECHENFUNKTION und nicht aus dem Satz: Die * Pkw-Gleichwerte stehen in HBS_PKW_GLEICHWERTE, die Spanne bildet * `lastzuganteilSpanne` - dieselbe Funktion, mit der der Pruefbericht die * Spanne je Strom beziffert. Ein Ausdruck, der eine Prozentzahl behauptet, die * zur eigenen Rechnung nicht passt, ist genau der Befund, um den es hier geht. * * DAS BEISPIEL BLEIBT BEI 20 % SCHWERVERKEHR und wird nicht auf den groessten * Anteil dieses Plans umgestellt: Der Absatz erklaert die Gleichung, nicht den * einzelnen Strom. Fuer den einzelnen Strom rechnet der Pruefbericht die * Spanne aus seinem eigenen Anteil aus - darauf verweist der letzte Satz. */ function fehlerrichtungSatz(qS: number, lage: Aufteilungslage | undefined): string { if (lage !== undefined && lage.ohneAufteilung === 0) { return ( 'Der Vorbehalt, der sonst an Gl. 2-6 hängt – der Pauschalwert unterschätzt einen ' + 'lastzuglastigen Strom –, geht damit in keine Zahl dieses Plans ein. ' ); } const beispiel = 0.2; const spanne = lastzuganteilSpanne(beispiel); const pauschal = fmt.numShort(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2); const lastzug = fmt.numShort(HBS_PKW_GLEICHWERTE.lkwMitAnhaenger, 2); return ( 'FEHLERRICHTUNG, WO DIE AUFTEILUNG FEHLT: Gl. 2-6 rechnet jedes Schwerfahrzeug mit dem ' + `Pkw-Gleichwert ${pauschal}; ein Lastzug trägt nach Gl. 2-5 aber ${lastzug}. Besteht der ` + 'Schwerverkehr eines solchen Stroms überwiegend aus Lastzügen, fällt seine ' + `Sättigungsverkehrsstärke ZU GROSS aus – bei ${fmt.percent(beispiel)} Schwerverkehr aus ` + `lauter Lastzügen um ${fmt.percent(spanne.hoechstensZuGross, 1)} ` + `(${fmt.vehiclesPerHour(qS / spanne.pauschal)} statt ` + `${fmt.vehiclesPerHour(qS / spanne.nurLastzuege)}), mit steigendem Anteil bis zu ` + `${fmt.percent(lastzuganteilSpanne(1).hoechstensZuGross, 1)}; enthält er keine Lastzüge, ` + `rechnet das Programm ${fmt.percent(spanne.hoechstensZuKlein, 1)} zu klein, also auf der ` + 'sicheren Seite. Kapazität, Auslastungsgrad und Qualitätsstufe eines lastzuglastigen Stroms ' + 'ohne erfasste Aufteilung fallen entsprechend günstiger aus, als sie sind; wo der ' + // KEINE MELDESCHWELLE IM SATZ (SCHWERVERKEHR_AUFTEILUNG_MELDESCHWELLE in // validation/rules.ts): Ob der Bericht einen Strom anspricht, entscheidet // der Fachkern. "Der Prüfbericht beziffert es je Strom" waere fuer einen // Strom mit 5 % Schwerverkehr schlicht falsch - dort schweigt er. 'Schwerverkehrsanteil ins Gewicht fällt, beziffert der Prüfbericht beide Richtungen für den ' + 'Anteil des jeweiligen Stroms. Für Ströme MIT erfasster Aufteilung entfällt dieser Posten. ' ); } /** * Wirksamer Wert und - falls er davon abweicht - der Regelwert dahinter. * * Ein Fundstellenverzeichnis, das nur den Regelwert nennt, behauptet vor der * Behoerde eine Uebereinstimmung, die der Plan nicht hat; eines, das nur den * wirksamen Wert nennt, verschweigt die Abweichung. Es stehen beide da. */ function wirksamMitRegelwert(wirksam: string, regelwert: string): string { return wirksam === regelwert ? wirksam : `${wirksam} – abweichend vom Regelwert ${regelwert}`; } /** * VwV-StVO zu Par. 37: Einsatzgrenze und Signalfolge - fuer jede Anlagenart. * * `defaults` sind die Kennwerte, mit denen gerechnet wird (Befund C21). Die * Ueberschrift behauptete frueher "keine einstellbaren Vorgaben"; das stimmt * fuer die Einsatzgrenze und die Rot-Gelb-Obergrenze, nicht aber fuer die * Rot-Gelb-Zeit und die Gelbzeitstaffel - beide sind Vorgabenfelder. */ function vwvStvoEintrag(defaults: RilsaDefaults): QuelleOhneFeld { return { schluessel: 'vwv-stvo', quelle: QUELLEN['vwv-stvo'], bezeichnung: 'Einsatzgrenze der Lichtsignalanlage und Signalfolge (Einsatzgrenze und Rot-Gelb-Obergrenze ' + 'sind harte Schranken; Rot-Gelb-Zeit und Gelbzeitstaffel sind einstellbar – hier steht der ' + 'wirksame Wert)', kennwerte: [ { label: 'Zulässige Höchstgeschwindigkeit für den Betrieb einer LSA', wert: `bis ${fmt.numShort(VZUL_OBERGRENZE_LSA)} km/h (Rn. 10)`, }, { label: 'Rot-Gelb-Zeit', wert: `${wirksamMitRegelwert(fmt.seconds(defaults.redYellow), fmt.seconds(RILSA_DEFAULTS.redYellow))}, ` + `höchstens ${fmt.seconds(SAFETY_FLOORS.maxRedYellow)} (Rn. 17)`, }, { label: 'Gelbzeit nach zulässiger Höchstgeschwindigkeit', wert: `${wirksamMitRegelwert(gelbzeitstaffelSatz(defaults), gelbzeitstaffelSatz())} (Rn. 17)`, }, ], }; } /** * RSA 21: Anhaltswerte der einstreifigen Verkehrsfuehrung - nur dort. Die * Zeile zu 400/600 m steht dabei, obwohl sie KEIN Wert der RSA ist: Der * Pruefbericht nennt diese Laengen im selben Atemzug wie die RSA-Kriterien, * und der Nachweis muss sagen, welche der Zahlen aus dem Regelwerk stammt und * welche aus der Behoerdenpraxis (Befund C5). */ function rsaEintrag(): QuelleOhneFeld { return { schluessel: 'rsa-arbeitsstellen', quelle: QUELLEN['rsa-arbeitsstellen'], bezeichnung: 'Anhaltswerte für die Hinweise des Prüfberichts bei einstreifiger Verkehrsführung (keine ' + 'Rechengröße; die Umlaufzeit- und Wartezeitschranken der Anlagenart stehen im Abschnitt ' + '„Schranken der Anlagenart")', kennwerte: [ { label: 'Kurze Engstelle, für die eine Regelung ohne Lichtsignalanlage genügen kann', wert: `bis ${fmt.meters(RSA_EINSTREIFIG.kurzeEngstelleBis, 0)} (Regelung nach § 6 StVO bzw. mit den ` + 'Zeichen 208/308; Teil B)', }, { label: 'Richtwert der Verkehrsstärke für die Anordnung (beide Richtungen zusammen)', wert: `über ${fmt.numShort(RSA_EINSTREIFIG.verkehrsstaerkeRichtwert)} Kfz/h (Teil B)`, }, { label: 'Zulässige Höchstgeschwindigkeit an Arbeitsstellen längerer Dauer', wert: `in der Regel ${fmt.numShort(RSA_EINSTREIFIG.vZulRegel)} km/h (Teil C 2.3.2)`, }, { label: `Engstellenlänge ${fmt.meters(ENGSTELLE_PRAXIS.hinweisAb, 0)} / ` + `${fmt.meters(ENGSTELLE_PRAXIS.warnungAb, 0)} (Hinweis / Warnung)`, wert: 'Behördenpraxis (z. B. Hessen Mobil), KEIN Wert der RSA - Baustellenampeln werden ' + `üblicherweise bis ${fmt.meters(ENGSTELLE_PRAXIS.hinweisAb, 0)} aufgestellt, ausnahmsweise ` + `und mit Zustimmung bis ${fmt.meters(ENGSTELLE_PRAXIS.warnungAb, 0)}`, }, ], }; } /** * Was das Fundstellenverzeichnis je Kennwert nennt: den Regelwert - und, wo * dieser Plan davon abweicht, den angesetzten Wert. */ export interface Fundstellenkennwert { /** Der Wert, gegen den die Abweichungsliste misst. */ readonly regelwert: number; /** Angesetzter Wert, wo diese Planung abweicht; sonst null. */ readonly angesetzt: number | null; } /** * Regelwert und angesetzter Wert je Kennwert - beides aus dem Fachkern, keiner * davon hier noch einmal entschieden. * * Der Regelwert ist das Feld `regelwert` der Vorgabenzeile (settings.ts, * `regelwertVon`) - die eine Stelle, aus der auch die Abweichungsliste ihren * Bezug nimmt und aus der die Vorgabenansicht liest. Er haengt allein an der * Anlagenart und nie am Eintrag des Anwenders: Eine Spalte "Regelwert", die * zwischen 40 und 60 s wechselt, je nachdem ob jemand etwas eingetragen hat, * waere ein Widerspruch in einer Zeile. WAS ANGESETZT WIRD, sagt dagegen die * Abweichungsliste (`resolveRilsaSettings`), also dieselbe Funktion, aus der * die gedruckte Aufstellung "Abweichungen von den Regelwerten" entsteht. * * KORREKTUR (Fassung 5.12.0): Das Verzeichnis bildete beides selbst - aus * `settingFields(...).standard`, also der Zahl des Regelwerkssatzes, und einem * Wertvergleich daneben. Bei den beiden Regelbereichsfeldern der Umlaufzeit ist * der Bezug aber der Wert, der OHNE Eintrag fuer die ANLAGENART gilt; an einer * Fussgaengerschutzanlage nannte dieselbe Unterlage damit zwei Regelwerte fuer * denselben Kennwert (40 gegen 60 s). * * EINE QUELLE, KEIN UMWEG MEHR (Fassung 5.13.0): Bis dahin * holte diese Funktion den Regelwert ueber eine zweite Abweichungsliste - eine * Vorlage, in der jeder Kennwert auf `standard` stand, durch * `resolveRilsaSettings` gefuehrt, und der Bezug aus deren `standardValue` * gelesen. Seit der Fassung 5.12.0 fuehrt die Vorgabenzeile * denselben Wert unmittelbar; gemessen ueber alle 45 Kennwerte und alle drei * Anlagenarten liefern beide Wege dieselbe Zahl. Dass die Vorgabenzeile und die * Abweichungsliste zusammenbleiben, haelt * tests/domain/regelwertBezugJeFeld.test.ts fest - fuer jedes Feld und jede * Anlagenart, nicht nur fuer die beiden Regelbereichsfelder. */ export function fundstellenkennwerte( overrides: RilsaOverrides | undefined, anlagenart: Anlagenart = 'knotenpunkt', ): ReadonlyMap { const abweichung = new Map( resolveRilsaSettings(overrides, RILSA_DEFAULTS, anlagenart).deviations.map( (d): readonly [keyof RilsaOverrides, SettingDeviation] => [d.key, d], ), ); return new Map( settingFields(RILSA_DEFAULTS, anlagenart).map( (f): readonly [keyof RilsaOverrides, Fundstellenkennwert] => [ f.key, { regelwert: f.regelwert, angesetzt: abweichung.get(f.key)?.appliedValue ?? null, }, ], ), ); } /** * Zusatz zur Kennwertbeschriftung im Fundstellenverzeichnis, wenn ein Kennwert * in dieser Planung in keine Zahl eingeht - in Klammern und mit dem Grund: * " (ohne Wirkung: Bewertungsverfahren HCM)" bzw. " (ohne Wirkung in dieser * Fassung)". Sonst der leere Text. * * WELCHE Kennwerte das sind, entscheidet der Fachkern * (settings.ts, `wirkungslosigkeit`) - hier steht nur noch die Klammer. * Vor Fassung 5.4.0 (Befund C20) entschied diese Funktion es * selbst und kannte dabei nur den Instationaritaetsfaktor; die * Koordinierungs-Kennwerte standen im Nachweis wie angewandte Werte, obwohl * coordination.ts von keiner Rechnung aufgerufen wird. * * Der `kontext` traegt die projektabhaengigen Faelle (Fassung 5.5.0, Fragen E1 und E2): * die beiden festen Ueberfahrzeiten, die nur beim Ansatz 'fest' wirken, und die * Raeumgeschwindigkeit bei engem Innenradius, die nur wirkt, wenn wenigstens * eine Beziehung das Merkmal traegt. Jedes Feld ist optional wie im Fachkern - * wer es nicht durchreicht, bekommt "der Wert wirkt" und damit eine * Begruendungspflicht zu viel statt einer Wirkungsbehauptung zu wenig. Jeder * Aufrufer, der das Projekt kennt, reicht den Kontext durch. */ export function wirkungsvermerk( quellenSchluessel: QuellenSchluessel, verfahren: Bewertungsverfahren, kontext: WirkungsKontext = {}, ): string { const ohneWirkung = wirkungslosigkeit(quellenSchluessel, verfahren, kontext); return ohneWirkung === null ? '' : ` (${ohneWirkung.vermerk})`; }