lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests export reststauNachrechnen.test.ts
| 1 | import { inflateSync } from 'node:zlib'; |
| 2 | import { describe, expect, it } from 'vitest'; |
| 3 | import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf'; |
| 4 | import { buildSignalPlan } from '@/domain/plan/signalPlan'; |
| 5 | import { validateProject } from '@/domain/validation'; |
| 6 | import { createStandardIntersectionProject } from '@/domain/model/factory'; |
| 7 | import { roundTo } from '@/domain/units'; |
| 8 | import { toWinAnsi } from '@/render/pdfSurface'; |
| 9 | |
| 10 | /* |
| 11 | * BEFUND 50 (Fassung 5.11.0): Die Tabelle "Leistungsfaehigkeit - Rechenweg |
| 12 | * Wartezeit" druckt NGE, C und tW,R nebeneinander, und der Formelabsatz |
| 13 | * unmittelbar darunter sagte als einzige Einschraenkung "Angaben mit |
| 14 | * Zwischenrundung auf die gedruckten Stellen". |
| 15 | * |
| 16 | * Was der Pruefer sonst erlebt: Er rechnet die Zeile nach - tW,R = NGE * 3600 / |
| 17 | * C - und kommt auf eine andere Zahl als die gedruckte. Im Regelbeispiel geht |
| 18 | * das an zwei von vier Kfz-Zeilen nicht auf (K1: 5,7 gegen gedruckte 5,6; |
| 19 | * K4: 3,8 gegen 3,7). Der Grund ist begruendet und bleibt: delay.ts bildet |
| 20 | * tW,R als Differenz tW - tW,G auf den AUSGEGEBENEN Ziffern, damit |
| 21 | * tW,G + tW,R die gedruckte Gesamtwartezeit ergibt. Ungesagt blieb er |
| 22 | * trotzdem - und der Kopfkommentar desselben Abschnitts verlangt: "Was |
| 23 | * gedruckt wird, muss sich aus den gedruckten Groessen nachrechnen lassen." |
| 24 | * |
| 25 | * Behoben wird auf der Druckseite, nicht in delay.ts: Die Differenzbildung |
| 26 | * selbst ist durch tests/domain/hbsWartezeit.test.ts mit eigener Begruendung |
| 27 | * festgehalten. |
| 28 | */ |
| 29 | |
| 30 | const FEST = new Date('2026-01-01T12:00:00Z'); |
| 31 | |
| 32 | /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */ |
| 33 | function pdfText(bytes: Uint8Array): string { |
| 34 | const roh = new TextDecoder('latin1').decode(bytes); |
| 35 | const teile: string[] = []; |
| 36 | const suche = /stream\r?\n/g; |
| 37 | let treffer: RegExpExecArray | null; |
| 38 | |
| 39 | while ((treffer = suche.exec(roh)) !== null) { |
| 40 | const start = treffer.index + treffer[0].length; |
| 41 | const ende = roh.indexOf('endstream', start); |
| 42 | if (ende < 0) continue; |
| 43 | let inhalt: string; |
| 44 | try { |
| 45 | inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1'); |
| 46 | } catch { |
| 47 | continue; |
| 48 | } |
| 49 | for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) { |
| 50 | teile.push(t[1]!.replace(/\\([()\\])/g, '$1')); |
| 51 | } |
| 52 | } |
| 53 | return teile.join(' ').replace(/\s+/g, ' '); |
| 54 | } |
| 55 | |
| 56 | const gedruckt = (text: string): string => toWinAnsi(text).replace(/\s+/g, ' '); |
| 57 | |
| 58 | const projekt = createStandardIntersectionProject('Hauptstrasse / Bahnhofstrasse', FEST); |
| 59 | const plan = buildSignalPlan(projekt); |
| 60 | |
| 61 | describe('Befund 50 - Reststauanteil im Rechenweg der Wartezeit', () => { |
| 62 | it('Vorbedingung: die gedruckte Spalte tW,R weicht von NGE * 3600 / C ab', () => { |
| 63 | // Ohne diese Abweichung gaebe es nichts zu erklaeren; der Zusatz waere |
| 64 | // dann eine Behauptung ueber einen Fall, den es nicht gibt. |
| 65 | const abweichend = plan.groups.filter((group) => { |
| 66 | const delay = group.delay; |
| 67 | const kapazitaet = group.capacity; |
| 68 | if (delay === null || kapazitaet === null) return false; |
| 69 | const nge = delay.rechenweg.reststau; |
| 70 | if (nge === null) return false; |
| 71 | return roundTo((nge * 3600) / kapazitaet.capacity, 1) !== roundTo(delay.incrementalDelay, 1); |
| 72 | }); |
| 73 | |
| 74 | expect( |
| 75 | abweichend.map((g) => g.name), |
| 76 | 'im Regelbeispiel gehen zwei von vier Kfz-Zeilen nicht auf', |
| 77 | ).not.toHaveLength(0); |
| 78 | }); |
| 79 | |
| 80 | it('sagt im Formelabsatz, dass tW,R als Differenz gebildet ist', () => { |
| 81 | const bericht = validateProject(projekt, plan, FEST); |
| 82 | const text = pdfText(buildProjectPdf(projekt, plan, bericht, DEFAULT_PDF_OPTIONS, FEST)); |
| 83 | |
| 84 | expect(text, 'Vorbedingung: der Abschnitt steht in der Regelausgabe').toContain( |
| 85 | gedruckt('tW,R = NGE · 3600 / C'), |
| 86 | ); |
| 87 | expect(text).toContain( |
| 88 | gedruckt( |
| 89 | 'Die Spalte tW,R ist nicht aus NGE und C gerundet, sondern als Differenz tW - tW,G auf ' + |
| 90 | 'den ausgegebenen Ziffern gebildet, damit tW,G + tW,R die gedruckte Gesamtwartezeit ' + |
| 91 | 'ergibt; gegenüber NGE · 3600 / C kann sie deshalb um bis zu 0,1 s abweichen.', |
| 92 | ), |
| 93 | ); |
| 94 | }); |
| 95 | }); |