feat(ms-ai-architect): Spor 1 — ref-type-klassifiserer (pure core, MS-sitering-proxy + edge-flagg, TDD) [skip-docs]
This commit is contained in:
parent
dc2146aa7a
commit
6b8e1b3767
2 changed files with 170 additions and 0 deletions
65
scripts/kb-update/lib/classify-ref-type.mjs
Normal file
65
scripts/kb-update/lib/classify-ref-type.mjs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// classify-ref-type.mjs — Spor 1 Step 2: pure ref-type classifier.
|
||||
//
|
||||
// First-pass proxy: a file citing ≥1 learn.microsoft.com URL is a `reference`.
|
||||
// Both false-positive directions are flagged for adjudication, never guessed:
|
||||
// - zero-MS files get a path-heuristic candidate type + reviewFlag:true
|
||||
// - methodology/template-shaped paths that DO cite MS get reviewFlag:true
|
||||
// Pure core: no disk, no clock, no randomness; classifyCorpus output is
|
||||
// relpath-keyed and key-sorted so repeated runs are byte-identical.
|
||||
|
||||
import { extractUrls } from './url-normalize.mjs';
|
||||
|
||||
// Mirrors transform.mjs:33 (not exported there; transform.mjs is out of this
|
||||
// step's touch scope). The Port-1 contract's closed type set.
|
||||
export const VALID_TYPES = ['reference', 'template', 'methodology', 'regulatory'];
|
||||
|
||||
// Path shapes that indicate a non-reference artifact. Matched against the full
|
||||
// relpath, case-insensitive: directory names or filename stems.
|
||||
const RE_TEMPLATE_PATH = /template|-mal(?:er)?[-./]|sjekkliste|checklist/i;
|
||||
const RE_METHODOLOGY_PATH = /methodolog|metodikk|framework|rammeverk|playbook|runbook/i;
|
||||
const RE_REGULATORY_PATH = /eu-ai-act|ai-act|gdpr|eur-lex|regulator|forordning|schrems/i;
|
||||
|
||||
// Content signal for regulatory texts: cites the EU law corpus directly.
|
||||
const RE_REGULATORY_CONTENT = /eur-lex\.europa\.eu|celex/i;
|
||||
|
||||
/** Candidate type from path (and regulatory content signal). Pure heuristic. */
|
||||
function candidateType(path, content) {
|
||||
if (RE_REGULATORY_PATH.test(path) || RE_REGULATORY_CONTENT.test(content)) return 'regulatory';
|
||||
if (RE_TEMPLATE_PATH.test(path)) return 'template';
|
||||
if (RE_METHODOLOGY_PATH.test(path)) return 'methodology';
|
||||
return 'reference';
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify one file. Pure.
|
||||
* @param {{path: string, content: string}} file
|
||||
* @returns {{type: string, signal: string, mscite: boolean, reviewFlag: boolean}}
|
||||
*/
|
||||
export function classifyRefType({ path, content }) {
|
||||
const mscite = extractUrls(content).length > 0;
|
||||
const candidate = candidateType(path ?? '', content ?? '');
|
||||
|
||||
if (mscite && candidate === 'reference') {
|
||||
return { type: 'reference', signal: 'ms-citation', mscite: true, reviewFlag: false };
|
||||
}
|
||||
if (mscite) {
|
||||
// Shaped like a non-reference but cites MS — conflicting signals, adjudicate.
|
||||
return { type: candidate, signal: 'ms-citation+path-conflict', mscite: true, reviewFlag: true };
|
||||
}
|
||||
// Zero-MS: never silently a reference — candidate from the path heuristic.
|
||||
return { type: candidate, signal: 'path-heuristic', mscite: false, reviewFlag: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify a corpus. Pure, deterministic: relpath-keyed, key-sorted manifest.
|
||||
* @param {Array<{path: string, content: string}>} entries
|
||||
* @returns {Record<string, {type: string, signal: string, mscite: boolean, reviewFlag: boolean}>}
|
||||
*/
|
||||
export function classifyCorpus(entries) {
|
||||
const manifest = {};
|
||||
const sorted = [...(entries ?? [])].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
||||
for (const entry of sorted) {
|
||||
manifest[entry.path] = classifyRefType(entry);
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue