feat: remember project exclusions and merge selected people

This commit is contained in:
muena
2026-09-25 08:08:20 +02:00
parent 392db8601a
commit e7e21e9281
7 changed files with 366 additions and 38 deletions

View File

@@ -1,5 +1,8 @@
import { expect, it } from "bun:test";
import { groupReviewHits, makeTextBatches, spansBySegment } from "../src/lib/project-review";
import {
canonicalPerson, groupReviewHits, makeTextBatches, mappingForSelection, mergePeople,
spansBySegment, withoutWhitelistedHits, withoutWhitelistedRules,
} from "../src/lib/project-review";
it("ordnet gebündelte API-Treffer nach Unicode-Positionen den richtigen Dateien zu", () => {
const [batch] = makeTextBatches([
@@ -30,3 +33,38 @@ it("zeigt gleiche Zuordnungen einmal mit der Anzahl ihrer Vorkommen", () => {
expect(groups.find((group) => group.placeholder === "PERSON_01")?.hits).toHaveLength(2);
expect(groups.find((group) => group.placeholder === "PERSON_01")?.hits.map((hit) => hit.id)).toEqual(["a", "b"]);
});
it("unterdrückt projektweit nur exakt abgewählte Begriffe samt erzwungener Regel", () => {
const whitelist = [{ type: "PERSON", original: " Max Muster " }];
const hits = [
{ id: "1", type: "PERSON", original: "max muster", placeholder: "PERSON_01" },
{ id: "2", type: "ORG", original: "Max Muster", placeholder: "ORG_01" },
{ id: "3", type: "PERSON", original: "Max Mustermann", placeholder: "PERSON_02" },
];
expect(withoutWhitelistedHits(hits, whitelist).map((hit) => hit.id)).toEqual(["2", "3"]);
expect(withoutWhitelistedRules([
{ type: "PERSON", value: "Max Muster" }, { type: "ORG", value: "Max Muster" },
], whitelist)).toEqual([{ type: "ORG", value: "Max Muster" }]);
});
it("fasst ausgewählte Personen zu einem kanonischen Platzhalter zusammen und bewahrt alte Zuordnungen", () => {
let merges = mergePeople({}, "PERSON_02", "PERSON_01");
merges = mergePeople(merges, "PERSON_03", "PERSON_02");
expect(canonicalPerson("PERSON_03", merges)).toBe("PERSON_01");
const previous = { PERSON_09: { original: "Alt", type: "PERSON" } };
const preview = {
...previous,
PERSON_01: { original: "Franz Fuchs", type: "PERSON" },
PERSON_02: { original: "Franz", type: "PERSON" },
PERSON_03: { original: "Hr. Fuchs", type: "PERSON" },
PERSON_04: { original: "Fehlalarm", type: "PERSON" },
};
const accepted = ["PERSON_01", "PERSON_02", "PERSON_03"].map((placeholder) => ({
id: placeholder, type: "PERSON", original: preview[placeholder as keyof typeof preview].original, placeholder,
}));
const mapping = mappingForSelection(preview, previous, accepted, merges);
expect(mapping.PERSON_02.merged_into).toBe("PERSON_01");
expect(mapping.PERSON_03.merged_into).toBe("PERSON_01");
expect(mapping.PERSON_09.original).toBe("Alt");
expect(mapping.PERSON_04).toBeUndefined();
});

View File

@@ -20,6 +20,14 @@ describe("Velum-Projektstände", () => {
const rules = project.parseProjectRules("PERSON: Max Mustermann\nKundenSuite");
await project.saveProjectRules(source, rules, support);
expect(await project.loadProjectRules(source, support)).toEqual(rules);
await project.saveProjectWhitelist(source, [
{ type: "PERSON", original: "Falscher Treffer" },
{ type: "PERSON", original: "falscher treffer" },
], support);
expect(await project.loadProjectWhitelist(source, support)).toEqual([
{ type: "PERSON", original: "falscher treffer" },
]);
expect(await fs.readFile(join(source, "Notiz.txt"), "utf8")).toBe("Max Mustermann");
const created = await project.createRun(source, support);
created.run.mapping = { PERSON_01: { original: "Max Mustermann", type: "PERSON" } };
created.run.files.push({ source: "Notiz.txt", output: "PERSON_01.txt", kind: "text" });
@@ -82,4 +90,22 @@ describe("Velum-Projektstände", () => {
expect(await fs.readFile(join(source, "Bericht.txt"), "utf8")).toBe("Original");
expect(await fs.readFile(join(source, "PERSON_01.txt"), "utf8")).toBe("Andere Datei");
});
it("erkennt unveränderte Ausgaben für einen erneuten Lauf desselben Ordners", async () => {
const root = await fs.mkdtemp(join(tmpdir(), "velum-repeat-test-"));
roots.push(root);
const source = join(root, "Quelle");
const support = join(root, "Raycast");
await fs.mkdir(source);
const created = await project.createRun(source, support);
const filename = "PERSON_01.txt";
await fs.writeFile(join(created.directory, "files", filename), "PERSON_01");
const current = join(source, filename);
await fs.writeFile(current, "PERSON_01");
const hash = project.digest(await fs.readFile(current));
expect(await project.matchesPreviousOutput(created.directory, filename, hash)).toBe(true);
await fs.writeFile(current, "PERSON_01 und neuer Text");
expect(await project.matchesPreviousOutput(created.directory, filename,
project.digest(await fs.readFile(current)))).toBe(false);
});
});