- 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>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
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,00–4,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),
|
||
},
|
||
});
|
||
}
|