lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain vorgabeOhneRaeumweg.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { validateProject } from '@/domain/validation/engine'; |
| 3 | import { buildSignalPlan, intergreenKey } from '@/domain/plan/signalPlan'; |
| 4 | import { createStandardIntersectionProject } from '@/domain/model/factory'; |
| 5 | import type { Conflict, Project } from '@/domain/model/project'; |
| 6 | |
| 7 | /** |
| 8 | * Befund 19, behoben in Fassung 5.10.0. |
| 9 | * |
| 10 | * 'konflikte.raeumweg-fehlt' greift nur bei `manualIntergreen === null`, und |
| 11 | * die Gegenprobe zur Handvorgabe (signalPlan.ts) vergleicht nur bei |
| 12 | * `clearingDistance > 0`. Bei "Handvorgabe gesetzt UND Raeumweg 0 m" schwiegen |
| 13 | * damit beide gleichzeitig: Der Eintrag im Nachbarfeld verwandelte eine |
| 14 | * sperrende Beanstandung in einen ausgabefaehigen Plan, ohne dass irgendeine |
| 15 | * Meldung den fehlenden Weg noch erwaehnte. Mit der Vorgabe entfaellt zugleich |
| 16 | * der Rechenweg der Beziehung - ein Pruefer kann die Zahl daneben durch nichts |
| 17 | * nachrechnen. |
| 18 | * |
| 19 | * Die Richtung ist die unsichere: Jeder beliebig kleine Wert ab 0 s ist so |
| 20 | * meldungsfrei und ohne Sperre eintragbar. |
| 21 | */ |
| 22 | |
| 23 | const STICHTAG = new Date('2026-01-01T00:00:00Z'); |
| 24 | |
| 25 | /** Regelknotenpunkt, in dem genau die Beziehung K1 nach K3 abgewandelt ist. */ |
| 26 | function mitKonflikt(aenderung: Partial<Conflict>): { projekt: Project; konflikt: Conflict } { |
| 27 | const roh = createStandardIntersectionProject('Vorgabe', STICHTAG); |
| 28 | const [k1, , k3] = roh.signalGroups; |
| 29 | const ziel = roh.conflicts.find((c) => c.fromId === k1!.id && c.toId === k3!.id)!; |
| 30 | const geaendert = { ...ziel, ...aenderung }; |
| 31 | return { |
| 32 | konflikt: geaendert, |
| 33 | projekt: { |
| 34 | ...roh, |
| 35 | conflicts: roh.conflicts.map((c) => (c.id === ziel.id ? geaendert : c)), |
| 36 | meta: { |
| 37 | ...roh.meta, |
| 38 | projectNumber: 'VP-2026-001', |
| 39 | client: 'Stadt Musterhausen', |
| 40 | planner: 'O. Willerding', |
| 41 | location: 'Vorgabe', |
| 42 | }, |
| 43 | }, |
| 44 | }; |
| 45 | } |
| 46 | |
| 47 | function regeln(projekt: Project): readonly string[] { |
| 48 | return validateProject(projekt, buildSignalPlan(projekt), STICHTAG).findings.map((f) => f.rule); |
| 49 | } |
| 50 | |
| 51 | describe('Eine Handvorgabe hebt die Raeumweg-Pflicht nicht auf', () => { |
| 52 | it('sperrt weiterhin ohne Handvorgabe', () => { |
| 53 | // Ausgangslage: derselbe Plan, nur ohne die Zahl im Nachbarfeld. |
| 54 | const { projekt } = mitKonflikt({ clearingDistance: 0 }); |
| 55 | const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG); |
| 56 | |
| 57 | expect(bericht.findings.map((f) => f.rule)).toContain('konflikte.raeumweg-fehlt'); |
| 58 | expect(bericht.exportBlocked).toBe(true); |
| 59 | }); |
| 60 | |
| 61 | it('sperrt auch mit einer gueltigen Handvorgabe neben dem Raeumweg 0 m', () => { |
| 62 | const { projekt, konflikt } = mitKonflikt({ manualIntergreen: 5, clearingDistance: 0 }); |
| 63 | const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG); |
| 64 | |
| 65 | const befund = bericht.findings.find((f) => f.rule === 'konflikte.vorgabe-ohne-raeumweg'); |
| 66 | expect( |
| 67 | befund, |
| 68 | 'Die Vorgabe ohne Vergleichsgrundlage darf nicht stillschweigend bleiben', |
| 69 | ).toBeDefined(); |
| 70 | expect(befund!.severity).toBe('fehler'); |
| 71 | expect(befund!.category).toBe('konflikte'); |
| 72 | expect(befund!.target?.kind).toBe('konflikt'); |
| 73 | expect(befund!.target?.id).toBe(konflikt.id); |
| 74 | expect(befund!.target?.label).toBe('K1 nach K3'); |
| 75 | // Beide Zahlen des Widerspruchs gehoeren in den Text. |
| 76 | expect(befund!.message).toContain('5 s'); |
| 77 | expect(befund!.message).toContain('Räumweg'); |
| 78 | expect(bericht.exportBlocked).toBe(true); |
| 79 | }); |
| 80 | |
| 81 | it('meldet dabei nicht den Wortlaut der Raeumwegregel', () => { |
| 82 | // 'konflikte.raeumweg-fehlt' sagt "Die Zwischenzeit wird dadurch zu kurz". |
| 83 | // Bei einer Handvorgabe wird gar nicht gerechnet; der Satz traefe nicht zu. |
| 84 | const { projekt } = mitKonflikt({ manualIntergreen: 5, clearingDistance: 0 }); |
| 85 | |
| 86 | expect(regeln(projekt)).not.toContain('konflikte.raeumweg-fehlt'); |
| 87 | }); |
| 88 | |
| 89 | it('behauptet ueber die Planunterlage nur, was in ihr steht', () => { |
| 90 | /* |
| 91 | * Nachtrag zu Befund 19: Die Meldung nannte "einen Prüfer, dem die |
| 92 | * Zwischenzeitenmatrix den Räumweg mit 0,00 m ausweist". Beides gibt die |
| 93 | * Unterlage nicht her. Bei vorgegebener Zwischenzeit ist |
| 94 | * `resolved.calculation` null; im Ausdruck steht in der Spalte des |
| 95 | * Raeumwegs deshalb "vorgegeben" und in den uebrigen Spalten des |
| 96 | * Rechenwegs ein Strich (services/export/pdf.ts, drawIntergreenDetails). |
| 97 | * Der unvermassene Weg selbst wird im Ausdruck nirgends gedruckt. |
| 98 | * |
| 99 | * Genau der Fehlertyp, gegen den die Befunde 29 und 30 dieser Gruppe |
| 100 | * angetreten sind: ein Prueftext, der etwas ueber die Unterlage behauptet, |
| 101 | * was die Unterlage nicht zeigt. |
| 102 | */ |
| 103 | const { projekt, konflikt } = mitKonflikt({ manualIntergreen: 5, clearingDistance: 0 }); |
| 104 | const plan = buildSignalPlan(projekt); |
| 105 | const aufgeloest = plan.intergreens.get(intergreenKey(konflikt.fromId, konflikt.toId))!; |
| 106 | |
| 107 | // Die Tatsache, auf die sich der Satz stuetzen darf. |
| 108 | expect(aufgeloest.source).toBe('vorgegeben'); |
| 109 | expect(aufgeloest.calculation, 'Ohne Rechnung gibt es keinen Rechenweg').toBeNull(); |
| 110 | |
| 111 | const befund = validateProject(projekt, plan, STICHTAG).findings.find( |
| 112 | (f) => f.rule === 'konflikte.vorgabe-ohne-raeumweg', |
| 113 | )!; |
| 114 | |
| 115 | expect(befund.message).not.toContain('0,00'); |
| 116 | expect(befund.message).not.toContain('Zwischenzeitenmatrix'); |
| 117 | expect(befund.message).toContain('Rechenweg'); |
| 118 | }); |
| 119 | |
| 120 | it('schweigt, sobald der Raeumweg vermasst ist', () => { |
| 121 | const { projekt } = mitKonflikt({ manualIntergreen: 5 }); |
| 122 | |
| 123 | expect(regeln(projekt)).not.toContain('konflikte.vorgabe-ohne-raeumweg'); |
| 124 | }); |
| 125 | |
| 126 | it('laesst die unbrauchbare Handvorgabe beim bisherigen Weg', () => { |
| 127 | // Eine negative Vorgabe gilt als nicht gesetzt: Es wird gerechnet, und |
| 128 | // 'signalplan.raeumweg-null' ist dort die zustaendige Meldung |
| 129 | // (tests/domain/doppelmeldungen.test.ts). Eine zweite Meldung |
| 130 | // ueber denselben Sachverhalt waere die Doppelung, die dort abgewehrt wird. |
| 131 | const { projekt } = mitKonflikt({ manualIntergreen: -1, clearingDistance: 0 }); |
| 132 | const gefunden = regeln(projekt); |
| 133 | |
| 134 | expect(gefunden).toContain('signalplan.raeumweg-null'); |
| 135 | expect(gefunden).not.toContain('konflikte.vorgabe-ohne-raeumweg'); |
| 136 | }); |
| 137 | }); |