lsa-planer

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

/ src domain rilsa ansaetze.ts

22,1 KB Rohdatei
src/domain/rilsa/ansaetze.ts — 481 Zeilen
1 import { roundTo } from '../units';
2 import type { Seconds } from '../units';
3 import {
4 MOVEMENT_LABELS,
5 OEPNV_UEBERFAHRZEIT_STAFFEL,
6 RILSA_DEFAULTS,
7 RILSA_ENGSTELLE,
8 UEBERFAHRZEIT_ANSATZ_LABELS,
9 type RilsaDefaults,
10 } from './constants';
11 import type { IntergreenResult, TrafficMode, UeberfahrzeitAnsatz } from './types';
12 import { intergreenKey, massgebendeRaeumbeziehung } from '../plan/signalPlan';
13 import type { SignalPlan } from '../plan/signalPlan';
14 import type { Conflict, Project, SignalGroup } from '../model/project';
15
16 /**
17 * Richtung, Groessenordnung und Herkunft der beiden waehlbaren Praxisansaetze -
18 * an EINER Stelle, fuer Pruefbericht, Ausdruck, Tabellenausgabe und Bildschirm.
19 * Dazu der Satz zum raeumenden Strom, der aus demselben Grund hier steht.
20 *
21 * WARUM ES DIESE DATEI GIBT: Saetze wie "beim Abbiegen um bis zu 3 s je Uebergang" und
22 * "raeumt mit 5,0 m/s statt mit 7,0 m/s; die Zwischenzeit faellt laenger aus"
23 * standen an fuenf bzw. drei Stellen als fest eingeschriebener Text. Beide
24 * Aussagen gelten aber nur fuer die REGELWERTE der zugehoerigen Kennwerte:
25 * `crossingTime.kfzAbbiegend` ist von 1 bis 10 s einstellbar,
26 * `clearingSpeed.kfzTurning` von 3 bis 10 m/s. Wer sie anfasst, bekam einen
27 * gedruckten Satz, der seiner eigenen Rechnung widersprach - derselbe
28 * Fehlertyp wie Befund C21, nur an der Fehlerrichtung statt am Zahlenwert.
29 *
30 * Die Texte stehen im Fachkern und nicht in jeder Ausgabe einzeln, aus dem
31 * Grund, der schon fuer `wirkungslosigkeit` (settings.ts) und
32 * UEBERFAHRZEIT_ANSATZ_LABELS (constants.ts) galt: Drei Fassungen derselben
33 * Aussage laufen genau dann auseinander, wenn niemand daran denkt.
34 *
35 * KEINE ABHAENGIGKEIT ZUR OBERFLAECHE: Der Fachkern formatiert seine Zahlen
36 * selbst (formatZahl weiter unten), wie intergreen.ts und settings.ts auch.
37 * Ein Fachkern, der `@/ui/format` einbindet, waere die erste Kehrtwendung in
38 * der Schichtung dieses Programms.
39 */
40
41 /** Zahl mit Dezimalkomma und hoechstens zwei Nachkommastellen. */
42 function formatZahl(value: number): string {
43 return roundTo(value, 2).toString().replace('.', ',');
44 }
45
46 /**
47 * Raeumgeschwindigkeit mit Einheit und EINER Nachkommastelle - dieselbe
48 * Schreibweise wie fmt.metersPerSecond in Ansicht und Ausdruck ("5,0 m/s").
49 * Eine Geschwindigkeit, die am Bildschirm "5,0 m/s" heisst und im Pruefbericht
50 * "5 m/s", liest sich wie zwei verschiedene Werte.
51 */
52 function formatGeschwindigkeit(value: number): string {
53 return `${roundTo(value, 1).toFixed(1).replace('.', ',')} m/s`;
54 }
55
56 /**
57 * Groesste Gelbzeit der Staffel - der Wert, gegen den sich die feste
58 * Ueberfahrzeit im unguenstigsten Fall vergleicht.
59 */
60 function hoechsteGelbzeit(defaults: RilsaDefaults): Seconds {
61 const werte = defaults.yellowBySpeed.map((stufe) => stufe.yellow);
62 return werte.length > 0 ? Math.max(...werte) : (RILSA_DEFAULTS.yellowBySpeed[2]?.yellow ?? 5);
63 }
64
65 /**
66 * Um wie viel der Ansatz "feste Ueberfahrzeit" die Ueberfahrzeit gegenueber
67 * dem Regelansatz hoechstens verkuerzt - je Fahrbeziehung.
68 *
69 * Bezugsgroesse ist die GROESSTE Gelbzeit der Staffel: Der Ansatz gilt
70 * unabhaengig von der zulaessigen Hoechstgeschwindigkeit, die Verkuerzung faellt
71 * also dort am groessten aus, wo die Gelbzeit am laengsten ist. Ein negatives
72 * Ergebnis bedeutet, dass die eingetragene feste Ueberfahrzeit laenger ist als
73 * jede Gelbzeit - dann verkuerzt der Ansatz nichts.
74 */
75 export function ueberfahrzeitVerkuerzung(defaults: RilsaDefaults = RILSA_DEFAULTS): {
76 readonly geradeaus: Seconds;
77 readonly abbiegend: Seconds;
78 } {
79 const gelb = hoechsteGelbzeit(defaults);
80 return {
81 geradeaus: roundTo(gelb - defaults.crossingTime.kfzGeradeaus, 2),
82 abbiegend: roundTo(gelb - defaults.crossingTime.kfzAbbiegend, 2),
83 };
84 }
85
86 /**
87 * Fehlerrichtung des Ansatzes "feste Ueberfahrzeit" als vollstaendiger Satz.
88 *
89 * Der ganze Satz und nicht nur die Zahl: Wer nur die Groessenordnung aus den
90 * Vorgaben holte und das Wort "VERKUERZT" im eigenen Text stehen liesse, druckte
91 * bei einer festen Ueberfahrzeit von 6 s weiterhin eine Verkuerzung - obwohl der
92 * Ansatz dann jede Zwischenzeit verlaengert. Die Richtung gehoert zur Zahl.
93 */
94 export function ueberfahrzeitAnsatzRichtungSatz(defaults: RilsaDefaults = RILSA_DEFAULTS): string {
95 const { geradeaus, abbiegend } = ueberfahrzeitVerkuerzung(defaults);
96 const teile: string[] = [];
97 if (abbiegend > 0) teile.push(`beim Abbiegen um bis zu ${formatZahl(abbiegend)} s`);
98 if (geradeaus > 0) teile.push(`geradeaus um bis zu ${formatZahl(geradeaus)} s`);
99
100 if (teile.length === 0) {
101 return (
102 'Mit den hier angesetzten festen Überfahrzeiten fällt die Überfahrzeit nirgends kürzer aus ' +
103 'als die Gelbzeit; die Zwischenzeiten werden durch die Wahl nicht verkürzt. Die ' +
104 'eingetragenen Werte weichen dann allerdings von den Regelwerten der RiLSA 2015 ab und ' +
105 'sind zu begründen.'
106 );
107 }
108 return (
109 `Diese Wahl VERKÜRZT die Zwischenzeiten gegenüber dem Regelansatz ` +
110 `„${UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit}" – ${teile.join(' und ')} je Übergang.`
111 );
112 }
113
114 /**
115 * Wirkung des Merkmals "enger Innenradius" als Satzteil, anzuhaengen an
116 * "... raeumt an dieser Beziehung": "mit 5,0 m/s statt mit 7,0 m/s; die
117 * Zwischenzeit faellt laenger aus".
118 *
119 * Der zweite Zweig ist seit der Kappung in resolveRilsaSettings (Fassung 5.5.0,
120 * Befund B1) der einzige verbleibende Grenzfall: Wer die Raeumgeschwindigkeit
121 * des Abbiegers auf den Wert des Merkmals absenkt, bekommt ein Kaestchen, das
122 * nichts mehr aendert. Das ist zulaessig - aber es dann als Verlaengerung
123 * auszuweisen waere die Behauptung einer Verschaerfung, die es nicht gibt.
124 */
125 export function engerRadiusRichtungSatzteil(defaults: RilsaDefaults = RILSA_DEFAULTS): string {
126 const mit = defaults.clearingSpeed.kfzTurningEngerRadius;
127 const ohne = defaults.clearingSpeed.kfzTurning;
128 if (mit < ohne) {
129 return (
130 `mit ${formatGeschwindigkeit(mit)} statt mit ${formatGeschwindigkeit(ohne)}; die ` +
131 'Zwischenzeit fällt länger aus'
132 );
133 }
134 return (
135 `mit ${formatGeschwindigkeit(mit)} – dem Wert, mit dem er auch ohne dieses Merkmal räumt, weil die ` +
136 'Räumgeschwindigkeit des Abbiegers in den Vorgaben auf denselben Wert gesetzt ist; die ' +
137 'Zwischenzeit ändert sich dadurch nicht'
138 );
139 }
140
141 /**
142 * "3 s geradeaus und 2 s abbiegend" - die festen Ueberfahrzeiten des
143 * Regelwerks, nicht die dieses Plans.
144 */
145 function regelwerksFesteWerte(): string {
146 return (
147 `${formatZahl(RILSA_DEFAULTS.crossingTime.kfzGeradeaus)} s geradeaus und ` +
148 `${formatZahl(RILSA_DEFAULTS.crossingTime.kfzAbbiegend)} s abbiegend`
149 );
150 }
151
152 /**
153 * Entsprechen die beiden festen Ueberfahrzeiten dieses Plans den Regelwerten
154 * aus constants.ts?
155 *
156 * WARUM IM FACHKERN UND NICHT IN JEDER AUSGABE (Fassung 5.43.0):
157 * Diese eine Antwort traegt vier Saetze - den Zuschreibungssatz hier, den
158 * Feldhinweis der Projektansicht, den Warnbalken daneben und die Meldung
159 * 'zwischenzeiten.ueberfahrzeit-ansatz-fest' des Pruefberichts. Sie stand
160 * zweimal nachgebaut im Baum (projectView.ts, services/export/bewertung.ts als
161 * `festeWerteNachLeitfaden`) und fehlte im Pruefbericht und im Warnbalken ganz:
162 * Dort war die Zuschreibung an das Regelwerk unbedingt, obwohl beide Felder
163 * ueber die Vorgabenverwaltung von 1 bis 10 s einstellbar sind. Die Kopie in
164 * bewertung.ts ist noch nicht abgeloest - dort haengt derselbe Satz in einem
165 * anderen Satzbau daran.
166 *
167 * DER NAME SAGT JETZT "REGELWERT": Bis 5.26.0 hiess dieselbe Frage
168 * `festeWerteNachLeitfaden`, weil die beiden Zahlen nur in behoerdlichen
169 * Leitfaeden belegt waren. Sie stehen in der RiLSA 2015 (Abschnitt 2.5.2,
170 * Faelle 1 und 2); der Leitfaden bleibt eine zweite, uebereinstimmende Quelle.
171 */
172 export function festeWerteNachRegelwert(defaults: RilsaDefaults = RILSA_DEFAULTS): boolean {
173 return (
174 defaults.crossingTime.kfzGeradeaus === RILSA_DEFAULTS.crossingTime.kfzGeradeaus &&
175 defaults.crossingTime.kfzAbbiegend === RILSA_DEFAULTS.crossingTime.kfzAbbiegend
176 );
177 }
178
179 /**
180 * Woher die beiden ANGESETZTEN festen Ueberfahrzeiten stammen und wie der
181 * Regelansatz dieses Programms dagegen ausfaellt - ein Satz fuer Pruefbericht
182 * und Projektansicht.
183 *
184 * ZU VERWENDEN, WO DIE ZAHLEN SCHON DAVOR STEHEN: Der Satz nennt sie nicht
185 * erneut, sondern beurteilt sie ("Die Werte sind ...").
186 *
187 * "NIE KUERZER" UND NICHT "LAENGER" (Fassung 5.43.0): Bei 50 km/h geradeaus
188 * ergeben beide Ansaetze 3 s. Wahr bei JEDER zulaessigen Gelbzeitstaffel ist
189 * nur "nie kuerzer" - und auch das nur gegen die Regelwerte der RiLSA:
190 * SAFETY_FLOORS.yellowKfz liegt mit 3 s genau auf dem Regelwert geradeaus und
191 * ueber dem abbiegenden, aber unter jeder festen Vorgabe darueber. Deshalb
192 * vergleicht der zweite Zweig ausdruecklich mit den Regelwerten und nicht mit
193 * den eingetragenen Zahlen: Bei 6 s abbiegend rechnet die Gelbzeit kuerzer als
194 * die Vorgabe, und der Richtungssatz (`ueberfahrzeitAnsatzRichtungSatz`) sagt
195 * das daneben.
196 */
197 export function festeWerteZuschreibungSatz(defaults: RilsaDefaults = RILSA_DEFAULTS): string {
198 if (festeWerteNachRegelwert(defaults)) {
199 return (
200 'Die Werte sind die Regelwerte der RiLSA 2015 (Abschnitt 2.5.2, Fälle 1 und 2); der ' +
201 'Regelansatz dieses Programms – die Kopplung an die Gelbzeit – rechnet nie kürzer als diese.'
202 );
203 }
204 return (
205 `Die Werte sind Vorgaben dieses Plans, nicht die ${regelwerksFesteWerte()} der RiLSA 2015 ` +
206 '(Abschnitt 2.5.2, Fälle 1 und 2); der Regelansatz dieses Programms – die Kopplung an die ' +
207 'Gelbzeit – rechnet nie kürzer als die Regelwerte der RiLSA 2015.'
208 );
209 }
210
211 /** Woher die Ueberfahrzeit einer Beziehung stammt - Zusatzangaben des Stroms. */
212 export interface UeberfahrzeitHerkunftKontext {
213 /** OePNV mit Halt vor dem Knotenpunkt (RiLSA 2015, Fall 4). */
214 readonly haltVorKnoten?: boolean;
215 /** An der Beziehung ist eine Ueberfahrzeit von Hand eingetragen. */
216 readonly vorgegeben?: boolean;
217 /**
218 * Der von Hand eingetragene Wert und der in der Rechnung ANGESETZTE - nur
219 * zusammen auswertbar (Fassung 5.9.0). Weichen sie voneinander ab, hat
220 * resolveCrossingTime die Eingabe verworfen und mit dem Regelwert gerechnet;
221 * die Spalte darf sie dann nicht weiter als Herkunft des gezeigten Werts
222 * ausgeben. Fehlt eines von beiden, bleibt es beim bisherigen Verhalten -
223 * kein Aufrufer muss die Angaben nachruesten.
224 */
225 readonly vorgabewert?: Seconds;
226 readonly angesetzt?: Seconds;
227 }
228
229 /**
230 * Woher die Ueberfahrzeit DIESER Beziehung stammt - je Verkehrsart.
231 *
232 * KORREKTUR (Fassung 5.5.0): Die Tabellenausgabe druckte neben JEDE
233 * Ueberfahrzeit die Beschriftung des Kfz-Ueberfahrzeit-Ansatzes - auch neben
234 * die 0,00 s eines Fussgaengerstroms und neben die 5 s eines OePNV-Stroms aus
235 * der Vmax-Staffel. Der Ansatz gilt ausschliesslich fuer Kraftfahrzeuge
236 * (intergreen.ts, kfzUeberfahrzeit wird nur aus dem Kfz-Zweig gerufen); bei
237 * allen uebrigen Verkehrsarten war die Angabe schlicht falsch.
238 *
239 * Eine Handeingabe geht vor: Sie ersetzt den Regelwert des Ansatzes, und eine
240 * Spalte, die dann weiter den Rechenansatz nennt, behauptet eine Herleitung,
241 * die nicht stattgefunden hat. Genannt wird - wie beim Merkmal "enger
242 * Innenradius" -, was ERFASST ist.
243 *
244 * KORREKTUR (Fassung 5.9.0): "Eine Handeingabe geht vor" gilt
245 * nicht immer. resolveCrossingTime VERWIRFT jede Kfz-Ueberfahrzeit unter dem
246 * Regelwert des gewaehlten Ansatzes und jede ungueltige Vorgabe und rechnet
247 * mit dem Regelwert weiter. Die Zeile zeigte dann den Regelwert und behauptete
248 * daneben, er sei von Hand eingetragen worden - genau der Widerspruch, den
249 * dieser Docblock ausschliessen will. Entschieden wird jetzt am ANGESETZTEN
250 * Wert: Nur eine uebernommene Eingabe heisst Handeingabe, eine verworfene wird
251 * als verworfen ausgewiesen.
252 *
253 * KORREKTUR (Fassung 5.43.0): DAS VERFAHREN GEHT VOR. An einer
254 * einstreifigen Verkehrsfuehrung rechnet der Fachkern nach RiLSA 2015,
255 * Abschnitt 5.2.2 mit einer festen Ueberfahrzeit von 4 s
256 * (`computeEngstellenIntergreen`); der gewaehlte Kfz-Ansatz erreicht dieses
257 * Verfahren gar nicht. Neben den 4 s stand gleichwohl seine Beschriftung - im
258 * Rechenweg des Zwischenzeitfensters und in der Spalte
259 * "Ueberfahrzeit-Herkunft" der Tabellenausgabe -, also entweder "3/4/5 s nach
260 * zulaessiger Hoechstgeschwindigkeit" oder "3 s geradeaus / 2 s abbiegend".
261 * Keiner der beiden Ansaetze gibt 4 s her. Dieselbe Korrektur haben die drei
262 * Felder darueber am 11.09.2026 bekommen (`bezugswerte` in intergreen.ts), und
263 * das Verfahren steht hier aus demselben Grund als ERSTES Argument: So muss es
264 * jede Aufrufstelle mitgeben, und eine vergessene bricht beim Uebersetzen statt
265 * still in der Ausgabe.
266 *
267 * UND DIE HANDEINGABE GEHT DORT NICHT VOR, sondern gar nicht ein: Das
268 * Engstellenverfahren liest `crossingTimeOverride` nicht. Eine Eingabe von
269 * genau 4 s hiess deshalb "von Hand eingetragen" - eine Herleitung, die nicht
270 * stattgefunden hat -, eine von 6 s "verworfen", als haette eine Pruefung sie
271 * abgelehnt. Gesagt wird jetzt, was zutrifft: Sie geht in dieses Verfahren
272 * nicht ein.
273 */
274 export function ueberfahrzeitHerkunft(
275 verfahren: IntergreenResult['verfahren'],
276 mode: TrafficMode,
277 ansatz: UeberfahrzeitAnsatz,
278 kontext: UeberfahrzeitHerkunftKontext = {},
279 ): string {
280 if (verfahren === 'engstelle') {
281 const regel =
282 `Regelwert der Engstelle: tü = ${formatZahl(RILSA_ENGSTELLE.ueberfahrzeit)} s ` +
283 '(RiLSA 2015, Abschnitt 5.2.2)';
284 if (kontext.vorgegeben !== true || kontext.vorgabewert === undefined) return regel;
285 return (
286 `${regel} – die an dieser Beziehung eingetragenen ` +
287 `${formatZahl(kontext.vorgabewert)} s gehen in dieses Verfahren nicht ein`
288 );
289 }
290
291 const verworfen =
292 kontext.vorgegeben === true &&
293 kontext.vorgabewert !== undefined &&
294 kontext.angesetzt !== undefined &&
295 roundTo(kontext.vorgabewert, 6) !== roundTo(kontext.angesetzt, 6);
296
297 if (kontext.vorgegeben === true && !verworfen) {
298 return 'an dieser Beziehung von Hand eingetragen';
299 }
300
301 const herleitung = ((): string => {
302 switch (mode) {
303 case 'kfz':
304 return UEBERFAHRZEIT_ANSATZ_LABELS[ansatz];
305 case 'oepnv':
306 return kontext.haltVorKnoten === true
307 ? 'Halt vor dem Knotenpunkt: tü = 0 s (RiLSA 2015, Fall 4)'
308 : // Die Stufen aus der Staffel und nicht aus dem Satz - sie sind zwar
309 // kein Vorgabenfeld, aber dieselbe Regel gilt: eine Zahl steht dort,
310 // wo mit ihr gerechnet wird.
311 `Vmax-Staffel des ÖPNV (${OEPNV_UEBERFAHRZEIT_STAFFEL.map((s) => formatZahl(s.crossing)).join('/')} s ` +
312 'nach zulässiger Höchstgeschwindigkeit, RiLSA 2015, Fall 3)';
313 case 'rad':
314 return 'Regelwert des Radverkehrs aus den Vorgaben';
315 case 'fuss':
316 return 'Regelwert der Fußgänger aus den Vorgaben';
317 }
318 })();
319
320 if (!verworfen) return herleitung;
321 return (
322 `${herleitung} – die an dieser Beziehung eingetragenen ` +
323 `${formatZahl(kontext.vorgabewert ?? 0)} s sind verworfen`
324 );
325 }
326
327 /** Wodurch die Zwischenzeit eines raeumenden Kraftfahrzeugs zustande kam. */
328 export type KfzRaeumverfahren = IntergreenResult['verfahren'] | 'vorgegeben';
329
330 /**
331 * Nach welchen Verfahren die Zwischenzeiten der RAEUMENDEN Kraftfahrzeuge
332 * dieses Plans zustande gekommen sind - die plangrosse Schwester von
333 * `ueberfahrzeitHerkunft` daneben, die dieselbe Frage je Beziehung beantwortet.
334 *
335 * WARUM DIESE FRAGE EINE STELLE BRAUCHT (Fassung 5.43.0): Vier Ausgaben nennen
336 * den gewaehlten Kfz-Ueberfahrzeit-Ansatz, ohne eine einzelne Beziehung vor
337 * sich zu haben - das Deckblatt der Planunterlage, der Absatz unter der
338 * Zwischenzeitenmatrix, die Tafel "Abweichungen von den Regelwerten" und der
339 * Pruefbericht. Alle vier nannten ihn unbedingt. An einer einstreifigen
340 * Verkehrsfuehrung rechnet `buildSignalPlan` aber jeden raeumenden Kfz-Strom
341 * nach dem Verfahren der Engstellensignalisierung, und die Ueberfahrzeit liegt
342 * dort mit RILSA_ENGSTELLE.ueberfahrzeit fest: Der Ansatz geht in keine Zahl
343 * ein, und vier Stellen behaupteten eine Rechnung, die nicht stattgefunden
344 * hat - neben den Zahlen, die der Pruefer nachrechnet.
345 *
346 * GELESEN WIRD DER PLAN, NICHT DIE ANLAGENART: Ob der Ansatz gerechnet hat,
347 * steht im Ergebnis (`calculation.verfahren`). Die Bedingung aus
348 * `buildSignalPlan` hier ein zweites Mal nachzubauen, waere die naechste Stelle,
349 * die beim naechsten Verfahren auseinanderlaeuft. Deshalb traegt die Antwort
350 * auch den Fall, den keine Anlagenart hergibt: Ist jede Kfz-Zwischenzeit von
351 * Hand vorgegeben, hat der Ansatz ebenfalls nicht gerechnet
352 * (`calculation: null`).
353 *
354 * KFZ HEISST HIER: DER RAEUMENDE STROM IST EIN KRAFTFAHRZEUG. Der Ansatz gilt
355 * allein fuer die Ueberfahrzeit des raeumenden Kfz (intergreen.ts,
356 * `kfzUeberfahrzeit`); einfahrende Kraftfahrzeuge beruehrt er nicht.
357 */
358 export function verfahrenDerKfzRaeumstroeme(
359 project: Project,
360 plan: SignalPlan,
361 ): ReadonlySet<KfzRaeumverfahren> {
362 const verfahren = new Set<KfzRaeumverfahren>();
363 for (const conflict of project.conflicts) {
364 const from = project.signalGroups.find((g) => g.id === conflict.fromId);
365 if (from === undefined || from.mode !== 'kfz') continue;
366 const zwischenzeit = plan.intergreens.get(intergreenKey(conflict.fromId, conflict.toId));
367 if (zwischenzeit === undefined) continue;
368 verfahren.add(zwischenzeit.calculation?.verfahren ?? 'vorgegeben');
369 }
370 return verfahren;
371 }
372
373 /**
374 * Vermerk, Begruendung und Zu-tun zu einem Ansatz, der an diesem Plan in keine
375 * Zahl eingeht - in derselben Gestalt wie `Wirkungslosigkeit` (settings.ts),
376 * damit die Ausgaben ihn behandeln wie einen wirkungslosen Kennwert.
377 */
378 export interface AnsatzOhneWirkung {
379 /** Kurz, zum Anhaengen an die Benennung des Ansatzes. */
380 readonly vermerk: string;
381 /** Ganzer Satz: was stattdessen gerechnet hat. */
382 readonly begruendung: string;
383 /** Was zu tun ist - zu begruenden ist nichts. */
384 readonly zutun: string;
385 }
386
387 /**
388 * Geht der gewaehlte Kfz-Ueberfahrzeit-Ansatz in keine Zwischenzeit dieses
389 * Plans ein? Dann steht hier, warum - sonst `null`.
390 *
391 * `null` HEISST: ER RECHNET. Die Richtung ist mit Absicht diese, wie bei
392 * `wirkungslosigkeit` in settings.ts: Eine zu viel verlangte Begruendung ist
393 * harmlos, die Behauptung "ohne Wirkung" ueber einen Ansatz, der gerade jede
394 * Zwischenzeit bildet, waere es nicht. Genannt wird der Grund und nicht nur die
395 * Wirkungslosigkeit - "kein Ansatz genannt" liest sich am Papier wie ein
396 * fehlender Kennwert.
397 */
398 export function ueberfahrzeitAnsatzOhneWirkung(
399 project: Project,
400 plan: SignalPlan,
401 ): AnsatzOhneWirkung | null {
402 const verfahren = verfahrenDerKfzRaeumstroeme(project, plan);
403 if (verfahren.has('knotenpunkt')) return null;
404
405 if (verfahren.has('engstelle')) {
406 return {
407 vermerk: 'ohne Wirkung an dieser Anlage: gerechnet nach RiLSA 2015, Abschnitt 5.2.2',
408 begruendung:
409 'Die Zwischenzeiten mit räumenden Kraftfahrzeugen sind nach dem Verfahren der ' +
410 'Engstellensignalisierung gerechnet (RiLSA 2015, Abschnitt 5.2.2), mit einer festen ' +
411 `Überfahrzeit von tü = ${formatZahl(RILSA_ENGSTELLE.ueberfahrzeit)} s. Der eingestellte ` +
412 'Rechenansatz der Kfz-Überfahrzeit geht damit in keine Zwischenzeit dieses Plans ein.',
413 zutun:
414 'Es ist nichts zu begründen. Prüfen Sie, ob die Anlagenart richtig erfasst ist: An einem ' +
415 'Knotenpunkt geht die Wahl in jede Zwischenzeit eines räumenden Kraftfahrzeugs ein.',
416 };
417 }
418
419 if (verfahren.has('vorgegeben')) {
420 return {
421 vermerk: 'ohne Wirkung an dieser Anlage: Kfz-Zwischenzeiten von Hand vorgegeben',
422 begruendung:
423 'Jede Zwischenzeit mit einem räumenden Kraftfahrzeug ist an diesem Plan von Hand ' +
424 'vorgegeben und ersetzt die Berechnung; der eingestellte Rechenansatz der ' +
425 'Kfz-Überfahrzeit geht in keine Zwischenzeit dieses Plans ein. Er wirkt allenfalls in ' +
426 'der Gegenprobe, mit der jede Vorgabe gegen den Rechenwert gehalten wird.',
427 zutun:
428 'Es ist nichts zu begründen. Zu begründen sind die vorgegebenen Zwischenzeiten, nicht ' +
429 'der Ansatz.',
430 };
431 }
432
433 return {
434 vermerk: 'ohne Wirkung an dieser Anlage: kein räumendes Kraftfahrzeug erfasst',
435 begruendung:
436 'Dieser Plan führt keine Konfliktbeziehung, an der ein Kraftfahrzeug räumt; der ' +
437 'eingestellte Rechenansatz der Kfz-Überfahrzeit geht in keine Zwischenzeit dieses Plans ' +
438 'ein.',
439 zutun:
440 'Es ist nichts zu begründen. Erfassen Sie die Konfliktbeziehungen, wenn der Ansatz wirken ' +
441 'soll.',
442 };
443 }
444
445 /**
446 * DER SATZ ZUM MASSGEBENDEN STROM - einmal formuliert.
447 *
448 * Wo `raeumbeziehung` von der Fahrbeziehung der Signalgruppe abweicht,
449 * beurteilen Pruefbericht, Konfliktfenster und Planunterlage die Beziehung am
450 * STROM - und die Signalgruppentabelle daneben fuehrt weiter die Gruppe. Ohne
451 * diesen Satz stand im Bericht "der raeumende Strom "K1" ist kein abbiegender
452 * Kraftfahrzeugstrom" ueber einer Gruppe, die als rechts abbiegend erfasst
453 * ist, und umgekehrt "Der abbiegende Kraftfahrzeugstrom raeumt deshalb ..."
454 * ueber einer Gruppe, die geradeaus fuehrt.
455 *
456 * Er steht hier und nicht dreimal: Das Konfliktfenster (ui/views/
457 * conflictsView.ts) setzt ihn in seinen Kopf, der Bericht (validation/
458 * engine.ts) an beide Meldungen zum engen Innenradius. Drei Formulierungen
459 * desselben Sachverhalts waeren drei Gelegenheiten, auseinanderzulaufen - aus
460 * genau diesem Befund ist die Funktion entstanden.
461 *
462 * IN DIESER DATEI UND NICHT MEHR IM PRUEFBERICHT: Er ist ein Satz des Fachkerns wie
463 * `engerRadiusRichtungSatzteil` und `ueberfahrzeitHerkunft` daneben, und keine
464 * Pruefregel. Im Bericht stehend, band er das Konfliktfenster an die
465 * Pruefschicht, obwohl es sie sonst nicht braucht. Wortlaut und Verhalten sind
466 * beim Umzug unveraendert geblieben.
467 *
468 * Leer, wo die Beziehung mit der Fahrbeziehung ihrer Gruppe raeumt: Dann gibt
469 * es nichts zu erklaeren.
470 */
471 export function raeumbeziehungSatz(
472 from: Pick<SignalGroup, 'name' | 'movement'>,
473 conflict: Pick<Conflict, 'raeumbeziehung'>,
474 ): string {
475 const raeumbeziehung = massgebendeRaeumbeziehung(from, conflict);
476 if (raeumbeziehung === from.movement) return '';
477 return (
478 `Den Räumweg dieser Beziehung fährt ein Strom der Signalgruppe „${from.name}“ mit der ` +
479 `Fahrbeziehung „${MOVEMENT_LABELS[raeumbeziehung]}“; geräumt wird mit ihr.`
480 );
481 }