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:
parent
ead1697ff0
commit
3a1dd8a70f
2 changed files with 65 additions and 44 deletions
|
|
@ -2508,13 +2508,25 @@
|
||||||
const reqKey = checklistTbl.headers.find(function (h) { return /krav|requirement/i.test(h); }) || checklistTbl.headers[0];
|
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 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];
|
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 checklist = checklistTbl.rows.map(function (row) {
|
||||||
|
const status = (row[statusKey] || '').toLowerCase().trim();
|
||||||
return {
|
return {
|
||||||
requirement: row[reqKey] || '',
|
requirement: row[reqKey] || '',
|
||||||
status: (row[statusKey] || '').toLowerCase().trim(),
|
status: status,
|
||||||
|
bucket: bucketOf(status),
|
||||||
evidence: row[evidKey] || ''
|
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 deadlinesTbl = parseTable(md, /##\s*Frister/i);
|
||||||
const deadlines = deadlinesTbl ? deadlinesTbl.rows.map(function (row) {
|
const deadlines = deadlinesTbl ? deadlinesTbl.rows.map(function (row) {
|
||||||
const dateKey = deadlinesTbl.headers.find(function (h) { return /dato|date/i.test(h); }) || deadlinesTbl.headers[0];
|
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()
|
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) {
|
function parseMatrixRisk(md) {
|
||||||
|
|
@ -3162,6 +3174,29 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderConformity(data, slot) {
|
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 stateOf = function (status) {
|
||||||
const s = (status || '').toLowerCase();
|
const s = (status || '').toLowerCase();
|
||||||
if (s === 'passed' || s === 'met' || s === 'done') return 'passed';
|
if (s === 'passed' || s === 'met' || s === 'done') return 'passed';
|
||||||
|
|
@ -3182,38 +3217,24 @@
|
||||||
'</div>';
|
'</div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
timelineHtml =
|
timelineHtml =
|
||||||
'<div class="aiact-timeline">' +
|
'<section class="report-meta"><h4>Frister</h4>' +
|
||||||
'<div class="aiact-timeline__track">' +
|
'<div class="aiact-timeline">' +
|
||||||
'<div class="aiact-timeline__progress" style="width: 0%"></div>' +
|
'<div class="aiact-timeline__track">' +
|
||||||
milestones +
|
'<div class="aiact-timeline__progress" style="width: 0%"></div>' +
|
||||||
|
milestones +
|
||||||
|
'</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>';
|
'</section>';
|
||||||
}
|
}
|
||||||
const sevForStatus = function (status) {
|
|
||||||
const s = (status || '').toLowerCase();
|
const body = kanbanHtml + timelineHtml;
|
||||||
if (s === 'met') return 'low';
|
slot.innerHTML = renderPageShell({
|
||||||
if (s === 'partial') return 'medium';
|
eyebrow: 'SAMSVAR',
|
||||||
if (s === 'missing') return 'critical';
|
title: data.title || 'Samsvarsvurdering (Art. 43)',
|
||||||
return 'info';
|
lede: data.lede || 'Annex IV-sjekkliste fordelt på Bestått / Med betingelser / Ikke bestått.',
|
||||||
};
|
verdict: data.verdict || inferVerdict(data, 'conformity-checklist'),
|
||||||
const items = (data.checklist || []).map(function (it, idx) {
|
keyStats: data.keyStats || inferKeyStats(data, 'conformity-checklist')
|
||||||
return '<li class="findings__item" data-status="' + escapeAttr(it.status) + '">' +
|
}, body);
|
||||||
'<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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDpia(data, slot) {
|
function renderDpia(data, slot) {
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,18 @@ Vurderingsprosedyre: Annex VI (intern kontroll)
|
||||||
|
|
||||||
| Krav | Status | Bevis |
|
| Krav | Status | Bevis |
|
||||||
|------|--------|-------|
|
|------|--------|-------|
|
||||||
| Risk Management System dokumentert | met | RMS-rapport v2.1 (2026-04-15) |
|
| Risk Management System dokumentert | bestått | RMS-rapport v2.1 (2026-04-15) |
|
||||||
| Treningsdata-governance med kvalitetskriterier | met | Data-governance handbook §4.2 |
|
| Treningsdata-governance med kvalitetskriterier | bestått | Data-governance handbook §4.2 |
|
||||||
| Teknisk dokumentasjon Annex IV komplett | partial | Mangler ytelsesmål per stratum |
|
| Teknisk dokumentasjon Annex IV komplett | betinget | Mangler ytelsesmål per stratum |
|
||||||
| Logging av hendelser implementert | met | OpenTelemetry-spans i Azure Monitor |
|
| Logging av hendelser implementert | bestått | OpenTelemetry-spans i Azure Monitor |
|
||||||
| Transparens-instruksjoner skrevet | missing | Skal leveres innen 2026-09-01 |
|
| Transparens-instruksjoner skrevet | avvist | Skal leveres innen 2026-09-01 |
|
||||||
| Menneskelig oversikt på saksbehandler | met | Workflow-design godkjent av juridisk |
|
| Menneskelig oversikt på saksbehandler | bestått | Workflow-design godkjent av juridisk |
|
||||||
| Nøyaktighetsmål dokumentert | partial | 96.3% overall, men ikke per objekt-ID-region |
|
| Nøyaktighetsmål dokumentert | betinget | 96.3% overall, men ikke per objekt-ID-region |
|
||||||
| Robusthet under adversarielle forhold | partial | Test-suite mangler skitne plater og natt-scenarier |
|
| Robusthet under adversarielle forhold | betinget | Test-suite mangler skitne plater og natt-scenarier |
|
||||||
| Cybersikkerhetstiltak per Art. 15 | met | NSM Grunnprinsipper-vurdering bestått |
|
| Cybersikkerhetstiltak per Art. 15 | bestått | NSM Grunnprinsipper-vurdering bestått |
|
||||||
| Conformity assessment underskrevet | missing | Avhengig av FRIA-resultat |
|
| Conformity assessment underskrevet | avvist | Avhengig av FRIA-resultat |
|
||||||
| EU declaration of conformity utstedt | missing | Avhenger av Art. 47 |
|
| EU declaration of conformity utstedt | avvist | Avhenger av Art. 47 |
|
||||||
| CE-merking påført | missing | Markedsplassering ikke aktuell (intern bruk) — vurder om Art. 48 gjelder |
|
| CE-merking påført | avvist | Markedsplassering ikke aktuell (intern bruk) — vurder om Art. 48 gjelder |
|
||||||
|
|
||||||
## Frister
|
## Frister
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue