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