import { describe, expect, it } from 'vitest'; import { RILSA_DEFAULTS } from '@/domain/rilsa/constants'; import { computeCoordination } from '@/domain/rilsa/coordination'; /* * Befunde S2 und S4, behoben in Fassung 5.10.0. * * Der Versatz wurde zyklisch gebildet (modulo Umlaufzeit), die Bandbreite * dagegen auf der Geraden: "greenStart - Reisezeit" wurde ohne Faltung in den * Umlauf gegen die uebrigen Fenster geschnitten. Ein Signalprogramm wiederholt * sich aber mit jedem Umlauf; die Fenster liegen auf einem Kreis der Laenge tU. * Sobald "greenStart - Reisezeit" unter den Umlaufbeginn fiel, meldete die * Rechnung "keine Bandbreite" - und zwar fuer genau die Versaetze, die sie in * intersections[i].offset selbst empfiehlt. * * Fuer Fassung 5 ist gegenueber 4.x zugesagt, die Rechnung * ermittle "die tatsächliche Schnittmenge der Freigabefenster". Sie wird von * keiner Ansicht aufgerufen, gehoert aber ueber src/domain/rilsa/index.ts zur * oeffentlichen Schnittstelle des Fachkerns. */ describe('Bandbreite der Koordinierung auf dem Umlauf (Befunde S2 und S4)', () => { it('faltet Freigabefenster in den Umlauf, wenn die Reisezeit den Umlauf uebersteigt', () => { // tU 60 s, 50 km/h, 1000 m -> Reisezeit 72,0 s, empfohlener Versatz 12,0 s. // Mit genau diesem Versatz decken sich beide Fenster vollstaendig. const result = computeCoordination( [ { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 30 }, { id: 'b', name: 'Knoten B', distanceToPrevious: 1000, greenStart: 12, greenDuration: 30 }, ], 60, 50, ); expect(result.intersections[1]!.travelTime).toBeCloseTo(72, 1); expect(result.intersections[1]!.offset).toBeCloseTo(12, 1); expect(result.bandwidth).toBe(30); expect(result.bandwidthRatio).toBe(0.5); expect(result.quality).toBe('gut'); expect(result.notes.some((n) => n.code === 'keine-bandbreite')).toBe(false); }); it('faltet auch, wenn nur der Umlaufbeginn ueberschritten wird', () => { // tU 60 s, 60 km/h, 500 m -> Reisezeit 30,0 s. A ab 40 s, B ab 10 s: der // Versatz passt, das zurueckgerechnete Fenster von B liegt bei 40 s. const result = computeCoordination( [ { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 40, greenDuration: 20 }, { id: 'b', name: 'Knoten B', distanceToPrevious: 500, greenStart: 10, greenDuration: 20 }, ], 60, 60, ); expect(result.intersections[1]!.travelTime).toBeCloseTo(30, 1); expect(result.bandwidth).toBe(20); expect(result.notes.some((n) => n.code === 'keine-bandbreite')).toBe(false); }); it('nimmt den laengsten zusammenhaengenden Bogen, nicht die Summe der Teilbogen', () => { // tU 60 s, 60 km/h, 1000 m -> Reisezeit 60,0 s, also ein voller Umlauf. // A [0; 50), B zurueckgerechnet [20; 70) = [20; 60) und [0; 10). Die // Schnittmenge zerfaellt in zwei Boegen von 30 s und 10 s; nutzbar ist der // laengere, nicht die Summe von 40 s - und nicht der ungefaltete Rest von // 10 s, den die geradlinige Rechnung uebrig liess. const result = computeCoordination( [ { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 50 }, { id: 'b', name: 'Knoten B', distanceToPrevious: 1000, greenStart: 20, greenDuration: 50 }, ], 60, 60, ); expect(result.intersections[1]!.travelTime).toBeCloseTo(60, 1); expect(result.bandwidth).toBe(30); }); it('nimmt die Progressionsgeschwindigkeit aus den Projektvorgaben, nicht aus den Regelwerten', () => { const vorgaben = { ...RILSA_DEFAULTS, coordination: { ...RILSA_DEFAULTS.coordination, defaultSpeed: 40 }, }; const result = computeCoordination( [ { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 30 }, { id: 'b', name: 'Knoten B', distanceToPrevious: 400, greenStart: 36, greenDuration: 30 }, ], 90, undefined, vorgaben, ); expect(RILSA_DEFAULTS.coordination.defaultSpeed).toBe(50); expect(result.speed).toBe(40); // 400 m bei 40 km/h = 11,111 m/s -> 36,0 s, nicht 28,8 s wie bei 50 km/h. expect(result.intersections[1]!.travelTime).toBeCloseTo(36, 1); }); });