Risiko-Minderung dezimal + editierbar, Bedrohungs-/Schwachstellen-Kataloge

- Minderung je Maßnahmen-Verknüpfung jetzt dezimal (0,00–4,00 statt
  ganzer Punkte): mehrere Maßnahmen summieren sich, bis eine Dimension
  einen vollen Punkt sinkt; Rest-Risiko entsprechend dezimal (z. B.
  2,5 × 4 = 10), gerundet auf 2 Nachkommastellen, lokalisierte Anzeige
- Minderung bereits verknüpfter Maßnahmen direkt im Risiko-Bearbeiten
  editierbar (Eingabefelder je Verknüpfung + Speichern-Button);
  Komma-Eingaben werden akzeptiert
- Globale Kataloge Bedrohungen/Schwachstellen (je 15 Einträge, Seed):
  Eingabefelder schlagen per datalist Katalogwerte vor, Freitext bleibt
  weiterhin möglich (SPEC §5 Threat/Vulnerability)
- Schema: reduction*/residual* auf Float, Threat-/Vulnerability-Modelle

Verifiziert: −1 + −0,5 Minderung → Rest 2,5 × 4 = 10 · Erhöht (DB +
Anzeige), Neuberechnung auch beim Entfernen einer Verknüpfung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-02 16:55:27 +02:00
co-authored by Claude Fable 5
parent 668e587881
commit dfe27de51f
10 changed files with 274 additions and 70 deletions
+7 -1
View File
@@ -18,7 +18,13 @@ const measureSchema = z.object({
dueDate: z.string().optional(),
});
const reduction = z.coerce.number().int().min(0).max(4);
// Minderung dezimal (0,004,00) — Komma-Eingaben werden akzeptiert
const reduction = z
.preprocess(
(v) => (typeof v === "string" ? v.replace(",", ".") : v),
z.coerce.number().min(0).max(4)
)
.transform((v) => Math.round(v * 100) / 100);
function parseMeasureForm(formData: FormData) {
const parsed = measureSchema.parse({
+11 -6
View File
@@ -3,10 +3,15 @@ import type { TenantDb } from "./db";
/**
* Rest-Risiko-Berechnung (SPEC §4.2): Das Rest-Risiko wird NICHT manuell
* gepflegt, sondern ergibt sich aus den verknüpften Maßnahmen. Jede
* Verknüpfung trägt eine erwartete Minderung (Stufen 04) je Dimension;
* die Minderungen werden summiert und von der Brutto-Bewertung abgezogen
* (Untergrenze 1). Ohne Maßnahmen gibt es kein Rest-Risiko (null).
* Verknüpfung trägt eine erwartete Minderung (0,004,00) je Dimension
* bewusst dezimal: oft senkt erst die Summe mehrerer Maßnahmen eine
* Dimension um einen vollen Punkt. Die Minderungen werden summiert und
* von der Brutto-Bewertung abgezogen (Untergrenze 1). Ohne Maßnahmen
* gibt es kein Rest-Risiko (null).
*/
const round2 = (v: number) => Math.round(v * 100) / 100;
export async function recomputeResidualRisk(db: TenantDb, riskId: string) {
const risk = await db.risk.findUnique({
where: { id: riskId },
@@ -24,15 +29,15 @@ export async function recomputeResidualRisk(db: TenantDb, riskId: string) {
const reductionL = risk.riskMeasures.reduce((s, m) => s + m.reductionLikelihood, 0);
const reductionI = risk.riskMeasures.reduce((s, m) => s + m.reductionImpact, 0);
const residualLikelihood = Math.max(1, risk.likelihood - reductionL);
const residualImpact = Math.max(1, risk.impact - reductionI);
const residualLikelihood = round2(Math.max(1, risk.likelihood - reductionL));
const residualImpact = round2(Math.max(1, risk.impact - reductionI));
await db.risk.update({
where: { id: riskId },
data: {
residualLikelihood,
residualImpact,
residualScore: residualLikelihood * residualImpact,
residualScore: round2(residualLikelihood * residualImpact),
},
});
}