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:
@@ -72,6 +72,7 @@ describe("AI-orkestern", () => {
|
||||
["/api/auth/registrera", "/api/auth/registrera"],
|
||||
["/api/auth/logga-in", "/api/auth/logga-in"],
|
||||
["/api/anvandare", "/api/anvandare"],
|
||||
["/api/ecm/regler", "/api/ecm/regler"],
|
||||
["/api/organisation", "/api/organisation"],
|
||||
["/api/organisation/installningar", "/api/organisation/installningar"],
|
||||
["/api/arenden", "/api/arenden"],
|
||||
@@ -151,7 +152,7 @@ describe("arbetsorderskanning", () => {
|
||||
expect(falt.map((f) => f.id)).toEqual(["fordon_regnr", "fordon_vin"]);
|
||||
expect(falt[0].konfidens).toBe(1);
|
||||
expect(falt[0].varde).toBe("abc123");
|
||||
expect(falt[0].grupp).toBe("Fordon");
|
||||
expect(falt[0].grupp).toBe("Personbil");
|
||||
expect(falt[1].omrade?.x).toBe(0.5);
|
||||
expect(() => normaliseraArbetsorder({})).toThrow();
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+10
-10
@@ -101,7 +101,7 @@ export function tolkaAiSvar(data: unknown): AiSvar {
|
||||
// (OCR + layoutförståelse) och returnerar strukturerade fält med
|
||||
// konfidens per värde. Bara osäkra fält behöver granskas.
|
||||
|
||||
export type ArbetsorderGrupp = "Kund" | "Fordon" | "Verkstad" | "Ärende";
|
||||
export type ArbetsorderGrupp = "Kund" | "Personbil" | "Verkstad" | "Ärende";
|
||||
|
||||
export interface TolkatFalt {
|
||||
id: string;
|
||||
@@ -118,15 +118,15 @@ export const ARBETSORDER_FALT: { id: string; grupp: ArbetsorderGrupp; etikett: s
|
||||
{ id: "kund_foretag", grupp: "Kund", etikett: "Företag" },
|
||||
{ id: "kund_telefon", grupp: "Kund", etikett: "Telefon" },
|
||||
{ id: "kund_epost", grupp: "Kund", etikett: "E-post" },
|
||||
{ id: "fordon_regnr", grupp: "Fordon", etikett: "Regnr" },
|
||||
{ id: "fordon_vin", grupp: "Fordon", etikett: "VIN" },
|
||||
{ id: "fordon_marke", grupp: "Fordon", etikett: "Märke" },
|
||||
{ id: "fordon_modell", grupp: "Fordon", etikett: "Modell" },
|
||||
{ id: "fordon_motor", grupp: "Fordon", etikett: "Motor" },
|
||||
{ id: "fordon_arsmodell", grupp: "Fordon", etikett: "Årsmodell" },
|
||||
{ id: "fordon_matarstallning", grupp: "Fordon", etikett: "Mätarställning" },
|
||||
{ id: "fordon_motorkod", grupp: "Fordon", etikett: "Motorkod" },
|
||||
{ id: "fordon_vaxellada", grupp: "Fordon", etikett: "Växellåda" },
|
||||
{ id: "fordon_regnr", grupp: "Personbil", etikett: "Regnr" },
|
||||
{ id: "fordon_vin", grupp: "Personbil", etikett: "VIN" },
|
||||
{ id: "fordon_marke", grupp: "Personbil", etikett: "Märke" },
|
||||
{ id: "fordon_modell", grupp: "Personbil", etikett: "Modell" },
|
||||
{ id: "fordon_motor", grupp: "Personbil", etikett: "Motor" },
|
||||
{ id: "fordon_arsmodell", grupp: "Personbil", etikett: "Årsmodell" },
|
||||
{ id: "fordon_matarstallning", grupp: "Personbil", etikett: "Mätarställning" },
|
||||
{ id: "fordon_motorkod", grupp: "Personbil", etikett: "Motorkod" },
|
||||
{ id: "fordon_vaxellada", grupp: "Personbil", etikett: "Växellåda" },
|
||||
{ id: "ao_nummer", grupp: "Verkstad", etikett: "Arbetsordernr" },
|
||||
{ id: "ao_claim", grupp: "Verkstad", etikett: "Claim-/garantinr" },
|
||||
{ id: "ao_skadenummer", grupp: "Verkstad", etikett: "Skadenummer" },
|
||||
|
||||
@@ -151,12 +151,12 @@ export function byggDemoTolkning(): TolkatFalt[] {
|
||||
return [
|
||||
{ id: "kund_namn", etikett: "Namn", grupp: "Kund", varde: "Anders Svensson", konfidens: 0.98 },
|
||||
{ id: "kund_telefon", etikett: "Telefon", grupp: "Kund", varde: "070-123 45 67", konfidens: 0.91, omrade: { x: 0.08, y: 0.22, bredd: 0.3, hojd: 0.04 } },
|
||||
{ id: "fordon_regnr", etikett: "Regnr", grupp: "Fordon", varde: "ABC123", konfidens: 0.99, omrade: { x: 0.62, y: 0.12, bredd: 0.2, hojd: 0.05 } },
|
||||
{ id: "fordon_vin", etikett: "VIN", grupp: "Fordon", varde: "YV1DZ8256F2123456", konfidens: 0.74, omrade: { x: 0.55, y: 0.19, bredd: 0.38, hojd: 0.04 } },
|
||||
{ id: "fordon_marke", etikett: "Märke", grupp: "Fordon", varde: "Volvo", konfidens: 0.99 },
|
||||
{ id: "fordon_modell", etikett: "Modell", grupp: "Fordon", varde: "XC60 D4", konfidens: 0.97 },
|
||||
{ id: "fordon_arsmodell", etikett: "Årsmodell", grupp: "Fordon", varde: "2019", konfidens: 0.96 },
|
||||
{ id: "fordon_matarstallning", etikett: "Mätarställning", grupp: "Fordon", varde: "8 432 mil", konfidens: 0.87, omrade: { x: 0.62, y: 0.27, bredd: 0.18, hojd: 0.04 } },
|
||||
{ id: "fordon_regnr", etikett: "Regnr", grupp: "Personbil", varde: "ABC123", konfidens: 0.99, omrade: { x: 0.62, y: 0.12, bredd: 0.2, hojd: 0.05 } },
|
||||
{ id: "fordon_vin", etikett: "VIN", grupp: "Personbil", varde: "YV1DZ8256F2123456", konfidens: 0.74, omrade: { x: 0.55, y: 0.19, bredd: 0.38, hojd: 0.04 } },
|
||||
{ id: "fordon_marke", etikett: "Märke", grupp: "Personbil", varde: "Volvo", konfidens: 0.99 },
|
||||
{ id: "fordon_modell", etikett: "Modell", grupp: "Personbil", varde: "XC60 D4", konfidens: 0.97 },
|
||||
{ id: "fordon_arsmodell", etikett: "Årsmodell", grupp: "Personbil", varde: "2019", konfidens: 0.96 },
|
||||
{ id: "fordon_matarstallning", etikett: "Mätarställning", grupp: "Personbil", varde: "8 432 mil", konfidens: 0.87, omrade: { x: 0.62, y: 0.27, bredd: 0.18, hojd: 0.04 } },
|
||||
{ id: "ao_nummer", etikett: "Arbetsordernr", grupp: "Verkstad", varde: "AO-2496", konfidens: 0.99 },
|
||||
{ id: "ao_serviceradgivare", etikett: "Servicerådgivare", grupp: "Verkstad", varde: "M. Lindqvist", konfidens: 0.82 },
|
||||
{ id: "felbeskrivning", etikett: "Felbeskrivning", grupp: "Ärende", varde: "Kunden upplever vibrationer i ratten vid ca 90 km/h", konfidens: 0.97, omrade: { x: 0.08, y: 0.45, bredd: 0.84, hojd: 0.07 } },
|
||||
|
||||
+128
-37
@@ -241,13 +241,31 @@ export function arendetyp(arende: Arende): Arendetyp {
|
||||
return typ;
|
||||
}
|
||||
|
||||
interface ComplianceKrav {
|
||||
// ---- ECM Knowledge Library --------------------------------------------
|
||||
// Regelpaketet är data, inte kod: reglerna är deklarativa (krav-typ i
|
||||
// stället för funktioner) och kan därför distribueras från servern
|
||||
// (GET /api/ecm/regler, filen services/plattform/ecm-regler.json — i
|
||||
// klustret utbytbar via ConfigMap) och uppdateras utan appändring.
|
||||
// Klienten cachar senast hämtade paket och faller tillbaka till det
|
||||
// inbyggda standardpaketet offline/i lokalt läge.
|
||||
|
||||
export type ComplianceKravTyp = "miltal" | "historik" | "claim" | "skadenummer" | "foto";
|
||||
|
||||
export interface ComplianceRegel {
|
||||
id: string;
|
||||
rubrik: string;
|
||||
uppfyllt: (arende: Arende) => boolean;
|
||||
krav: ComplianceKravTyp;
|
||||
detaljVidBrist: string;
|
||||
}
|
||||
|
||||
export interface RegelPaket {
|
||||
version: string;
|
||||
arendetypRegler: Partial<Record<string, ComplianceRegel[]>>;
|
||||
undantagsorsaker: string[];
|
||||
orsakskategorier: string[];
|
||||
underlagskallor: string[];
|
||||
}
|
||||
|
||||
function objektFalt(arende: Arende, falt: "miltal" | "claim" | "skadenummer" | "arbetsorder"): string | undefined {
|
||||
for (const post of arende.handelser) {
|
||||
if (post.handelse.typ === "objekt_identifierat") return post.handelse.objekt[falt];
|
||||
@@ -255,37 +273,107 @@ function objektFalt(arende: Arende, falt: "miltal" | "claim" | "skadenummer" | "
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const harMatarstallning = (arende: Arende) =>
|
||||
arende.handelser.some((p) => p.handelse.typ === "matarstallning" && !p.handelse.undantag) ||
|
||||
!!objektFalt(arende, "miltal");
|
||||
const harHistorik = (arende: Arende) =>
|
||||
arende.handelser.some((p) => p.handelse.typ === "historik_kontrollerad" && p.handelse.kontrollerad);
|
||||
const harFoto = (arende: Arende) => arende.handelser.some((p) => p.handelse.typ === "foto");
|
||||
|
||||
// Regelpaket per ärendetyp — här ansluter framtida serverdistribuerade
|
||||
// regler (garantivillkor per tillverkare, försäkringsbolagens krav …).
|
||||
const COMPLIANCE_REGLER: Partial<Record<Arendetyp, ComplianceKrav[]>> = {
|
||||
Garanti: [
|
||||
{ id: "garanti_miltal", rubrik: "Miltal dokumenterat", uppfyllt: harMatarstallning, detaljVidBrist: "Garantiärenden kräver dokumenterad mätarställning." },
|
||||
{ id: "garanti_historik", rubrik: "Servicehistorik kontrollerad", uppfyllt: harHistorik, detaljVidBrist: "Garantiärenden kräver kontrollerad servicehistorik." },
|
||||
{ id: "garanti_claim", rubrik: "Claim-/garantinummer registrerat", uppfyllt: (a) => !!objektFalt(a, "claim"), detaljVidBrist: "Ange claim-/garantinummer (läses ur arbetsordern)." },
|
||||
],
|
||||
Goodwill: [
|
||||
{ id: "goodwill_miltal", rubrik: "Miltal dokumenterat", uppfyllt: harMatarstallning, detaljVidBrist: "Goodwillärenden kräver dokumenterad mätarställning." },
|
||||
{ id: "goodwill_historik", rubrik: "Servicehistorik kontrollerad", uppfyllt: harHistorik, detaljVidBrist: "Goodwillärenden kräver kontrollerad servicehistorik." },
|
||||
],
|
||||
Försäkring: [
|
||||
{ id: "forsakring_skadenummer", rubrik: "Skadenummer registrerat", uppfyllt: (a) => !!objektFalt(a, "skadenummer"), detaljVidBrist: "Försäkringsärenden kräver skadenummer (läses ur arbetsordern)." },
|
||||
{ id: "forsakring_bildbevis", rubrik: "Bildbevis finns", uppfyllt: harFoto, detaljVidBrist: "Försäkringsärenden kräver bilddokumentation." },
|
||||
],
|
||||
Reklamation: [
|
||||
{ id: "reklamation_historik", rubrik: "Historik och tidigare försök kontrollerade", uppfyllt: harHistorik, detaljVidBrist: "Reklamationer kräver kontrollerad historik (tidigare reparationer/försök)." },
|
||||
],
|
||||
Begagnatgaranti: [
|
||||
{ id: "begagnat_miltal", rubrik: "Miltal dokumenterat", uppfyllt: harMatarstallning, detaljVidBrist: "Begagnatgaranti kräver dokumenterad mätarställning." },
|
||||
],
|
||||
// Krav-typ → verifiering mot händelseloggen. Nya krav-typer läggs till
|
||||
// här; vilka regler som använder dem styrs helt av regelpaketet.
|
||||
const KRAV_KONTROLLER: Record<ComplianceKravTyp, (arende: Arende) => boolean> = {
|
||||
miltal: (a) =>
|
||||
a.handelser.some((p) => p.handelse.typ === "matarstallning" && !p.handelse.undantag) ||
|
||||
!!objektFalt(a, "miltal"),
|
||||
historik: (a) => a.handelser.some((p) => p.handelse.typ === "historik_kontrollerad" && p.handelse.kontrollerad),
|
||||
claim: (a) => !!objektFalt(a, "claim"),
|
||||
skadenummer: (a) => !!objektFalt(a, "skadenummer"),
|
||||
foto: (a) => a.handelser.some((p) => p.handelse.typ === "foto"),
|
||||
};
|
||||
|
||||
// Inbyggt standardpaket = fallback när servern inte nåtts ännu.
|
||||
export const STANDARD_REGELPAKET: RegelPaket = {
|
||||
version: ECM_VERSION,
|
||||
arendetypRegler: {
|
||||
Garanti: [
|
||||
{ id: "garanti_miltal", rubrik: "Miltal dokumenterat", krav: "miltal", detaljVidBrist: "Garantiärenden kräver dokumenterad mätarställning." },
|
||||
{ id: "garanti_historik", rubrik: "Servicehistorik kontrollerad", krav: "historik", detaljVidBrist: "Garantiärenden kräver kontrollerad servicehistorik." },
|
||||
{ id: "garanti_claim", rubrik: "Claim-/garantinummer registrerat", krav: "claim", detaljVidBrist: "Ange claim-/garantinummer (läses ur arbetsordern)." },
|
||||
],
|
||||
Goodwill: [
|
||||
{ id: "goodwill_miltal", rubrik: "Miltal dokumenterat", krav: "miltal", detaljVidBrist: "Goodwillärenden kräver dokumenterad mätarställning." },
|
||||
{ id: "goodwill_historik", rubrik: "Servicehistorik kontrollerad", krav: "historik", detaljVidBrist: "Goodwillärenden kräver kontrollerad servicehistorik." },
|
||||
],
|
||||
Försäkring: [
|
||||
{ id: "forsakring_skadenummer", rubrik: "Skadenummer registrerat", krav: "skadenummer", detaljVidBrist: "Försäkringsärenden kräver skadenummer (läses ur arbetsordern)." },
|
||||
{ id: "forsakring_bildbevis", rubrik: "Bildbevis finns", krav: "foto", detaljVidBrist: "Försäkringsärenden kräver bilddokumentation." },
|
||||
],
|
||||
Reklamation: [
|
||||
{ id: "reklamation_historik", rubrik: "Historik och tidigare försök kontrollerade", krav: "historik", detaljVidBrist: "Reklamationer kräver kontrollerad historik (tidigare reparationer/försök)." },
|
||||
],
|
||||
Begagnatgaranti: [
|
||||
{ id: "begagnat_miltal", rubrik: "Miltal dokumenterat", krav: "miltal", detaljVidBrist: "Begagnatgaranti kräver dokumenterad mätarställning." },
|
||||
],
|
||||
},
|
||||
undantagsorsaker: UNDANTAGSORSAKER,
|
||||
orsakskategorier: ORSAKSKATEGORIER,
|
||||
underlagskallor: UNDERLAGSKALLOR,
|
||||
};
|
||||
|
||||
const REGELPAKET_NYCKEL = "gf-ecm-regelpaket";
|
||||
|
||||
// Validerar och normaliserar ett paket från servern/cachen — trasiga
|
||||
// eller ofullständiga paket faller tillbaka till standard per fält.
|
||||
export function normaliseraRegelpaket(data: unknown): RegelPaket {
|
||||
const d = (data ?? {}) as Partial<RegelPaket>;
|
||||
const lista = (varden: unknown, standard: string[]) =>
|
||||
Array.isArray(varden) && varden.length > 0 && varden.every((v) => typeof v === "string")
|
||||
? (varden as string[])
|
||||
: standard;
|
||||
const regler: RegelPaket["arendetypRegler"] = {};
|
||||
if (d.arendetypRegler && typeof d.arendetypRegler === "object") {
|
||||
for (const [typ, rader] of Object.entries(d.arendetypRegler)) {
|
||||
if (!Array.isArray(rader)) continue;
|
||||
const giltiga = rader.filter(
|
||||
(r): r is ComplianceRegel =>
|
||||
!!r && typeof r.id === "string" && typeof r.rubrik === "string" &&
|
||||
typeof r.detaljVidBrist === "string" && typeof r.krav === "string" && r.krav in KRAV_KONTROLLER,
|
||||
);
|
||||
if (giltiga.length > 0) regler[typ] = giltiga;
|
||||
}
|
||||
}
|
||||
return {
|
||||
version: typeof d.version === "string" && d.version ? d.version : STANDARD_REGELPAKET.version,
|
||||
arendetypRegler: Object.keys(regler).length > 0 ? regler : STANDARD_REGELPAKET.arendetypRegler,
|
||||
undantagsorsaker: lista(d.undantagsorsaker, STANDARD_REGELPAKET.undantagsorsaker),
|
||||
orsakskategorier: lista(d.orsakskategorier, STANDARD_REGELPAKET.orsakskategorier),
|
||||
underlagskallor: lista(d.underlagskallor, STANDARD_REGELPAKET.underlagskallor),
|
||||
};
|
||||
}
|
||||
|
||||
export function aktivtRegelpaket(): RegelPaket {
|
||||
try {
|
||||
const rad = localStorage.getItem(REGELPAKET_NYCKEL);
|
||||
return rad ? normaliseraRegelpaket(JSON.parse(rad)) : STANDARD_REGELPAKET;
|
||||
} catch {
|
||||
return STANDARD_REGELPAKET;
|
||||
}
|
||||
}
|
||||
|
||||
// Hämtar aktuellt regelpaket från plattformen (inloggat läge) och cachar
|
||||
// det. Anropas vid sidladdning — misslyckas hämtningen gäller cachen
|
||||
// eller standardpaketet.
|
||||
export async function hamtaRegelpaket(): Promise<RegelPaket> {
|
||||
try {
|
||||
const { plattformAktiv, plattformToken, plattformFetch } = await import("./plattform");
|
||||
if (plattformAktiv() && plattformToken()) {
|
||||
const res = await plattformFetch("/api/ecm/regler");
|
||||
if (res.ok) {
|
||||
const paket = normaliseraRegelpaket(await res.json());
|
||||
localStorage.setItem(REGELPAKET_NYCKEL, JSON.stringify(paket));
|
||||
return paket;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Nätverksfel: cache/standard gäller.
|
||||
}
|
||||
return aktivtRegelpaket();
|
||||
}
|
||||
|
||||
// ---- 4. Validation Engine ---------------------------------------------
|
||||
|
||||
// Kärnprincipen — inga påståenden utan underlag — verkar i tre lager:
|
||||
@@ -482,16 +570,18 @@ export function kvalitetsgrind(arende: Arende, metodik: Metodik): GrindRad[] {
|
||||
detalj: fotoKravUtanFoto > 0 ? `${fotoKravUtanFoto} fotokrävande kontroll(er) utan bild i loggen.` : undefined,
|
||||
});
|
||||
|
||||
// Compliance Engine: ärendetypens regelpaket.
|
||||
// Compliance Engine: ärendetypens regler ur det aktiva regelpaketet
|
||||
// (serverdistribuerat via ECM Knowledge Library, standard som fallback).
|
||||
const paket = aktivtRegelpaket();
|
||||
const typ = arendetyp(arende);
|
||||
for (const krav of COMPLIANCE_REGLER[typ] ?? []) {
|
||||
const ok = krav.uppfyllt(arende);
|
||||
for (const regel of paket.arendetypRegler[typ] ?? []) {
|
||||
const ok = KRAV_KONTROLLER[regel.krav](arende);
|
||||
rader.push({
|
||||
id: krav.id,
|
||||
rubrik: `${typ}: ${krav.rubrik}`,
|
||||
id: regel.id,
|
||||
rubrik: `${typ}: ${regel.rubrik}`,
|
||||
ok,
|
||||
kravs: true,
|
||||
detalj: ok ? undefined : krav.detaljVidBrist,
|
||||
detalj: ok ? undefined : regel.detaljVidBrist,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -530,6 +620,7 @@ export function grindGodkand(arende: Arende, metodik: Metodik): boolean {
|
||||
export function sparbarhetspaket(arende: Arende, metodik: Metodik) {
|
||||
return {
|
||||
ecmVersion: ECM_VERSION,
|
||||
regelpaketVersion: aktivtRegelpaket().version,
|
||||
arendetyp: arendetyp(arende),
|
||||
evidensniva: evidensNiva(arende),
|
||||
kvalitetsgrind: kvalitetsgrind(arende, metodik).map(({ id, rubrik, ok, kravs }) => ({ id, rubrik, ok, kravs })),
|
||||
|
||||
@@ -42,14 +42,13 @@ import {
|
||||
MARKOR_FELBESKRIVNING_VERIFIERAD,
|
||||
MARKOR_INGA_TIDIGA_OBSERVATIONER,
|
||||
MARKOR_TIDIGA_OBSERVATIONER_KLARA,
|
||||
ORSAKSKATEGORIER,
|
||||
UNDANTAGSORSAKER,
|
||||
UNDERLAGSKALLOR,
|
||||
aktivtRegelpaket,
|
||||
arendetyp,
|
||||
felorsaker,
|
||||
granskaAvvikelse,
|
||||
grindGodkand,
|
||||
kvalitetsgrind,
|
||||
hamtaRegelpaket,
|
||||
preDiagnostik,
|
||||
reproducering,
|
||||
reproduceringsText,
|
||||
@@ -115,6 +114,9 @@ export default function ArendeSida() {
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => setNu(new Date().toISOString()), 30000);
|
||||
// ECM Knowledge Library: färskt regelpaket från plattformen (cache/
|
||||
// standardpaket gäller offline).
|
||||
hamtaRegelpaket();
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
@@ -551,7 +553,7 @@ function FelorsaksPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handel
|
||||
/>
|
||||
<p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">2. Mest sannolik orsak (en eller flera)</p>
|
||||
<div className="mb-3 grid grid-cols-2 gap-1 sm:grid-cols-3">
|
||||
{ORSAKSKATEGORIER.map((o) => (
|
||||
{aktivtRegelpaket().orsakskategorier.map((o) => (
|
||||
<button
|
||||
key={o}
|
||||
onClick={() => vaxla(orsaker, setOrsaker, o)}
|
||||
@@ -565,7 +567,7 @@ function FelorsaksPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handel
|
||||
</div>
|
||||
<p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-[#4A5560]">3. Underlag som stöder bedömningen</p>
|
||||
<div className="mb-3 grid grid-cols-2 gap-1 sm:grid-cols-4">
|
||||
{UNDERLAGSKALLOR.map((k) => (
|
||||
{aktivtRegelpaket().underlagskallor.map((k) => (
|
||||
<button
|
||||
key={k}
|
||||
onClick={() => vaxla(underlag, setUnderlag, k)}
|
||||
@@ -1103,7 +1105,7 @@ function Undantag({ vidUndantag }: { vidUndantag: (orsak: string) => void }) {
|
||||
Kontrollen dokumenteras utan underlag — ange orsak (obligatoriskt). Detta flaggas i brief och rapport.
|
||||
</p>
|
||||
<div className="mb-2 grid grid-cols-1 gap-1">
|
||||
{UNDANTAGSORSAKER.map((val) => (
|
||||
{aktivtRegelpaket().undantagsorsaker.map((val) => (
|
||||
<button
|
||||
key={val}
|
||||
onClick={() => setOrsak(val)}
|
||||
@@ -1787,7 +1789,7 @@ function RapportFlik({
|
||||
</Panel>
|
||||
<Panel rubrik="Fordonsinformation">
|
||||
{identitetsRader([
|
||||
["Fordon", idn.beskrivning],
|
||||
["Personbil", idn.beskrivning],
|
||||
["Regnr", idn.identifierare],
|
||||
["VIN", idn.vin],
|
||||
["Mätarställning in", matIn],
|
||||
|
||||
@@ -295,7 +295,7 @@ function GranskaTolkning({
|
||||
|
||||
const obekraftade = rader.filter((r) => !r.bekraftad);
|
||||
const vald = rader.find((r) => r.id === markerad);
|
||||
const grupper: ArbetsorderGrupp[] = ["Kund", "Fordon", "Verkstad", "Ärende"];
|
||||
const grupper: ArbetsorderGrupp[] = ["Kund", "Personbil", "Verkstad", "Ärende"];
|
||||
|
||||
const uppdatera = (id: string, andring: Partial<(typeof rader)[number]>) =>
|
||||
setRader((r) => r.map((rad) => (rad.id === id ? { ...rad, ...andring } : rad)));
|
||||
|
||||
Reference in New Issue
Block a user