From ca58f1738f175af2e4c31fef87ca8f3df33c6329 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 11:29:14 +0000 Subject: [PATCH] feat(ai-act-kompass): printable training materials (paper test, answer key, handout) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Paper version of the AI literacy test: name/date/department fields, 12 questions with tick boxes (A/B/C), marking line and signature block — for employees without computer access; contains no answers - Answer key for markers: correct letter plus explanation per question, pass threshold noted - Training handout: the 12 core rules (question + explanation) as Art. 4 training material to distribute before the test - All printable with company letterhead via the existing print/PDF pipeline and downloadable; buttons in the quiz view under 'Print training materials'; UI labels in all nine languages (document body DE/EN with EN fallback, full localization follows the content pack) - 3 new tests (72 total) Claude-Session: https://claude.ai/code/session_017YjohVNx1ZGNM4MX7tHpfv --- ai-act-kompass/src/engine/quiz.test.ts | 34 +++++- ai-act-kompass/src/engine/quiz.ts | 150 +++++++++++++++++++++++++ ai-act-kompass/src/i18n/ui.cs.ts | 4 + ai-act-kompass/src/i18n/ui.es.ts | 4 + ai-act-kompass/src/i18n/ui.fr.ts | 4 + ai-act-kompass/src/i18n/ui.it.ts | 4 + ai-act-kompass/src/i18n/ui.pl.ts | 4 + ai-act-kompass/src/i18n/ui.ro.ts | 4 + ai-act-kompass/src/i18n/ui.sk.ts | 4 + ai-act-kompass/src/i18n/ui.ts | 8 ++ ai-act-kompass/src/ui/QuizView.tsx | 40 ++++++- 11 files changed, 258 insertions(+), 2 deletions(-) diff --git a/ai-act-kompass/src/engine/quiz.test.ts b/ai-act-kompass/src/engine/quiz.test.ts index 0051319..9aff374 100644 --- a/ai-act-kompass/src/engine/quiz.test.ts +++ b/ai-act-kompass/src/engine/quiz.test.ts @@ -1,6 +1,14 @@ import { describe, expect, it } from 'vitest'; import { CONTENT_PACK } from '../content/pack'; -import { certificateDoc, getQuiz, scoreQuiz, trainingLogDoc } from './quiz'; +import { + answerKeyDoc, + certificateDoc, + getQuiz, + handoutDoc, + printableTestDoc, + scoreQuiz, + trainingLogDoc, +} from './quiz'; import { QUIZ_PASS_PCT, QUIZ_QUESTIONS } from './quizContent'; import type { CompanyProfile } from './types'; @@ -65,6 +73,30 @@ describe('KI-Kompetenztest', () => { expect(md).toContain('keine Rechtsberatung'); }); + it('Papierfassung enthält alle Fragen, Ankreuzfelder und Auswertungszeile', () => { + const md = printableTestDoc(CONTENT_PACK, '2026-07-04', profile); + expect(md).toContain('Papierfassung'); + expect((md.match(/☐/g) ?? []).length).toBe(36); // 12 Fragen × 3 Optionen + expect(md).toContain('**Name:**'); + expect(md).toContain('Ergebnis: ______ / 12'); + expect(md).not.toContain('richtige Antwort'); // keine Lösungen im Testbogen + }); + + it('Lösungsbogen nennt je Frage den richtigen Buchstaben', () => { + const md = answerKeyDoc(CONTENT_PACK, '2026-07-04', profile); + expect(md).toContain('Lösungsbogen'); + expect((md.match(/\| \d+ \| \*\*[ABC]\*\* \|/g) ?? []).length).toBe(12); + }); + + it('Schulungshandout enthält die 12 Erklärungen und ist lokalisiert', () => { + const de = handoutDoc(CONTENT_PACK, '2026-07-04', profile, 'de'); + expect(de).toContain('Schulungshandout'); + expect((de.match(/^## \d+\./gm) ?? []).length).toBe(12); + const en = handoutDoc(CONTENT_PACK, '2026-07-04', profile, 'en'); + expect(en).toContain('Training handout'); + expect(en).not.toBe(de); + }); + it('erzeugt das Schulungsregister mit allen Durchläufen', () => { const md = trainingLogDoc( [ diff --git a/ai-act-kompass/src/engine/quiz.ts b/ai-act-kompass/src/engine/quiz.ts index 8cc4de9..0fa5037 100644 --- a/ai-act-kompass/src/engine/quiz.ts +++ b/ai-act-kompass/src/engine/quiz.ts @@ -168,6 +168,156 @@ export function certificateDoc( ].join('\n'); } +interface PrintStrings { + readonly testTitle: string; + readonly testIntro: string; + readonly nameDateLine: string; + readonly testFooter: string; + readonly keyTitle: string; + readonly keyIntro: string; + readonly keyCols: string; + readonly handoutTitle: string; + readonly handoutIntro: string; + readonly handoutFooter: string; +} + +const PRINT: Readonly> = { + de: { + testTitle: 'KI-Kompetenztest (Art. 4 EU-KI-VO) — Papierfassung', + testIntro: + 'Bitte kreuzen Sie je Frage genau eine Antwort an. Bestehensgrenze: {pct} % ({min} von {total} Fragen). Die Auswertung erfolgt mit dem Lösungsbogen; das Ergebnis wird im Schulungsregister dokumentiert.', + nameDateLine: '**Name:** ______________________________ **Datum:** ______________ **Abteilung:** ______________________________', + testFooter: 'Unterschrift Beschäftigte/r: _______________ Ausgewertet durch (Name, Unterschrift): _______________ Ergebnis: ______ / {total}', + keyTitle: 'KI-Kompetenztest — Lösungsbogen (nur für Auswertende)', + keyIntro: 'Richtige Antworten und Erklärungen. Bestehensgrenze: {pct} % ({min} von {total} Fragen).', + keyCols: '| Nr. | Richtig | Erklärung |', + handoutTitle: 'Schulungshandout: Die 12 wichtigsten Regeln im Umgang mit KI', + handoutIntro: + 'Dieses Handout fasst die Kernregeln für den Arbeitsalltag zusammen und dient als Schulungsunterlage nach Art. 4 EU-KI-VO. Es kann vor dem Kompetenztest ausgeteilt werden.', + handoutFooter: + 'Bei Fragen oder Vorfällen: an die/den KI-Beauftragte/n wenden (siehe interne Melde- und Eskalationsrichtlinie).', + }, + en: { + testTitle: 'AI literacy test (Art. 4 EU AI Act) — paper version', + testIntro: + 'Please tick exactly one answer per question. Pass threshold: {pct} % ({min} of {total} questions). Marking is done with the answer key; the result is documented in the training register.', + nameDateLine: '**Name:** ______________________________ **Date:** ______________ **Department:** ______________________________', + testFooter: 'Signature employee: _______________ Marked by (name, signature): _______________ Score: ______ / {total}', + keyTitle: 'AI literacy test — answer key (for markers only)', + keyIntro: 'Correct answers and explanations. Pass threshold: {pct} % ({min} of {total} questions).', + keyCols: '| No. | Correct | Explanation |', + handoutTitle: 'Training handout: the 12 most important rules for working with AI', + handoutIntro: + 'This handout summarises the core rules for everyday work and serves as training material under Art. 4 EU AI Act. It can be handed out before the literacy test.', + handoutFooter: + 'For questions or incidents: contact the AI officer (see the internal reporting and escalation policy).', + }, +}; + +function printStrings(locale: Locale): PrintStrings { + return locale === 'de' ? PRINT.de : PRINT.en; +} + +const OPTION_LETTERS = ['A', 'B', 'C'] as const; + +function minCorrect(): number { + return Math.ceil((QUIZ_PASS_PCT / 100) * QUIZ_QUESTIONS.length); +} + +function fillIn(template: string): string { + return template + .replace('{pct}', String(QUIZ_PASS_PCT)) + .replace('{min}', String(minCorrect())) + .replaceAll('{total}', String(QUIZ_QUESTIONS.length)); +} + +/** + * Erzeugt die Papierfassung des Kompetenztests (zum Ausdrucken und + * handschriftlichen Ausfüllen) als Markdown. + */ +export function printableTestDoc( + pack: ContentPack, + today: string, + profile?: CompanyProfile, + locale: Locale = 'de', +): string { + const s = docStrings(locale); + const p = printStrings(locale); + const company = profile && profile.name.trim() !== '' ? profile.name : '_______________'; + const lines = [ + `# ${p.testTitle}`, + '', + `**${s.meta.company}:** ${company} · ${s.inventory.asOf}: ${formatDocDate(today, locale)} · Content-Pack v${pack.version}`, + '', + p.nameDateLine, + '', + fillIn(p.testIntro), + '', + ]; + getQuiz(locale).forEach((q, i) => { + lines.push(`## ${i + 1}. ${q.question}`, ''); + q.options.forEach((option, oi) => { + lines.push(`- ☐ **${OPTION_LETTERS[oi]})** ${option}`); + }); + lines.push(''); + }); + lines.push(fillIn(p.testFooter)); + return lines.join('\n'); +} + +/** Erzeugt den Lösungsbogen (richtige Antworten + Erklärungen) als Markdown. */ +export function answerKeyDoc( + pack: ContentPack, + today: string, + profile?: CompanyProfile, + locale: Locale = 'de', +): string { + const s = docStrings(locale); + const p = printStrings(locale); + const company = profile && profile.name.trim() !== '' ? profile.name : '_______________'; + const lines = [ + `# ${p.keyTitle}`, + '', + `**${s.meta.company}:** ${company} · ${s.inventory.asOf}: ${formatDocDate(today, locale)} · Content-Pack v${pack.version}`, + '', + fillIn(p.keyIntro), + '', + p.keyCols, + '|---|---|---|', + ]; + getQuiz(locale).forEach((q, i) => { + lines.push(`| ${i + 1} | **${OPTION_LETTERS[q.correctIndex]}** | ${q.explanation} |`); + }); + return lines.join('\n'); +} + +/** Erzeugt das Schulungshandout (Kernregeln aus den Erklärungen) als Markdown. */ +export function handoutDoc( + pack: ContentPack, + today: string, + profile?: CompanyProfile, + locale: Locale = 'de', +): string { + const s = docStrings(locale); + const p = printStrings(locale); + const company = profile && profile.name.trim() !== '' ? profile.name : '_______________'; + const lines = [ + `# ${p.handoutTitle}`, + '', + `**${s.meta.company}:** ${company} · ${s.inventory.asOf}: ${formatDocDate(today, locale)} · Content-Pack v${pack.version}`, + '', + s.disclaimer, + '', + p.handoutIntro, + '', + ]; + getQuiz(locale).forEach((q, i) => { + lines.push(`## ${i + 1}. ${q.question}`, '', q.explanation, ''); + }); + lines.push(p.handoutFooter); + return lines.join('\n'); +} + /** * Erzeugt das druckbare Schulungsregister (alle Testdurchläufe) als Markdown. */ diff --git a/ai-act-kompass/src/i18n/ui.cs.ts b/ai-act-kompass/src/i18n/ui.cs.ts index adc9049..4015968 100644 --- a/ai-act-kompass/src/i18n/ui.cs.ts +++ b/ai-act-kompass/src/i18n/ui.cs.ts @@ -62,6 +62,10 @@ export const CS: UIStrings = { quizLogPrint: 'Vytisknout registr / PDF', quizColScore: 'Výsledek', quizColStatus: 'Stav', + quizDocsTitle: 'Tisk školicích materiálů', + quizPrintTest: 'Papírová verze testu', + quizPrintKey: 'List s řešením', + quizPrintHandout: 'Školicí materiál (12 pravidel)', wizardTitleNew: 'Posoudit nový systém AI', wizardTitleRe: 'Nové posouzení: {name}', diff --git a/ai-act-kompass/src/i18n/ui.es.ts b/ai-act-kompass/src/i18n/ui.es.ts index c14a428..ed1eaf8 100644 --- a/ai-act-kompass/src/i18n/ui.es.ts +++ b/ai-act-kompass/src/i18n/ui.es.ts @@ -62,6 +62,10 @@ export const ES: UIStrings = { quizLogPrint: 'Imprimir registro / PDF', quizColScore: 'Resultado', quizColStatus: 'Estado', + quizDocsTitle: 'Imprimir materiales de formación', + quizPrintTest: 'Versión en papel del test', + quizPrintKey: 'Hoja de soluciones', + quizPrintHandout: 'Material de formación (12 reglas)', wizardTitleNew: 'Evaluar un nuevo sistema de IA', wizardTitleRe: 'Reevaluación: {name}', diff --git a/ai-act-kompass/src/i18n/ui.fr.ts b/ai-act-kompass/src/i18n/ui.fr.ts index 71a8656..6c7423b 100644 --- a/ai-act-kompass/src/i18n/ui.fr.ts +++ b/ai-act-kompass/src/i18n/ui.fr.ts @@ -62,6 +62,10 @@ export const FR: UIStrings = { quizLogPrint: 'Imprimer le registre / PDF', quizColScore: 'Résultat', quizColStatus: 'Statut', + quizDocsTitle: 'Imprimer les supports de formation', + quizPrintTest: 'Version papier du test', + quizPrintKey: 'Corrigé', + quizPrintHandout: 'Support de formation (12 règles)', wizardTitleNew: 'Évaluer un nouveau système d’IA', wizardTitleRe: 'Réévaluation : {name}', diff --git a/ai-act-kompass/src/i18n/ui.it.ts b/ai-act-kompass/src/i18n/ui.it.ts index 11212be..c67a228 100644 --- a/ai-act-kompass/src/i18n/ui.it.ts +++ b/ai-act-kompass/src/i18n/ui.it.ts @@ -62,6 +62,10 @@ export const IT: UIStrings = { quizLogPrint: 'Stampa registro / PDF', quizColScore: 'Risultato', quizColStatus: 'Stato', + quizDocsTitle: 'Stampa materiali formativi', + quizPrintTest: 'Versione cartacea del test', + quizPrintKey: 'Foglio delle soluzioni', + quizPrintHandout: 'Dispensa formativa (12 regole)', wizardTitleNew: 'Valutare un nuovo sistema di IA', wizardTitleRe: 'Rivalutazione: {name}', diff --git a/ai-act-kompass/src/i18n/ui.pl.ts b/ai-act-kompass/src/i18n/ui.pl.ts index dd4b605..643b991 100644 --- a/ai-act-kompass/src/i18n/ui.pl.ts +++ b/ai-act-kompass/src/i18n/ui.pl.ts @@ -62,6 +62,10 @@ export const PL: UIStrings = { quizLogPrint: 'Drukuj rejestr / PDF', quizColScore: 'Wynik', quizColStatus: 'Status', + quizDocsTitle: 'Drukuj materiały szkoleniowe', + quizPrintTest: 'Wersja papierowa testu', + quizPrintKey: 'Arkusz odpowiedzi', + quizPrintHandout: 'Materiał szkoleniowy (12 zasad)', wizardTitleNew: 'Oceń nowy system AI', wizardTitleRe: 'Ponowna ocena: {name}', diff --git a/ai-act-kompass/src/i18n/ui.ro.ts b/ai-act-kompass/src/i18n/ui.ro.ts index fdd0f20..5d13fbc 100644 --- a/ai-act-kompass/src/i18n/ui.ro.ts +++ b/ai-act-kompass/src/i18n/ui.ro.ts @@ -62,6 +62,10 @@ export const RO: UIStrings = { quizLogPrint: 'Tipărește registrul / PDF', quizColScore: 'Rezultat', quizColStatus: 'Stare', + quizDocsTitle: 'Tipărește materialele de formare', + quizPrintTest: 'Versiunea pe hârtie a testului', + quizPrintKey: 'Foaia de răspunsuri', + quizPrintHandout: 'Material de formare (12 reguli)', wizardTitleNew: 'Evaluează un sistem de IA nou', wizardTitleRe: 'Reevaluare: {name}', diff --git a/ai-act-kompass/src/i18n/ui.sk.ts b/ai-act-kompass/src/i18n/ui.sk.ts index 804ff79..3e62823 100644 --- a/ai-act-kompass/src/i18n/ui.sk.ts +++ b/ai-act-kompass/src/i18n/ui.sk.ts @@ -62,6 +62,10 @@ export const SK: UIStrings = { quizLogPrint: 'Vytlačiť register / PDF', quizColScore: 'Výsledok', quizColStatus: 'Stav', + quizDocsTitle: 'Tlač školiacich materiálov', + quizPrintTest: 'Papierová verzia testu', + quizPrintKey: 'Hárok s riešeniami', + quizPrintHandout: 'Školiaci materiál (12 pravidiel)', wizardTitleNew: 'Posúdiť nový systém AI', wizardTitleRe: 'Opätovné posúdenie: {name}', diff --git a/ai-act-kompass/src/i18n/ui.ts b/ai-act-kompass/src/i18n/ui.ts index 1267d57..7585de9 100644 --- a/ai-act-kompass/src/i18n/ui.ts +++ b/ai-act-kompass/src/i18n/ui.ts @@ -62,6 +62,10 @@ const DE = { quizLogPrint: 'Register drucken / PDF', quizColScore: 'Ergebnis', quizColStatus: 'Status', + quizDocsTitle: 'Schulungsunterlagen drucken', + quizPrintTest: 'Papierfassung des Tests', + quizPrintKey: 'Lösungsbogen', + quizPrintHandout: 'Schulungshandout (12 Regeln)', wizardTitleNew: 'Neues KI-System bewerten', wizardTitleRe: 'Neubewertung: {name}', @@ -256,6 +260,10 @@ const EN: UIStrings = { quizLogPrint: 'Print register / PDF', quizColScore: 'Score', quizColStatus: 'Status', + quizDocsTitle: 'Print training materials', + quizPrintTest: 'Paper version of the test', + quizPrintKey: 'Answer key', + quizPrintHandout: 'Training handout (12 rules)', wizardTitleNew: 'Assess a new AI system', wizardTitleRe: 'Reassessment: {name}', diff --git a/ai-act-kompass/src/ui/QuizView.tsx b/ai-act-kompass/src/ui/QuizView.tsx index 88374dc..efe6cda 100644 --- a/ai-act-kompass/src/ui/QuizView.tsx +++ b/ai-act-kompass/src/ui/QuizView.tsx @@ -1,5 +1,13 @@ import { useMemo, useState } from 'react'; -import { certificateDoc, getQuiz, scoreQuiz, trainingLogDoc } from '../engine/quiz'; +import { + answerKeyDoc, + certificateDoc, + getQuiz, + handoutDoc, + printableTestDoc, + scoreQuiz, + trainingLogDoc, +} from '../engine/quiz'; import type { QuizScore, TrainingRecord } from '../engine/quiz'; import { QUIZ_PASS_PCT } from '../engine/quizContent'; import type { CompanyProfile } from '../engine/types'; @@ -142,6 +150,36 @@ export function QuizView({ profile, records, onRecord }: QuizViewProps): JSX.Ele )} +
+

{t.quizDocsTitle}

+
+ + + +
+
+

{t.quizLogTitle}