okr/lib/innboks-frontmatter.mjs

75 lines
2.9 KiB
JavaScript

// innboks-frontmatter.mjs
// Step 5: deterministisk frontmatter-projeksjon for et konsept (fra splitConcepts).
// Regel-basert (INGEN LLM): type/tags utledes fra title+sti og snappes mot det
// lukkede vokabularet (okf-vocab); resource = original relativ sti (kanonisk navn);
// description = foerste ikke-tomme avsnitt (whitespace kollapset, trunkert);
// timestamp = ISO-8601 av original-fil-mtime (IKKE veggklokke -> idempotens by
// construction per maskin); kilde:innboks provenans-markoer (extension key for
// retrieval-rangering). Beregner ogsaa konseptets mal-sti
// (concept.destRel = routeLevel(type)/slug.md) FOER relasjons-steget (Step 6),
// jf. Pass-2 ordering-fiks (Revisions #21). Serialiserer via writeFrontmatter
// (additiv array-gren fra Step 3). Zero npm dependencies.
import { writeFrontmatter } from './frontmatter.mjs';
import { snapType, snapTags, routeLevel, TYPE_VOCAB, TAGS_VOCAB } from './okf-vocab.mjs';
const DESCRIPTION_MAX = 240;
// Vokab sortert lengst-foerst saa "Overordnede OKR" matcher foer "OKR".
const TYPE_BY_LENGTH = [...TYPE_VOCAB].sort((a, b) => b.length - a.length);
// Regel-utledning: foerste vokab-term som forekommer i title+sti -> kanonisk type.
function deriveType(title, sourcePath) {
const hay = `${title} ${sourcePath}`.toLowerCase();
for (const t of TYPE_BY_LENGTH) {
if (hay.includes(t.toLowerCase())) return snapType(t);
}
return snapType('');
}
// Tags: vokab-termer som forekommer i title (vokab-rekkefolge bevart), saa snappet.
function deriveTags(title) {
const hay = String(title).toLowerCase();
return snapTags(TAGS_VOCAB.filter((t) => hay.includes(t.toLowerCase())));
}
// Foerste ikke-tomme avsnitt, whitespace kollapset, trunkert deterministisk.
function deriveDescription(body) {
const para = String(body)
.split(/\n\s*\n/)
.map((p) => p.replace(/\s+/g, ' ').trim())
.find((p) => p !== '');
if (!para) return '';
return para.length > DESCRIPTION_MAX ? `${para.slice(0, DESCRIPTION_MAX).trimEnd()}...` : para;
}
// concept (fra splitConcepts) -> beriket konsept med OKF-frontmatter + destRel.
export function projectFrontmatter(concept, { sourcePath, sourceMtime } = {}) {
const resource = String(sourcePath ?? '');
const type = deriveType(concept.title, resource);
const description = deriveDescription(concept.body);
const tags = deriveTags(concept.title);
const timestamp = new Date(sourceMtime).toISOString();
const destRel = `${routeLevel(type)}/${concept.slug}.md`;
// Kanonisk noekkel-rekkefolge; description/tags utelates naar tomme.
const fields = { type, resource, title: concept.title };
if (description) fields.description = description;
if (tags.length > 0) fields.tags = tags;
fields.timestamp = timestamp;
fields.kilde = 'innboks';
const frontmatter = writeFrontmatter(fields);
return {
...concept,
type,
resource,
description,
tags,
timestamp,
kilde: 'innboks',
destRel,
frontmatter,
};
}