feat(ms-ai-architect): renderer A.6 dpia adopt page-header + residual-pair
- parseMatrixRisk utvidet med residualPair-felt + _consumer-diskriminator (R15) - Stotter "Restrisiko: AxB > CxD"-syntax (numerisk) og "Restrisiko: label > label" (fallback) - Sesjon 4 vil sette _consumer='ros' nar Ros-spesifikk markdown oppdages - renderDpia: matrix + residual-pair (B6) + threats-table, wrapped i renderPageShell (eyebrow DPIA) - KeyStats utvidet med RESTRISIKO-stat nar residualPair eksisterer (modifier high hvis score>=9) - Fixture dpia.md utvidet med "Restrisiko: 4x3 -> 2x2"-linje under Konklusjon
This commit is contained in:
parent
3a1dd8a70f
commit
dc670f3208
2 changed files with 83 additions and 2 deletions
|
|
@ -2579,7 +2579,35 @@
|
||||||
score: intOrZero(row[scKey] || '0')
|
score: intOrZero(row[scKey] || '0')
|
||||||
};
|
};
|
||||||
}) : null;
|
}) : null;
|
||||||
return { ok: true, data: { matrix_cells: matrix_cells, threats: threats, radar_axes: radar_axes } };
|
// Restrisiko / residual-pair (Sesjon 3 — Dpia, men felt er optional og
|
||||||
|
// gjelder også fremtidig Ros-bruk per R15). Markdown-syntaks:
|
||||||
|
// Restrisiko: 4×3 → 2×2 (numerisk before/after med score)
|
||||||
|
// Restrisiko: medium → lav (label-only fallback)
|
||||||
|
let residualPair = null;
|
||||||
|
const rrMatch = md.match(/^Restrisiko\s*:\s*([^\n]+)$/im);
|
||||||
|
if (rrMatch) {
|
||||||
|
const txt = rrMatch[1];
|
||||||
|
const num = /(\d+)\s*[×x*]\s*(\d+)\s*(?:[-=]?[>→]|->)\s*(\d+)\s*[×x*]\s*(\d+)/.exec(txt);
|
||||||
|
if (num) {
|
||||||
|
const b1 = +num[1], b2 = +num[2], a1 = +num[3], a2 = +num[4];
|
||||||
|
residualPair = {
|
||||||
|
before: { prob: b1, cons: b2, score: b1 * b2 },
|
||||||
|
after: { prob: a1, cons: a2, score: a1 * a2 }
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const parts = txt.split(/(?:[-=]?[>→]|->)/);
|
||||||
|
if (parts.length === 2) {
|
||||||
|
residualPair = {
|
||||||
|
before: { label: parts[0].trim() },
|
||||||
|
after: { label: parts[1].trim() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// _consumer-diskriminator (R15 forward-compat): Sesjon 4 setter denne til
|
||||||
|
// 'ros' når den oppdager Ros-spesifikk markdown (## Top-risikoer e.l.).
|
||||||
|
// Sesjon 3 lar den være null så Dpia-rendereren ikke trenger å skille.
|
||||||
|
return { ok: true, data: { matrix_cells: matrix_cells, threats: threats, radar_axes: radar_axes, residualPair: residualPair, _consumer: null } };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMatrixRisk6x5(md) {
|
function parseMatrixRisk6x5(md) {
|
||||||
|
|
@ -3238,7 +3266,58 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDpia(data, slot) {
|
function renderDpia(data, slot) {
|
||||||
slot.innerHTML = renderMatrixHtml(data, 5) + renderThreatsTable(data.threats);
|
const matrixHtml = renderMatrixHtml(data, 5);
|
||||||
|
const threatsHtml = renderThreatsTable(data.threats);
|
||||||
|
const rp = data.residualPair;
|
||||||
|
let residualHtml = '';
|
||||||
|
if (rp && rp.before && rp.after) {
|
||||||
|
const sevFor = function (s) {
|
||||||
|
if (s == null) return '';
|
||||||
|
if (s >= 16) return 'critical';
|
||||||
|
if (s >= 9) return 'high';
|
||||||
|
if (s >= 4) return 'medium';
|
||||||
|
return 'low';
|
||||||
|
};
|
||||||
|
const labelOf = function (cell) {
|
||||||
|
if (cell.score != null) return cell.prob + '×' + cell.cons + ' = ' + cell.score;
|
||||||
|
return cell.label || '—';
|
||||||
|
};
|
||||||
|
const sevBefore = rp.before.score != null ? sevFor(rp.before.score) : '';
|
||||||
|
const sevAfter = rp.after.score != null ? sevFor(rp.after.score) : '';
|
||||||
|
const attrBefore = sevBefore ? ' data-severity="' + sevBefore + '"' : '';
|
||||||
|
const attrAfter = sevAfter ? ' data-severity="' + sevAfter + '"' : '';
|
||||||
|
residualHtml = '<div class="residual-pair">' +
|
||||||
|
'<div class="residual-pair__cell"' + attrBefore + '>' +
|
||||||
|
'<span class="residual-pair__cell-label">FØR TILTAK</span>' +
|
||||||
|
'<span class="residual-pair__cell-value">' + escapeHtml(labelOf(rp.before)) + '</span>' +
|
||||||
|
'<span class="residual-pair__cell-meta">Sannsynlighet × konsekvens</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="residual-pair__arrow" aria-hidden="true">→</div>' +
|
||||||
|
'<div class="residual-pair__cell"' + attrAfter + '>' +
|
||||||
|
'<span class="residual-pair__cell-label">ETTER TILTAK</span>' +
|
||||||
|
'<span class="residual-pair__cell-value">' + escapeHtml(labelOf(rp.after)) + '</span>' +
|
||||||
|
'<span class="residual-pair__cell-meta">Restrisiko</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
const body = matrixHtml + residualHtml + threatsHtml;
|
||||||
|
// Utvid matrix-risk-keyStats med RESTRISIKO når residualPair finnes.
|
||||||
|
const baseStats = inferKeyStats(data, 'matrix-risk');
|
||||||
|
const stats = (data.keyStats || (rp && rp.after
|
||||||
|
? baseStats.concat([{
|
||||||
|
label: 'RESTRISIKO',
|
||||||
|
value: rp.after.score != null ? String(rp.after.score) : (rp.after.label || '—'),
|
||||||
|
modifier: rp.after.score != null && rp.after.score >= 9 ? 'high' : 'low',
|
||||||
|
hint: 'etter tiltak'
|
||||||
|
}])
|
||||||
|
: baseStats));
|
||||||
|
slot.innerHTML = renderPageShell({
|
||||||
|
eyebrow: 'DPIA',
|
||||||
|
title: data.title || 'DPIA / Personvernkonsekvensvurdering',
|
||||||
|
lede: data.lede || 'Risikomatrise og tiltak iht. Datatilsynets metodikk og GDPR Art. 35.',
|
||||||
|
verdict: data.verdict || inferVerdict(data, 'matrix-risk'),
|
||||||
|
keyStats: stats
|
||||||
|
}, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Sub-batch B: Security (3) ----
|
// ---- Sub-batch B: Security (3) ----
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,6 @@ Metodikk: Datatilsynets veileder + ISO/IEC 29134
|
||||||
|
|
||||||
## Konklusjon
|
## Konklusjon
|
||||||
|
|
||||||
|
Restrisiko: 4×3 → 2×2
|
||||||
|
|
||||||
Restrisiko etter tiltak: medium-lav. DPIA godkjent av Datatilsynet 2026-04-22.
|
Restrisiko etter tiltak: medium-lav. DPIA godkjent av Datatilsynet 2026-04-22.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue