diff --git a/plugins/ms-ai-architect/playground/ms-ai-architect-playground.html b/plugins/ms-ai-architect/playground/ms-ai-architect-playground.html index 3431e91..8c5a93e 100644 --- a/plugins/ms-ai-architect/playground/ms-ai-architect-playground.html +++ b/plugins/ms-ai-architect/playground/ms-ai-architect-playground.html @@ -2579,7 +2579,35 @@ score: intOrZero(row[scKey] || '0') }; }) : 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) { @@ -3238,7 +3266,58 @@ } 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 = '