feat(ms-ai-architect): playground v3 markdown parsers (14 archetypes) [skip-docs]
14 tolerant parsers per kanonisk archetype-routing-tabell (Step 11) + 3 helpers (parseTable, parseSections, extractField). Each parser returns {ok:true, data} or {ok:false, errors:[{section, reason}]} — never throws on bad input. PARSERS routing-objekt eksponert via window.__PARSERS.
Verified against all 17 fixtures: every parser produces expected shape. Empty input returns structured error per Verify-asserts.
This commit is contained in:
parent
b4a5ff0c75
commit
1034777d6b
1 changed files with 531 additions and 0 deletions
|
|
@ -2080,6 +2080,537 @@
|
|||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MARKDOWN PARSERS (Step 11)
|
||||
// ============================================================
|
||||
//
|
||||
// 14 parser-arketyper per kanonisk routing-tabell. Hver parser tar
|
||||
// markdown-streng og returnerer { ok: true, data: {...} } eller
|
||||
// { ok: false, errors: [{section, reason}] }. Parsers er tolerante
|
||||
// (kaster aldri unntak) — tom/uventet input gir strukturert feil.
|
||||
//
|
||||
// Routing: PARSERS[archetype] for oppslag i handlePasteImport.
|
||||
|
||||
// ---- Felles helpers ----
|
||||
|
||||
function parseTableRow(line) {
|
||||
const inner = line.replace(/^\|/, '').replace(/\|$/, '');
|
||||
return inner.split('|').map(function (c) { return c.trim(); });
|
||||
}
|
||||
|
||||
function parseTable(md, anchorRegex) {
|
||||
if (typeof md !== 'string') return null;
|
||||
let body = md;
|
||||
if (anchorRegex) {
|
||||
const m = anchorRegex.exec(md);
|
||||
if (!m) return null;
|
||||
body = md.slice(m.index + m[0].length);
|
||||
}
|
||||
const lines = body.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
const line = lines[i].trim();
|
||||
const next = (lines[i + 1] || '').trim();
|
||||
if (line.indexOf('|') === 0 && /^\|[\s\-:|]+\|$/.test(next)) {
|
||||
const headers = parseTableRow(line);
|
||||
const rows = [];
|
||||
for (let j = i + 2; j < lines.length; j++) {
|
||||
const rowLine = lines[j].trim();
|
||||
if (rowLine.indexOf('|') !== 0) break;
|
||||
const cells = parseTableRow(rowLine);
|
||||
if (cells.length === 0) break;
|
||||
const row = {};
|
||||
for (let k = 0; k < headers.length; k++) {
|
||||
row[headers[k]] = (cells[k] || '').trim();
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return { headers: headers, rows: rows };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseSections(md) {
|
||||
if (typeof md !== 'string') return [];
|
||||
const sections = [];
|
||||
const lines = md.split(/\r?\n/);
|
||||
let current = null;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const m = /^##\s+(.+)$/.exec(line);
|
||||
if (m && line.charAt(2) === ' ') { // exactly two #
|
||||
if (current) sections.push(current);
|
||||
current = { heading: m[1].trim(), body: '' };
|
||||
} else if (current) {
|
||||
current.body += (current.body ? '\n' : '') + line;
|
||||
}
|
||||
}
|
||||
if (current) sections.push(current);
|
||||
return sections.map(function (s) {
|
||||
return { heading: s.heading, body: s.body.trim() };
|
||||
});
|
||||
}
|
||||
|
||||
function extractField(md, label) {
|
||||
if (typeof md !== 'string') return null;
|
||||
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const re = new RegExp('^\\s*' + escaped + '\\s*:\\s*(.+)$', 'mi');
|
||||
const m = re.exec(md);
|
||||
return m ? m[1].trim() : null;
|
||||
}
|
||||
|
||||
function intOrZero(s) {
|
||||
if (typeof s !== 'string') return 0;
|
||||
const v = parseInt(s.replace(/[^\d-]/g, ''), 10);
|
||||
return isNaN(v) ? 0 : v;
|
||||
}
|
||||
|
||||
function emptyInput(md) {
|
||||
return !md || typeof md !== 'string' || !md.trim();
|
||||
}
|
||||
|
||||
// ---- 14 archetype parsers ----
|
||||
|
||||
function parseAiAct(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const errors = [];
|
||||
const sections = parseSections(md);
|
||||
|
||||
let risk_level = extractField(md, 'Risk-level') || extractField(md, 'Risikonivå');
|
||||
if (!risk_level) {
|
||||
const sec = sections.find(function (s) { return /risikoniv|risk.level/i.test(s.heading); });
|
||||
if (sec) {
|
||||
const firstLine = sec.body.split(/\r?\n/)[0] || '';
|
||||
risk_level = firstLine.replace(/^Risk-level:\s*/i, '').replace(/^Risikonivå:\s*/i, '').trim();
|
||||
}
|
||||
}
|
||||
if (!risk_level) errors.push({ section: 'risk_level', reason: 'Fant ikke risikonivå' });
|
||||
|
||||
const role = extractField(md, 'Rolle') || extractField(md, 'Role') || '';
|
||||
if (!role) errors.push({ section: 'role', reason: 'Fant ikke rolle' });
|
||||
|
||||
let reasoning = extractField(md, 'Reasoning') || extractField(md, 'Begrunnelse') || '';
|
||||
if (!reasoning) {
|
||||
const sec = sections.find(function (s) { return /begrunnelse|reasoning/i.test(s.heading); });
|
||||
if (sec) reasoning = sec.body;
|
||||
}
|
||||
|
||||
const obligations = [];
|
||||
const oblSec = sections.find(function (s) { return /forpliktelser|obligations/i.test(s.heading); });
|
||||
if (oblSec) {
|
||||
oblSec.body.split(/\r?\n/).forEach(function (line) {
|
||||
const m = /^[-*]\s+(.+)$/.exec(line.trim());
|
||||
if (m) obligations.push(m[1].trim());
|
||||
});
|
||||
}
|
||||
|
||||
if (errors.length > 0) return { ok: false, errors: errors };
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
risk_level: (risk_level || '').toLowerCase(),
|
||||
role: role,
|
||||
reasoning: reasoning,
|
||||
obligations: obligations
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function parseRequirements(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const tbl = parseTable(md);
|
||||
if (!tbl) return { ok: false, errors: [{ section: 'table', reason: 'Ingen krav-tabell funnet' }] };
|
||||
const reqKey = tbl.headers.find(function (h) { return /krav|requirement/i.test(h); }) || tbl.headers[0];
|
||||
const statusKey = tbl.headers.find(function (h) { return /status/i.test(h); }) || tbl.headers[1];
|
||||
const sourceKey = tbl.headers.find(function (h) { return /kilde|source|art/i.test(h); }) || tbl.headers[2];
|
||||
const items = tbl.rows.map(function (row) {
|
||||
return {
|
||||
requirement: row[reqKey] || '',
|
||||
status: (row[statusKey] || '').toLowerCase().trim(),
|
||||
source_article: row[sourceKey] || ''
|
||||
};
|
||||
});
|
||||
return { ok: true, data: { items: items } };
|
||||
}
|
||||
|
||||
function parseTextDocument(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const sections = parseSections(md);
|
||||
if (!sections.length) {
|
||||
return { ok: true, data: { sections: [{ heading: 'Innhold', body: md.trim() }] } };
|
||||
}
|
||||
return { ok: true, data: { sections: sections } };
|
||||
}
|
||||
|
||||
function parseFria(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const tbl = parseTable(md);
|
||||
if (!tbl) return { ok: false, errors: [{ section: 'table', reason: 'Ingen rettighet-tabell funnet' }] };
|
||||
const nameKey = tbl.headers.find(function (h) { return /rettighet|right/i.test(h); }) || tbl.headers[0];
|
||||
const impactKey = tbl.headers.find(function (h) { return /impact|påvirkning/i.test(h); }) || tbl.headers[1];
|
||||
const mitigKey = tbl.headers.find(function (h) { return /tiltak|mitigation/i.test(h); }) || tbl.headers[2];
|
||||
const rights = tbl.rows.map(function (row) {
|
||||
return {
|
||||
name: row[nameKey] || '',
|
||||
impact: intOrZero(row[impactKey] || '0'),
|
||||
mitigation: row[mitigKey] || ''
|
||||
};
|
||||
});
|
||||
return { ok: true, data: { rights: rights } };
|
||||
}
|
||||
|
||||
function parseConformityChecklist(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const checklistTbl = parseTable(md, /##\s*Sjekkliste/i) || parseTable(md);
|
||||
if (!checklistTbl) return { ok: false, errors: [{ section: 'checklist', reason: 'Ingen sjekkliste-tabell funnet' }] };
|
||||
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];
|
||||
const checklist = checklistTbl.rows.map(function (row) {
|
||||
return {
|
||||
requirement: row[reqKey] || '',
|
||||
status: (row[statusKey] || '').toLowerCase().trim(),
|
||||
evidence: row[evidKey] || ''
|
||||
};
|
||||
});
|
||||
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];
|
||||
const mileKey = deadlinesTbl.headers.find(function (h) { return /milepæl|milestone/i.test(h); }) || deadlinesTbl.headers[1];
|
||||
const stKey = deadlinesTbl.headers.find(function (h) { return /status/i.test(h); }) || deadlinesTbl.headers[2];
|
||||
return {
|
||||
date: row[dateKey] || '',
|
||||
milestone: row[mileKey] || '',
|
||||
status: (row[stKey] || '').toLowerCase().trim()
|
||||
};
|
||||
}) : [];
|
||||
return { ok: true, data: { checklist: checklist, deadlines: deadlines } };
|
||||
}
|
||||
|
||||
function parseMatrixRisk(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const matrixTbl = parseTable(md, /Risikomatrise.*5/i) || parseTable(md);
|
||||
if (!matrixTbl) return { ok: false, errors: [{ section: 'matrix', reason: 'Ingen risikomatrise funnet' }] };
|
||||
const labelKey = matrixTbl.headers[0];
|
||||
const sannKey = matrixTbl.headers.find(function (h) { return /sannsynlig/i.test(h); });
|
||||
const konsKey = matrixTbl.headers.find(function (h) { return /konsekvens/i.test(h); });
|
||||
const scoreKey = matrixTbl.headers.find(function (h) { return /score/i.test(h); });
|
||||
const matrix_cells = matrixTbl.rows.map(function (row) {
|
||||
return {
|
||||
label: row[labelKey] || '',
|
||||
prob: intOrZero(row[sannKey] || '0'),
|
||||
cons: intOrZero(row[konsKey] || '0'),
|
||||
score: intOrZero(row[scoreKey] || '0')
|
||||
};
|
||||
});
|
||||
const threatsTbl = parseTable(md, /##\s*Trusler/i);
|
||||
const threats = threatsTbl ? threatsTbl.rows.map(function (row) {
|
||||
const idKey = threatsTbl.headers[0];
|
||||
const descKey = threatsTbl.headers.find(function (h) { return /beskrivelse|description/i.test(h); }) || threatsTbl.headers[1];
|
||||
const sevKey = threatsTbl.headers.find(function (h) { return /severity|alvorlighet/i.test(h); });
|
||||
const mitKey = threatsTbl.headers.find(function (h) { return /tiltak|mitigation/i.test(h); });
|
||||
return {
|
||||
id: row[idKey] || '',
|
||||
description: row[descKey] || '',
|
||||
severity: (row[sevKey] || '').toLowerCase().trim(),
|
||||
mitigation: row[mitKey] || ''
|
||||
};
|
||||
}) : [];
|
||||
const radarTbl = parseTable(md, /Radar.akser/i);
|
||||
const radar_axes = radarTbl ? radarTbl.rows.map(function (row) {
|
||||
const akseKey = radarTbl.headers.find(function (h) { return /akse|axis/i.test(h); }) || radarTbl.headers[0];
|
||||
const scKey = radarTbl.headers.find(function (h) { return /score/i.test(h); }) || radarTbl.headers[1];
|
||||
return {
|
||||
name: row[akseKey] || '',
|
||||
score: intOrZero(row[scKey] || '0')
|
||||
};
|
||||
}) : null;
|
||||
return { ok: true, data: { matrix_cells: matrix_cells, threats: threats, radar_axes: radar_axes } };
|
||||
}
|
||||
|
||||
function parseMatrixRisk6x5(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const dimsTbl = parseTable(md, /Score per dimensjon/i);
|
||||
if (!dimsTbl) return { ok: false, errors: [{ section: 'dimensions', reason: 'Ingen dimensjon-tabell funnet' }] };
|
||||
const dimNameKey = dimsTbl.headers.find(function (h) { return /dimensjon/i.test(h); }) || dimsTbl.headers[0];
|
||||
const dimScoreKey = dimsTbl.headers.find(function (h) { return /score/i.test(h); }) || dimsTbl.headers[1];
|
||||
const dimVurdKey = dimsTbl.headers.find(function (h) { return /vurdering/i.test(h); });
|
||||
const dimensions = dimsTbl.rows.map(function (row) {
|
||||
return {
|
||||
name: row[dimNameKey] || '',
|
||||
score: intOrZero(row[dimScoreKey] || '0'),
|
||||
assessment: row[dimVurdKey] || ''
|
||||
};
|
||||
});
|
||||
const matrixTbl = parseTable(md, /Risikomatrise.*6/i);
|
||||
const matrix_cells = matrixTbl ? matrixTbl.rows.map(function (row) {
|
||||
const labelKey = matrixTbl.headers[0];
|
||||
const sannKey = matrixTbl.headers.find(function (h) { return /sannsynlig/i.test(h); });
|
||||
const konsKey = matrixTbl.headers.find(function (h) { return /konsekvens/i.test(h); });
|
||||
const scoreKey = matrixTbl.headers.find(function (h) { return /score/i.test(h); });
|
||||
return {
|
||||
label: row[labelKey] || '',
|
||||
prob: intOrZero(row[sannKey] || '0'),
|
||||
cons: intOrZero(row[konsKey] || '0'),
|
||||
score: intOrZero(row[scoreKey] || '0')
|
||||
};
|
||||
}) : [];
|
||||
const findingsTbl = parseTable(md, /##\s*Funn/i);
|
||||
const findings = findingsTbl ? findingsTbl.rows.map(function (row) {
|
||||
const idKey = findingsTbl.headers[0];
|
||||
const sevKey = findingsTbl.headers.find(function (h) { return /severity|alvorlighet/i.test(h); });
|
||||
const locKey = findingsTbl.headers.find(function (h) { return /lokasjon|location/i.test(h); });
|
||||
const recKey = findingsTbl.headers.find(function (h) { return /anbefaling|recommendation/i.test(h); });
|
||||
return {
|
||||
id: row[idKey] || '',
|
||||
severity: (row[sevKey] || '').toLowerCase().trim(),
|
||||
location: row[locKey] || '',
|
||||
recommendation: row[recKey] || ''
|
||||
};
|
||||
}) : [];
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
dimensions: dimensions,
|
||||
matrix_cells: matrix_cells,
|
||||
findings: findings,
|
||||
scores: dimensions.map(function (d) { return d.score; })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function parseFindings(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const tbl = parseTable(md, /##\s*Funn/i) || parseTable(md);
|
||||
if (!tbl) return { ok: false, errors: [{ section: 'table', reason: 'Ingen funn-tabell funnet' }] };
|
||||
const idKey = tbl.headers[0];
|
||||
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 findings = tbl.rows.map(function (row) {
|
||||
return {
|
||||
id: row[idKey] || '',
|
||||
severity: (row[sevKey] || '').toLowerCase().trim(),
|
||||
location: row[locKey] || '',
|
||||
recommendation: row[recKey] || ''
|
||||
};
|
||||
});
|
||||
return { ok: true, data: { findings: findings } };
|
||||
}
|
||||
|
||||
function parseCostDistribution(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const distTbl = parseTable(md, /Distribusjon/i);
|
||||
if (!distTbl) return { ok: false, errors: [{ section: 'distribution', reason: 'Ingen distribusjons-tabell funnet' }] };
|
||||
const persKey = distTbl.headers.find(function (h) { return /persentil|percentile/i.test(h); }) || distTbl.headers[0];
|
||||
const monthlyKey = distTbl.headers.find(function (h) { return /månedlig|monthly/i.test(h); }) || distTbl.headers[1];
|
||||
const yearlyKey = distTbl.headers.find(function (h) { return /årlig|yearly/i.test(h); });
|
||||
let p10 = null, p50 = null, p90 = null;
|
||||
distTbl.rows.forEach(function (row) {
|
||||
const monthly = intOrZero(row[monthlyKey] || '0');
|
||||
const yearly = yearlyKey ? intOrZero(row[yearlyKey] || '0') : null;
|
||||
const entry = { monthly: monthly, yearly: yearly };
|
||||
const tag = (row[persKey] || '').toUpperCase();
|
||||
if (/P10|P\.10|P 10/.test(tag)) p10 = entry;
|
||||
else if (/P50|P\.50|P 50/.test(tag)) p50 = entry;
|
||||
else if (/P90|P\.90|P 90/.test(tag)) p90 = entry;
|
||||
});
|
||||
const monthlyTbl = parseTable(md, /Månedlig fordeling/i);
|
||||
const monthly_breakdown = monthlyTbl ? monthlyTbl.rows.map(function (row) {
|
||||
const compKey = monthlyTbl.headers[0];
|
||||
const costKey = monthlyTbl.headers[1];
|
||||
return {
|
||||
component: row[compKey] || '',
|
||||
cost: intOrZero(row[costKey] || '0')
|
||||
};
|
||||
}) : [];
|
||||
const tcoTbl = parseTable(md, /TCO/i);
|
||||
const tco_table = tcoTbl ? tcoTbl.rows : [];
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
p10: p10, p50: p50, p90: p90,
|
||||
monthly_breakdown: monthly_breakdown,
|
||||
tco_table: tco_table,
|
||||
tco_headers: tcoTbl ? tcoTbl.headers : []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function parseCapabilityMatrix(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const tbl = parseTable(md, /##\s*Matrise/i) || parseTable(md);
|
||||
if (!tbl) return { ok: false, errors: [{ section: 'matrix', reason: 'Ingen matrise funnet' }] };
|
||||
const capKey = tbl.headers[0];
|
||||
const licenseNames = tbl.headers.slice(1);
|
||||
const licenses = licenseNames.map(function (name) {
|
||||
return { name: name, capabilities: [] };
|
||||
});
|
||||
tbl.rows.forEach(function (row) {
|
||||
const capName = row[capKey];
|
||||
licenseNames.forEach(function (licName, i) {
|
||||
licenses[i].capabilities.push({
|
||||
name: capName,
|
||||
status: (row[licName] || '').toLowerCase().trim()
|
||||
});
|
||||
});
|
||||
});
|
||||
return { ok: true, data: { licenses: licenses } };
|
||||
}
|
||||
|
||||
function parsePhasedPlan(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const phases = [];
|
||||
const lines = md.split(/\r?\n/);
|
||||
let current = null;
|
||||
let bucket = null;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const phaseMatch = /^###\s+(?:Fase\s+\d+\s*[—-]\s*)?(.+?)\s*(?:\(.*\))?\s*$/i.exec(line.trim());
|
||||
const isH3 = /^###\s+/.test(line);
|
||||
const isH2 = /^##\s+/.test(line) && !isH3;
|
||||
if (isH3 && phaseMatch) {
|
||||
if (current) phases.push(current);
|
||||
current = {
|
||||
name: phaseMatch[1].trim(),
|
||||
milestones: [],
|
||||
success_criteria: [],
|
||||
duration_weeks: null
|
||||
};
|
||||
bucket = null;
|
||||
continue;
|
||||
}
|
||||
if (isH2) {
|
||||
if (current) { phases.push(current); current = null; }
|
||||
bucket = null;
|
||||
continue;
|
||||
}
|
||||
if (!current) continue;
|
||||
const trimmed = line.trim();
|
||||
const durMatch = /^Varighet:\s*(\d+)\s*uke/i.exec(trimmed);
|
||||
if (durMatch) {
|
||||
current.duration_weeks = parseInt(durMatch[1], 10);
|
||||
continue;
|
||||
}
|
||||
if (/^Milep[æa]ler\s*:?\s*$/i.test(trimmed)) { bucket = 'milestones'; continue; }
|
||||
if (/^Suksesskriterier\s*:?\s*$/i.test(trimmed)) { bucket = 'success_criteria'; continue; }
|
||||
const bulletMatch = /^[-*]\s+(.+)$/.exec(trimmed);
|
||||
if (bulletMatch && bucket && current[bucket]) {
|
||||
current[bucket].push(bulletMatch[1].trim());
|
||||
}
|
||||
}
|
||||
if (current) phases.push(current);
|
||||
|
||||
const risksTbl = parseTable(md, /##\s*Risiko/i);
|
||||
const risks = risksTbl ? risksTbl.rows.map(function (row) {
|
||||
const risikoKey = risksTbl.headers[0];
|
||||
const sannKey = risksTbl.headers.find(function (h) { return /sannsynlig/i.test(h); });
|
||||
const konsKey = risksTbl.headers.find(function (h) { return /konsekvens/i.test(h); });
|
||||
const tiltakKey = risksTbl.headers.find(function (h) { return /tiltak|mitigation/i.test(h); });
|
||||
return {
|
||||
risk: row[risikoKey] || '',
|
||||
probability: row[sannKey] || '',
|
||||
consequence: row[konsKey] || '',
|
||||
mitigation: row[tiltakKey] || ''
|
||||
};
|
||||
}) : [];
|
||||
|
||||
if (!phases.length) return { ok: false, errors: [{ section: 'phases', reason: 'Ingen faser funnet (### Fase N)' }] };
|
||||
return { ok: true, data: { phases: phases, risks: risks } };
|
||||
}
|
||||
|
||||
function parseMarkdown(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const titleMatch = /^#\s+(.+)$/m.exec(md);
|
||||
const title = titleMatch ? titleMatch[1].trim() : '';
|
||||
const sections = parseSections(md);
|
||||
// Frontmatter-style fields (Status, Date, Deciders) — typisk i ADR
|
||||
const status = extractField(md, 'Status') || '';
|
||||
const date = extractField(md, 'Date') || extractField(md, 'Dato') || '';
|
||||
const deciders = extractField(md, 'Deciders') || extractField(md, 'Beslutningstakere') || '';
|
||||
return { ok: true, data: { title: title, sections: sections, raw: md, status: status, date: date, deciders: deciders } };
|
||||
}
|
||||
|
||||
function parseVerdict(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const verdictRaw = extractField(md, 'Verdict') || '';
|
||||
const verdict = verdictRaw.toLowerCase().trim();
|
||||
const sub = extractField(md, 'Sub') || '';
|
||||
const sections = parseSections(md);
|
||||
const ratSec = sections.find(function (s) { return /rationale|begrunnelse/i.test(s.heading); });
|
||||
const rationale = ratSec ? ratSec.body : '';
|
||||
const metricsTbl = parseTable(md, /Key Metrics|Nøkkelmetrikker/i);
|
||||
const key_metrics = metricsTbl ? metricsTbl.rows : [];
|
||||
const metrics_headers = metricsTbl ? metricsTbl.headers : [];
|
||||
const nextSec = sections.find(function (s) { return /next steps|neste steg/i.test(s.heading); });
|
||||
const next_steps = [];
|
||||
if (nextSec) {
|
||||
nextSec.body.split(/\r?\n/).forEach(function (line) {
|
||||
const m = /^[-*]\s+(.+)$/.exec(line.trim());
|
||||
if (m) next_steps.push(m[1].trim());
|
||||
});
|
||||
}
|
||||
if (!verdict) return { ok: false, errors: [{ section: 'verdict', reason: 'Fant ikke "Verdict:"-linje' }] };
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
verdict: verdict,
|
||||
sub: sub,
|
||||
rationale: rationale,
|
||||
key_metrics: key_metrics,
|
||||
metrics_headers: metrics_headers,
|
||||
next_steps: next_steps
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function parseComparison(md) {
|
||||
if (emptyInput(md)) return { ok: false, errors: [{ section: 'input', reason: 'Tom input' }] };
|
||||
const subject1 = extractField(md, 'Subject 1') || '';
|
||||
const subject2 = extractField(md, 'Subject 2') || '';
|
||||
const tbl = parseTable(md, /##\s*Sammenligning|##\s*Comparison/i) || parseTable(md);
|
||||
if (!tbl) return { ok: false, errors: [{ section: 'table', reason: 'Ingen sammenligningstabell funnet' }] };
|
||||
const aspectKey = tbl.headers[0];
|
||||
const v1Key = tbl.headers[1];
|
||||
const v2Key = tbl.headers[2];
|
||||
const winnerKey = tbl.headers[3];
|
||||
const subjects = [subject1 || v1Key || '', subject2 || v2Key || ''];
|
||||
const rows = tbl.rows.map(function (row) {
|
||||
return {
|
||||
aspect: row[aspectKey] || '',
|
||||
value1: row[v1Key] || '',
|
||||
value2: row[v2Key] || '',
|
||||
winner: winnerKey ? (row[winnerKey] || '') : ''
|
||||
};
|
||||
});
|
||||
return { ok: true, data: { subjects: subjects, rows: rows } };
|
||||
}
|
||||
|
||||
// ---- PARSERS routing-objekt ----
|
||||
|
||||
const PARSERS = {
|
||||
'aiact': parseAiAct,
|
||||
'requirements-list': parseRequirements,
|
||||
'text-document': parseTextDocument,
|
||||
'fria': parseFria,
|
||||
'conformity-checklist': parseConformityChecklist,
|
||||
'matrix-risk': parseMatrixRisk,
|
||||
'matrix-risk-6x5': parseMatrixRisk6x5,
|
||||
'findings': parseFindings,
|
||||
'cost-distribution': parseCostDistribution,
|
||||
'capability': parseCapabilityMatrix,
|
||||
'phased-plan': parsePhasedPlan,
|
||||
'markdown': parseMarkdown,
|
||||
'verdict': parseVerdict,
|
||||
'comparison': parseComparison
|
||||
};
|
||||
|
||||
// Eksponer for Verify-asserts og Step 12.
|
||||
window.__PARSERS = PARSERS;
|
||||
window.__parseTable = parseTable;
|
||||
window.__parseSections = parseSections;
|
||||
window.__extractField = extractField;
|
||||
|
||||
// ---- Paste-import stub (Step 12 erstatter med faktisk routing) ----
|
||||
|
||||
function handlePasteImport(commandId, markdown) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue