gute
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Qualitätstor für Suggestions (S06).
|
||||
* Failt bei Müll, ungültigem type/weight, Duplikaten oder zu wenig Originalen.
|
||||
*
|
||||
* Usage: npm run check:suggestions
|
||||
*/
|
||||
import { readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(__dirname, "..");
|
||||
const SUGGESTIONS_PATH = join(ROOT, "src", "data", "suggestions.ts");
|
||||
const CLEAN_PATH = join(__dirname, "clean-phrases.json");
|
||||
const MIN_ORIGINAL_SHARE = 0.7;
|
||||
const VALID_TYPES = new Set(["betrieb", "schule"]);
|
||||
|
||||
/** Müll: Header/Signatur/Nummern-Artefakte – nicht echte Sätze mit „zur Unterschrift …“. */
|
||||
const JUNK_RE =
|
||||
/Datum und Unterschrift|\bLfd\.?\s*Nr\b|……|…………|\.{4,}|\d{5,}|Ausbildungsnachweis|Für die Zeit|^Unterschrift\b|^Abteilung\b|Zuordnung zum Lernziel/i;
|
||||
|
||||
/**
|
||||
* @param {string} src
|
||||
* @returns {{ suggestions: any[], themes: string[] }}
|
||||
*/
|
||||
function parseSuggestionsTs(src) {
|
||||
const sugKey = "export const SUGGESTIONS: Suggestion[] = ";
|
||||
const themeKey = "export const WEEK_THEMES: string[] = ";
|
||||
const sugStart = src.indexOf(sugKey);
|
||||
if (sugStart < 0) throw new Error("SUGGESTIONS-Export nicht gefunden");
|
||||
const afterSug = sugStart + sugKey.length;
|
||||
const themeStart = src.indexOf(themeKey, afterSug);
|
||||
if (themeStart < 0) throw new Error("WEEK_THEMES-Export nicht gefunden");
|
||||
const sugJson = src.slice(afterSug, themeStart).trim().replace(/;$/, "");
|
||||
const afterTheme = themeStart + themeKey.length;
|
||||
const nextExport = src.indexOf("\nexport const ", afterTheme);
|
||||
if (nextExport < 0) throw new Error("Ende von WEEK_THEMES nicht gefunden");
|
||||
const themeJson = src.slice(afterTheme, nextExport).trim().replace(/;$/, "");
|
||||
return {
|
||||
suggestions: JSON.parse(sugJson),
|
||||
themes: JSON.parse(themeJson),
|
||||
};
|
||||
}
|
||||
|
||||
function fail(msg) {
|
||||
console.error(`FAIL: ${msg}`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const errors = [];
|
||||
const { suggestions, themes } = parseSuggestionsTs(
|
||||
readFileSync(SUGGESTIONS_PATH, "utf8"),
|
||||
);
|
||||
const clean = JSON.parse(readFileSync(CLEAN_PATH, "utf8"));
|
||||
const originalTexts = new Set(
|
||||
(clean.activities ?? []).map((a) =>
|
||||
(typeof a === "string" ? a : a.text).toLowerCase().replace(/\s+/g, " ").trim(),
|
||||
),
|
||||
);
|
||||
const themeText = (t) => (typeof t === "string" ? t : t?.text);
|
||||
const realThemes = new Set(
|
||||
[...(clean.themes ?? []), ...(clean.themesWeighted ?? [])]
|
||||
.map(themeText)
|
||||
.map((t) =>
|
||||
String(t ?? "")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/,\s*$/, "")
|
||||
.trim()
|
||||
.toLowerCase(),
|
||||
)
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
if (!Array.isArray(suggestions) || suggestions.length === 0) {
|
||||
errors.push("SUGGESTIONS leer oder ungültig");
|
||||
}
|
||||
|
||||
const seen = new Map();
|
||||
let junkCount = 0;
|
||||
let badType = 0;
|
||||
let badWeight = 0;
|
||||
let dupCount = 0;
|
||||
|
||||
for (const s of suggestions) {
|
||||
const text = String(s?.text ?? "").replace(/\s+/g, " ").trim();
|
||||
if (JUNK_RE.test(text)) {
|
||||
junkCount++;
|
||||
if (junkCount <= 8) fail(`Müll-Regex: id=${s.id} "${text}"`);
|
||||
}
|
||||
if (!VALID_TYPES.has(s?.type)) {
|
||||
badType++;
|
||||
if (badType <= 8) fail(`type fehlt/ungültig: id=${s?.id} type=${s?.type}`);
|
||||
}
|
||||
if (!(Number(s?.weight) >= 1)) {
|
||||
badWeight++;
|
||||
if (badWeight <= 8) fail(`weight < 1: id=${s?.id} weight=${s?.weight}`);
|
||||
}
|
||||
const key = text.toLowerCase();
|
||||
if (seen.has(key)) {
|
||||
dupCount++;
|
||||
if (dupCount <= 8) fail(`Duplikat: "${text}" (ids ${seen.get(key)}, ${s.id})`);
|
||||
} else {
|
||||
seen.set(key, s.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (junkCount) errors.push(`${junkCount} Phrase(n) matchen Müll-Regex`);
|
||||
if (badType) errors.push(`${badType} Phrase(n) ohne gültigen type`);
|
||||
if (badWeight) errors.push(`${badWeight} Phrase(n) mit weight < 1`);
|
||||
if (dupCount) errors.push(`${dupCount} case-insensitive Duplikat(e)`);
|
||||
|
||||
const betrieb = suggestions.filter((s) => s.type === "betrieb");
|
||||
const schule = suggestions.filter((s) => s.type === "schule");
|
||||
const exactBetrieb = betrieb.filter((s) =>
|
||||
originalTexts.has(String(s.text).toLowerCase().replace(/\s+/g, " ").trim()),
|
||||
);
|
||||
const originalShare = betrieb.length ? exactBetrieb.length / betrieb.length : 0;
|
||||
if (originalShare < MIN_ORIGINAL_SHARE) {
|
||||
errors.push(
|
||||
`Original-Anteil Betrieb ${(originalShare * 100).toFixed(1)}% < ${MIN_ORIGINAL_SHARE * 100}% (${exactBetrieb.length}/${betrieb.length})`,
|
||||
);
|
||||
}
|
||||
|
||||
const fakeThemes = themes.filter((t) => !realThemes.has(String(t).trim().toLowerCase()));
|
||||
if (fakeThemes.length) {
|
||||
errors.push(
|
||||
`${fakeThemes.length} WEEK_THEMES nicht in clean-phrases: ${fakeThemes.slice(0, 5).join(" · ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const top10 = [...betrieb]
|
||||
.sort((a, b) => b.weight - a.weight || a.text.localeCompare(b.text, "de"))
|
||||
.slice(0, 10);
|
||||
|
||||
console.log("=== Suggestions Qualitätstor ===");
|
||||
console.log(`Gesamt: ${suggestions.length} (Betrieb ${betrieb.length}, Schule ${schule.length})`);
|
||||
console.log(
|
||||
`Original-Anteil Betrieb: ${(originalShare * 100).toFixed(1)}% (${exactBetrieb.length}/${betrieb.length})`,
|
||||
);
|
||||
console.log(`WEEK_THEMES: ${themes.length} (echte Themen)`);
|
||||
console.log("Top-10 Betrieb:");
|
||||
for (const s of top10) console.log(` ${s.weight}× ${s.text}`);
|
||||
|
||||
if (errors.length) {
|
||||
console.error("\nQualitätstor FEHLGESCHLAGEN:");
|
||||
for (const e of errors) console.error(` - ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("\nQualitätstor OK");
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user