lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain rueckstaulaenge.test.ts
| 1 | import { readFileSync } from 'node:fs'; |
| 2 | import { describe, expect, it } from 'vitest'; |
| 3 | import { TrafficSimulation } from '@/domain/simulation/simulation'; |
| 4 | // Mit Fassung 5.31.0 aus der Simulation nach `rilsa/delay.ts` gezogen: Die |
| 5 | // Umrechnung wird jetzt von zwei Rechnungen gebraucht, und zwei Fassungen |
| 6 | // derselben Formel waeren der Fehler des Altbestands. |
| 7 | import { rueckstaulaenge } from '@/domain/rilsa/delay'; |
| 8 | import { buildSignalPlan } from '@/domain/plan/signalPlan'; |
| 9 | import { createStandardIntersectionProject } from '@/domain/model/factory'; |
| 10 | import { STAURAUMBEDARF_JE_FAHRZEUG } from '@/domain/rilsa/constants'; |
| 11 | import { QUELLEN } from '@/domain/rilsa/quellen'; |
| 12 | import type { Project } from '@/domain/model/project'; |
| 13 | |
| 14 | const FIXED_DATE = new Date('2026-01-01T00:00:00Z'); |
| 15 | const SIMULATION_QUELLTEXT = readFileSync('src/domain/simulation/simulation.ts', 'utf8'); |
| 16 | const DELAY_QUELLTEXT = readFileSync('src/domain/rilsa/delay.ts', 'utf8'); |
| 17 | |
| 18 | /** |
| 19 | * Fassung 5.28.0, Punkte A4 und A5. |
| 20 | * |
| 21 | * A4: Der Stauraumbedarf je Fahrzeug stand modullokal in `simulation.ts` - |
| 22 | * ohne Ausfuhr, ohne Eintrag in `constants.ts`, ohne Quelle, ohne |
| 23 | * Pruefstand und ohne einen einzigen Test. Angezeigt wurde er trotzdem. |
| 24 | * A5: Die daraus gebildete Laenge teilte nicht durch die Fahrstreifenzahl. An |
| 25 | * einer zweistreifigen Zufahrt war die angezeigte Laenge doppelt so lang |
| 26 | * wie die wirkliche - ein Fehler mit Faktor, kein Rundungsfehler. |
| 27 | */ |
| 28 | describe('Rueckstaulaenge - der Kennwert hat eine Herkunft (A4)', () => { |
| 29 | it('steht in constants.ts und traegt 7 m', () => { |
| 30 | expect(STAURAUMBEDARF_JE_FAHRZEUG).toBe(7); |
| 31 | }); |
| 32 | |
| 33 | it('steht nicht mehr als eigene Zahl in der Simulation', () => { |
| 34 | // Die Hausregel: constants.ts ist die einzige Quelle aller Kennwerte. |
| 35 | expect(SIMULATION_QUELLTEXT).not.toContain('QUEUE_SPACING_METERS'); |
| 36 | /* |
| 37 | * Seit 5.31.0 nennt die Simulation den Kennwert gar nicht mehr selbst - |
| 38 | * sie ruft `rueckstaulaenge` aus dem Fachkern, und dort steht er. |
| 39 | * |
| 40 | * GEPRUEFT WIRD DIE EINBINDUNG, NICHT DER NAME: Eine eigene Funktion |
| 41 | * gleichen Namens in der Simulation waere genau der Rueckfall, den dieser |
| 42 | * Fall verhindern soll - zwei Fassungen derselben Umrechnung. Ein |
| 43 | * `toContain('rueckstaulaenge')` haette sie durchgelassen. |
| 44 | */ |
| 45 | expect(SIMULATION_QUELLTEXT).toMatch( |
| 46 | /import \{[^}]*rueckstaulaenge[^}]*\} from '\.\.\/rilsa\/delay'/u, |
| 47 | ); |
| 48 | expect(SIMULATION_QUELLTEXT).not.toContain('STAURAUMBEDARF_JE_FAHRZEUG'); |
| 49 | expect(DELAY_QUELLTEXT).toContain('STAURAUMBEDARF_JE_FAHRZEUG'); |
| 50 | }); |
| 51 | |
| 52 | it('hat eine Quelle mit Pruefstand, und die behauptet kein Regelwerk', () => { |
| 53 | const quelle = QUELLEN['praxis-stauraum-je-fahrzeug']; |
| 54 | expect(quelle.pruefstand).toBe('abzugleichen'); |
| 55 | // Weder RiLSA noch HBS nennen eine Laenge je wartendem Fahrzeug. Ein |
| 56 | // Praxiswert, der sich als Regelwerkswert ausgibt, ist der Fehlertyp, den |
| 57 | // dieses Verzeichnis verhindern soll. |
| 58 | expect(quelle.regelwerk).toBe('Planungspraxis'); |
| 59 | expect(quelle.fundstelle).toBeUndefined(); |
| 60 | expect(quelle.hinweis).toContain('Kein Regelwerkswert'); |
| 61 | }); |
| 62 | }); |
| 63 | |
| 64 | describe('Rueckstaulaenge - die Fahrstreifen zaehlen mit (A5)', () => { |
| 65 | it('verteilt die wartenden Fahrzeuge auf die Fahrstreifen', () => { |
| 66 | expect(rueckstaulaenge(10, 1)).toBe(70); |
| 67 | expect(rueckstaulaenge(10, 2)).toBe(35); |
| 68 | // 10 * 7 / 3 = 23,333... - auf eine Stelle wie jede andere Anzeige. |
| 69 | expect(rueckstaulaenge(10, 3)).toBe(23.3); |
| 70 | }); |
| 71 | |
| 72 | it('nimmt eine fehlende Fahrstreifenangabe als einen Fahrstreifen', () => { |
| 73 | // Dieselbe Behandlung wie in `saturationFlow`: keine Angabe heisst einer, |
| 74 | // nicht null - sonst entstuende eine unendliche Laenge. |
| 75 | expect(rueckstaulaenge(10, 0)).toBe(70); |
| 76 | expect(rueckstaulaenge(10, -2)).toBe(70); |
| 77 | expect(rueckstaulaenge(10, Number.NaN)).toBe(70); |
| 78 | }); |
| 79 | |
| 80 | it('meldet fuer eine leere Warteschlange keine Laenge', () => { |
| 81 | expect(rueckstaulaenge(0, 2)).toBe(0); |
| 82 | expect(rueckstaulaenge(Number.NaN, 2)).toBe(0); |
| 83 | }); |
| 84 | |
| 85 | it('zeigt am zweistreifigen Strom die halbe Laenge an', () => { |
| 86 | const project = zweistreifig(createStandardIntersectionProject('Rueckstau', FIXED_DATE)); |
| 87 | const plan = buildSignalPlan(project); |
| 88 | const simulation = new TrafficSimulation(plan, project, { timeStep: 0.5 }); |
| 89 | |
| 90 | let gesehen = false; |
| 91 | for (let i = 0; i < 12; i += 1) { |
| 92 | simulation.advance(30); |
| 93 | for (const gruppe of simulation.snapshot().groups) { |
| 94 | if (gruppe.queue <= 0) continue; |
| 95 | gesehen = true; |
| 96 | /* |
| 97 | * DIE WACHE: Bis zur Behebung stand hier `queue * 7`, und zwar |
| 98 | * unabhaengig von der Zahl der Fahrstreifen. Bei zwei Fahrstreifen |
| 99 | * war die Anzeige damit doppelt so lang wie der wirkliche Rueckstau. |
| 100 | */ |
| 101 | expect(gruppe.queueLength, gruppe.name).toBe(rueckstaulaenge(gruppe.queue, 2)); |
| 102 | expect(gruppe.queueLength, gruppe.name).toBeLessThan( |
| 103 | gruppe.queue * STAURAUMBEDARF_JE_FAHRZEUG, |
| 104 | ); |
| 105 | } |
| 106 | } |
| 107 | // Ein Test, der nie in die Pruefung laeuft, bewacht nichts. |
| 108 | expect(gesehen, 'in keinem Schritt stand ein Fahrzeug im Rueckstau').toBe(true); |
| 109 | }); |
| 110 | |
| 111 | it('laesst den einstreifigen Strom unveraendert', () => { |
| 112 | // Die Behebung darf den Regelfall nicht verschieben: Bei einem |
| 113 | // Fahrstreifen bleibt es bei Fahrzeuge mal Stauraumbedarf. |
| 114 | const project = createStandardIntersectionProject('Rueckstau einstreifig', FIXED_DATE); |
| 115 | const plan = buildSignalPlan(project); |
| 116 | const simulation = new TrafficSimulation(plan, project, { timeStep: 0.5 }); |
| 117 | |
| 118 | let gesehen = false; |
| 119 | for (let i = 0; i < 12; i += 1) { |
| 120 | simulation.advance(30); |
| 121 | for (const gruppe of simulation.snapshot().groups) { |
| 122 | if (gruppe.queue <= 0) continue; |
| 123 | gesehen = true; |
| 124 | expect(gruppe.queueLength, gruppe.name).toBe(gruppe.queue * STAURAUMBEDARF_JE_FAHRZEUG); |
| 125 | } |
| 126 | } |
| 127 | expect(gesehen, 'in keinem Schritt stand ein Fahrzeug im Rueckstau').toBe(true); |
| 128 | }); |
| 129 | }); |
| 130 | |
| 131 | /** Dasselbe Projekt, aber jede Signalgruppe auf zwei Fahrstreifen. */ |
| 132 | function zweistreifig(project: Project): Project { |
| 133 | return { |
| 134 | ...project, |
| 135 | signalGroups: project.signalGroups.map((gruppe) => ({ ...gruppe, lanes: 2 })), |
| 136 | }; |
| 137 | } |