lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests ui furtEinfahrweg.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import type { Project } from '@/domain/model/project'; |
| 3 | import { buildSignalPlan } from '@/domain/plan/signalPlan'; |
| 4 | import { computeIntergreen } from '@/domain/rilsa/intergreen'; |
| 5 | import { QUELLEN } from '@/domain/rilsa/quellen'; |
| 6 | import type { ClearingStream, EnteringStream } from '@/domain/rilsa/types'; |
| 7 | import type { Meters } from '@/domain/units'; |
| 8 | import { validateProject } from '@/domain/validation/engine'; |
| 9 | import { |
| 10 | ASSISTENT_VORGABE, |
| 11 | FURT_MINDESTWEGE, |
| 12 | assistentZusammenfassung, |
| 13 | furtAnnahmeText, |
| 14 | furtWegeAnnahme, |
| 15 | projektAusAngaben, |
| 16 | } from '@/ui/assistent'; |
| 17 | import { HILFE } from '@/ui/help/texte'; |
| 18 | |
| 19 | /** |
| 20 | * Der Einfahrweg, den der gefuehrte Einstieg einer Fussgaengerfurt gibt. |
| 21 | * |
| 22 | * VORGESCHICHTE (Befund 9): Der Einstieg hob den halben Kfz-Einfahrweg auf eine |
| 23 | * Untergrenze von 3 m an. In tz = tue + tr - te wird die Einfahrzeit ABGEZOGEN; |
| 24 | * die Furt ist an dieser Stelle der einfahrende Strom, ein groesserer |
| 25 | * Einfahrweg verkuerzt also die Zwischenzeit ZUM Fussgaenger. Die Untergrenze |
| 26 | * fiel weg, die Halbierung blieb. |
| 27 | * |
| 28 | * JETZT (Fassung 5.43.0): Auch die Halbierung faellt weg - der Einfahrweg ist |
| 29 | * 0 m. Nach RiLSA 2015 (Abschnitt 2.5.3) ist die Einfahrzeit des Fussgaengers |
| 30 | * 0 s, wenn die Konfliktflaeche am Fahrbahnrand beginnt; mit 1,5 m/s wird nur |
| 31 | * gerechnet, wenn der raeumende Verkehr den Randfahrstreifen nicht benutzen |
| 32 | * darf. Die Arme des KNOTENPUNKTS sind einstreifig (`createArm(..., 1, ...)`), |
| 33 | * und jede Kfz-Gruppe darin fuehrt einen Fahrstreifen (`lanes ?? 1`) - auch die |
| 34 | * eigene Linksabbiegergruppe. Der Kfz-Strom darf den Randfahrstreifen also |
| 35 | * benutzen, und die Furt beginnt an der Bordkante unmittelbar daneben. |
| 36 | * Dieselbe Rechnung setzen seit 5.43.0 die Vermessung aus dem Lageplan |
| 37 | * (`vermesseWege`) und die Vorlage (`factory.ts`) an; die |
| 38 | * Fussgaengerschutzanlage des Einstiegs tat es schon immer - sie legt zwar zwei |
| 39 | * Fahrstreifen an, und beide sind benutzbar. |
| 40 | * |
| 41 | * DIE GRENZE DIESER AUSSAGE (Stand 17.09.2026): Der Knotenpunkt war |
| 42 | * die letzte Stelle, die einen Plan NEU ANLEGT oder VERMISST und dabei einem |
| 43 | * Fussgaenger einen Einfahrweg annahm. Nicht die letzte im Programm: Beim |
| 44 | * Einlesen eines 4.x-Projekts bekommt jede Beziehung den global hinterlegten |
| 45 | * Einfahrweg der Altdatei, auch eine einfahrende Furt (`migrate.ts`; fehlt er, |
| 46 | * sind es 10 m). Hier stand die Aussage bis dahin ohne diese Grenze. |
| 47 | * |
| 48 | * WO 0 m eine VERSCHAERFUNG waere: vor einem Strom, der den Randfahrstreifen |
| 49 | * nicht benutzen darf - eine mehrstreifige Zufahrt mit eigenem |
| 50 | * Abbiegestreifen, wie in der mitgelieferten Beispieldatei. Die Richtung |
| 51 | * stimmt auch dort (laengere Zwischenzeit), aber es ist dann eine Annahme |
| 52 | * dieses Programms und keine Umsetzung der RiLSA. Ebenso verfaehrt die |
| 53 | * Vermessung aus dem Lageplan (siehe `vermessung.ts`). AM KNOTENPUNKT DES |
| 54 | * ASSISTENTEN kommt der Fall nicht vor; bis zum 17.09.2026 |
| 55 | * stand hier seine eigene Linksabbiegergruppe als Beispiel, und die fuehrt im |
| 56 | * Randfahrstreifen ihrer Zufahrt. |
| 57 | */ |
| 58 | |
| 59 | const FURT_MINDESTWEGE_FELDER = FURT_MINDESTWEGE as Readonly<Record<string, number>>; |
| 60 | const FESTES_DATUM = new Date('2026-01-01T00:00:00Z'); |
| 61 | |
| 62 | /** Zwischenzeit vom raeumenden Kfz-Strom zur einfahrenden Furt. */ |
| 63 | function zwischenzeitZurFurt(einfahrwegFurt: Meters, raeumwegKfz: Meters): number { |
| 64 | const raeumend: ClearingStream = { |
| 65 | mode: 'kfz', |
| 66 | movement: 'geradeaus', |
| 67 | clearingDistance: raeumwegKfz, |
| 68 | vehicleClass: 'pkw', |
| 69 | vZul: 50, |
| 70 | }; |
| 71 | const einfahrend: EnteringStream = { |
| 72 | mode: 'fuss', |
| 73 | movement: 'geradeaus', |
| 74 | enteringDistance: einfahrwegFurt, |
| 75 | }; |
| 76 | return computeIntergreen(raeumend, einfahrend).intergreen; |
| 77 | } |
| 78 | |
| 79 | /** |
| 80 | * Der Altstand am fertigen Projekt nachgebaut: Jede einfahrende Furt bekommt |
| 81 | * den halben Kfz-Einfahrweg von 4 m zurueck, alles andere bleibt. |
| 82 | * |
| 83 | * Nur so lassen sich die Wirkungen auf den GERECHNETEN Plan nebeneinander |
| 84 | * messen - die Funktion `furtWegeAnnahme` nimmt den Kfz-Einfahrweg nicht mehr |
| 85 | * entgegen, und ueber die Angaben ist der Altstand deshalb nicht mehr |
| 86 | * erreichbar. |
| 87 | */ |
| 88 | function altstandMitEinfahrweg(projekt: Project, se: Meters): Project { |
| 89 | const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id)); |
| 90 | return { |
| 91 | ...projekt, |
| 92 | conflicts: projekt.conflicts.map((c) => |
| 93 | furten.has(c.toId) ? { ...c, enteringDistance: se } : c, |
| 94 | ), |
| 95 | }; |
| 96 | } |
| 97 | |
| 98 | /** Zwischenzeiten eines Plans als "K1->F1" auf Sekunden. */ |
| 99 | function zwischenzeiten(projekt: Project): ReadonlyMap<string, number> { |
| 100 | const name = new Map(projekt.signalGroups.map((g) => [g.id, g.name])); |
| 101 | return new Map( |
| 102 | [...buildSignalPlan(projekt).intergreens.values()].map((z) => [ |
| 103 | `${name.get(z.fromId) ?? '?'}->${name.get(z.toId) ?? '?'}`, |
| 104 | z.value, |
| 105 | ]), |
| 106 | ); |
| 107 | } |
| 108 | |
| 109 | describe('Assistent: kein angenommener Einfahrweg fuer Fussgaenger (5.43.0)', () => { |
| 110 | it('setzt den Einfahrweg der Furt auf 0 m, gleich welcher Kfz-Einfahrweg eingetragen ist', () => { |
| 111 | // Am angelegten Projekt und nicht an der Funktion: Der Kfz-Einfahrweg geht |
| 112 | // dort ueberhaupt nicht mehr ein (`furtWegeAnnahme` nimmt ihn nicht mehr |
| 113 | // entgegen), und genau das ist die Aussage. |
| 114 | for (const einfahrweg of [1, 2, 3, 4, 5, 6, 8, 12, 20]) { |
| 115 | const projekt = projektAusAngaben({ ...ASSISTENT_VORGABE, einfahrweg }, 'F', FESTES_DATUM); |
| 116 | const furten = new Set( |
| 117 | projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id), |
| 118 | ); |
| 119 | const einfahrendeFurt = projekt.conflicts.filter((c) => furten.has(c.toId)); |
| 120 | expect(einfahrendeFurt.length, `Kfz-Einfahrweg ${einfahrweg} m`).toBeGreaterThan(0); |
| 121 | for (const c of einfahrendeFurt) { |
| 122 | expect(c.enteringDistance, `Kfz-Einfahrweg ${einfahrweg} m`).toBe(0); |
| 123 | } |
| 124 | } |
| 125 | }); |
| 126 | |
| 127 | it('laesst den Raeumweg der Furt unveraendert - dort ist die Untergrenze die sichere Seite', () => { |
| 128 | expect(furtWegeAnnahme({ raeumweg: 22 })).toEqual({ raeumweg: 11, einfahrweg: 0 }); |
| 129 | expect(furtWegeAnnahme({ raeumweg: 8 })).toEqual({ |
| 130 | raeumweg: FURT_MINDESTWEGE.raeumweg, |
| 131 | einfahrweg: 0, |
| 132 | }); |
| 133 | expect(furtWegeAnnahme({ raeumweg: 4 })).toEqual({ |
| 134 | raeumweg: FURT_MINDESTWEGE.raeumweg, |
| 135 | einfahrweg: 0, |
| 136 | }); |
| 137 | }); |
| 138 | |
| 139 | it('fuehrt in FURT_MINDESTWEGE weiterhin nur eine Untergrenze fuer den Raeumweg', () => { |
| 140 | expect(FURT_MINDESTWEGE.raeumweg).toBe(6); |
| 141 | expect(Object.keys(FURT_MINDESTWEGE_FELDER)).toEqual(['raeumweg']); |
| 142 | }); |
| 143 | |
| 144 | it('verlaengert dadurch die Zwischenzeit zum Fussgaenger um zwei ganze Sekunden', () => { |
| 145 | /* |
| 146 | * Die Zahlen des Assistenten (Vorgabe): Raeumweg 22 m, Kfz geradeaus, |
| 147 | * vr 10 m/s, lFZ 6 m, tue = Gelbzeit 3 s bei 50 km/h, Furt als |
| 148 | * einfahrender Strom mit ve = 1,5 m/s. |
| 149 | * |
| 150 | * bisher (4 m): 3 + (22 + 6)/10 - 4/1,5 = 5,80 - 2,67 = 3,13 s -> 4 s |
| 151 | * jetzt (0 m): 3 + (22 + 6)/10 - 0 = 5,80 s -> 6 s |
| 152 | * |
| 153 | * Die Untergrenze tue + tr >= tG + 1 s (RiLSA 2015, Abschnitt 2.5.2) |
| 154 | * greift in beiden Faellen nicht: 5,80 s >= 4 s. |
| 155 | */ |
| 156 | const furt = furtWegeAnnahme(ASSISTENT_VORGABE); |
| 157 | expect(furt.einfahrweg).toBe(0); |
| 158 | expect(zwischenzeitZurFurt(furt.einfahrweg, ASSISTENT_VORGABE.raeumweg)).toBe(6); |
| 159 | expect(zwischenzeitZurFurt(4, ASSISTENT_VORGABE.raeumweg)).toBe(4); |
| 160 | }); |
| 161 | |
| 162 | it('traegt die 0 in jede Konfliktbeziehung, in der eine Furt einfaehrt', () => { |
| 163 | const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM); |
| 164 | const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id)); |
| 165 | expect(furten.size).toBe(4); |
| 166 | |
| 167 | const einfahrendeFurt = projekt.conflicts.filter((c) => furten.has(c.toId)); |
| 168 | expect(einfahrendeFurt.length).toBeGreaterThan(0); |
| 169 | for (const c of einfahrendeFurt) { |
| 170 | expect(c.enteringDistance).toBe(0); |
| 171 | expect(c.note).toContain('Einfahrweg 0 m'); |
| 172 | } |
| 173 | |
| 174 | // Die Kfz-Beziehungen behalten den eingetragenen Einfahrweg: Die Regel der |
| 175 | // RiLSA gilt dem einfahrenden FUSSGAENGER, nicht jedem Strom. |
| 176 | for (const c of projekt.conflicts) { |
| 177 | if (furten.has(c.toId) || furten.has(c.fromId)) continue; |
| 178 | expect(c.enteringDistance).toBe(ASSISTENT_VORGABE.einfahrweg); |
| 179 | expect(c.clearingDistance).toBe(ASSISTENT_VORGABE.raeumweg); |
| 180 | } |
| 181 | }); |
| 182 | |
| 183 | it('steht die laengere Zwischenzeit auch im gerechneten Plan der angelegten Kreuzung', () => { |
| 184 | // Nicht die Formel, sondern der Plan, den der Anwender sieht: K1 raeumt, |
| 185 | // eine Furt faehrt ein - 6 s statt der 4 s des Altstands. |
| 186 | const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM); |
| 187 | const plan = buildSignalPlan(projekt); |
| 188 | const k1 = projekt.signalGroups.find((g) => g.name === 'K1')!; |
| 189 | const furtIds = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id)); |
| 190 | |
| 191 | const vonK1ZurFurt = [...plan.intergreens.values()].filter( |
| 192 | (z) => z.fromId === k1.id && furtIds.has(z.toId), |
| 193 | ); |
| 194 | expect(vonK1ZurFurt.length).toBeGreaterThan(0); |
| 195 | for (const z of vonK1ZurFurt) expect(z.value).toBe(6); |
| 196 | }); |
| 197 | |
| 198 | it('verlaengert die Zwischenzeit von Furt zu Furt sogar um drei Sekunden', () => { |
| 199 | /* |
| 200 | * DIE GROESSERE DER BEIDEN WIRKUNGEN, und bis zum 17.09.2026 nannte sie |
| 201 | * kein Text: Raeumt eine Furt und faehrt eine andere ein, ist tue = 0 s |
| 202 | * (Fussgaenger, RiLSA 2015 Fall 6) und tr = 11 m / 1,2 m/s = 9,17 s. Der |
| 203 | * alte Einfahrweg von 4 m zog 4 / 1,5 = 2,67 s ab, macht 6,50 s -> 7 s; |
| 204 | * ohne ihn bleiben 9,17 s -> 10 s. |
| 205 | * |
| 206 | * Unveraendert bleiben die beiden Beziehungen ohne einfahrende Furt: Kfz vor |
| 207 | * Kfz 6 s und Furt vor Kfz 9 s. |
| 208 | */ |
| 209 | const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM); |
| 210 | const neu = zwischenzeiten(projekt); |
| 211 | const alt = zwischenzeiten(altstandMitEinfahrweg(projekt, 4)); |
| 212 | |
| 213 | expect(neu.get('F1->F3')).toBe(10); |
| 214 | expect(alt.get('F1->F3')).toBe(7); |
| 215 | expect(neu.get('K1->F1')).toBe(6); |
| 216 | expect(alt.get('K1->F1')).toBe(4); |
| 217 | expect(neu.get('F1->K1')).toBe(9); |
| 218 | expect(alt.get('F1->K1')).toBe(9); |
| 219 | expect(neu.get('K1->K3')).toBe(6); |
| 220 | expect(alt.get('K1->K3')).toBe(6); |
| 221 | |
| 222 | // Keine einzige Zwischenzeit wird kuerzer - die Richtung der ganzen |
| 223 | // Aenderung, an allen 32 Beziehungen der Kreuzung geprueft. |
| 224 | expect(neu.size).toBe(32); |
| 225 | for (const [paar, wert] of neu) { |
| 226 | expect(wert, `Beziehung ${paar}`).toBeGreaterThanOrEqual(alt.get(paar) ?? 0); |
| 227 | } |
| 228 | }); |
| 229 | |
| 230 | it('macht den Umlauf laenger und meldet die ungerade Sekunde im Pruefbericht', () => { |
| 231 | /* |
| 232 | * Die Folge der laengeren Zwischenzeiten steht im Plan, den der Anfaenger |
| 233 | * als Ersten sieht: Der Umlauf der Vorgabekreuzung steigt von 40 s auf |
| 234 | * 45 s, und die Freigabezeitverteilung laesst eine ungerade Sekunde uebrig. |
| 235 | * Nach dem Grundsatz des Projekts gehoert die in den Pruefbericht, und dort |
| 236 | * steht sie - ohne einen neuen Fehler. |
| 237 | */ |
| 238 | const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM); |
| 239 | const alt = altstandMitEinfahrweg(projekt, 4); |
| 240 | |
| 241 | const planNeu = buildSignalPlan(projekt); |
| 242 | const planAlt = buildSignalPlan(alt); |
| 243 | expect(planNeu.cycleTime).toBe(45); |
| 244 | expect(planAlt.cycleTime).toBe(40); |
| 245 | |
| 246 | const berichtNeu = validateProject(projekt, planNeu, FESTES_DATUM); |
| 247 | const berichtAlt = validateProject(alt, planAlt, FESTES_DATUM); |
| 248 | const regeln = (bericht: ReturnType<typeof validateProject>): readonly string[] => |
| 249 | bericht.findings.map((b) => b.rule); |
| 250 | |
| 251 | expect(regeln(berichtNeu)).toContain('signalplan.freigabezeit-restsekunde'); |
| 252 | expect(regeln(berichtAlt)).not.toContain('signalplan.freigabezeit-restsekunde'); |
| 253 | expect(berichtNeu.errorCount).toBe(0); |
| 254 | expect(berichtNeu.errorCount).toBe(berichtAlt.errorCount); |
| 255 | expect(berichtNeu.warningCount).toBe(berichtAlt.warningCount); |
| 256 | }); |
| 257 | |
| 258 | it('legt jeden Arm und jede Kfz-Gruppe einstreifig an - darauf beruht die 0', () => { |
| 259 | /* |
| 260 | * Die Begruendung des Einfahrwegs 0 m haengt daran, dass der raeumende |
| 261 | * Kfz-Strom den Fahrstreifen am Fahrbahnrand benutzen darf. Beim |
| 262 | * Knotenpunkt dieses Assistenten ist das der einzige Fahrstreifen, den es |
| 263 | * gibt - auch fuer die eigene Linksabbiegergruppe, die keinen eigenen |
| 264 | * Abbiegestreifen bekommt. Bis zum 17.09.2026 nannten |
| 265 | * Kommentar und Unterlagen sie als Fall der Verschaerfung; belegt ist das |
| 266 | * hier nicht. |
| 267 | */ |
| 268 | const projekt = projektAusAngaben( |
| 269 | { ...ASSISTENT_VORGABE, linksabbieger: true }, |
| 270 | 'F', |
| 271 | FESTES_DATUM, |
| 272 | ); |
| 273 | for (const arm of projekt.intersection.arms) { |
| 274 | expect(arm.lanes, `Arm ${arm.name}`).toBe(1); |
| 275 | } |
| 276 | const linksabbieger = projekt.signalGroups.filter((g) => g.movement === 'links'); |
| 277 | expect(linksabbieger.length).toBeGreaterThan(0); |
| 278 | for (const g of projekt.signalGroups) { |
| 279 | expect(g.lanes, `Signalgruppe ${g.name}`).toBe(1); |
| 280 | } |
| 281 | }); |
| 282 | }); |
| 283 | |
| 284 | /** |
| 285 | * DER RAEUMWEG DER FURT bleibt der halbe Kfz-Raeumweg (mindestens 6 m) - ein |
| 286 | * Faustwert des Assistenten, kein Regelwerkswert. Er wirkt an ZWEI Stellen, |
| 287 | * und dieser Block haelt beide fest, weil der angezeigte Satz sie zusagt. |
| 288 | */ |
| 289 | describe('Assistent: der angenommene Furt-Raeumweg wirkt doppelt', () => { |
| 290 | it('ist zugleich die Furtlaenge der Mindestfreigabezeit', () => { |
| 291 | /* |
| 292 | * `furtlaengeDerGruppe` (plan/signalPlan.ts) nimmt den laengsten Raeumweg |
| 293 | * der Beziehungen, in denen die Gruppe raeumt - beim Assistenten also |
| 294 | * genau die Annahme. Bei der Vorgabe 22 m sind das 11,0 m; mit |
| 295 | * Zusatzeinrichtung fuer Blinde und Sehbehinderte waeren 11/1,2 = 9,17 s |
| 296 | * -> 10 s massgebend, ohne sie 11/2/1,2 = 4,58 s -> 5 s und damit der |
| 297 | * Regelwert. Ein zu kurz angenommener Raeumweg verkuerzt also nicht nur |
| 298 | * die Zwischenzeit, sondern auch die Freigabezeit des Fussgaengers. |
| 299 | */ |
| 300 | const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM); |
| 301 | const plan = buildSignalPlan(projekt); |
| 302 | const furt = plan.groups.find((g) => g.mode === 'fuss')!; |
| 303 | expect(furt.mindestfreigabezeit.furt?.furtlaenge).toBe( |
| 304 | furtWegeAnnahme(ASSISTENT_VORGABE).raeumweg, |
| 305 | ); |
| 306 | expect(furt.mindestfreigabezeit.furt?.furtlaenge).toBe(11); |
| 307 | }); |
| 308 | |
| 309 | it('und der Anwender erfaehrt beide Wirkungen', () => { |
| 310 | // Die Messung oben ist nur dann eine Auskunft, wenn sie irgendwo steht: |
| 311 | // im Satz des Assistenten und in der Hilfe zum Raeumweg. |
| 312 | expect(furtAnnahmeText(ASSISTENT_VORGABE)).toContain( |
| 313 | 'aus ihm folgt auch die Mindestfreigabezeit', |
| 314 | ); |
| 315 | expect(HILFE.raeumweg.woher ?? '').toContain('zugleich die Furtlänge'); |
| 316 | expect(HILFE.raeumweg.woher ?? '').toContain('verkürzt deshalb beides'); |
| 317 | }); |
| 318 | }); |
| 319 | |
| 320 | describe('Wortlaut: die sichtbaren Texte nennen den Wert, der angesetzt wird', () => { |
| 321 | it('nennt der Satz des Assistenten den Einfahrweg 0 m und seinen Grund', () => { |
| 322 | const satz = furtAnnahmeText({ raeumweg: 22 }); |
| 323 | expect(satz).toContain('halber Räumweg: 11,0 m'); |
| 324 | expect(satz).toContain('mindestens 6 m'); |
| 325 | expect(satz).toContain('Einfahrweg 0 m'); |
| 326 | expect(satz).toContain('Fahrbahnrand'); |
| 327 | expect(satz).toContain('Annahme, im Lageplan prüfen'); |
| 328 | // Die beiden ueberholten Staende duerfen nicht zurueckkehren. |
| 329 | expect(satz).not.toContain('halber Einfahrweg'); |
| 330 | expect(satz).not.toContain('mindestens 3 m'); |
| 331 | }); |
| 332 | |
| 333 | it('sagt derselbe Satz, dass der Raeumweg die ganze Furt abdecken muss', () => { |
| 334 | const satz = furtAnnahmeText({ raeumweg: 22 }); |
| 335 | expect(satz).toContain('Bordkante zu Bordkante'); |
| 336 | expect(satz).toContain('Mittelinsel'); |
| 337 | }); |
| 338 | |
| 339 | it('steht er unveraendert in der Zusammenfassung des letzten Schritts', () => { |
| 340 | const text = assistentZusammenfassung({ ...ASSISTENT_VORGABE, fussgaenger: true }).join(' '); |
| 341 | expect(text).toContain(furtAnnahmeText(ASSISTENT_VORGABE)); |
| 342 | }); |
| 343 | |
| 344 | it('nennt die Bemerkung der Konfliktbeziehung dieselbe Zahl', () => { |
| 345 | const projekt = projektAusAngaben( |
| 346 | { ...ASSISTENT_VORGABE, raeumweg: 8, einfahrweg: 4 }, |
| 347 | 'F', |
| 348 | FESTES_DATUM, |
| 349 | ); |
| 350 | const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id)); |
| 351 | const beziehung = projekt.conflicts.find((c) => furten.has(c.toId))!; |
| 352 | expect(beziehung.enteringDistance).toBe(0); |
| 353 | expect(beziehung.note).toContain('Einfahrweg 0 m (Fahrbahnrand'); |
| 354 | expect(beziehung.note).not.toContain('halber Einfahrweg'); |
| 355 | }); |
| 356 | |
| 357 | it('sagt die Bemerkung je Beziehung nur, was an IHR angesetzt wurde', () => { |
| 358 | /* |
| 359 | * Stand 17.09.2026: Bis dahin stand an jeder Beziehung mit |
| 360 | * Furtbeteiligung derselbe Satz mit beiden Zahlen. An "Furt raeumt, Kfz |
| 361 | * faehrt ein" nannte er "Einfahrweg 0 m", waehrend dort der Kfz-Einfahrweg |
| 362 | * von 8 m gespeichert ist, und an "Kfz raeumt, Furt faehrt ein" den halben |
| 363 | * Raeumweg von 11,0 m, waehrend dort der ganze Kfz-Raeumweg von 22 m steht. |
| 364 | * Eine Bemerkung, die eine Zahl nennt, die an dieser Beziehung nicht gilt, |
| 365 | * ist keine Auskunft. |
| 366 | */ |
| 367 | const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM); |
| 368 | const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id)); |
| 369 | let raeumendeFurt = 0; |
| 370 | let einfahrendeFurt = 0; |
| 371 | let furtVorFurt = 0; |
| 372 | |
| 373 | for (const c of projekt.conflicts) { |
| 374 | const raeumt = furten.has(c.fromId); |
| 375 | const faehrtEin = furten.has(c.toId); |
| 376 | const note = c.note ?? ''; |
| 377 | if (raeumt && !faehrtEin) { |
| 378 | raeumendeFurt += 1; |
| 379 | expect(c.clearingDistance).toBe(11); |
| 380 | expect(c.enteringDistance).toBe(ASSISTENT_VORGABE.einfahrweg); |
| 381 | expect(note).toContain('Furt räumt: halber Räumweg (11,0 m)'); |
| 382 | expect(note, 'die Bemerkung nennt einen Einfahrweg, der hier nicht gilt').not.toContain( |
| 383 | 'Einfahrweg 0 m', |
| 384 | ); |
| 385 | } |
| 386 | if (faehrtEin && !raeumt) { |
| 387 | einfahrendeFurt += 1; |
| 388 | expect(c.clearingDistance).toBe(ASSISTENT_VORGABE.raeumweg); |
| 389 | expect(c.enteringDistance).toBe(0); |
| 390 | expect(note).toContain('Furt fährt ein: Einfahrweg 0 m (Fahrbahnrand'); |
| 391 | expect(note, 'die Bemerkung nennt einen Räumweg, der hier nicht gilt').not.toContain( |
| 392 | 'halber Räumweg', |
| 393 | ); |
| 394 | } |
| 395 | if (raeumt && faehrtEin) { |
| 396 | furtVorFurt += 1; |
| 397 | expect(note).toContain('Furt räumt: halber Räumweg (11,0 m)'); |
| 398 | expect(note).toContain('Furt fährt ein: Einfahrweg 0 m (Fahrbahnrand'); |
| 399 | } |
| 400 | expect(note).toContain('im Lageplan prüfen'); |
| 401 | } |
| 402 | |
| 403 | // Alle drei Faelle kommen an der Vorgabekreuzung wirklich vor. |
| 404 | expect([raeumendeFurt, einfahrendeFurt, furtVorFurt].every((n) => n > 0)).toBe(true); |
| 405 | }); |
| 406 | |
| 407 | it('nennt die Hilfe zum Raeumweg den neuen Ansatz', () => { |
| 408 | const woher = HILFE.raeumweg.woher ?? ''; |
| 409 | expect(woher).toContain('halben Räumweg'); |
| 410 | expect(woher).toContain('mindestens 6 m'); |
| 411 | expect(woher).toContain('Einfahrweg 0 m'); |
| 412 | expect(woher).not.toContain('halben Einfahrweg'); |
| 413 | expect(woher).not.toContain('ohne Untergrenze'); |
| 414 | }); |
| 415 | |
| 416 | it('nennt die Hilfe zur Mittelinsel den uebernommenen Einfahrweg', () => { |
| 417 | const woher = HILFE.mittelinsel.woher ?? ''; |
| 418 | expect(woher).toContain('0 m'); |
| 419 | expect(woher).toContain('nicht benutzen darf'); |
| 420 | }); |
| 421 | |
| 422 | it('bleibt bei der Aussage der Hilfe zum Einfahrweg', () => { |
| 423 | // Die Regel, gegen die der Assistent verstiess - sie stand schon da. |
| 424 | expect(HILFE.einfahrweg.wennFalsch ?? '').toContain( |
| 425 | 'Ein zu langer Einfahrweg verkürzt die Zwischenzeit', |
| 426 | ); |
| 427 | }); |
| 428 | it('nennt die Fundstelle BEIDE Wirkungen und nicht nur die kleinere', () => { |
| 429 | // Bis zum 17.09.2026 nannten die Texte allein "4 s statt 6 s". |
| 430 | // Die groessere Verkuerzung lag bei drei Sekunden (Furt vor Furt), und ein |
| 431 | // Text, der nur die kleinere nennt, laesst den Leser zu guenstig rechnen. |
| 432 | const hinweis = QUELLEN['rilsa-zwischenzeiten'].hinweis ?? ''; |
| 433 | expect(hinweis).toContain('4 s statt 6 s'); |
| 434 | expect(hinweis).toContain('7 s statt 10 s'); |
| 435 | }); |
| 436 | }); |