waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
/ app src shared druck fehlerprotokoll.ts
| 1 | /** |
| 2 | * Das Fehlerprotokoll – die Fragen, die zuletzt falsch beantwortet wurden. |
| 3 | * |
| 4 | * Das Dokument, mit dem jemand ohne Rechner weiterarbeitet. Es enthält genau |
| 5 | * die Menge, die auf dem Startbildschirm hinter „Nur Fehler“ steht: Was dort |
| 6 | * als Fehler gilt, steht hier auf dem Papier. Eine zweite Zählweise daneben |
| 7 | * wäre die Art Widerspruch, die diese Anwendung schon zweimal abgeschafft hat. |
| 8 | * |
| 9 | * **Die Antwortmöglichkeiten stehen vollständig da.** Nachgemessen nennen |
| 10 | * 302 der 471 Erklärungen zu Auswahlfragen eine *falsche* Option beim |
| 11 | * Buchstaben; ein Dokument mit nur der richtigen Antwort machte seine eigenen |
| 12 | * Begründungen unlesbar. |
| 13 | * |
| 14 | * **Der Umfang ist erheblich und wird vorher gesagt.** Gemessen an wirklich |
| 15 | * gedruckten PDF: 60 Fragen mit Kurzerklärung ergeben 21 Seiten, 150 Fragen |
| 16 | * mit voller Begründung 161 Seiten, und der schlimmste Fall – 486 Fragen, |
| 17 | * volle Begründung, Großdruck – 982 Seiten. Die Oberfläche nennt die |
| 18 | * geschätzte Zahl, bevor der Speicherdialog aufgeht. |
| 19 | */ |
| 20 | |
| 21 | import { absatz, dokumentBauen, type Quellenangabe } from './dokument'; |
| 22 | import { frageblock, type Papierfrage } from './frageblock'; |
| 23 | import type { Schriftgroesse } from './stil'; |
| 24 | |
| 25 | /** Wie ausführlich die Begründungen ausfallen. */ |
| 26 | export type Erklaerungstiefe = 'kurz' | 'voll'; |
| 27 | |
| 28 | export interface Fehlerprotokolldaten { |
| 29 | readonly fragen: readonly Papierfrage[]; |
| 30 | readonly profilName: string; |
| 31 | readonly erstelltAm: string; |
| 32 | readonly tiefe: Erklaerungstiefe; |
| 33 | readonly quelle: Quellenangabe; |
| 34 | readonly schriftgroesse: Schriftgroesse; |
| 35 | } |
| 36 | |
| 37 | /** Deutsches Datum – dieselbe Formatierung wie im Lernbericht. */ |
| 38 | function datum(iso: string): string { |
| 39 | const wert = new Date(iso); |
| 40 | return Number.isNaN(wert.getTime()) |
| 41 | ? 'ohne Datum' |
| 42 | : wert.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * Der Vorspann. |
| 47 | * |
| 48 | * Er sagt, worauf die Auswahl beruht, und er sagt es im Singular, wenn es |
| 49 | * eine einzige Frage ist – der Fall ist erreichbar und kommt nach einer |
| 50 | * guten Sitzung sogar häufig vor. |
| 51 | */ |
| 52 | function vorspann(anzahl: number, tiefe: Erklaerungstiefe): string { |
| 53 | if (anzahl === 0) { |
| 54 | return absatz( |
| 55 | 'In diesem Lernprofil ist derzeit keine Frage als zuletzt falsch beantwortet vermerkt. ' + |
| 56 | 'Das Dokument bleibt deshalb leer.', |
| 57 | ); |
| 58 | } |
| 59 | |
| 60 | const menge = |
| 61 | anzahl === 1 |
| 62 | ? 'Hier steht die eine Frage, die Sie zuletzt falsch beantwortet haben.' |
| 63 | : `Hier stehen die ${String(anzahl)} Fragen, die Sie zuletzt falsch beantwortet haben.`; |
| 64 | |
| 65 | const nachtrag = |
| 66 | tiefe === 'voll' |
| 67 | ? 'Zu jeder Frage stehen die vollständige Begründung und die Fundstellen im Gesetz.' |
| 68 | : 'Zu jeder Frage steht die Kurzerklärung. Die ausführliche Begründung lässt sich beim Erzeugen zuschalten.'; |
| 69 | |
| 70 | return absatz( |
| 71 | `${menge} Sobald Sie eine davon wieder richtig beantworten, verschwindet sie aus dieser Liste. ${nachtrag}`, |
| 72 | ); |
| 73 | } |
| 74 | |
| 75 | export function fehlerprotokollBauen(daten: Fehlerprotokolldaten): string { |
| 76 | const form = { loesungZeigen: true, erklaerung: daten.tiefe } as const; |
| 77 | |
| 78 | const bloecke = daten.fragen.map((papier) => frageblock(papier, form, 3)); |
| 79 | |
| 80 | return dokumentBauen({ |
| 81 | titel: 'Fehlerprotokoll Waffensachkunde', |
| 82 | augenbraue: `${daten.profilName} · erstellt am ${datum(daten.erstelltAm)}`, |
| 83 | quelle: daten.quelle, |
| 84 | schriftgroesse: daten.schriftgroesse, |
| 85 | abschnitte: [ |
| 86 | `<h2>Ihre offenen Fehler</h2>\n${vorspann(daten.fragen.length, daten.tiefe)}\n${bloecke.join('\n')}`, |
| 87 | ], |
| 88 | }); |
| 89 | } |
| 90 | |
| 91 | /** Vorschlag für den Dateinamen; enthält keine Zeichen, die Dateisysteme stören. */ |
| 92 | export function fehlerprotokollDateiname(profilName: string, erstelltAm: string): string { |
| 93 | const tag = Number.isNaN(Date.parse(erstelltAm)) |
| 94 | ? 'ohne-datum' |
| 95 | : new Date(erstelltAm).toISOString().slice(0, 10); |
| 96 | const name = profilName |
| 97 | .normalize('NFKD') |
| 98 | .replace(/[̀-ͯ]/gu, '') |
| 99 | .replace(/[^A-Za-z0-9]+/gu, '-') |
| 100 | .replace(/^-+|-+$/gu, '') |
| 101 | .slice(0, 40); |
| 102 | |
| 103 | return `Fehlerprotokoll${name === '' ? '' : `-${name}`}-${tag}.pdf`; |
| 104 | } |