feat: Ergebnisdarstellung mit Herleitung (Schritt 4 der Baureihenfolge)

POST /antraege berechnet und speichert direkt beim Einreichen eine
append-only bewertung (Datenklasse, KI-VO-Einstufung, Anforderungen,
zulässige/ausgeschlossene Werkzeuge), GET /antraege/{id} zeigt sie
inklusive Herleitung und dem Pflicht-Hinweis, dass das System nicht
entscheidet, sondern nur vorbereitet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
noroot
2026-08-29 10:22:24 +02:00
parent b1b121bb23
commit e968cf9761
13 changed files with 835 additions and 54 deletions

View File

@@ -28,8 +28,8 @@ func TestEvaluateDatenklasseOnlyOeffentlich(t *testing.T) {
dk, _, _ := loadRealRegelwerke(t)
antworten := rules.Antworten{"b1": "nein", "b2": "nein", "b3": "nein", "b4": "nein", "b5": "nein", "b6": "nein", "b7": "ja"}
got := rules.EvaluateDatenklasse(dk, antworten)
if got != "oeffentlich" {
t.Fatalf("EvaluateDatenklasse = %q, want oeffentlich", got)
if got.ID != "oeffentlich" {
t.Fatalf("EvaluateDatenklasse.ID = %q, want oeffentlich", got.ID)
}
}
@@ -39,8 +39,8 @@ func TestEvaluateDatenklasseHighestWins(t *testing.T) {
// besondere_kategorie hat den hoeheren Rang und muss gewinnen.
antworten := rules.Antworten{"b1": "ja", "b2": "ja", "b7": "nein"}
got := rules.EvaluateDatenklasse(dk, antworten)
if got != "besondere_kategorie" {
t.Fatalf("EvaluateDatenklasse = %q, want besondere_kategorie", got)
if got.ID != "besondere_kategorie" {
t.Fatalf("EvaluateDatenklasse.ID = %q, want besondere_kategorie", got.ID)
}
}
@@ -48,8 +48,8 @@ func TestEvaluateDatenklasseUnsicherZaehltWieJa(t *testing.T) {
dk, _, _ := loadRealRegelwerke(t)
antworten := rules.Antworten{"b3": "unsicher"}
got := rules.EvaluateDatenklasse(dk, antworten)
if got != "berufsgeheimnis" {
t.Fatalf("EvaluateDatenklasse = %q, want berufsgeheimnis (unsicher zaehlt wie ja)", got)
if got.ID != "berufsgeheimnis" {
t.Fatalf("EvaluateDatenklasse.ID = %q, want berufsgeheimnis (unsicher zaehlt wie ja)", got.ID)
}
}
@@ -57,8 +57,8 @@ func TestEvaluateDatenklasseFallsBackToInternWhenNothingMatches(t *testing.T) {
dk, _, _ := loadRealRegelwerke(t)
antworten := rules.Antworten{"b1": "nein", "b2": "nein", "b3": "nein", "b4": "nein", "b5": "nein", "b6": "nein", "b7": "nein"}
got := rules.EvaluateDatenklasse(dk, antworten)
if got != "intern" {
t.Fatalf("EvaluateDatenklasse = %q, want intern (konservativer Fallback)", got)
if got.ID != "intern" {
t.Fatalf("EvaluateDatenklasse.ID = %q, want intern (konservativer Fallback)", got.ID)
}
}
@@ -69,10 +69,10 @@ func TestEvaluateEinstufungVerboten(t *testing.T) {
if err != nil {
t.Fatalf("EvaluateEinstufung: %v", err)
}
if got != "verboten" {
t.Fatalf("EvaluateEinstufung = %q, want verboten", got)
if got.ID != "verboten" {
t.Fatalf("EvaluateEinstufung.ID = %q, want verboten", got.ID)
}
if !rules.IstVerboten(got) {
if !rules.IstVerboten(got.ID) {
t.Error("IstVerboten sollte true liefern")
}
}
@@ -84,8 +84,8 @@ func TestEvaluateEinstufungHochrisiko(t *testing.T) {
if err != nil {
t.Fatalf("EvaluateEinstufung: %v", err)
}
if got != "hochrisiko" {
t.Fatalf("EvaluateEinstufung = %q, want hochrisiko", got)
if got.ID != "hochrisiko" {
t.Fatalf("EvaluateEinstufung.ID = %q, want hochrisiko", got.ID)
}
}
@@ -96,8 +96,8 @@ func TestEvaluateEinstufungTransparenzpflicht(t *testing.T) {
if err != nil {
t.Fatalf("EvaluateEinstufung: %v", err)
}
if got != "transparenzpflicht" {
t.Fatalf("EvaluateEinstufung = %q, want transparenzpflicht", got)
if got.ID != "transparenzpflicht" {
t.Fatalf("EvaluateEinstufung.ID = %q, want transparenzpflicht", got.ID)
}
}
@@ -108,10 +108,10 @@ func TestEvaluateEinstufungMinimalFallback(t *testing.T) {
if err != nil {
t.Fatalf("EvaluateEinstufung: %v", err)
}
if got != "minimal" {
t.Fatalf("EvaluateEinstufung = %q, want minimal", got)
if got.ID != "minimal" {
t.Fatalf("EvaluateEinstufung.ID = %q, want minimal", got.ID)
}
if rules.IstVerboten(got) {
if rules.IstVerboten(got.ID) {
t.Error("IstVerboten sollte fuer minimal false liefern")
}
}
@@ -121,8 +121,11 @@ func TestDeriveAnforderungenPersonenbezogen(t *testing.T) {
got := rules.DeriveAnforderungen(an, "personenbezogen", "minimal")
want := map[string]bool{"avv_erforderlich": true, "kein_training_auf_eingabe": true, "loeschfrist_max_tage": true}
gotSet := map[string]bool{}
for _, id := range got {
gotSet[id] = true
for _, a := range got {
gotSet[a.ID] = true
if a.Herleitung == "" {
t.Errorf("Anforderung %q hat keine Herleitung", a.ID)
}
}
for id := range want {
if !gotSet[id] {
@@ -138,8 +141,8 @@ func TestDeriveAnforderungenHochrisikoAddsAufsichtUndDsfa(t *testing.T) {
_, _, an := loadRealRegelwerke(t)
got := rules.DeriveAnforderungen(an, "oeffentlich", "hochrisiko")
gotSet := map[string]bool{}
for _, id := range got {
gotSet[id] = true
for _, a := range got {
gotSet[a.ID] = true
}
if !gotSet["menschliche_aufsicht"] || !gotSet["dsfa_erforderlich"] {
t.Errorf("expected menschliche_aufsicht und dsfa_erforderlich fuer hochrisiko, got %v", got)