- 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
257 lines
8.6 KiB
TypeScript
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');
|
|
}
|