lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain anlagenartDatei.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { createEmptyProject, createStandardIntersectionProject } from '@/domain/model/factory'; |
| 3 | import { loadProject } from '@/domain/model/migrate'; |
| 4 | import { parseProject } from '@/domain/model/schema'; |
| 5 | import { CURRENT_SCHEMA_VERSION } from '@/domain/model/project'; |
| 6 | import { ANLAGENART_GRENZEN } from '@/domain/rilsa/constants'; |
| 7 | |
| 8 | /** |
| 9 | * Die Anlagenart auf ihrem Weg durch Vorlage, Datei und Altprojekt. |
| 10 | * |
| 11 | * WOGEGEN DIESE TESTS STEHEN: Bis Schema 8 galten die Schranken des |
| 12 | * Knotenpunkts fuer jede Anlage - vor allem der Raeumweg von hoechstens 500 m. |
| 13 | * Bei einer einstreifigen Verkehrsfuehrung IST der Raeumweg die ganze |
| 14 | * Engstelle; eine 800 m lange Fuehrung waere beim Einlesen stillschweigend auf |
| 15 | * 500 m gekuerzt worden, und die daraus gerechnete Zwischenzeit haette danach |
| 16 | * rund 30 s zu kurz dagestanden. |
| 17 | * |
| 18 | * Die Gegenrichtung ist genauso wichtig: Eine Datei ohne Angabe darf nicht |
| 19 | * plotzlich die weiten Schranken der Arbeitsstelle bekommen. Fehlende Angabe |
| 20 | * heisst "knotenpunkt", und zwar ohne Meldung - das Feld gab es damals nicht. |
| 21 | */ |
| 22 | |
| 23 | const FIXED_DATE = new Date('2026-01-01T00:00:00Z'); |
| 24 | |
| 25 | /** Zwei Signalgruppen und eine Beziehung mit dem angegebenen Raeumweg. */ |
| 26 | function dateiMit(anlagenart: unknown, raeumweg: number, schemaVersion = CURRENT_SCHEMA_VERSION) { |
| 27 | return { |
| 28 | schemaVersion, |
| 29 | anlagenart, |
| 30 | signalGroups: [ |
| 31 | { id: 'a', name: 'K1', mode: 'kfz' }, |
| 32 | { id: 'b', name: 'K2', mode: 'kfz' }, |
| 33 | ], |
| 34 | conflicts: [{ fromId: 'a', toId: 'b', clearingDistance: raeumweg, enteringDistance: 5 }], |
| 35 | }; |
| 36 | } |
| 37 | |
| 38 | describe('Modell und Vorlage', () => { |
| 39 | it('fuehrt die Schemaversion 12', () => { |
| 40 | // ANPASSUNG (Befund A2): Aufstieg auf 10 fuer die Fahrzeugart |
| 41 | // 'strassenbahn' und das Konfliktfeld haltVorKnoten. ANPASSUNG (Befund C1): |
| 42 | // Aufstieg auf 11 fuer das Signalgruppenfeld blindenzusatz (Freigabe fuer |
| 43 | // die ganze Furt). ANPASSUNG (Fassung 5.5.0, Fragen E1/E2): Aufstieg auf 12 |
| 44 | // fuer das Konfliktfeld engerRadius und den Ueberfahrzeit-Ansatz. |
| 45 | // ANPASSUNG: Aufstieg auf 13 fuer den Lastzuganteil am Schwerverkehr |
| 46 | // (TrafficDemand.lastzugAnteil, HBS 2015 Gl. 2-5). |
| 47 | expect(CURRENT_SCHEMA_VERSION).toBe(19); |
| 48 | }); |
| 49 | |
| 50 | it('legt ein leeres Projekt ohne Angabe als Knotenpunkt an', () => { |
| 51 | expect(createEmptyProject('Leer', FIXED_DATE).anlagenart).toBe('knotenpunkt'); |
| 52 | }); |
| 53 | |
| 54 | it('nimmt die Anlagenart als Vorgabe entgegen', () => { |
| 55 | // Der Assistent legt Fussgaengerschutzanlagen und einstreifige Fuehrungen |
| 56 | // an. Die Art gehoert in denselben Schritt wie das Projekt, sonst |
| 57 | // durchlaeuft es einen Zustand mit den falschen Schranken. |
| 58 | expect(createEmptyProject('Baustelle', FIXED_DATE, 'einstreifig').anlagenart).toBe( |
| 59 | 'einstreifig', |
| 60 | ); |
| 61 | expect(createEmptyProject('Furt', FIXED_DATE, 'fussgaengerschutzanlage').anlagenart).toBe( |
| 62 | 'fussgaengerschutzanlage', |
| 63 | ); |
| 64 | }); |
| 65 | |
| 66 | it('weist den Beispielknotenpunkt als Knotenpunkt aus', () => { |
| 67 | expect(createStandardIntersectionProject('Vorlage', FIXED_DATE).anlagenart).toBe('knotenpunkt'); |
| 68 | }); |
| 69 | }); |
| 70 | |
| 71 | describe('Einlesen', () => { |
| 72 | it('liest ein selbst erzeugtes Projekt mit seiner Art wieder ein', () => { |
| 73 | const original = createEmptyProject('Baustelle B7', FIXED_DATE, 'einstreifig'); |
| 74 | expect(parseProject(original, FIXED_DATE).project.anlagenart).toBe('einstreifig'); |
| 75 | }); |
| 76 | |
| 77 | it('gibt einer Datei ohne Angabe "knotenpunkt", und zwar ohne Meldung', () => { |
| 78 | // Eine Datei aus Schema 8 ist nicht fehlerhaft: Sie kannte das Feld nicht, |
| 79 | // und etwas anderes als einen Knotenpunkt konnte sie gar nicht beschreiben. |
| 80 | const { project, issues } = parseProject( |
| 81 | { schemaVersion: 8, signalGroups: [{ id: 'a', name: 'K1', mode: 'kfz' }] }, |
| 82 | FIXED_DATE, |
| 83 | ); |
| 84 | |
| 85 | expect(project.anlagenart).toBe('knotenpunkt'); |
| 86 | expect(issues.filter((i) => i.path === 'anlagenart')).toHaveLength(0); |
| 87 | }); |
| 88 | |
| 89 | it('faellt bei unbekannter Art auf "knotenpunkt" zurueck und meldet das', () => { |
| 90 | // Hier steht etwas, das jemand gemeint hat. Stillschweigend die engsten |
| 91 | // Schranken zu setzen waere sicher, aber der Bearbeiter wuesste nicht, |
| 92 | // warum seine Engstelle nicht rechnet. |
| 93 | const { project, issues } = parseProject(dateiMit('kreisverkehr', 20), FIXED_DATE); |
| 94 | |
| 95 | expect(project.anlagenart).toBe('knotenpunkt'); |
| 96 | const meldung = issues.find((i) => i.path === 'anlagenart'); |
| 97 | expect(meldung).toBeDefined(); |
| 98 | expect(meldung!.severity).toBe('warnung'); |
| 99 | expect(meldung!.message).toContain('kreisverkehr'); |
| 100 | }); |
| 101 | }); |
| 102 | |
| 103 | describe('Raeumweg an der Anlagenart', () => { |
| 104 | it('nimmt bei einstreifiger Fuehrung eine Engstelle ueber 500 m unveraendert an', () => { |
| 105 | // DER FEHLER, UM DEN ES GEHT: Bis Schema 8 stand hier fest 500 m, und die |
| 106 | // 800 m waeren beim Oeffnen der Datei verschwunden. |
| 107 | const { project, issues } = parseProject(dateiMit('einstreifig', 800), FIXED_DATE); |
| 108 | |
| 109 | expect(project.conflicts[0]!.clearingDistance).toBe(800); |
| 110 | expect(issues.filter((i) => i.path.includes('clearingDistance'))).toHaveLength(0); |
| 111 | }); |
| 112 | |
| 113 | it('begrenzt den Raeumweg auf die Grenze der jeweiligen Art und sagt es', () => { |
| 114 | const grenze = ANLAGENART_GRENZEN.einstreifig.maxClearingDistance; |
| 115 | const { project, issues } = parseProject(dateiMit('einstreifig', grenze + 500), FIXED_DATE); |
| 116 | |
| 117 | expect(project.conflicts[0]!.clearingDistance).toBe(grenze); |
| 118 | const meldung = issues.find((i) => i.path.includes('clearingDistance')); |
| 119 | expect(meldung).toBeDefined(); |
| 120 | // Ohne die Art in der Meldung liest sich die Zahl wie eine Programmgrenze. |
| 121 | expect(meldung!.message).toContain('Einstreifige'); |
| 122 | }); |
| 123 | |
| 124 | it('haelt am Knotenpunkt die bisherigen 500 m', () => { |
| 125 | const { project, issues } = parseProject(dateiMit('knotenpunkt', 800), FIXED_DATE); |
| 126 | |
| 127 | expect(project.conflicts[0]!.clearingDistance).toBe( |
| 128 | ANLAGENART_GRENZEN.knotenpunkt.maxClearingDistance, |
| 129 | ); |
| 130 | expect(issues.some((i) => i.path.includes('clearingDistance'))).toBe(true); |
| 131 | }); |
| 132 | |
| 133 | it('gibt einer Datei ohne Angabe nicht die weiten Schranken der Arbeitsstelle', () => { |
| 134 | // Die Gegenprobe: Der Rueckfall darf nicht die grosszuegigste Art sein. |
| 135 | const { project } = parseProject(dateiMit(undefined, 800, 8), FIXED_DATE); |
| 136 | expect(project.conflicts[0]!.clearingDistance).toBe(500); |
| 137 | }); |
| 138 | }); |
| 139 | |
| 140 | describe('Uebernahme von Altprojekten', () => { |
| 141 | const legacyProject = { |
| 142 | metadata: { version: '4.3.3' }, |
| 143 | projektdaten: { |
| 144 | projektBezeichnung: 'Alt', |
| 145 | // Das Altformat fuehrte Baustellenfelder. Daraus wird NICHT auf eine |
| 146 | // einstreifige Fuehrung geschlossen: Auch ein Knotenpunkt in einer |
| 147 | // Baustelle wird angemietet, und die Fehlannahme haette seine |
| 148 | // Umlaufzeitschranke still von 120 s auf 300 s angehoben. |
| 149 | baustellenVon: true, |
| 150 | mietkosten: { tagessatz: 40 }, |
| 151 | }, |
| 152 | signalgruppen: [ |
| 153 | { id: 'SG-1', nummer: 'K1', typ: 'kfz' }, |
| 154 | { id: 'SG-2', nummer: 'K2', typ: 'kfz' }, |
| 155 | ], |
| 156 | sperrmatrix: { |
| 157 | matrix: [ |
| 158 | [false, true], |
| 159 | [true, false], |
| 160 | ], |
| 161 | }, |
| 162 | zwischenzeiten: { parameter: { raeumweg: 24, einfahrweg: 9 } }, |
| 163 | }; |
| 164 | |
| 165 | it('uebernimmt ein Altprojekt als Knotenpunkt', () => { |
| 166 | const result = loadProject(legacyProject, FIXED_DATE); |
| 167 | |
| 168 | expect(result.migrated).toBe(true); |
| 169 | expect(result.project.anlagenart).toBe('knotenpunkt'); |
| 170 | }); |
| 171 | |
| 172 | it('sagt beim Oeffnen einer Datei aus Schema 8 nichts zur Anlagenart', () => { |
| 173 | // Anders als beim Wegnachweis wird hier nichts behauptet, was unbekannt |
| 174 | // waere - eine Meldung wuerde nur das Gewicht der uebrigen verwaessern. |
| 175 | const result = loadProject(dateiMit(undefined, 24, 8), FIXED_DATE); |
| 176 | |
| 177 | expect(result.project.anlagenart).toBe('knotenpunkt'); |
| 178 | expect(result.issues.some((i) => i.path === 'anlagenart')).toBe(false); |
| 179 | }); |
| 180 | |
| 181 | it('warnt bei einer Datei aus einer neueren Schemaversion', () => { |
| 182 | // Der Grund fuer den Aufstieg auf 9: Ein aelterer Programmstand soll eine |
| 183 | // Arbeitsstelle mit 250 s Umlauf nicht stillschweigend auf 120 s stutzen. |
| 184 | const result = loadProject( |
| 185 | { schemaVersion: CURRENT_SCHEMA_VERSION + 1, signalGroups: [] }, |
| 186 | FIXED_DATE, |
| 187 | ); |
| 188 | expect(result.issues.some((i) => i.path === 'schemaVersion')).toBe(true); |
| 189 | }); |
| 190 | }); |