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>
140 lines
4.8 KiB
Go
140 lines
4.8 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// BewertungAnforderung ist eine abgeleitete Anforderung mit Herleitung,
|
|
// wie sie in bewertung.anforderungen (JSONB) gespeichert wird.
|
|
type BewertungAnforderung struct {
|
|
ID string `json:"id"`
|
|
Beschreibung string `json:"beschreibung"`
|
|
Herleitung string `json:"herleitung"`
|
|
}
|
|
|
|
// BewertungAusschluss hält fest, warum ein Werkzeug für eine Bewertung
|
|
// ausgeschlossen wurde.
|
|
type BewertungAusschluss struct {
|
|
WerkzeugID string `json:"werkzeug_id"`
|
|
NichtErfuellt []string `json:"nicht_erfuellt"`
|
|
}
|
|
|
|
// Bewertung ist der berechnete Vorschlag für einen Antrag. Append-only
|
|
// — siehe Migration.
|
|
type Bewertung struct {
|
|
ID string
|
|
AntragID string
|
|
Datenklasse string
|
|
DatenklasseHerleitung string
|
|
Einstufung string
|
|
EinstufungHerleitung string
|
|
Verboten bool
|
|
Anforderungen []BewertungAnforderung
|
|
ZulaessigeWerkzeuge []string
|
|
AusgeschlosseneWerkzeuge []BewertungAusschluss
|
|
RegelwerkVersion string
|
|
KatalogVersion string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
const bewertungColumns = `id, antrag_id, datenklasse, datenklasse_herleitung, einstufung, einstufung_herleitung,
|
|
verboten, anforderungen, zulaessige_werkzeuge, ausgeschlossene_werkzeuge, regelwerk_version, katalog_version, created_at`
|
|
|
|
func scanBewertung(row interface {
|
|
Scan(dest ...any) error
|
|
}) (Bewertung, error) {
|
|
var b Bewertung
|
|
var anforderungenRaw, ausschlussRaw []byte
|
|
err := row.Scan(
|
|
&b.ID, &b.AntragID, &b.Datenklasse, &b.DatenklasseHerleitung, &b.Einstufung, &b.EinstufungHerleitung,
|
|
&b.Verboten, &anforderungenRaw, &b.ZulaessigeWerkzeuge, &ausschlussRaw, &b.RegelwerkVersion, &b.KatalogVersion, &b.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
return Bewertung{}, err
|
|
}
|
|
if err := json.Unmarshal(anforderungenRaw, &b.Anforderungen); err != nil {
|
|
return Bewertung{}, fmt.Errorf("store: anforderungen unmarshal: %w", err)
|
|
}
|
|
if err := json.Unmarshal(ausschlussRaw, &b.AusgeschlosseneWerkzeuge); err != nil {
|
|
return Bewertung{}, fmt.Errorf("store: ausgeschlossene_werkzeuge unmarshal: %w", err)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// BewertungInput bündelt die Felder einer neuen Bewertung — bei elf
|
|
// Werten lesbarer als eine positionale Parameterliste (gleiches Muster
|
|
// wie WerkzeugInput).
|
|
type BewertungInput struct {
|
|
AntragID string
|
|
Datenklasse string
|
|
DatenklasseHerleitung string
|
|
Einstufung string
|
|
EinstufungHerleitung string
|
|
Verboten bool
|
|
Anforderungen []BewertungAnforderung
|
|
ZulaessigeWerkzeuge []string
|
|
AusgeschlosseneWerkzeuge []BewertungAusschluss
|
|
RegelwerkVersion string
|
|
KatalogVersion string
|
|
}
|
|
|
|
// CreateBewertung speichert eine berechnete Bewertung für einen Antrag.
|
|
func (s *Store) CreateBewertung(ctx context.Context, in BewertungInput) (Bewertung, error) {
|
|
if in.ZulaessigeWerkzeuge == nil {
|
|
in.ZulaessigeWerkzeuge = []string{}
|
|
}
|
|
if in.Anforderungen == nil {
|
|
in.Anforderungen = []BewertungAnforderung{}
|
|
}
|
|
if in.AusgeschlosseneWerkzeuge == nil {
|
|
in.AusgeschlosseneWerkzeuge = []BewertungAusschluss{}
|
|
}
|
|
anforderungenJSON, err := json.Marshal(in.Anforderungen)
|
|
if err != nil {
|
|
return Bewertung{}, fmt.Errorf("store: anforderungen marshal: %w", err)
|
|
}
|
|
ausschlussJSON, err := json.Marshal(in.AusgeschlosseneWerkzeuge)
|
|
if err != nil {
|
|
return Bewertung{}, fmt.Errorf("store: ausgeschlossene_werkzeuge marshal: %w", err)
|
|
}
|
|
|
|
row := s.Pool.QueryRow(ctx, `
|
|
INSERT INTO bewertung (
|
|
antrag_id, datenklasse, datenklasse_herleitung, einstufung, einstufung_herleitung,
|
|
verboten, anforderungen, zulaessige_werkzeuge, ausgeschlossene_werkzeuge, regelwerk_version, katalog_version
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
RETURNING `+bewertungColumns,
|
|
in.AntragID, in.Datenklasse, in.DatenklasseHerleitung, in.Einstufung, in.EinstufungHerleitung,
|
|
in.Verboten, anforderungenJSON, in.ZulaessigeWerkzeuge, ausschlussJSON, in.RegelwerkVersion, in.KatalogVersion,
|
|
)
|
|
b, err := scanBewertung(row)
|
|
if err != nil {
|
|
return Bewertung{}, fmt.Errorf("store: create bewertung: %w", err)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// GetLatestBewertungForAntrag liefert die zuletzt berechnete Bewertung
|
|
// eines Antrags. Liefert ErrNotFound, wenn noch keine Bewertung
|
|
// existiert.
|
|
func (s *Store) GetLatestBewertungForAntrag(ctx context.Context, antragID string) (Bewertung, error) {
|
|
row := s.Pool.QueryRow(ctx, `
|
|
SELECT `+bewertungColumns+`
|
|
FROM bewertung WHERE antrag_id = $1 ORDER BY created_at DESC LIMIT 1
|
|
`, antragID)
|
|
b, err := scanBewertung(row)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return Bewertung{}, ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return Bewertung{}, fmt.Errorf("store: get latest bewertung: %w", err)
|
|
}
|
|
return b, nil
|
|
}
|