Files
certvia/src/server/risk-calc.ts
T
MartinandClaude Fable 5 dfe27de51f 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>
2026-07-02 16:55:27 +02:00

44 lines
1.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 (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 },
include: { riskMeasures: { select: { reductionLikelihood: true, reductionImpact: true } } },
});
if (!risk) return;
if (risk.riskMeasures.length === 0) {
await db.risk.update({
where: { id: riskId },
data: { residualLikelihood: null, residualImpact: null, residualScore: null },
});
return;
}
const reductionL = risk.riskMeasures.reduce((s, m) => s + m.reductionLikelihood, 0);
const reductionI = risk.riskMeasures.reduce((s, m) => s + m.reductionImpact, 0);
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: round2(residualLikelihood * residualImpact),
},
});
}