Claude 06574c5cfd
feat(ai-act-kompass): anti-AI print design and company personalization
- Full redesign: paper tone, serif typography, hairlines, rectangular
  stamp badges, mono labels — technical print look instead of SaaS style,
  no gradients/shadows/emoji
- Company profile settings (name, address, contact person, email, phone,
  website, logo upload as local data URL) persisted in localStorage and
  included in JSON backup (schema v2, v1 imports still supported)
- Print/PDF output via letterhead print view (window.print): logo,
  company name and contact data in header, contact line in footer;
  applies to risk report, action plan, Annex IV skeleton and inventory
- Company block also embedded in generated Markdown headers
- Minimal auditable Markdown renderer (no external parser), HTML-escaped
- 8 new tests (32 total)

Claude-Session: https://claude.ai/code/session_017YjohVNx1ZGNM4MX7tHpfv
2026-07-04 09:18:07 +00:00

257 lines
8.6 KiB
TypeScript

import type { AISystem, CompanyProfile, ContentPack, Obligation } from './types';
const RISK_LABELS: Record<string, string> = {
'out-of-scope': 'Nicht anwendbar (keine KI i. S. d. Art. 3 Nr. 1)',
prohibited: 'VERBOTEN (Art. 5)',
high: 'Hochrisiko',
limited: 'Begrenztes Risiko (Transparenzpflichten)',
minimal: 'Minimales Risiko',
};
const ROLE_LABELS: Record<string, string> = {
provider: 'Anbieter (Provider)',
deployer: 'Betreiber (Deployer)',
importer: 'Einführer (Importer)',
distributor: 'Händler (Distributor)',
};
const STATUS_LABELS: Record<string, string> = {
open: 'Offen',
'in-progress': 'In Umsetzung',
done: 'Erledigt',
'not-applicable': 'Nicht anwendbar',
};
const DISCLAIMER =
'> **Hinweis:** Dieses Dokument wurde mit dem AI-Act-Kompass erstellt und ist eine strukturierte Arbeitshilfe — **keine Rechtsberatung**. Die rechtliche Bewertung im Einzelfall obliegt dem Unternehmen bzw. seiner Rechtsberatung.';
/** Formatiert ein ISO-Datum als deutsches Datum (TT.MM.JJJJ). */
function formatDate(iso: string): string {
const [y, m, d] = iso.split('-');
return d && m && y ? `${d}.${m}.${y}` : iso;
}
/** Kontaktzeile aus dem Firmenprofil (leer, wenn kein Profil gepflegt). */
function companyRows(profile: CompanyProfile | undefined): readonly string[] {
if (!profile || profile.name.trim() === '') return [];
const contact = [profile.street, profile.zipCity, profile.email, profile.phone, profile.website]
.filter((p) => p.trim() !== '')
.join(' · ');
return [
`| **Unternehmen** | ${profile.name} |`,
...(contact ? [`| **Kontakt** | ${contact} |`] : []),
...(profile.contactPerson.trim() ? [`| **Ansprechpartner** | ${profile.contactPerson} |`] : []),
];
}
function header(
title: string,
system: AISystem,
pack: ContentPack,
today: string,
profile?: CompanyProfile,
): string {
return [
`# ${title}`,
'',
`| | |`,
`|---|---|`,
...companyRows(profile),
`| **KI-System** | ${system.name} |`,
`| **Zweckbestimmung** | ${system.purpose || '—'} |`,
`| **Hersteller/Anbieter** | ${system.vendor || '—'} |`,
`| **Verantwortlich (intern)** | ${system.owner || '—'} |`,
`| **Rolle des Unternehmens** | ${ROLE_LABELS[system.answers.role] ?? system.answers.role} |`,
`| **Risikoklasse** | ${RISK_LABELS[system.classification.riskClass] ?? system.classification.riskClass} |`,
`| **Erstellt am** | ${formatDate(today)} |`,
`| **Content-Pack** | v${pack.version} (${pack.legalBasis}) |`,
'',
DISCLAIMER,
'',
].join('\n');
}
/**
* Erzeugt den Risikoklassifizierungs-Bericht für ein System als Markdown.
*
* @param system - Bewertetes KI-System
* @param pack - Regulatorischer Content-Pack
* @param today - Stichtag im ISO-Format
* @param profile - Firmenprofil für den Berichtskopf (optional)
*/
export function riskReport(
system: AISystem,
pack: ContentPack,
today: string,
profile?: CompanyProfile,
): string {
const lines = [header('Risikoklassifizierung nach EU AI Act', system, pack, today, profile)];
lines.push('## Ergebnis', '');
lines.push(`**${RISK_LABELS[system.classification.riskClass] ?? system.classification.riskClass}**`, '');
lines.push('## Begründung', '');
for (const reason of system.classification.reasons) {
lines.push(`- ${reason}`);
}
lines.push('');
if (system.classification.gpaiObligations) {
lines.push('## GPAI', '', 'Für dieses System sind zusätzlich GPAI-Modellpflichten (Art. 53 ff.) relevant.', '');
}
if (system.classification.art64DocumentationDuty) {
lines.push(
'## Dokumentationspflicht Art. 6 Abs. 4',
'',
'Die Inanspruchnahme der Ausnahme nach Art. 6 Abs. 3 wurde dokumentiert. Diese Einschätzung ist auf Verlangen der Marktüberwachung vorzulegen.',
'',
);
}
return lines.join('\n');
}
/**
* Erzeugt den Maßnahmenplan (Pflichten-Checkliste) als Markdown.
*
* @param system - Bewertetes KI-System
* @param obligations - Anwendbare Pflichten
* @param pack - Regulatorischer Content-Pack
* @param today - Stichtag im ISO-Format
* @param profile - Firmenprofil für den Berichtskopf (optional)
*/
export function actionPlan(
system: AISystem,
obligations: readonly Obligation[],
pack: ContentPack,
today: string,
profile?: CompanyProfile,
): string {
const lines = [header('Maßnahmenplan EU AI Act', system, pack, today, profile)];
if (obligations.length === 0) {
lines.push('Für dieses System bestehen keine umsetzbaren Pflichten (verboten oder außerhalb des Anwendungsbereichs).');
return lines.join('\n');
}
lines.push('## Pflichten', '');
lines.push('| Pflicht | Rechtsgrundlage | Frist | Status |');
lines.push('|---|---|---|---|');
for (const ob of obligations) {
const status = system.obligationStatus[ob.id] ?? 'open';
lines.push(`| ${ob.title} | ${ob.articles} | ${formatDate(ob.deadline)} | ${STATUS_LABELS[status]} |`);
}
lines.push('', '## Details', '');
for (const ob of obligations) {
lines.push(`### ${ob.title} (${ob.articles})`, '', ob.description, '');
}
return lines.join('\n');
}
/**
* Erzeugt das Grundgerüst der technischen Dokumentation nach Annex IV.
*
* Vorbefüllte Abschnitte stammen aus den Wizard-Antworten; offene Punkte
* sind als Platzhalter markiert.
*
* @param system - Bewertetes KI-System
* @param pack - Regulatorischer Content-Pack
* @param today - Stichtag im ISO-Format
* @param profile - Firmenprofil für den Berichtskopf (optional)
*/
export function annexIVSkeleton(
system: AISystem,
pack: ContentPack,
today: string,
profile?: CompanyProfile,
): string {
const todo = '_[AUSFÜLLEN]_';
const lines = [
header('Technische Dokumentation (Annex IV) — Grundgerüst', system, pack, today, profile),
];
lines.push(
'## 1. Allgemeine Beschreibung des KI-Systems',
'',
`- **Zweckbestimmung:** ${system.purpose || todo}`,
`- **Anbieter:** ${system.vendor || todo}`,
`- **Versionen und Datum des Inverkehrbringens:** ${todo}`,
`- **Hardware-Anforderungen / Bereitstellungsform:** ${todo}`,
`- **Interaktion mit externer Hard-/Software:** ${todo}`,
'',
'## 2. Detaillierte Beschreibung der Elemente und des Entwicklungsprozesses',
'',
`- **Entwicklungsmethoden und verwendete Werkzeuge:** ${todo}`,
`- **Systemarchitektur und Rechenressourcen:** ${todo}`,
`- **Datenanforderungen (Trainings-, Validierungs-, Testdaten, Herkunft, Aufbereitung):** ${todo}`,
`- **Maßnahmen der menschlichen Aufsicht (Art. 14):** ${todo}`,
`- **Vorab festgelegte Änderungen und Kontinuität der Konformität:** ${todo}`,
'',
'## 3. Überwachung, Funktionsweise und Kontrolle',
'',
`- **Fähigkeiten und Leistungsgrenzen (Genauigkeitsmetriken):** ${todo}`,
`- **Vorhersehbare unbeabsichtigte Ergebnisse und Risikoquellen:** ${todo}`,
`- **Eingabedaten-Spezifikation:** ${todo}`,
'',
'## 4. Risikomanagementsystem (Art. 9)',
'',
`- **Beschreibung des Risikomanagementprozesses:** ${todo}`,
'',
'## 5. Änderungen über den Lebenszyklus',
'',
`- **Änderungshistorie und Update-Prozess:** ${todo}`,
'',
'## 6. Angewandte harmonisierte Normen und Spezifikationen',
'',
`- **Liste der Normen (z. B. ISO/IEC 42001, ISO/IEC 23894):** ${todo}`,
'',
'## 7. EU-Konformitätserklärung',
'',
`- **Kopie der Konformitätserklärung (Art. 47):** ${todo}`,
'',
'## 8. Bewertung nach dem Inverkehrbringen (Art. 72)',
'',
`- **Plan zur Beobachtung nach dem Inverkehrbringen:** ${todo}`,
);
return lines.join('\n');
}
/**
* Erzeugt das KI-Inventar über alle registrierten Systeme als Markdown.
*
* @param systems - Alle registrierten KI-Systeme
* @param pack - Regulatorischer Content-Pack
* @param today - Stichtag im ISO-Format
* @param profile - Firmenprofil für den Berichtskopf (optional)
*/
export function inventoryReport(
systems: readonly AISystem[],
pack: ContentPack,
today: string,
profile?: CompanyProfile,
): string {
const company = profile && profile.name.trim() !== '' ? `${profile.name} · ` : '';
const lines = [
'# KI-Inventar (AI Asset Register)',
'',
`${company}Stand: ${formatDate(today)} · Content-Pack v${pack.version}`,
'',
DISCLAIMER,
'',
`Registrierte KI-Systeme: **${systems.length}**`,
'',
'| System | Zweck | Rolle | Risikoklasse | Verantwortlich |',
'|---|---|---|---|---|',
];
for (const s of systems) {
lines.push(
`| ${s.name} | ${s.purpose || '—'} | ${ROLE_LABELS[s.answers.role] ?? s.answers.role} | ${
RISK_LABELS[s.classification.riskClass] ?? s.classification.riskClass
} | ${s.owner || '—'} |`,
);
}
return lines.join('\n');
}