# Deklarix > KI-Antragsprüfung für Unternehmen: Mitarbeitende beschreiben ein > KI-Vorhaben in eigenen Worten, das System leitet Datenklasse, > KI-VO-Einstufung und Anforderungsprofil ab, gleicht sie gegen einen > gepflegten Werkzeugkatalog ab und erzeugt einen begründeten > Entscheidungsvorschlag. Ein Mensch entscheidet, das System bereitet > nur vor. --- ## Produktwechsel (2026-08-28) Deklarix war ursprünglich eine Pre-Publish-Kennzeichnungsprüfung für Werbe-Content (UWG/MStV) für Creator, Agenturen, Marken und Kanzleien. Dieser Scope wurde **komplett verworfen** und durch das oben beschriebene Produkt ersetzt — kein Feature-Zusatz, ein Produktwechsel. Wiederverwendet wurden nur produktunabhängige Bausteine: Auth/Session/ Account-Mandantentrennung, das Prinzip "Regelwerk als versionierte YAML-Dateien, kein ML für die Bewertung", die apt/systemd/Gitea- Deployment-Pipeline und das Design-System. Alles Werberecht-Spezifische (Kennzeichnungsregeln, Beweisarchiv für Social-Media-Posts, Instagram/ TikTok-OAuth, Insights-Erinnerung) wurde entfernt, nicht nur ersetzt — siehe Migration `0008_pivot_ki_antragspruefung` für den genauen Schnitt. --- ## Grundregel **Das System entscheidet nicht. Es bereitet vor.** Der/die Verantwortliche bestätigt, ändert oder überschreibt einen Bewertungsvorschlag — mit Begründungsfeld bei Abweichung. Jede Entscheidung wird als Registereintrag protokolliert. Die Bewertung wird **nie** ohne ihre vollständige Herleitung angezeigt (welche Antwort welche Anforderung ausgelöst hat) — ohne das ist ein Vorschlag im Audit wertlos. Regelbasiert, nicht ML: die Ableitungsregeln (Datenklasse, KI-VO- Einstufung, Anforderungsprofil) liegen als versionierte YAML-Dateien vor, nicht im Code — ein fachlich Verantwortlicher soll sie anpassen können, ohne Go-Code anzufassen. Ein LLM ist v1 höchstens optional zur Zweckerkennung aus dem Freitext vorgesehen (Textgenerierung/Übersetzung/ Code/Bild/Transkription/Recherche erkennen), nie für die Einstufung selbst — und auch dort nur als Vorschlag, den der Mensch bestätigt. --- ## Fünf Ebenen, fünf Rollen Die Oberfläche gliedert sich in fünf Ebenen mit steigender Sensibilität. Vier Mandanten-Rollen sind eine Voreinstellung, kein fest verdrahtetes Modell (spätere Ausbaustufe: konfigurierbare Rollen) — `betreiber` ist technisch getrennt vom Mandantenbereich, keine Voreinstellung. | Ebene | Zugang | Rolle(n) | Zweck | |---|---|---|---| | 1 — Öffentlich | ohne Konto | — | Anmeldung, Passwort zurücksetzen, Einladung annehmen, Antrag über Mandantenlink | | 2 — Mitarbeiter | Konto nötig | `mitarbeiter` | Antrag stellen, eigene Anträge verfolgen, Rückfragen beantworten, freigegebene Werkzeuge einsehen. Bewusst schlank — dauert der Antrag zu lange, wird er umgangen | | 3 — Fachebene | Konto nötig | `verantwortlicher` (Entscheidungsrecht), `pruefer` (identische Sicht, kein Entscheidungsrecht) | Posteingang offener Anträge, Bewertungsvorschlag mit Herleitung, entscheiden, Register, Berichte/Exporte, Audit-Log lesend | | 4 — Mandanten-Verwaltung | Konto nötig | `admin` | Nutzer/Rollen, Abteilungen, Anmeldeverfahren, eigene Werkzeug-Freigaben/-Sperrungen, Rechnungsdaten — bezogen auf GENAU EINEN Mandanten | | 5 — Plattform | Konto nötig, technisch getrennt | `betreiber` | Werkzeugkatalog, Regelwerk-Versionierung, Mandantenverwaltung/Freischaltung, Nutzungssicht — Netcell-IT-Personal, plattformweit über alle Mandanten | **Funktionstrennung bleibt bestehen**, auch wenn in kleinen Firmen dieselbe Person Admin und Verantwortlicher ist — das sind zwei Rollen auf demselben Menschen, keine verschmolzene Rolle. `admin` (Ebene 4, Mandanten-Verwaltung für den eigenen Account) und `betreiber` (Ebene 5, Plattform-Betrieb für Netcell-IT über alle Mandanten) sind nicht dasselbe, auch wenn beide "Admin"-artige Rechte haben — nur `betreiber` ist der Nachfolger dessen, was früher (vor dem Produktwechsel) `admin` hieß. **Praktische Umsetzung dieser Doppelrolle:** `admin` bekommt zusätzlich dieselben Fachebene-Rechte wie `verantwortlicher` (siehe `requireFachebene`/`hatEntscheidungsrecht`) — pragmatisch gelöst ohne Datenmodell-Umbau, siehe Offene Punkte weiter unten. Rechte werden **als Prüfung an jeder Aktion** durchgesetzt (Middleware je Handler), nicht als grob unterschiedene Seitenbereiche. **Ebene 4 ist inzwischen zu großen Teilen umgesetzt** (Anspruch: alles, was im Datenmodell existiert, muss über das Frontend steuerbar sein, nicht nur einsehbar — Deklarix soll ein buchbarer Service werden, ein reiner Anzeige-Screen reicht nicht): - **Nutzerverwaltung** (`internal/web/admin_handlers.go`, Middleware `requireAdmin`): `GET /verwaltung/nutzer` listet alle Logins des eigenen Mandanten mit Status (aktiv/deaktiviert), `GET/POST /verwaltung/nutzer/neu` legt einen weiteren Login mit einer der vier Mandanten-Rollen an (`mitarbeiter`, `verantwortlicher`, `pruefer`, `admin` — `betreiber` kann kein Mandanten-Admin vergeben, das ist Ebene 5). Es gibt noch **keine** Einladungsmail — der Admin setzt das Initialpasswort direkt im Formular. Nutzer werden **nicht gelöscht** (`app_user` wird von `antrag`/`entscheidung`/`audit_log` per Foreign Key referenziert — ein Hard-Delete würde die Historie zerstören), sondern über `POST /verwaltung/nutzer/{id}/deaktivieren` bzw. `.../aktivieren` (de-)aktiviert (Spalte `app_user.active`, Migration 0012). Ein deaktivierter Login kann sich nicht mehr anmelden (`handleLogin` prüft `Active` erst NACH der Passwortprüfung, um keine Kontoexistenz zu verraten) und verliert eine bereits laufende Sitzung sofort (`authenticate`-Middleware prüft `Active` bei jedem Request). Ein Admin kann sich nicht selbst deaktivieren (Aussperr-Schutz). - **Abteilungen** (`GET /verwaltung/abteilungen`, anlegen + löschen): reine Stammdaten für das Fragebogen-Feld "Abteilung" — ohne diese Seite blieb die Abteilung-Auswahl im Fragebogen faktisch leer und unbenutzbar, das war kein Komfort-, sondern ein Funktionsdefizit. - **Eigene Werkzeug-Sperrungen und -Einträge** (`internal/web/mandant_werkzeug_handlers.go`, `GET /verwaltung/werkzeuge`): ein Mandant kann einen zentralen Katalogeintrag für sich sperren/entsperren (`werkzeug_sperre`), ohne den zentralen Katalog selbst zu verändern — das bleibt Sache der Plattform (Ebene 5, `betreiber_werkzeug_handlers.go`). Zusätzlich kann ein Mandant eigene, nur für sich sichtbare Katalogeinträge anlegen/ bearbeiten/löschen (`GET/POST /verwaltung/werkzeuge/neu`, `GET/POST /verwaltung/werkzeuge/{id}`, `POST /verwaltung/werkzeuge/{id}/loeschen` — `werkzeug.account_id` = der eigene Account). Nutzt dasselbe Formular wie der zentrale Katalog (`werkzeugFormData`/`betreiber-werkzeug-form.html`, ein `ActionBase`-Feld unterscheidet Betreiber- von Mandanten-Ziel-URL) — ein zentraler oder fremder Eintrag ist über diese Route nicht erreichbar (404). **Weiterhin nicht gebaut:** Anmeldeverfahren-Konfiguration, Rechnungsdaten/ Abrechnung (kein Abo-System, bewusst "Nicht bauen v1"), Account- Verwaltung durch den Betreiber (Ebene 5 zeigt Accounts nur lesend an — Bearbeiten/Sperren hängt an der noch nicht getroffenen Abrechnungs-/ Freischaltungs-Architektur, siehe Offene Punkte: `account.verified` wurde beim Produktwechsel sogar entfernt). **Mandantenfähigkeit:** jede Tabelle trägt `account_id`. Aktuell wird Isolation in der Anwendungsschicht erzwungen (Handler vergleichen `AccountID` gegen den angemeldeten Account, wie bisher) — die Zielvorgabe "Isolation auf Datenbankebene erzwungen" (Postgres Row- Level Security) ist eine bewusst noch nicht getroffene Entscheidung, siehe Offene Punkte unten. --- ## Fragebogen (Ebene 2, "Antrag stellen") Adaptiv: Folgefragen erscheinen nur, wenn die Antwort sie auslöst. Alltagssprache, keine Fachbegriffe — die stehen nur in der Ableitung. **A — Vorhaben:** `titel`, `beschreibung` (Freitext: was soll die KI tun?), `ergebnis` (Freitext: was soll herauskommen?), `haeufigkeit` (einmalig/gelegentlich/täglich/automatisiert), `abteilung` (aus Stammdaten). **B — Daten** (bestimmt die Datenklasse). Mehrfachauswahl Ja/Nein/ Unsicher, "Unsicher" zählt wie "Ja": Namen/E-Mail/Personenangaben (B1), Gesundheit/Religion/Herkunft/Gewerkschaft/Sexualleben (B2), Patienten-/ Mandanten-/Versichertendaten (B3), Bewerbungs-/Personal-/ Leistungsdaten Beschäftigter (B4), von Kunden anvertraute Daten (B5), Konstruktions-/Kalkulations-/Vertragsunterlagen (B6), nur allgemein zugängliche/erfundene Inhalte (B7). **C — Verwendung des Ergebnisses** (bestimmt die KI-VO-Einstufung): geht das Ergebnis unverändert nach außen (C1)? beeinflusst es eine Entscheidung über einen Menschen (C2, Folgefrage: Einstellung/ Beförderung/Kündigung, Kreditwürdigkeit, Leistungsbewilligung, Zugang zu Bildung, medizinische Beurteilung, keins davon)? erkennt/bewertet es Emotionen, Verhalten oder biometrische Merkmale (C3)? läuft es ohne menschliche Prüfung (C4)? merkt der Empfänger, dass es von einer KI stammt (C5)? **D — Werkzeug:** ggf. schon ein Werkzeug im Sinn (optional, Freitext), Firmen- oder privater Zugang, Firmen- oder Privatgerät. --- ## Ableitungen (`rules/*.yaml`, `internal/rules`) **Datenklasse** (`rules/datenklasse.yaml`) — höchste zutreffende Stufe gewinnt: `oeffentlich` (B7) < `intern` (B6) < `auftragsdaten` (B5) < `personenbezogen` (B1, B4) < `berufsgeheimnis` (B3, § 203 StGB) < `besondere_kategorie` (B2, Art. 9 DSGVO). **Die Rangfolge ist eine Annahme** (Spezifikation nennt keine expliziten Ränge) — siehe `rules/OPEN.md`, vor Produktiveinsatz mit dem/der Datenschutz- beauftragten bestätigen. **KI-VO-Einstufung** (`rules/kivo_einstufung.yaml`) — Prüfreihenfolge, erste zutreffende Stufe gewinnt: `verboten` (Art. 5 KI-VO: C3 mit Emotionserkennung am Arbeitsplatz, Social Scoring oder biometrischer Kategorisierung) → `hochrisiko` (C2 mit Beschäftigung/Kreditwürdigkeit/ Leistungsbewilligung/Bildung/Gesundheit) → `transparenzpflicht` (C1 ja und C5 nein) → `minimal` (alles übrige). Die genauen Fragebogen-Felder für die drei "verboten"-Varianten sind noch nicht abschließend spezifiziert — siehe `rules/OPEN.md`. **Anforderungsprofil** (`rules/anforderungen.yaml`) — aus Datenklasse und/oder Einstufung abgeleitete Menge von Anforderungen an ein Werkzeug: `avv_erforderlich`, `eu_verarbeitung`, `kein_training_auf_eingabe`, `loeschfrist_max_tage`, `menschliche_aufsicht`, `kennzeichnungspflicht`, `dsfa_erforderlich`. `internal/rules` lädt und validiert diese drei Dateien (eindeutige IDs, eindeutige Ränge, jede Anforderung braucht mindestens einen Auslöser) und wertet sie seit Schritt 3 auch aus: `EvaluateDatenklasse`, `EvaluateEinstufung`, `IstVerboten`, `DeriveAnforderungen` operieren auf `rules.Antworten` (geparst aus `antrag.antworten` via `ParseAntworten`) — reine, für sich getestete Funktionen ohne DB-/Web-Zugriff. `FilterWerkzeuge`/`ErfuelltAnforderung` filtern einen Werkzeugkatalog hart gegen die abgeleiteten Anforderungen (`WerkzeugEigenschaften` ist ein schlanker, von `store.Werkzeug` unabhängiger Typ, damit `internal/rules` weiterhin ohne `internal/store` auskommt). Seit Schritt 4 wird das auch tatsächlich aufgerufen: `POST /antraege` berechnet nach dem Einreichen sofort eine `bewertung` (append-only, `internal/web/antrag_handlers.go:bewerteAntrag`) — bei `verboten` (K.-o.-Prüfung) ohne Werkzeugsuche — und `GET /antraege/{id}` zeigt sie inklusive Herleitung, RegelwerkVersion/KatalogVersion und dem Pflicht-Hinweis "Das System entscheidet nicht. Es bereitet vor." --- ## Werkzeugkatalog (`werkzeug`, `werkzeug_sperre`) Der eigentliche Wert des Produkts. Zentral gepflegt (Ebene 5, `account_id IS NULL`), für alle Mandanten identisch sichtbar; jeder Mandant kann zusätzlich eigene Einträge (`account_id` gesetzt) und Sperrungen zentraler Einträge (`werkzeug_sperre`) führen, ohne den zentralen Katalog zu verändern. **Pflicht:** `letzte_pruefung` und `quelle` sind NOT NULL — jede Zusicherung im Katalog (AVV verfügbar, Verarbeitungsort, Training-Opt- out, Zertifizierungen) muss belegbar sein, sonst haftet Deklarix für eine Aussage, die nicht nachweisbar ist. Einträge älter als 180 Tage sollen im Ergebnis als "nicht aktuell geprüft" markiert werden (noch nicht in der UI umgesetzt, siehe Offene Punkte). `CurrentKatalogVersion` liefert eine reproduzierbare Kennung des aktuellen Katalogzustands (Anzahl Einträge + letzte Änderung) — wird in jeder Bewertung/Entscheidung eingefroren, damit im Audit nachvollziehbar bleibt, mit welchem Katalogstand ein Vorschlag erzeugt wurde. --- ## Bewertungslogik (geplant, Schritt 3+) 1. **K.-o.-Prüfung.** Einstufung `verboten` → sofort Ablehnung (Art. 5 KI-VO), keine Werkzeugsuche. 2. **Harte Filter.** Werkzeuge aussortieren, die eine Pflichtanforderung nicht erfüllen — Grund je Werkzeug festhalten, auch aussortierte Werkzeuge werden im Ergebnis mit Begründung gezeigt. 3. **Zweckabgleich.** Aus `beschreibung`/`ergebnis` den Zweck ableiten (Textgenerierung, Übersetzung, Code, Bild, Transkription, Recherche) und gegen `geeignete_zwecke` filtern. 4. **Rangfolge.** Verbleibende Werkzeuge sortieren: EU-Verarbeitung, Training standardmäßig aus, kurze Aufbewahrung, Aktualität der Prüfung. 5. **Auflagen ableiten.** Statt Ablehnung Bedingungen erzeugen (Pseudonymisierung, keine Klarnamen, Vier-Augen-Prüfung, Kennzeichnung, Aufbewahrung begrenzen). 6. **Vorschlag erzeugen.** Empfehlung, Alternativen, Auflagen, vollständige Herleitung. Jede Bewertung wird als eigene Zeile gespeichert (append-only, wie `finding`/`extraction` im alten Produkt) — eine neue Bewertung ersetzt nicht die alte, sie ergänzt sie, damit Nachvollziehbarkeit auch bei mehrfacher Neubewertung eines Antrags erhalten bleibt. --- ## Entscheidung, Register, Wiedervorlage (Schritt 5-7 erledigt) **Schritt 5 (Entscheidung, Snapshot, Audit-Log) ist umgesetzt.** Ebene 3 (Fachebene) hat einen Posteingang (`GET /faelle`, alle offenen — Status "eingereicht" — Anträge des Mandanten, nicht nur die eigenen) und eine Entscheiden-Seite (`GET /faelle/{id}`, `POST /faelle/{id}/entscheiden`, siehe `internal/web/fachebene_handlers.go`). Rollen `verantwortlicher` und `pruefer` sehen dieselbe Sicht (`requireFachebene`), nur `verantwortlicher` hat Entscheidungsrecht — ein `pruefer`-POST auf `/entscheiden` bekommt 403 (er darf wissen, dass der Fall existiert, nur nicht entscheiden; anders als bei `requireFachebene`/`requireBetreiber`, die bei falscher Rolle 404 liefern, um die Existenz der Seite selbst zu verbergen). Der/die Verantwortliche wählt: genehmigt / genehmigt mit Auflagen / abgelehnt / Rückfrage. **Vorschlag-Vergleich:** `vorschlagFuer` leitet aus der Bewertung ab, was das System vorschlagen würde (`abgelehnt` bei `verboten`, sonst `genehmigt` wenn mindestens ein zulässiges Werkzeug existiert, sonst `rueckfrage`) — weicht die tatsächliche Entscheidung davon ab, ist das Begründungsfeld Pflicht (sonst 400). **Snapshot bei Entscheidung:** `entscheidung.werkzeug_snapshot` (JSONB) friert den vollständigen `store.Werkzeug`-Datensatz zum Entscheidungszeitpunkt ein — ein späterer Katalog-Wandel darf nicht rückwirkend verändern, worauf eine Entscheidung beruhte. Bei "genehmigt"/"genehmigt mit Auflagen" ist ein Werkzeug aus der zulässigen Liste der Bewertung Pflicht (hart geprüft, kein Override eines ausgeschlossenen Werkzeugs). `entscheidung` ist append-only (Migration 0010) wie `bewertung`; jede Entscheidung erzeugt zusätzlich einen `audit_log`-Eintrag (Action `antrag_entschieden`). Genehmigungen erhalten ein Ablaufdatum (`gueltig_bis`: 12 Monate, bei `hochrisiko` 6 — `gueltigkeitFuer`). **Schritt 7 (Wiedervorlage, Katalog-Benachrichtigung) ist umgesetzt** (`internal/web/wiedervorlage_handlers.go`, `GET /wiedervorlage`, Ebene 3). Es gibt keine E-Mail-Infrastruktur — "Benachrichtigung" ist bewusst eine In-App-Liste, kein Versand. Eine aktive Genehmigung erscheint dort, wenn mindestens einer zutrifft: (1) `gueltig_bis` ist erreicht oder liegt innerhalb von 30 Tagen, (2) das zugesagte Werkzeug wurde aus dem Katalog entfernt, (3) `werkzeugDiff` erkennt eine Abweichung zwischen dem eingefrorenen `werkzeug_snapshot` und dem aktuellen Katalogeintrag bei AVV-Verfügbarkeit, Training-Standard oder Verarbeitungsort. Keine gefundene Abweichung → die Genehmigung erscheint nicht (kein stiller Blanko-Eintrag für jede Genehmigung). `store.ListAktiveGenehmigungenForAccount` liefert dafür alle `genehmigt`/`genehmigt_mit_auflagen`-Entscheidungen eines Mandanten (Join über `antrag.account_id`, da `entscheidung` selbst kein `account_id` trägt). **Schritt 6 (Registereintrag, Export) ist umgesetzt.** Jede Genehmigung erzeugt automatisch einen `registereintrag` (append-only, Migration 0011): Zweck (`antrag.titel`), Abteilung, Werkzeug, Datenklasse, Einstufung, Auflagen (die Anforderungen der Bewertung), Verantwortliche/r, Entscheidungsdatum, Gültigkeit — siehe `internal/web/register_handlers.go`. `GET /registereintraege` (Ebene 3, `verantwortlicher`/`pruefer`) zeigt sie, `GET /registereintraege/export.{csv,pdf}` exportiert. Abgelehnte Entscheidungen und Rückfragen erzeugen bewusst KEINEN Registereintrag — das Register ist eine Genehmigungsliste, kein vollständiges Antragslog (das leistet bereits `GET /faelle`). --- ## Onboarding **Firma (Ebene 1, öffentlich, `POST /register`):** Registrierungsformular mit Firmendaten → Testzugang sofort aktiv → Freischaltung des Bezahlbetriebs nach Prüfung durch den Betreiber (letzteres noch nicht gebaut). Beim Anlegen entstehen automatisch: Account, erster Nutzer mit Rolle `admin` (siehe Rollentabelle — vereinfacht v1: nicht zusätzlich separat als `verantwortlicher` angelegt, siehe Offene Punkte), leere Abteilungsliste, Werkzeugkatalog aus der Plattform vorbelegt (noch nicht automatisiert — der zentrale Katalog ist aktuell leer, bis Ebene 5/Betreiber-Pflege gebaut ist). **Mitarbeiter, geplante Reihenfolge:** (1) ~~Einladungslink~~ — **erledigt** (`internal/web/einladung_handlers.go`: `account.einladung_token`, Migration 0013, ein Sammellink pro Mandant statt Einmal-Token pro Person, kein Ablaufdatum, per Admin unter `GET /verwaltung/einladung` jederzeit erneuerbar [macht den alten Link sofort ungültig]. `GET/POST /einladung/{token}` — öffentlich, Ebene 1 — legt einen Login mit Rolle `mitarbeiter` im Mandanten des Links an und meldet direkt an, ohne E-Mail-Bestätigung [noch nicht gebaut, siehe unten]. Die Abteilung wählt die Person weiterhin erst beim ersten Antrag, nicht bei der Einladung.), (2) ~~CSV-Import~~ — **erledigt** (`internal/web/csv_import_handlers.go`, `GET /verwaltung/nutzer/import`: CSV mit Kopfzeile, Pflichtspalte "email", optionale Spalte "role" [Standard `mitarbeiter`]. Wie beim einzelnen Nutzer-Anlegen gibt es keine Einladungsmail — pro Zeile wird ein Einmalpasswort erzeugt [wiederverwendet `auth.NewSessionToken()`], das NUR auf der Ergebnisseite direkt nach dem Import angezeigt wird und danach nirgendwo mehr abrufbar ist. Kein Alles-oder-nichts-Import: gültige Zeilen werden angelegt, eine ungültige Rolle oder bereits vergebene E-Mail in einer Zeile blockiert nicht die übrigen Zeilen — jede Zeile bekommt ihr eigenes Ergebnis [Passwort oder Fehlermeldung].), (3) SSO (Entra ID/Google Workspace), (4) Antrag ganz ohne vorheriges Konto über Mandantenlink + E-Mail-Bestätigungscode (Konto entsteht nebenbei). (3)-(4) sind weiterhin nicht gebaut. **Abrechnung:** nach vom Kunden angegebener Mitarbeiterzahl, **nicht** nach angelegten Nutzern — sonst entsteht der Anreiz, Mitarbeitende nicht anzulegen. Noch nicht gebaut (kein Abo-System). --- ## Frontend Anspruch (2026-08-29 explizit vom Nutzer bestätigt, da Deklarix ein buchbarer Service werden soll): **für Browser UND Mobile optimiert, und jede Entität im Datenmodell muss über das Frontend steuerbar sein** — eine reine Anzeige-Seite ohne CRUD ist ein Defizit, kein v1-Kompromiss. Stack bleibt `html/template` + htmx, kein React/Vue/SPA (siehe Baureihenfolge/Architektur-Entscheidungen) — die responsive Optik wird rein mit CSS erreicht, ohne das Template-Modell zu ändern. **Navigation** (`internal/web/templates/layout.html`, `"nav"`-Block, einmal definiert und von jeder Seite eingebunden — Änderungen hier wirken auf das ganze Produkt): ein Checkbox-Hack (versteckte Checkbox + `