Personbil-etikett i granskningen + ECM Knowledge Library

Arbetsordergranskningens fältgrupp heter nu Personbil (tidigare Fordon),
liksom raden i rapportens fordonsinformation.

ECM Knowledge Library: regelbiblioteket är nu serverdistribuerat —
regler uppdateras i driften utan appändring, precis som rekommenderat:

- Compliance-reglerna är deklarativ data (krav-typ i stället för kod):
  services/plattform/ecm-regler.json serveras på GET /api/ecm/regler,
  i klustret utbytbar via ConfigMap (ECM_REGLER_FIL)
- Klienten hämtar paketet vid sidladdning, cachar det och faller
  tillbaka till inbyggt standardpaket offline; trasiga paket och okända
  krav-typer filtreras (normaliseraRegelpaket)
- Kvalitetsgrinden, orsakskategorierna, evidenskällorna och
  undantagsorsakerna läses ur det aktiva paketet; spårbarhetspaketet
  bär regelpaketets version
- Testat: paritet server/standardpaket, fallback, att ett uppdaterat
  paket ändrar grinden utan appändring; integrationstestet verifierar
  serveringen (57 vitest-tester gröna)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-03 09:38:44 +00:00
parent ddc11c6ac2
commit a37cc027ae
14 changed files with 328 additions and 68 deletions
+55
View File
@@ -254,3 +254,58 @@ describe("Felorsaksanalys (Root Cause Analysis)", () => {
expect(orsak?.handelse.typ === "felorsak" && orsak.handelse.underlag.length > 0).toBe(true);
});
});
describe("ECM Knowledge Library (regelpaket)", () => {
it("serverpaketet och det inbyggda standardpaketet är samma regler", async () => {
const { readFileSync } = await import("node:fs");
const { STANDARD_REGELPAKET, normaliseraRegelpaket } = await import("../ecm");
const server = normaliseraRegelpaket(JSON.parse(readFileSync("services/plattform/ecm-regler.json", "utf8")));
expect(server.version).toBe(STANDARD_REGELPAKET.version);
expect(server.arendetypRegler).toEqual(STANDARD_REGELPAKET.arendetypRegler);
expect(server.orsakskategorier).toEqual(STANDARD_REGELPAKET.orsakskategorier);
});
it("trasiga paket faller tillbaka till standard, okända krav-typer filtreras", async () => {
const { STANDARD_REGELPAKET, normaliseraRegelpaket } = await import("../ecm");
expect(normaliseraRegelpaket(null)).toEqual(STANDARD_REGELPAKET);
const paket = normaliseraRegelpaket({
version: "2.1",
arendetypRegler: {
Garanti: [
{ id: "x", rubrik: "Ny regel", krav: "foto", detaljVidBrist: "Kräver foto." },
{ id: "y", rubrik: "Trasig", krav: "pahittad_kravtyp", detaljVidBrist: "…" },
],
},
});
expect(paket.version).toBe("2.1");
expect(paket.arendetypRegler.Garanti).toHaveLength(1);
expect(paket.undantagsorsaker).toEqual(STANDARD_REGELPAKET.undantagsorsaker);
});
it("ett uppdaterat regelpaket ändrar kvalitetsgrinden utan appändring", async () => {
const { normaliseraRegelpaket } = await import("../ecm");
localStorage.setItem(
"gf-ecm-regelpaket",
JSON.stringify(
normaliseraRegelpaket({
version: "2.1-test",
arendetypRegler: {
"Privat kund": [
{ id: "test_foto", rubrik: "Foto krävs för alla privatkunder", krav: "foto", detaljVidBrist: "Testregel." },
],
},
}),
),
);
try {
const utanFoto = byggArende([OBJEKT]);
const rad = kvalitetsgrind(utanFoto, VIBRATION_METODIK).find((r) => r.id === "test_foto");
expect(rad?.ok).toBe(false);
expect(rad?.rubrik).toContain("Privat kund");
// Spårbarheten bär paketversionen.
expect(sparbarhetspaket(utanFoto, VIBRATION_METODIK).regelpaketVersion).toBe("2.1-test");
} finally {
localStorage.removeItem("gf-ecm-regelpaket");
}
});
});