feat(ms-ai-architect): renderer A.5 conformity adopt page-header + kanban-board

- parseConformityChecklist utvidet med buckets {passed, conditional, failed} via bucketOf-mapping
- Status-mapping stotter bade engelsk (met/partial/missing) og norsk (bestatt/betinget/avvist) for backward-compat
- renderConformity: erstatter findings-listen med E1 kanban-board (3 kolonner: Bestatt, Med betingelser, Ikke bestatt)
- aiact-timeline beholdt for deadlines (under kanban som sekundaer report-meta-blokk)
- Wrapped med renderPageShell (eyebrow SAMSVAR)
- Fixture conformity.md oppdatert til norske status-markorer for tydeligere bucket-mapping (5 bestatt, 3 betinget, 4 avvist)
This commit is contained in:
Kjell Tore Guttormsen 2026-05-04 06:08:22 +02:00
commit 3a1dd8a70f
2 changed files with 65 additions and 44 deletions

View file

@ -2508,13 +2508,25 @@
const reqKey = checklistTbl.headers.find(function (h) { return /krav|requirement/i.test(h); }) || checklistTbl.headers[0];
const statusKey = checklistTbl.headers.find(function (h) { return /status/i.test(h); }) || checklistTbl.headers[1];
const evidKey = checklistTbl.headers.find(function (h) { return /bevis|evidence/i.test(h); }) || checklistTbl.headers[2];
// Bucket-klassifisering — støtter bade engelske og norske status-markører.
const bucketOf = function (status) {
const s = (status || '').toLowerCase().trim();
if (/^(pass|met|ok|bestått|bestatt|godkjent|approved|done)$/.test(s)) return 'passed';
if (/^(partial|conditional|betinget|delvis|in-progress|active)$/.test(s)) return 'conditional';
if (/^(missing|failed|avvist|underkjent|fail|rejected|blocked)$/.test(s)) return 'failed';
return 'conditional';
};
const checklist = checklistTbl.rows.map(function (row) {
const status = (row[statusKey] || '').toLowerCase().trim();
return {
requirement: row[reqKey] || '',
status: (row[statusKey] || '').toLowerCase().trim(),
status: status,
bucket: bucketOf(status),
evidence: row[evidKey] || ''
};
});
const buckets = { passed: [], conditional: [], failed: [] };
checklist.forEach(function (it) { buckets[it.bucket].push(it); });
const deadlinesTbl = parseTable(md, /##\s*Frister/i);
const deadlines = deadlinesTbl ? deadlinesTbl.rows.map(function (row) {
const dateKey = deadlinesTbl.headers.find(function (h) { return /dato|date/i.test(h); }) || deadlinesTbl.headers[0];
@ -2526,7 +2538,7 @@
status: (row[stKey] || '').toLowerCase().trim()
};
}) : [];
return { ok: true, data: { checklist: checklist, deadlines: deadlines } };
return { ok: true, data: { checklist: checklist, buckets: buckets, deadlines: deadlines } };
}
function parseMatrixRisk(md) {
@ -3162,6 +3174,29 @@
}
function renderConformity(data, slot) {
const buckets = data.buckets || { passed: [], conditional: [], failed: [] };
const cardFor = function (bucket, label) {
const items = buckets[bucket] || [];
const cards = items.length ? items.map(function (it, idx) {
return '<div class="kanban-card">' +
'<div class="kanban-card__name">C-' + String(idx + 1).padStart(2, '0') + ' — ' + escapeHtml(it.requirement) + '</div>' +
'<div class="kanban-card__meta">Bevis: ' + escapeHtml(it.evidence || '—') + '</div>' +
'</div>';
}).join('') : '<div class="kanban-col__empty">Ingen krav</div>';
return '<div class="kanban-col" data-bucket="' + escapeAttr(bucket) + '">' +
'<div class="kanban-col__head">' +
'<span class="kanban-col__title">' + escapeHtml(label) + '</span>' +
'<span class="kanban-col__count">' + items.length + '</span>' +
'</div>' +
cards +
'</div>';
};
const kanbanHtml = '<div class="kanban-board">' +
cardFor('passed', 'Bestått') +
cardFor('conditional', 'Med betingelser') +
cardFor('failed', 'Ikke bestått') +
'</div>';
const stateOf = function (status) {
const s = (status || '').toLowerCase();
if (s === 'passed' || s === 'met' || s === 'done') return 'passed';
@ -3182,38 +3217,24 @@
'</div>';
}).join('');
timelineHtml =
'<div class="aiact-timeline">' +
'<div class="aiact-timeline__track">' +
'<div class="aiact-timeline__progress" style="width: 0%"></div>' +
milestones +
'<section class="report-meta"><h4>Frister</h4>' +
'<div class="aiact-timeline">' +
'<div class="aiact-timeline__track">' +
'<div class="aiact-timeline__progress" style="width: 0%"></div>' +
milestones +
'</div>' +
'</div>' +
'</div>';
'</section>';
}
const sevForStatus = function (status) {
const s = (status || '').toLowerCase();
if (s === 'met') return 'low';
if (s === 'partial') return 'medium';
if (s === 'missing') return 'critical';
return 'info';
};
const items = (data.checklist || []).map(function (it, idx) {
return '<li class="findings__item" data-status="' + escapeAttr(it.status) + '">' +
'<span class="findings__item-severity-dot" data-severity="' + escapeAttr(sevForStatus(it.status)) + '"></span>' +
'<span class="findings__item-id">C-' + String(idx + 1).padStart(2, '0') + '</span>' +
'<span class="findings__item-title">' + escapeHtml(it.requirement) + '</span>' +
'<span class="findings__item-meta">Bevis: ' + escapeHtml(it.evidence || '—') + ' · ' + escapeHtml(it.status || '—') + '</span>' +
'</li>';
}).join('');
const findingsHtml =
'<div class="findings">' +
'<div class="findings__list">' +
'<div class="findings__group">' +
'<div class="findings__group-header"><span>Sjekkliste</span><span>' + (data.checklist || []).length + '</span></div>' +
'<ul class="findings__items">' + items + '</ul>' +
'</div>' +
'</div>' +
'</div>';
slot.innerHTML = timelineHtml + findingsHtml;
const body = kanbanHtml + timelineHtml;
slot.innerHTML = renderPageShell({
eyebrow: 'SAMSVAR',
title: data.title || 'Samsvarsvurdering (Art. 43)',
lede: data.lede || 'Annex IV-sjekkliste fordelt på Bestått / Med betingelser / Ikke bestått.',
verdict: data.verdict || inferVerdict(data, 'conformity-checklist'),
keyStats: data.keyStats || inferKeyStats(data, 'conformity-checklist')
}, body);
}
function renderDpia(data, slot) {

View file

@ -7,18 +7,18 @@ Vurderingsprosedyre: Annex VI (intern kontroll)
| Krav | Status | Bevis |
|------|--------|-------|
| Risk Management System dokumentert | met | RMS-rapport v2.1 (2026-04-15) |
| Treningsdata-governance med kvalitetskriterier | met | Data-governance handbook §4.2 |
| Teknisk dokumentasjon Annex IV komplett | partial | Mangler ytelsesmål per stratum |
| Logging av hendelser implementert | met | OpenTelemetry-spans i Azure Monitor |
| Transparens-instruksjoner skrevet | missing | Skal leveres innen 2026-09-01 |
| Menneskelig oversikt på saksbehandler | met | Workflow-design godkjent av juridisk |
| Nøyaktighetsmål dokumentert | partial | 96.3% overall, men ikke per objekt-ID-region |
| Robusthet under adversarielle forhold | partial | Test-suite mangler skitne plater og natt-scenarier |
| Cybersikkerhetstiltak per Art. 15 | met | NSM Grunnprinsipper-vurdering bestått |
| Conformity assessment underskrevet | missing | Avhengig av FRIA-resultat |
| EU declaration of conformity utstedt | missing | Avhenger av Art. 47 |
| CE-merking påført | missing | Markedsplassering ikke aktuell (intern bruk) — vurder om Art. 48 gjelder |
| Risk Management System dokumentert | bestått | RMS-rapport v2.1 (2026-04-15) |
| Treningsdata-governance med kvalitetskriterier | bestått | Data-governance handbook §4.2 |
| Teknisk dokumentasjon Annex IV komplett | betinget | Mangler ytelsesmål per stratum |
| Logging av hendelser implementert | bestått | OpenTelemetry-spans i Azure Monitor |
| Transparens-instruksjoner skrevet | avvist | Skal leveres innen 2026-09-01 |
| Menneskelig oversikt på saksbehandler | bestått | Workflow-design godkjent av juridisk |
| Nøyaktighetsmål dokumentert | betinget | 96.3% overall, men ikke per objekt-ID-region |
| Robusthet under adversarielle forhold | betinget | Test-suite mangler skitne plater og natt-scenarier |
| Cybersikkerhetstiltak per Art. 15 | bestått | NSM Grunnprinsipper-vurdering bestått |
| Conformity assessment underskrevet | avvist | Avhengig av FRIA-resultat |
| EU declaration of conformity utstedt | avvist | Avhenger av Art. 47 |
| CE-merking påført | avvist | Markedsplassering ikke aktuell (intern bruk) — vurder om Art. 48 gjelder |
## Frister