feat: T6 wave 4 – notice PDFs, compliance notifications, readiness core
Backend Image / build-and-push (push) Successful in 4m58s
Frontend Image / build-and-push (push) Successful in 7m57s

- Authority notice PDFs: pure model plus renderer, TSK count in A4
  landscape, FEHLT for missing particulars, stored totals with correction
  note; GET /authority-notices/:id/pdf with compliance:read.
- Compliance notifications in category COMPLIANCE: herd change and TSK
  reference day, at most one unread per topic and user (advisory lock);
  daily rule TskReferenceDayReached with Berlin day and catch-up; handlers
  in API and worker.
- Readiness core: live evaluation of seven pure rules, fingerprinted
  acknowledgements that reopen when revoked or stale, subject masking,
  read-only GET paths; READINESS_RULES injectable; kept out of the worker.
- e2e hygiene: no unscoped deletes, seeded settings rows saved/restored.
- ARCHITECTURE.md §12.8–12.10, §10 handler rows, Annahmen 78–85.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-15 14:48:57 +02:00
1 parent edc193f128
commit b912d29166
54 files changed
+5300 -14

No files matched your search

+22 -5
View File
@@ -9,6 +9,13 @@ export const PDF_MARGIN = 36;
export interface PdfColumn {
header: string;
width: number;
/** Numbers read better right-aligned; the default is left. */
align?: 'left' | 'right';
}
export interface DrawTableOptions {
/** Draws the last row in bold, e.g. a totals row. */
boldLastRow?: boolean;
}
export function formatDay(date: Date): string {
@@ -55,11 +62,12 @@ export function drawTable(
doc: PDFKit.PDFDocument,
columns: PdfColumn[],
rows: string[][],
options: DrawTableOptions = {},
): void {
const left = PDF_MARGIN;
const bottomLimit = doc.page.height - PDF_MARGIN - 20;
const drawRow = (cells: string[], bold: boolean) => {
const drawRow = (cells: string[], isHeader: boolean, bold = isHeader) => {
doc.font(bold ? 'Helvetica-Bold' : 'Helvetica').fontSize(8);
const heights = cells.map((cell, index) =>
doc.heightOfString(cell, { width: columns[index].width - 4 }),
@@ -67,18 +75,21 @@ export function drawTable(
const rowHeight = Math.max(...heights, 10) + 4;
if (doc.y + rowHeight > bottomLimit) {
doc.addPage();
if (!bold) {
if (!isHeader) {
drawRow(
columns.map((c) => c.header),
true,
);
doc.font('Helvetica').fontSize(8);
doc.font(bold ? 'Helvetica-Bold' : 'Helvetica').fontSize(8);
}
}
const top = doc.y;
let x = left;
cells.forEach((cell, index) => {
doc.text(cell, x + 2, top + 2, { width: columns[index].width - 4 });
doc.text(cell, x + 2, top + 2, {
width: columns[index].width - 4,
align: columns[index].align ?? 'left',
});
x += columns[index].width;
});
doc
@@ -102,6 +113,12 @@ export function drawTable(
doc.moveDown(0.5);
return;
}
for (const row of rows) drawRow(row, false);
rows.forEach((row, index) =>
drawRow(
row,
false,
options.boldLastRow === true && index === rows.length - 1,
),
);
doc.moveDown(0.5);
}