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 00643d7..db16f58 100644
--- a/plugins/ms-ai-architect/playground/ms-ai-architect-playground.html
+++ b/plugins/ms-ai-architect/playground/ms-ai-architect-playground.html
@@ -2831,15 +2831,40 @@
const sevKey = tbl.headers.find(function (h) { return /severity|alvorlighet/i.test(h); });
const locKey = tbl.headers.find(function (h) { return /lokasjon|location/i.test(h); });
const recKey = tbl.headers.find(function (h) { return /anbefaling|recommendation/i.test(h); });
+ const stKey = tbl.headers.find(function (h) { return /^status$/i.test(h); });
const findings = tbl.rows.map(function (row) {
return {
id: row[idKey] || '',
severity: (row[sevKey] || '').toLowerCase().trim(),
location: row[locKey] || '',
- recommendation: row[recKey] || ''
+ recommendation: row[recKey] || '',
+ status: stKey ? String(row[stKey] || '').toLowerCase().trim() : ''
};
});
- return { ok: true, data: { findings: findings } };
+ // Bucket-mapping (E1 kanban + E6 suppressed-panel).
+ // Eksplisitt status-felt vinner. Fallback: severity-basert.
+ // suppressed/waived/ignored/akseptert → suppressed
+ // keep/behold/accepted → keep
+ // review/tilsyn/escalate/eskaler → review
+ // remove/fjern/reject/avvis/blokker → remove
+ // severity critical/kritisk/high/høy → review
+ // severity medium/moderat/low/lav → keep
+ const bucketOf = function (f) {
+ const s = f.status || '';
+ if (/suppress|waive|ignore|akseptert/.test(s)) return 'suppressed';
+ if (/^keep$|behold|accepted/.test(s)) return 'keep';
+ if (/^review$|tilsyn|escalat|eskaler/.test(s)) return 'review';
+ if (/^remove$|fjern|reject|avvis|blokk/.test(s)) return 'remove';
+ const sev = f.severity || '';
+ if (/crit|kritisk/.test(sev)) return 'review';
+ if (/høy|high/.test(sev)) return 'review';
+ if (/medium|moderat/.test(sev)) return 'keep';
+ if (/lav|low/.test(sev)) return 'keep';
+ return 'review';
+ };
+ const buckets = { keep: [], review: [], remove: [], suppressed: [] };
+ findings.forEach(function (f) { buckets[bucketOf(f)].push(f); });
+ return { ok: true, data: { findings: findings, buckets: buckets } };
}
function parseCostDistribution(md) {
@@ -3645,7 +3670,59 @@
}
function renderReview(data, slot) {
- slot.innerHTML = renderFindingsBlock(data.findings || [], 'Funn');
+ const buckets = data.buckets || { keep: [], review: [], remove: [], suppressed: [] };
+ const cardFor = function (bucket, label) {
+ const items = buckets[bucket] || [];
+ const cards = items.length ? items.map(function (it) {
+ const sev = (it.severity || '').toUpperCase();
+ const head = it.id ? (it.id + ' — ' + (it.location || '')) : (it.location || '');
+ const recommendation = it.recommendation ? '
' + escapeHtml(it.recommendation) + '
' : '';
+ const sevTag = sev ? 'Severity: ' + escapeHtml(sev) + '
' : '';
+ return '' +
+ '
' + escapeHtml(head) + '
' +
+ sevTag +
+ recommendation +
+ '
';
+ }).join('') : 'Ingen funn
';
+ return '' +
+ '
' +
+ '' + escapeHtml(label) + '' +
+ '' + items.length + '' +
+ '
' +
+ cards +
+ '
';
+ };
+ const kanbanHtml = '' +
+ cardFor('keep', 'Keep') +
+ cardFor('review', 'Review') +
+ cardFor('remove', 'Remove') +
+ '
';
+ // E6 suppressed-panel for waived/akseptert items (collapsed by default).
+ const suppressed = buckets.suppressed || [];
+ const suppressedHtml = suppressed.length ? '' +
+ 'Undertrykt (' + suppressed.length + ') — godtatt eller waiver registrert
' +
+ '' + suppressed.map(function (it) {
+ return '
' +
+ '' + escapeHtml(it.id || '—') + '' +
+ '' + escapeHtml(it.location || it.recommendation || '') + '' +
+ '
';
+ }).join('') + '
' +
+ ' ' : '';
+ const body = kanbanHtml + suppressedHtml;
+ // KeyStats: utvid 'findings'-archetype med BUCKET-stats (KEEP/REVIEW/REMOVE).
+ const baseStats = inferKeyStats(data, 'findings');
+ const stats = data.keyStats || baseStats.concat([
+ { label: 'KEEP', value: (buckets.keep || []).length, modifier: 'low' },
+ { label: 'REVIEW', value: (buckets.review || []).length, modifier: (buckets.review || []).length ? 'high' : 'low' },
+ { label: 'REMOVE', value: (buckets.remove || []).length, modifier: (buckets.remove || []).length ? 'critical' : 'low' }
+ ]);
+ slot.innerHTML = renderPageShell({
+ eyebrow: 'REVIEW',
+ title: data.title || 'Arkitekturgjennomgang',
+ lede: data.lede || 'Funn fordelt på Keep / Review / Remove med suppressed-panel for waived items.',
+ verdict: data.verdict || inferVerdict(data, 'findings'),
+ keyStats: stats
+ }, body);
}
// ---- Sub-batch C: Economy (2) ----
diff --git a/plugins/ms-ai-architect/playground/test-fixtures/review.md b/plugins/ms-ai-architect/playground/test-fixtures/review.md
index fbe96f4..5d6e487 100644
--- a/plugins/ms-ai-architect/playground/test-fixtures/review.md
+++ b/plugins/ms-ai-architect/playground/test-fixtures/review.md
@@ -6,17 +6,17 @@ Reviewers: AI-arkitekt, sikkerhetsarkitekt, Datatilsynet
## Funn
-| ID | Severity | Lokasjon | Anbefaling |
-|----|----------|----------|------------|
-| F-01 | critical | Authentication layer | Tilgang til AI-forklaringer mangler attribute-based access control — alle saksbehandler ser alle saker. Implementer ABAC basert på sak-tildeling. |
-| F-02 | high | Data pipeline | Treningsdata oppdateres månedlig, men ingen formell drift-deteksjon. Etabler statistisk drift-monitoring i Azure Monitor. |
-| F-03 | high | Model serving | Modellen serves fra en enkelt regional endpoint uten failover. Replikér til en sekundær region for RTO < 1t. |
-| F-04 | high | Logging | Audit-logg lagres 30 dager — under arkivlovens krav for sak-relevant info. Endre retensjon til 7 år for sak-knyttede oppslag. |
-| F-05 | medium | Cost management | Ingen budsjettalarmer på Azure AI Services — prediction-kostnaden kan øke med 4× ved belastnings-topper uten varsel. |
-| F-06 | medium | Compliance | FRIA-rapport ikke vedlikeholdt etter modell-endring 2026-03-12. Re-evaluering trengs. |
-| F-07 | medium | UX | saksbehandler-grensesnitt viser ikke konfidensgrad tydelig nok — risiko for over-trust på AI-output. |
-| F-08 | low | Documentation | README mangler oppdatert arkitekturdiagram (siste fra 2025-11). |
-| F-09 | low | Testing | Manglende E2E-test for utenlandske objekt-ID. |
+| ID | Severity | Status | Lokasjon | Anbefaling |
+|----|----------|--------|----------|------------|
+| F-01 | critical | remove | Authentication layer | Tilgang til AI-forklaringer mangler attribute-based access control — alle saksbehandler ser alle saker. Implementer ABAC basert på sak-tildeling. |
+| F-02 | high | review | Data pipeline | Treningsdata oppdateres månedlig, men ingen formell drift-deteksjon. Etabler statistisk drift-monitoring i Azure Monitor. |
+| F-03 | high | review | Model serving | Modellen serves fra en enkelt regional endpoint uten failover. Replikér til en sekundær region for RTO < 1t. |
+| F-04 | high | review | Logging | Audit-logg lagres 30 dager — under arkivlovens krav for sak-relevant info. Endre retensjon til 7 år for sak-knyttede oppslag. |
+| F-05 | medium | keep | Cost management | Ingen budsjettalarmer på Azure AI Services — prediction-kostnaden kan øke med 4× ved belastnings-topper uten varsel. |
+| F-06 | medium | review | Compliance | FRIA-rapport ikke vedlikeholdt etter modell-endring 2026-03-12. Re-evaluering trengs. |
+| F-07 | medium | keep | UX | saksbehandler-grensesnitt viser ikke konfidensgrad tydelig nok — risiko for over-trust på AI-output. |
+| F-08 | low | suppressed | Documentation | README mangler oppdatert arkitekturdiagram (siste fra 2025-11). |
+| F-09 | low | suppressed | Testing | Manglende E2E-test for utenlandske objekt-ID. |
## Sammendrag
diff --git a/plugins/ms-ai-architect/tests/test-playground-v3.sh b/plugins/ms-ai-architect/tests/test-playground-v3.sh
index 6602659..ed61953 100755
--- a/plugins/ms-ai-architect/tests/test-playground-v3.sh
+++ b/plugins/ms-ai-architect/tests/test-playground-v3.sh
@@ -423,6 +423,39 @@ else
fail "residual-pair markup mangler (Step 10 must_contain krever >=1)"
fi
+# -------------------------------------------------------
+# 25c. SC8 — per-renderer verdict-pill emission for Sub-batch B (R7)
+# Hver av de 3 Sub-batch B-rendererene må enten emitte data-verdict direkte
+# i sin body, eller invokere renderPageShell (som emitter via helper).
+# -------------------------------------------------------
+SC8_RENDERERS_B="renderSecurity renderRos renderReview"
+for fn in $SC8_RENDERERS_B; do
+ body=$(awk "/function $fn\(/,/^ \}$/" "$HTML_FILE")
+ if echo "$body" | grep -qE "verdict[^A-Za-z]*data-verdict\s*=\s*[\"'](go|go-with-conditions|block|approved|failed|allow|warning|n-a)[\"']" \
+ || echo "$body" | grep -q "renderPageShell"; then
+ pass "SC8 verdict-pill: $fn (direkte eller via renderPageShell)"
+ else
+ fail "SC8 verdict-pill: $fn mangler både data-verdict og renderPageShell"
+ fi
+done
+
+# -------------------------------------------------------
+# 25d. Step 11 must_contain — top-risks + suppressed
+# -------------------------------------------------------
+toprisks_count=$( { grep -cE "top-risks" "$HTML_FILE" || true; } | tr -d ' ')
+if [ "${toprisks_count:-0}" -ge 1 ]; then
+ pass "top-risks markup til stede ($toprisks_count treff, Step 11 must_contain)"
+else
+ fail "top-risks markup mangler (Step 11 must_contain krever >=1)"
+fi
+
+suppressed_count=$( { grep -cE "suppressed" "$HTML_FILE" || true; } | tr -d ' ')
+if [ "${suppressed_count:-0}" -ge 1 ]; then
+ pass "suppressed markup til stede ($suppressed_count treff, Step 11 must_contain)"
+else
+ fail "suppressed markup mangler (Step 11 must_contain krever >=1)"
+fi
+
# -------------------------------------------------------
# 25. Inline-script eneste JS — ingen