import { inflateSync } from 'node:zlib'; import { describe, expect, it } from 'vitest'; import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { validateProject } from '@/domain/validation'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import { roundTo } from '@/domain/units'; import { toWinAnsi } from '@/render/pdfSurface'; /* * BEFUND 50 (Fassung 5.11.0): Die Tabelle "Leistungsfaehigkeit - Rechenweg * Wartezeit" druckt NGE, C und tW,R nebeneinander, und der Formelabsatz * unmittelbar darunter sagte als einzige Einschraenkung "Angaben mit * Zwischenrundung auf die gedruckten Stellen". * * Was der Pruefer sonst erlebt: Er rechnet die Zeile nach - tW,R = NGE * 3600 / * C - und kommt auf eine andere Zahl als die gedruckte. Im Regelbeispiel geht * das an zwei von vier Kfz-Zeilen nicht auf (K1: 5,7 gegen gedruckte 5,6; * K4: 3,8 gegen 3,7). Der Grund ist begruendet und bleibt: delay.ts bildet * tW,R als Differenz tW - tW,G auf den AUSGEGEBENEN Ziffern, damit * tW,G + tW,R die gedruckte Gesamtwartezeit ergibt. Ungesagt blieb er * trotzdem - und der Kopfkommentar desselben Abschnitts verlangt: "Was * gedruckt wird, muss sich aus den gedruckten Groessen nachrechnen lassen." * * Behoben wird auf der Druckseite, nicht in delay.ts: Die Differenzbildung * selbst ist durch tests/domain/hbsWartezeit.test.ts mit eigener Begruendung * festgehalten. */ const FEST = new Date('2026-01-01T12:00:00Z'); /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */ function pdfText(bytes: Uint8Array): string { const roh = new TextDecoder('latin1').decode(bytes); const teile: string[] = []; const suche = /stream\r?\n/g; let treffer: RegExpExecArray | null; while ((treffer = suche.exec(roh)) !== null) { const start = treffer.index + treffer[0].length; const ende = roh.indexOf('endstream', start); if (ende < 0) continue; let inhalt: string; try { inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1'); } catch { continue; } for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) { teile.push(t[1]!.replace(/\\([()\\])/g, '$1')); } } return teile.join(' ').replace(/\s+/g, ' '); } const gedruckt = (text: string): string => toWinAnsi(text).replace(/\s+/g, ' '); const projekt = createStandardIntersectionProject('Hauptstrasse / Bahnhofstrasse', FEST); const plan = buildSignalPlan(projekt); describe('Befund 50 - Reststauanteil im Rechenweg der Wartezeit', () => { it('Vorbedingung: die gedruckte Spalte tW,R weicht von NGE * 3600 / C ab', () => { // Ohne diese Abweichung gaebe es nichts zu erklaeren; der Zusatz waere // dann eine Behauptung ueber einen Fall, den es nicht gibt. const abweichend = plan.groups.filter((group) => { const delay = group.delay; const kapazitaet = group.capacity; if (delay === null || kapazitaet === null) return false; const nge = delay.rechenweg.reststau; if (nge === null) return false; return roundTo((nge * 3600) / kapazitaet.capacity, 1) !== roundTo(delay.incrementalDelay, 1); }); expect( abweichend.map((g) => g.name), 'im Regelbeispiel gehen zwei von vier Kfz-Zeilen nicht auf', ).not.toHaveLength(0); }); it('sagt im Formelabsatz, dass tW,R als Differenz gebildet ist', () => { const bericht = validateProject(projekt, plan, FEST); const text = pdfText(buildProjectPdf(projekt, plan, bericht, DEFAULT_PDF_OPTIONS, FEST)); expect(text, 'Vorbedingung: der Abschnitt steht in der Regelausgabe').toContain( gedruckt('tW,R = NGE · 3600 / C'), ); expect(text).toContain( gedruckt( 'Die Spalte tW,R ist nicht aus NGE und C gerundet, sondern als Differenz tW - tW,G auf ' + 'den ausgegebenen Ziffern gebildet, damit tW,G + tW,R die gedruckte Gesamtwartezeit ' + 'ergibt; gegenüber NGE · 3600 / C kann sie deshalb um bis zu 0,1 s abweichen.', ), ); }); });