lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain gegenrichtungUndSkala.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { validateProject } from '@/domain/validation/engine'; |
| 3 | import { buildSignalPlan } from '@/domain/plan/signalPlan'; |
| 4 | import { TrafficSimulation } from '@/domain/simulation/simulation'; |
| 5 | import { serviceLevelFor } from '@/domain/rilsa/delay'; |
| 6 | import { createStandardIntersectionProject } from '@/domain/model/factory'; |
| 7 | import type { Project } from '@/domain/model/project'; |
| 8 | |
| 9 | const STICHTAG = new Date('2026-01-01T00:00:00Z'); |
| 10 | |
| 11 | function vollstaendig(name: string): Project { |
| 12 | const base = createStandardIntersectionProject(name, STICHTAG); |
| 13 | return { |
| 14 | ...base, |
| 15 | meta: { |
| 16 | ...base.meta, |
| 17 | projectNumber: 'VP-2026-001', |
| 18 | client: 'Stadt Musterhausen', |
| 19 | planner: 'O. Willerding', |
| 20 | location: name, |
| 21 | }, |
| 22 | }; |
| 23 | } |
| 24 | |
| 25 | describe('Fehlende Gegenrichtung einer Konfliktbeziehung', () => { |
| 26 | /* |
| 27 | * Das war eine Warnung und liess den Export offen. Ohne erfasste |
| 28 | * Gegenrichtung gibt es keine Zwischenzeit - und damit auch keinen Sollwert, |
| 29 | * gegen den die Abstandspruefung des fertigen Plans messen koennte. Die |
| 30 | * fehlende Haelfte ist die einzige Stelle, an der ein feindliches Paar |
| 31 | * voellig ungesichert bleibt. |
| 32 | */ |
| 33 | it('sperrt den Export', () => { |
| 34 | const roh = vollstaendig('Gegenrichtung'); |
| 35 | const [k1, , k3] = roh.signalGroups; |
| 36 | const projekt: Project = { |
| 37 | ...roh, |
| 38 | conflicts: roh.conflicts.filter((c) => !(c.fromId === k3!.id && c.toId === k1!.id)), |
| 39 | }; |
| 40 | |
| 41 | const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG); |
| 42 | const befund = bericht.findings.find((f) => f.rule === 'konflikte.gegenrichtung-fehlt'); |
| 43 | |
| 44 | expect(befund).toBeDefined(); |
| 45 | expect(befund!.severity).toBe('fehler'); |
| 46 | expect(befund!.title).toContain('K1 nach K3'); |
| 47 | expect(befund!.message).toContain('K3'); |
| 48 | expect(bericht.exportBlocked).toBe(true); |
| 49 | }); |
| 50 | |
| 51 | it('schweigt beim Regelknotenpunkt, in dem jede Beziehung beidseitig erfasst ist', () => { |
| 52 | const bericht = validateProject(vollstaendig('Regelfall'), undefined, STICHTAG); |
| 53 | expect(bericht.findings.some((f) => f.rule === 'konflikte.gegenrichtung-fehlt')).toBe(false); |
| 54 | expect(bericht.findings.filter((f) => f.severity === 'fehler')).toHaveLength(0); |
| 55 | }); |
| 56 | }); |
| 57 | |
| 58 | describe('Bewertungsskala in der Simulation', () => { |
| 59 | /* |
| 60 | * serviceLevelFor wurde ohne Skala gerufen und fiel damit auf HBS zurueck, |
| 61 | * waehrend der Signalzeitenplan die eingestellte Skala korrekt weiterreicht. |
| 62 | * Dieselbe Wartezeit erhielt in der Simulation eine andere Qualitaetsstufe |
| 63 | * als in der Leistungsfaehigkeit - und im Ausdruck stand daneben "Bewertung |
| 64 | * nach HCM". |
| 65 | */ |
| 66 | function laufen(skala: 'HBS' | 'HCM'): ReturnType<TrafficSimulation['snapshot']> { |
| 67 | const roh = vollstaendig('Simulation'); |
| 68 | const projekt: Project = { |
| 69 | ...roh, |
| 70 | settings: { ...roh.settings, serviceLevelScale: skala }, |
| 71 | }; |
| 72 | const sim = new TrafficSimulation(buildSignalPlan(projekt), projekt, { seed: 42 }); |
| 73 | sim.advance(600); |
| 74 | return sim.snapshot(); |
| 75 | } |
| 76 | |
| 77 | it('bewertet nach der eingestellten Skala', () => { |
| 78 | for (const skala of ['HBS', 'HCM'] as const) { |
| 79 | const stand = laufen(skala); |
| 80 | expect(stand.overallServiceLevel.scale, skala).toBe(skala); |
| 81 | for (const gruppe of stand.groups) { |
| 82 | expect(gruppe.serviceLevel.scale, `${skala} / ${gruppe.name}`).toBe(skala); |
| 83 | // Die Stufe muss zu Wartezeit UND Skala passen, nicht nur die |
| 84 | // Beschriftung der Skala. |
| 85 | // (Ohne Ueberlastung entscheidet die Wartezeit allein; die Gruppen |
| 86 | // dieses Projekts sind nicht ueberlastet.) |
| 87 | expect(gruppe.serviceLevel.grade, `${skala} / ${gruppe.name}`).toBe( |
| 88 | serviceLevelFor(gruppe.averageDelay, skala)?.grade, |
| 89 | ); |
| 90 | } |
| 91 | } |
| 92 | }); |
| 93 | |
| 94 | it('kommt bei derselben Wartezeit je nach Skala zu verschiedenen Stufen', () => { |
| 95 | // Ohne diesen Nachweis koennte die Skala mitgefuehrt und doch wirkungslos |
| 96 | // sein. 31,9 s sind nach HBS Stufe B, nach HCM Stufe C. |
| 97 | expect(serviceLevelFor(31.9, 'HBS')?.grade).not.toBe(serviceLevelFor(31.9, 'HCM')?.grade); |
| 98 | }); |
| 99 | }); |