Iteration 4 (Maßnahmen) + Risikoanalyse nachgeschärft
Risikoanalyse:
- Risiko direkt aus dem Asset-Popup erstellbar ("+ Risiko erstellen"
→ Anlege-Popup mit vorverknüpftem Asset)
- Brutto-/Rest-Risiko detailliert: Eintrittswahrscheinlichkeit und
Schaden als beschriftete Werte (X × Y = Score-Pille)
- Rest-Risiko wird NICHT mehr manuell erfasst, sondern automatisch aus
den verknüpften Maßnahmen berechnet (Minderung je Dimension, Summe,
Untergrenze 1; src/server/risk-calc.ts); Neuberechnung bei jeder
Änderung an Bewertung oder Maßnahmen-Verknüpfungen
- Risikoregister unterhalb der Heatmap in voller Breite
Maßnahmen-Modul (SPEC §4.4):
- Measure/RiskMeasure-Schema mit RLS, laufende Nummer (M-001),
Status/Priorität/Owner/Fälligkeit
- Kanban-Board mit Drag-and-Drop (dnd-kit): Karten zwischen Offen /
In Umsetzung / Erledigt verschieben aktualisiert den Status per
Server-Action inkl. Audit-Log; überfällige Karten rot markiert
- Maßnahmen-Popups (Detail read-only / Bearbeiten / Anlegen) nach
App-Muster; Detail zeigt verknüpfte Risiken mit Minderung
- Im Risiko-Bearbeiten: bestehende Maßnahme verknüpfen ODER neue
Maßnahme direkt anlegen & verknüpfen (jeweils mit Minderung
Wahrscheinlichkeit/Schaden); "Notwendige Maßnahmen" im Risiko-Detail
jetzt echt
- Seed: 4 Beispiel-Maßnahmen, Rest-Risiken daraus berechnet
Verifiziert im Browser: DnD-Statuswechsel (beide Richtungen, Audit-
Einträge), Rest-Risiko-Berechnung (R-001: 4×5=20 → 2×4=8), Risiko-
Anlage aus dem Asset inkl. automatischer Verknüpfung.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
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 0–4) je Dimension;
|
||||
* die Minderungen werden summiert und von der Brutto-Bewertung abgezogen
|
||||
* (Untergrenze 1). Ohne Maßnahmen gibt es kein Rest-Risiko (null).
|
||||
*/
|
||||
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 = Math.max(1, risk.likelihood - reductionL);
|
||||
const residualImpact = Math.max(1, risk.impact - reductionI);
|
||||
|
||||
await db.risk.update({
|
||||
where: { id: riskId },
|
||||
data: {
|
||||
residualLikelihood,
|
||||
residualImpact,
|
||||
residualScore: residualLikelihood * residualImpact,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user