From e73decd3f5f5567ff25e4e79ececdff2a2feb730 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 13:32:00 +0000 Subject: [PATCH] =?UTF-8?q?Revisionen:=20C-1,=20C-2,=20M-2,=20M-3,=20m-1?= =?UTF-8?q?=20och=20m-2=20=C3=A5tg=C3=A4rdade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De fyra fynd som underminerade bevisvärdet sitter i samma kodväg och åtgärdas därför tillsammans. C-1 · Härkomsten sätts av servern. anvandare kommer ur den verifierade token och tidpunkt ur serverns klocka. Klientens tidsstämpel kastas inte — vid offline-arbete är den det enda som finns — utan bevaras som registrerad_tidpunkt bredvid mottagningstiden, så glappet blir synligt i stället för osynligt. C-2 · Kvalitetsgrinden flyttad till services/gemensam/grind.mjs och utvärderas nu på servern vid arende_avslutat. Ett avslut som inte passerar får 409 med de faktiska hindren. Metodikdatan flyttades till services/gemensam/metodiker.mjs och klientens metodiker.ts är en typad återexport, så det finns fortfarande exakt en sanning. M-2 · Högvoltsspärren är ett hinder. Ett nekande svar på behörighets- eller spänningsfrihetsfrågan stoppar metodiken i stället för att räknas som besvarad, och spärrar avslutet på servern. Ett obesvarat säkerhetskrav spärrar också — tystnad är inte ett ja. Reglerna ligger i data så nästa farliga metodik bara behöver en rad. M-3 · Händelser valideras mot schema före skrivning. Loggen är append-only, så en felaktig post kan aldrig rättas — kontrollen måste ske innan, efteråt är det för sent för alltid. Okänd typ avvisas. m-1 · Delningskoden använder förkastningsurval i stället för modulo. m-2 · Id-kollisioner räknas i stället för att passera tyst. 21 nya tester låser varje regel. Ett skräddarsytt test kräver dessutom att varje typ i händelseschemat är klassificerad i delningslistan — en ny typ kan alltså varken läcka eller tappas bort. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt --- .../src/felsokning/__tests__/grind.test.ts | 196 ++++ felsokning/app/src/felsokning/metodik.ts | 41 +- felsokning/app/src/felsokning/metodiker.ts | 905 +----------------- .../app/src/pages/felsokning/ArendeSida.tsx | 13 + felsokning/services/gemensam/grind.mjs | 188 ++++ felsokning/services/gemensam/handelser.mjs | 127 +++ felsokning/services/gemensam/metodiker.mjs | 902 +++++++++++++++++ felsokning/services/plattform/grind.mjs | 1 + felsokning/services/plattform/handelser.mjs | 1 + felsokning/services/plattform/metodiker.mjs | 1 + felsokning/services/plattform/server.mjs | 77 +- 11 files changed, 1563 insertions(+), 889 deletions(-) create mode 100644 felsokning/app/src/felsokning/__tests__/grind.test.ts create mode 100644 felsokning/services/gemensam/grind.mjs create mode 100644 felsokning/services/gemensam/handelser.mjs create mode 100644 felsokning/services/gemensam/metodiker.mjs create mode 120000 felsokning/services/plattform/grind.mjs create mode 120000 felsokning/services/plattform/handelser.mjs create mode 120000 felsokning/services/plattform/metodiker.mjs diff --git a/felsokning/app/src/felsokning/__tests__/grind.test.ts b/felsokning/app/src/felsokning/__tests__/grind.test.ts new file mode 100644 index 0000000..9372768 --- /dev/null +++ b/felsokning/app/src/felsokning/__tests__/grind.test.ts @@ -0,0 +1,196 @@ +// @vitest-environment node +// Revisionens kritiska fynd, låsta som test. +// +// De här reglerna är produktens hela värde: utan dem är loggen en +// signerad behållare för overifierade påståenden, och kvalitetsgrinden +// ett råd. Går något av testerna sönder har den garantin försvunnit — +// och det syns inte i gränssnittet förrän någon bestrider ett ärende. +import { describe, expect, it } from "vitest"; +import { granskaHändelse, tillPost } from "../../../../services/gemensam/handelser.mjs"; +import { SPARRFRAGOR as SPARR_SERVER, grinda, grindaArendetyp } from "../../../../services/gemensam/grind.mjs"; +import { ALLA_METODIKER } from "../../../../services/gemensam/metodiker.mjs"; +import { SPARRFRAGOR } from "../metodik"; + +const GENERISK = ALLA_METODIKER.at(-1)!; +const ANSPRÅK = { sub: "u-1", namn: "Anna Tekniker", org: "o-1", roll: "tekniker" }; + +/** Bygger en logg som passerar allt utom det testet handlar om. */ +function komplettLogg(extra: Record[] = []) { + return [ + { typ: "objekt_identifierat", objekt: { identifierare: "ABC123" } }, + { typ: "historik_kontrollerad", kontrollerad: true }, + { typ: "matarstallning", lage: "ingaende", varde: "14 200" }, + { typ: "matarstallning", lage: "utgaende", varde: "14 205" }, + { typ: "reproducering", status: "ja", beskrivning: "Reproducerat vid 88 km/h." }, + { typ: "felorsak", avvikelse: "Obalans", orsaker: ["Normalt slitage"], underlag: ["Mätvärde"], sakerhet: "hog" }, + { typ: "atgard_utford", text: "Balanserade hjulen." }, + { typ: "kundbeslut", utfall: "godkant", kanal: "Telefon" }, + { typ: "kvalitetskontroll", utfall: "borta", beskrivning: "Provkört, symptomet borta." }, + { typ: "matvarde", beskrivning: "Lufttryck", varde: "2,4" }, + { typ: "foto", beskrivning: "Objektet" }, + { typ: "foto", beskrivning: "Typskylt" }, + ...GENERISK.steg.flatMap((s) => + (s.kontroller ?? []).map((k) => ({ + typ: "kontroll_utford", + stegId: s.id, + kontrollId: k.id, + text: k.text, + resultat: "Utförd och dokumenterad.", + })), + ), + ...extra, + ]; +} + +describe("C-1 · härkomsten sätts av servern, inte av anroparen", () => { + it("kastar klientens användarnamn och använder den verifierade identiteten", () => { + const { post } = tillPost( + { id: "h1", anvandare: "Någon annan", tidpunkt: "2020-01-01T00:00:00.000Z", handelse: { typ: "observation", text: "x" } }, + ANSPRÅK, + new Date("2026-08-05T10:00:00.000Z"), + ); + expect(post.anvandare).toBe("Anna Tekniker"); + expect(post.handelse.anvandarId).toBe("u-1"); + }); + + it("sätter tidpunkten från serverns klocka men bevarar klientens", () => { + const nu = new Date("2026-08-05T10:00:00.000Z"); + const { post } = tillPost( + { id: "h1", tidpunkt: "2020-01-01T00:00:00.000Z", handelse: { typ: "observation", text: "x" } }, + ANSPRÅK, + nu, + ); + // Backdatering är poängen med angreppet — serverns tid vinner. + expect(post.tidpunkt).toBe(nu.toISOString()); + // Men offline-arbetets tid får inte försvinna; glappet ska synas. + expect(post.handelse.registrerad_tidpunkt).toBe("2020-01-01T00:00:00.000Z"); + }); +}); + +describe("M-3 · formen kontrolleras före skrivning", () => { + it("avvisar okända händelsetyper — append-only gör felet permanent", () => { + expect(granskaHändelse({ typ: "pahittad", text: "x" })).toMatch(/okänd händelsetyp/); + expect(granskaHändelse(null)).toBeTruthy(); + expect(granskaHändelse({ typ: "observation" })).toMatch(/observation\.text/); + }); + + it("hypotesen kan aldrig anta hög tillförlitlighet", () => { + expect(granskaHändelse({ typ: "hypotes", text: "x", niva: "hog" })).toBeTruthy(); + expect(granskaHändelse({ typ: "hypotes", text: "x", niva: "medel" })).toBeNull(); + }); + + it("avvisar id med otillåtna tecken", () => { + expect(tillPost({ id: "../etc/passwd", handelse: { typ: "observation", text: "x" } }, ANSPRÅK).fel).toBeTruthy(); + }); + + it("varje typ i schemat är känd av delningslistan — annars läcker eller tappas den", async () => { + const { HÄNDELSESCHEMA } = await import("../../../../services/gemensam/handelser.mjs"); + const { DELBART_KUND, ENDAST_INTERNT } = await import("../../../../services/plattform/server.mjs"); + for (const typ of Object.keys(HÄNDELSESCHEMA)) { + expect([...DELBART_KUND, ...ENDAST_INTERNT], typ).toContain(typ); + } + }); +}); + +describe("C-2 · kvalitetsgrinden spärrar på servern", () => { + it("en tom logg kan inte avslutas", () => { + const hinder = grinda([], GENERISK).map((h) => h.id); + expect(hinder).toContain("objekt"); + expect(hinder).toContain("reproducering"); + expect(hinder).toContain("felorsak"); + expect(hinder).toContain("evidens"); + }); + + it("ett komplett ärende passerar", () => { + expect(grinda(komplettLogg(), GENERISK)).toEqual([]); + }); + + it.each([ + ["objekt_identifierat", "objekt"], + ["reproducering", "reproducering"], + ["felorsak", "felorsak"], + ["kvalitetskontroll", "kvalitetskontroll"], + ])("saknad %s spärrar avslutet", (typ, id) => { + const logg = komplettLogg().filter((h) => h.typ !== typ); + expect(grinda(logg, GENERISK).map((h) => h.id)).toContain(id); + }); + + it("arbete utfört trots avböjt förslag är ett hårt fel, inte en varning", () => { + const logg = komplettLogg().map((h) => + h.typ === "kundbeslut" ? { ...h, utfall: "avbojt" } : h, + ); + expect(grinda(logg, GENERISK).map((h) => h.id)).toContain("avbojt_men_utfort"); + }); + + it("nekad historikkontroll utan motivering spärrar; med motivering passerar", () => { + const utan = komplettLogg().map((h) => + h.typ === "historik_kontrollerad" ? { typ: "historik_kontrollerad", kontrollerad: false } : h, + ); + expect(grinda(utan, GENERISK).map((h) => h.id)).toContain("historik"); + + const med = komplettLogg().map((h) => + h.typ === "historik_kontrollerad" + ? { typ: "historik_kontrollerad", kontrollerad: false, kommentar: "Fordonet nytt hos oss." } + : h, + ); + expect(grinda(med, GENERISK)).toEqual([]); + }); + + it("en kontroll utan resultat räknas inte som utförd", () => { + const logg = komplettLogg().map((h) => + h.typ === "kontroll_utford" ? { ...h, resultat: "" } : h, + ); + expect(grinda(logg, GENERISK).map((h) => h.id)).toContain("metodik_kontroller"); + }); + + it("ett dokumenterat undantag ersätter evidens — men bara med orsak", () => { + const utan = komplettLogg().map((h) => + h.typ === "kontroll_utford" ? { ...h, resultat: "", undantag: " " } : h, + ); + expect(grinda(utan, GENERISK).map((h) => h.id)).toContain("metodik_kontroller"); + + const med = komplettLogg().map((h) => + h.typ === "kontroll_utford" ? { ...h, resultat: "", undantag: "Lyft upptaget hela dagen." } : h, + ); + expect(grinda(med, GENERISK)).toEqual([]); + }); +}); + +describe("M-2 · högvoltsspärren är ett hinder, inte en varning", () => { + const HOGVOLT = ALLA_METODIKER.find((m) => m.id === "hogvolt")!; + + it("klientens och serverns spärrlistor är identiska", () => { + expect(Object.keys(SPARRFRAGOR).sort()).toEqual(Object.keys(SPARR_SERVER).sort()); + }); + + it("ett nekande svar på behörighetsfrågan spärrar avslutet", () => { + const logg = [ + ...komplettLogg(), + { typ: "fraga_besvarad", stegId: "sakerhet", frageId: "behorighet", fraga: "?", svar: "Nej" }, + { typ: "fraga_besvarad", stegId: "sakerhet", frageId: "avstangt", fraga: "?", svar: "Ja" }, + ]; + expect(grinda(logg, HOGVOLT).map((h) => h.id)).toContain("sparr_behorighet"); + }); + + it("ett obesvarat säkerhetskrav spärrar också — tystnad är inte ett ja", () => { + expect(grinda(komplettLogg(), HOGVOLT).map((h) => h.id)).toContain("sparr_behorighet"); + }); +}); + +describe("ärendetypens regelpaket", () => { + const paket = { arendetyper: { Garanti: { krav: [{ typ: "miltal" }, { typ: "historik" }] } } }; + + it("garantiärende kräver miltal och historik", () => { + const utan = [{ typ: "arendetyp_satt", arendetyp: "Garanti" }]; + expect(grindaArendetyp(utan, paket).map((h) => h.id)).toEqual([ + "arendetyp_miltal", + "arendetyp_historik", + ]); + }); + + it("en okänd kravtyp spärrar i stället för att tolkas som uppfylld", () => { + const trasigt = { arendetyper: { Garanti: { krav: [{ typ: "hittepa" }] } } }; + const logg = [{ typ: "arendetyp_satt", arendetyp: "Garanti" }]; + expect(grindaArendetyp(logg, trasigt)[0].rubrik).toMatch(/Okänt krav/); + }); +}); diff --git a/felsokning/app/src/felsokning/metodik.ts b/felsokning/app/src/felsokning/metodik.ts index 9402d7a..30fe11a 100644 --- a/felsokning/app/src/felsokning/metodik.ts +++ b/felsokning/app/src/felsokning/metodik.ts @@ -146,21 +146,60 @@ export interface NastaSteg { fraga?: Fraga; kontroll?: Kontroll; klart: boolean; + /** + * Satt när metodiken är stoppad av en säkerhetsspärr. Metodiken går + * inte vidare förrän förutsättningen är uppfylld — svaret är inte ett + * varningsmeddelande utan ett hinder. + */ + sparr?: { orsak: string; atgard: string }; } +/** + * Frågor som måste besvaras jakande innan arbetet får fortsätta. + * + * Revisionen (docs/QUALITY-AUDIT.md, M-2) fann att högvoltssteget gick + * att svara nej på och ändå fortsätta: `nastaSteg` gick vidare oavsett + * svar. En tekniker som ärligt uppgav sig sakna behörighet leddes alltså + * in i nästa steg av en procedur som kan döda. + * + * Regeln bor i data, inte i en if-sats i vyn, så att den gäller överallt + * där metodiken körs — och så att nästa farliga metodik bara behöver en + * rad här. + */ +export const SPARRFRAGOR: Record = { + "hogvolt/sakerhet/behorighet": { + orsak: "Arbete på högvoltsystem kräver dokumenterad behörighet.", + atgard: "Lämna över ärendet till en behörig tekniker. Arbetet får inte påbörjas utan behörighet.", + }, + "hogvolt/sakerhet/avstangt": { + orsak: "Fordonet är inte spänningslöst enligt tillverkarens rutin.", + atgard: "Gör fordonet spänningslöst enligt tillverkarens anvisning innan något ingrepp påbörjas.", + }, +}; + // Nästa steg = första obesvarade frågan, därefter första ej utförda kontrollen, // i metodikens ordning. Helt härlett ur händelseloggen. export function nastaSteg(arende: Arende, metodik: Metodik): NastaSteg { const besvarade = new Set(); + const svar = new Map(); const utforda = new Set(); for (const post of arende.handelser) { const h = post.handelse; - if (h.typ === "fraga_besvarad") besvarade.add(`${h.stegId}/${h.frageId}`); + if (h.typ === "fraga_besvarad") { + besvarade.add(`${h.stegId}/${h.frageId}`); + svar.set(`${h.stegId}/${h.frageId}`, h.svar); + } if (h.typ === "kontroll_utford") utforda.add(`${h.stegId}/${h.kontrollId}`); } for (const steg of metodik.steg) { for (const fraga of steg.fragor ?? []) { if (!besvarade.has(`${steg.id}/${fraga.id}`)) return { steg, fraga, klart: false }; + // Säkerhetsspärr: ett nekande svar stoppar metodiken här i stället + // för att räknas som besvarat och släppa fram nästa steg. + const sparr = SPARRFRAGOR[`${metodik.id}/${steg.id}/${fraga.id}`]; + if (sparr && svar.get(`${steg.id}/${fraga.id}`) !== "Ja") { + return { steg, fraga, klart: false, sparr }; + } } for (const kontroll of steg.kontroller ?? []) { if (!utforda.has(`${steg.id}/${kontroll.id}`)) return { steg, kontroll, klart: false }; diff --git a/felsokning/app/src/felsokning/metodiker.ts b/felsokning/app/src/felsokning/metodiker.ts index ca0e6f6..8f528d1 100644 --- a/felsokning/app/src/felsokning/metodiker.ts +++ b/felsokning/app/src/felsokning/metodiker.ts @@ -1,11 +1,11 @@ -// Metodikbiblioteket. +// Metodikbiblioteket — typad återexport av den delade datan. // -// En metodik är inte en checklista över vad som brukar vara fel — det -// vore att gissa åt teknikern. Den är en ordning för att utesluta: -// symptomet verifieras först, sedan det billiga och synliga, sedan det -// som kräver mätning, sist det som kräver demontering. +// Innehållet ligger i services/gemensam/metodiker.mjs eftersom servern +// måste kunna utvärdera kvalitetsgrindens metodikrader. Grinden fanns +// tidigare bara i webbläsaren, vilket gjorde hela ECM till ett råd i +// stället för en spärr (docs/QUALITY-AUDIT.md, C-2). // -// Tre regler gäller alla metodiker här: +// Tre regler gäller alla metodiker där: // // 1. Varje kontroll har ett minimikrav (mätvärde, foto eller // observation). En kryssruta är inte evidens. @@ -19,881 +19,24 @@ // system systematiskt och låta GENERISK vara ett strukturellt komplett // skyddsnät: den fungerar även för det ingen förutsett. +import * as delade from "../../../services/gemensam/metodiker.mjs"; import type { Metodik } from "./metodik"; -export const VIBRATION: Metodik = { - id: "vibration", - namn: "Vibration under körning", - omrade: "Hjul och balans", - nyckelord: ["vibration", "vibrer", "skak", "obalans", "ojämn gång", "rattvibration"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { id: "hastighetsberoende", text: "Är vibrationen hastighetsberoende?", svarstyp: "janej" }, - { id: "var", text: "Var känns vibrationen?", svarstyp: "val", val: ["I ratten", "I stolen", "I hela bilen"] }, - { - id: "nar", - text: "När uppstår vibrationen?", - svarstyp: "val", - val: ["Vid acceleration", "Vid jämn fart", "Vid inbromsning", "Alltid under körning"], - }, - { id: "intervall", text: "Försvinner den över eller under ett visst hastighetsintervall?", svarstyp: "text" }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - beskrivning: "Fotografera samtliga hjul. Skilj på observation och slutsats.", - kontroller: [ - { id: "foto_vf", text: "Fotografera vänster framhjul", krav: "foto" }, - { id: "foto_hf", text: "Fotografera höger framhjul", krav: "foto" }, - { id: "foto_vb", text: "Fotografera vänster bakhjul", krav: "foto" }, - { id: "foto_hb", text: "Fotografera höger bakhjul", krav: "foto" }, - { id: "dot", text: "Dokumentera DOT-/tillverkningsdatum", krav: "kommentar" }, - { id: "slitage", text: "Kontrollera däckslitage och slitagemönster", krav: "kommentar" }, - ], - }, - { - id: "kontroller", - rubrik: "Mätningar", - kontroller: [ - { id: "lufttryck", text: "Kontrollera lufttryck", krav: "matvarde" }, - { id: "hjulmoment", text: "Kontrollera hjulmoment", krav: "matvarde" }, - { id: "kast", text: "Kontrollera radial- och sidokast", krav: "matvarde" }, - { id: "balansering", text: "Kontrollera hjulbalansering", krav: "kommentar" }, - { id: "bussningar", text: "Kontrollera bussningar och leder", krav: "kommentar" }, - ], - }, - { - id: "provkorning", - rubrik: "Provkörning", - beskrivning: "Verifiera under provkörning:", - kontroller: [ - { id: "pk_hastighet", text: "Hastighet där vibration uppstår", krav: "kommentar" }, - { id: "pk_acc", text: "Förändring vid acceleration", krav: "kommentar" }, - { id: "pk_motorbroms", text: "Förändring vid motorbroms", krav: "kommentar" }, - { id: "pk_kurva", text: "Förändring vid kurvtagning", krav: "kommentar" }, - { id: "pk_var", text: "Om vibration känns i ratt eller kaross", krav: "kommentar" }, - ], - }, - ], -}; +export const VIBRATION = delade.VIBRATION as Metodik; +export const BROMSAR = delade.BROMSAR as Metodik; +export const STYRNING_FJADRING = delade.STYRNING_FJADRING as Metodik; +export const ELSYSTEM = delade.ELSYSTEM as Metodik; +export const START_LADDNING = delade.START_LADDNING as Metodik; +export const MOTOR_DRIFT = delade.MOTOR_DRIFT as Metodik; +export const KYLSYSTEM = delade.KYLSYSTEM as Metodik; +export const DRIVLINA = delade.DRIVLINA as Metodik; +export const AVGAS_EMISSION = delade.AVGAS_EMISSION as Metodik; +export const KLIMAT = delade.KLIMAT as Metodik; +export const HOGVOLT = delade.HOGVOLT as Metodik; +export const DIAGNOS_NATVERK = delade.DIAGNOS_NATVERK as Metodik; +export const LACKAGE = delade.LACKAGE as Metodik; +export const MISSLJUD = delade.MISSLJUD as Metodik; +export const ADAS = delade.ADAS as Metodik; +export const GENERISK = delade.GENERISK as Metodik; -export const ELSYSTEM: Metodik = { - id: "elsystem", - namn: "Elsystem och strömförsörjning", - omrade: "El", - nyckelord: ["relä", "rela", "säkring", "elektr", "spänning", "volt", "lampa", "belysning", "ström", "kortslut", "kabel", "kabla", "kontaktdon", "jordfel"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { id: "reproducerbart", text: "Går felet att reproducera?", svarstyp: "janej" }, - { - id: "omfattning", - text: "Är funktionen helt död eller delvis fungerande?", - svarstyp: "val", - val: ["Helt död", "Delvis fungerande", "Intermittent"], - }, - { id: "funktion", text: "Vilken funktion saknas eller felar?", svarstyp: "text" }, - { id: "forandring", text: "Har något åtgärdats eller förändrats nyligen?", svarstyp: "text" }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - kontroller: [ - { id: "foto_komponent", text: "Fotografera komponenten och dess anslutningar", krav: "foto" }, - { id: "skador", text: "Kontrollera synliga skador, korrosion och lösa kontaktdon", krav: "kommentar" }, - ], - }, - { - id: "matningar", - rubrik: "Mätningar", - beskrivning: "Mät under belastning. En spänning utan last säger nästan ingenting om en dålig förbindelse.", - kontroller: [ - { id: "batterispanning", text: "Mät batterispänning", krav: "matvarde" }, - { id: "sakringar", text: "Kontrollera berörda säkringar", krav: "kommentar" }, - { id: "matningsspanning", text: "Mät matningsspänning vid komponenten under last", krav: "matvarde" }, - { id: "jord", text: "Mät spänningsfall mot jord under last", krav: "matvarde" }, - ], - }, - { - id: "rela", - rubrik: "Relä och styrsignal", - kontroller: [ - { id: "rela_klick", text: "Kontrollera om reläet klickar vid aktivering", krav: "kommentar" }, - { id: "styrsignal", text: "Mät styrsignal till reläet", krav: "matvarde" }, - ], - }, - { - id: "funktionstest", - rubrik: "Funktionstest", - kontroller: [{ id: "test", text: "Genomför funktionstest och dokumentera resultatet", krav: "kommentar" }], - }, - ], -}; - -export const START_LADDNING: Metodik = { - id: "start_laddning", - namn: "Start- och laddningssystem", - omrade: "El", - nyckelord: ["startar inte", "startproblem", "startmotor", "generator", "laddning", "batteriet dör", "urladdat", "batterilampa", "drar inte runt", "klick vid start"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "beteende", - text: "Vad händer vid startförsök?", - svarstyp: "val", - val: ["Ingenting alls", "Ett klick", "Drar runt långsamt", "Drar runt normalt men tänder inte", "Startar ibland"], - }, - { id: "kallt_varmt", text: "Uppträder felet kallt, varmt eller alltid?", svarstyp: "val", val: ["Kallt", "Varmt", "Alltid"] }, - { id: "stillestand", text: "Hur länge har fordonet stått mellan försöken?", svarstyp: "text" }, - { id: "laddat", text: "Har batteriet laddats eller bytts nyligen?", svarstyp: "text" }, - ], - }, - { - id: "batteri", - rubrik: "Batteriets kondition", - beskrivning: "Ett svagt batteri ger följdfel i hela fordonet. Uteslut det först.", - kontroller: [ - { id: "vilospanning", text: "Mät vilospänning efter minst en timmes vila", krav: "matvarde" }, - { id: "batteritest", text: "Belastningstest — fotografera testarens resultat", krav: "foto" }, - { id: "poler", text: "Kontrollera polskor och jordflätor", krav: "kommentar" }, - ], - }, - { - id: "start", - rubrik: "Startsystem", - kontroller: [ - { id: "startspanning", text: "Mät spänning vid startmotorn under startförsök", krav: "matvarde" }, - { id: "spanningsfall_plus", text: "Mät spänningsfall i plusledningen under start", krav: "matvarde" }, - { id: "spanningsfall_jord", text: "Mät spänningsfall i jordledningen under start", krav: "matvarde" }, - { id: "startsignal", text: "Kontrollera styrsignal till startrelä", krav: "matvarde" }, - ], - }, - { - id: "laddning", - rubrik: "Laddningssystem", - kontroller: [ - { id: "laddspanning", text: "Mät laddspänning vid tomgång och vid förhöjt varvtal", krav: "matvarde" }, - { id: "rippel", text: "Mät rippel från generatorn", krav: "matvarde" }, - { id: "rem", text: "Kontrollera drivrem och spänning", krav: "kommentar" }, - ], - }, - { - id: "krypstrom", - rubrik: "Krypström", - beskrivning: "Endast relevant när batteriet laddar ur under stillestånd.", - kontroller: [ - { id: "vilostrom", text: "Mät viloström efter att fordonet somnat", krav: "matvarde" }, - { id: "sakringsdrag", text: "Lokalisera krets genom successiv säkringsurtagning", krav: "kommentar" }, - ], - }, - ], -}; - -export const MOTOR_DRIFT: Metodik = { - id: "motor_drift", - namn: "Motorgång och effekt", - omrade: "Motor", - nyckelord: ["ryck", "tappar kraft", "effektbrist", "misständ", "ojämn tomgång", "tomgång", "motorlampa", "kraftlös", "hack", "går på tre"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "nar", - text: "När uppträder felet?", - svarstyp: "val", - val: ["Vid kallstart", "Vid tomgång", "Under belastning", "Vid visst varvtal", "Alltid"], - }, - { id: "varvtal", text: "Vid vilket varvtal eller vilken last?", svarstyp: "text" }, - { id: "varningslampa", text: "Lyser eller blinkar motorlampan?", svarstyp: "val", val: ["Lyser fast", "Blinkar", "Slocknad"] }, - { id: "brankslebyte", text: "Har bränsle tankats eller service utförts strax innan?", svarstyp: "text" }, - ], - }, - { - id: "felkoder", - rubrik: "Felkoder och mätdata", - beskrivning: "Läs innan något demonteras. En felkod pekar på en krets, inte på en trasig komponent.", - kontroller: [ - { id: "dtc", text: "Läs felkoder — fotografera diagnosskärmen", krav: "foto" }, - { id: "frysdata", text: "Dokumentera frysramsdata för koden", krav: "kommentar" }, - { id: "misstandning", text: "Läs misständningsräknare per cylinder", krav: "matvarde" }, - { id: "branslekorrigering", text: "Läs kort- och långtidsbränslekorrigering", krav: "matvarde" }, - ], - }, - { - id: "mekanik", - rubrik: "Motorns grundkondition", - beskrivning: "Utan grundkondition är alla givarvärden svåra att tolka.", - kontroller: [ - { id: "kompression", text: "Mät kompression eller relativ kompression", krav: "matvarde" }, - { id: "vevhustryck", text: "Kontrollera vevhustryck", krav: "kommentar" }, - { id: "insugslackage", text: "Kontrollera insugssystemet för falskluft", krav: "kommentar" }, - ], - }, - { - id: "tandning_bransle", - rubrik: "Tändning och bränsle", - kontroller: [ - { id: "tandstift", text: "Kontrollera tändstift — fotografera samtliga", krav: "foto" }, - { id: "spolar", text: "Mät tändspolarnas resistans eller byt mellan cylindrar", krav: "matvarde" }, - { id: "bransletryck", text: "Mät bränsletryck under belastning", krav: "matvarde" }, - { id: "insprutare", text: "Kontrollera insprutarnas returmängd eller styrning", krav: "matvarde" }, - ], - }, - { - id: "provkorning", - rubrik: "Provkörning med loggning", - kontroller: [ - { id: "logg", text: "Logga mätdata under körning där felet uppträder", krav: "foto" }, - { id: "reproducerat", text: "Dokumentera under vilka förhållanden felet uppträdde", krav: "kommentar" }, - ], - }, - ], -}; - -export const BROMSAR: Metodik = { - id: "bromsar", - namn: "Bromssystem", - omrade: "Chassi", - nyckelord: ["broms", "bromspedal", "skevhet", "gnisslar vid inbromsning", "abs", "bromsvätska", "handbroms", "drar snett vid inbromsning"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "karaktar", - text: "Hur yttrar sig felet?", - svarstyp: "val", - val: ["Skevhet/pulsering", "Missljud", "Lång pedalväg", "Mjuk pedal", "Drar åt sidan", "Bromsen ligger på"], - }, - { id: "nar", text: "Vid vilken hastighet och bromskraft?", svarstyp: "text" }, - { id: "varningslampa", text: "Lyser broms- eller ABS-varning?", svarstyp: "janej" }, - { id: "senaste", text: "När byttes bromskomponenter senast?", svarstyp: "text" }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - kontroller: [ - { id: "foto_belagg", text: "Fotografera belägg och skivor på samtliga hjul", krav: "foto" }, - { id: "belaggtjocklek", text: "Mät beläggtjocklek", krav: "matvarde" }, - { id: "skivtjocklek", text: "Mät skivtjocklek och jämför med minmått", krav: "matvarde" }, - { id: "lackage", text: "Kontrollera slangar, rör och ok för läckage", krav: "kommentar" }, - ], - }, - { - id: "matningar", - rubrik: "Mätningar", - kontroller: [ - { id: "skivkast", text: "Mät skivkast med mätklocka", krav: "matvarde" }, - { id: "tjockleksvariation", text: "Mät tjockleksvariation på skivan", krav: "matvarde" }, - { id: "vatska", text: "Mät bromsvätskans kokpunkt eller vatteninnehåll", krav: "matvarde" }, - { id: "okens_rorlighet", text: "Kontrollera okens och styrbultarnas rörlighet", krav: "kommentar" }, - ], - }, - { - id: "system", - rubrik: "System och ABS", - kontroller: [ - { id: "dtc", text: "Läs felkoder ur ABS-styrenheten", krav: "foto" }, - { id: "hjulgivare", text: "Kontrollera hjulhastighetsgivarnas signal", krav: "matvarde" }, - { id: "bromsprov", text: "Utför bromsprov i provbänk — fotografera protokollet", krav: "foto" }, - ], - }, - ], -}; - -export const STYRNING_FJADRING: Metodik = { - id: "styrning_fjadring", - namn: "Styrning och fjädring", - omrade: "Chassi", - nyckelord: ["drar åt", "styrning", "ratt", "glapp", "stötdämpar", "fjäder", "fjädr", "hjulinställning", "spårar", "gungar", "knäpper i kurva", "servostyrning"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "karaktar", - text: "Hur yttrar sig felet?", - svarstyp: "val", - val: ["Drar åt sidan", "Glapp i ratten", "Missljud", "Dålig vägkänsla", "Ojämnt däckslitage", "Gungar/studsar"], - }, - { id: "underlag", text: "Uppträder det på visst underlag eller i viss kurva?", svarstyp: "text" }, - { id: "handelse", text: "Har fordonet kört på något eller varit i kontakt med trottoarkant?", svarstyp: "text" }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - kontroller: [ - { id: "foto_slitage", text: "Fotografera däckens slitagemönster", krav: "foto" }, - { id: "dampare", text: "Kontrollera dämpare för läckage", krav: "kommentar" }, - { id: "damask", text: "Kontrollera damasker på styrleder och drivknutar", krav: "kommentar" }, - ], - }, - { - id: "glapp", - rubrik: "Glappkontroll", - beskrivning: "Utförs med hjulet avlastat. Dokumentera var glappet finns, inte bara att det finns.", - kontroller: [ - { id: "hjullager", text: "Kontrollera hjullagerspel", krav: "kommentar" }, - { id: "styrleder", text: "Kontrollera styr- och bärleder", krav: "kommentar" }, - { id: "bussningar", text: "Kontrollera bussningar", krav: "kommentar" }, - { id: "kuggstang", text: "Kontrollera kuggstång och styrstagsändar", krav: "kommentar" }, - ], - }, - { - id: "installning", - rubrik: "Hjulinställning", - kontroller: [ - { id: "matning", text: "Mät hjulinställning — fotografera protokollet före justering", krav: "foto" }, - { id: "avvikelse", text: "Dokumentera avvikelser mot tillverkarens värden", krav: "matvarde" }, - ], - }, - ], -}; - -export const KYLSYSTEM: Metodik = { - id: "kylsystem", - namn: "Kylsystem och överhettning", - omrade: "Motor", - nyckelord: ["överhett", "koka", "kylvätska", "temperatur", "termostat", "kylare", "vattenpump", "värme i kupén", "expansionskärl"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "nar", - text: "När stiger temperaturen?", - svarstyp: "val", - val: ["Vid tomgång", "I låg fart", "I hög fart", "Vid belastning/backe", "Alltid"], - }, - { id: "kylvatskeforlust", text: "Förloras kylvätska? Hur mycket och hur ofta?", svarstyp: "text" }, - { id: "varme", text: "Fungerar kupévärmen?", svarstyp: "janej" }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - kontroller: [ - { id: "niva", text: "Kontrollera kylvätskenivå kall — fotografera expansionskärlet", krav: "foto" }, - { id: "lackage", text: "Kontrollera synligt läckage vid slangar, kylare och pump", krav: "kommentar" }, - { id: "kylarpaket", text: "Kontrollera kylarpaketet för igensättning", krav: "foto" }, - ], - }, - { - id: "matningar", - rubrik: "Mätningar", - kontroller: [ - { id: "trycktest", text: "Trycktesta kylsystemet", krav: "matvarde" }, - { id: "lock", text: "Trycktesta expansionskärlets lock", krav: "matvarde" }, - { id: "temperatur", text: "Mät temperatur in och ut ur kylaren med IR-termometer", krav: "matvarde" }, - { id: "flakt", text: "Kontrollera att kylfläkten startar vid rätt temperatur", krav: "kommentar" }, - { id: "givare", text: "Jämför styrenhetens temperaturvärde med uppmätt", krav: "matvarde" }, - ], - }, - { - id: "packning", - rubrik: "Topplockspackning", - beskrivning: "Görs när kylvätska förloras utan yttre läckage.", - kontroller: [ - { id: "avgastest", text: "Avgastest i kylvätskan (CO2-test)", krav: "foto" }, - { id: "kompression_kyl", text: "Kontrollera trycksättning av kylsystemet vid motorgång", krav: "kommentar" }, - ], - }, - ], -}; - -export const DRIVLINA: Metodik = { - id: "drivlina", - namn: "Växellåda och drivlina", - omrade: "Drivlina", - nyckelord: ["växellåda", "växlar", "koppling", "automatlåda", "rycker vid växling", "slir", "drivknut", "kardan", "differential", "kärvar i växeln"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "karaktar", - text: "Hur yttrar sig felet?", - svarstyp: "val", - val: ["Slirar", "Rycker vid växling", "Går inte i växel", "Missljud", "Vibration vid acceleration", "Läckage"], - }, - { id: "vaxel", text: "Vilken växel eller vilket varvtal?", svarstyp: "text" }, - { id: "kall_varm", text: "Kall eller varm låda?", svarstyp: "val", val: ["Kall", "Varm", "Ingen skillnad"] }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - kontroller: [ - { id: "olja", text: "Kontrollera oljenivå och oljans skick — fotografera", krav: "foto" }, - { id: "lackage", text: "Kontrollera läckage vid tätningar och sump", krav: "kommentar" }, - { id: "fasten", text: "Kontrollera växellådsfästen", krav: "kommentar" }, - ], - }, - { - id: "matningar", - rubrik: "Mätningar och avläsning", - kontroller: [ - { id: "dtc", text: "Läs felkoder ur växellådsstyrenheten", krav: "foto" }, - { id: "adaptioner", text: "Dokumentera adaptionsvärden och kopplingsslitage", krav: "matvarde" }, - { id: "oljetemp", text: "Mät oljetemperatur under provkörning", krav: "matvarde" }, - ], - }, - { - id: "provkorning", - rubrik: "Provkörning", - kontroller: [ - { id: "vaxlingspunkter", text: "Dokumentera växlingspunkter och beteende", krav: "kommentar" }, - { id: "belastning", text: "Provkör under belastning där felet uppträder", krav: "kommentar" }, - ], - }, - ], -}; - -export const AVGAS_EMISSION: Metodik = { - id: "avgas_emission", - namn: "Avgassystem och emissioner", - omrade: "Motor", - nyckelord: ["partikelfilt", "dpf", "adblue", "katalysator", "lambda", "avgas", "emission", "rökutveckling", "svart rök", "blå rök", "os", "egr", "regenerer", "utsläpp"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "karaktar", - text: "Hur yttrar sig felet?", - svarstyp: "val", - val: ["Varningslampa", "Rök", "Effektbegränsning", "Lukt", "Underkänd besiktning"], - }, - { id: "rokfarg", text: "Om rök — vilken färg och när?", svarstyp: "text" }, - { id: "korprofil", text: "Beskriv fordonets körprofil (mest kort- eller långkörning)?", svarstyp: "text" }, - ], - }, - { - id: "avlasning", - rubrik: "Avläsning", - kontroller: [ - { id: "dtc", text: "Läs felkoder — fotografera diagnosskärmen", krav: "foto" }, - { id: "sotmangd", text: "Läs beräknad och uppmätt sotmängd i partikelfiltret", krav: "matvarde" }, - { id: "regenerering", text: "Dokumentera antal och utfall av senaste regenereringar", krav: "kommentar" }, - { id: "lambda", text: "Läs lambdagivarnas signaler före och efter katalysator", krav: "matvarde" }, - ], - }, - { - id: "matningar", - rubrik: "Mätningar", - kontroller: [ - { id: "mottryck", text: "Mät avgasmottryck", krav: "matvarde" }, - { id: "differenstryck", text: "Mät differenstryck över partikelfiltret", krav: "matvarde" }, - { id: "adblue_niva", text: "Kontrollera AdBlue-nivå och kvalitet där sådant finns", krav: "matvarde" }, - { id: "egr", text: "Kontrollera EGR-ventilens funktion och läge", krav: "matvarde" }, - ], - }, - { - id: "orsak", - rubrik: "Bakomliggande orsak", - beskrivning: "Ett igensatt filter är oftast en följd, inte orsaken. Dokumentera vad som skapade sotet.", - kontroller: [ - { id: "grundorsak", text: "Kontrollera insprutning, turbo och luftflöde", krav: "kommentar" }, - { id: "korstrackor", text: "Bedöm om körprofilen tillåter regenerering", krav: "kommentar" }, - ], - }, - ], -}; - -export const KLIMAT: Metodik = { - id: "klimat", - namn: "Klimatanläggning", - omrade: "Komfort", - nyckelord: ["ac", "luftkonditionering", "klimatanläggning", "kyler inte", "kompressor", "köldmedi", "imma", "immig", "fläkt", "lukt i kupén", "värmepaket"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "karaktar", - text: "Hur yttrar sig felet?", - svarstyp: "val", - val: ["Kyler inte alls", "Kyler dåligt", "Kyler ibland", "Missljud", "Lukt", "Imma på rutorna"], - }, - { id: "sidor", text: "Skiljer det mellan höger och vänster sida?", svarstyp: "janej" }, - { id: "service", text: "När utfördes AC-service senast?", svarstyp: "text" }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - kontroller: [ - { id: "kondensor", text: "Fotografera kondensorn — kontrollera igensättning och skador", krav: "foto" }, - { id: "kupefilter", text: "Kontrollera kupéfiltret", krav: "foto" }, - { id: "kompressor_rem", text: "Kontrollera kompressorns drivning och magnetkoppling", krav: "kommentar" }, - ], - }, - { - id: "matningar", - rubrik: "Mätningar", - kontroller: [ - { id: "tryck", text: "Mät hög- och lågtryck vid känd omgivningstemperatur", krav: "matvarde" }, - { id: "utblastemp", text: "Mät utblåstemperatur vid mittre munstycke", krav: "matvarde" }, - { id: "mangd", text: "Väg upptagen köldmediemängd vid tömning", krav: "matvarde" }, - { id: "lackagesok", text: "Läcksök med spårgas eller UV", krav: "kommentar" }, - ], - }, - { - id: "styrning", - rubrik: "Styrning", - kontroller: [ - { id: "dtc", text: "Läs felkoder ur klimatstyrenheten", krav: "foto" }, - { id: "spjall", text: "Kontrollera spjällmotorernas rörelse", krav: "kommentar" }, - ], - }, - ], -}; - -export const HOGVOLT: Metodik = { - id: "hogvolt", - namn: "Högvoltsystem — elbil och hybrid", - omrade: "Högvolt", - nyckelord: ["högvolt", "elbil", "hybrid", "traktionsbatteri", "hv", "laddning av bil", "räckvidd", "laddbox", "orange kabel", "sköldpadda"], - steg: [ - { - id: "sakerhet", - rubrik: "Säkerhet — utförs först", - beskrivning: - "Högvoltsystem kan vara livsfarliga. Inget arbete på systemet påbörjas innan stegen nedan är dokumenterade. Kräver behörighet för arbete på högvoltsystem.", - fragor: [ - { id: "behorighet", text: "Har du behörighet för arbete på högvoltsystem?", svarstyp: "janej" }, - { id: "avstangt", text: "Är fordonet spänningslöst enligt tillverkarens rutin?", svarstyp: "janej" }, - ], - kontroller: [ - { id: "service_disconnect", text: "Dokumentera att servicebrytaren är urtagen och säkrad", krav: "foto" }, - { id: "vantetid", text: "Dokumentera väntetid enligt tillverkarens anvisning", krav: "kommentar" }, - { id: "spanningsfrihet", text: "Mät och dokumentera spänningsfrihet före ingrepp", krav: "matvarde" }, - { id: "skyddsutrustning", text: "Dokumentera använd skyddsutrustning och avspärrning", krav: "foto" }, - ], - }, - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "karaktar", - text: "Hur yttrar sig felet?", - svarstyp: "val", - val: ["Laddar inte", "Kortare räckvidd", "Effektbegränsning", "Varningsmeddelande", "Fordonet startar inte"], - }, - { id: "laddtyp", text: "Vid vilken laddning uppträder felet (AC, DC, vilken laddpunkt)?", svarstyp: "text" }, - { id: "temperatur", text: "Vid vilken omgivnings- och batteritemperatur?", svarstyp: "text" }, - ], - }, - { - id: "avlasning", - rubrik: "Avläsning", - beskrivning: "Utförs med diagnosverktyg — inga ingrepp i systemet krävs.", - kontroller: [ - { id: "dtc", text: "Läs felkoder ur samtliga högvoltstyrenheter", krav: "foto" }, - { id: "soh", text: "Läs batteriets hälsa (SOH) och laddnivå (SOC)", krav: "matvarde" }, - { id: "cellspanning", text: "Läs cellspänningar och dokumentera största avvikelse", krav: "matvarde" }, - { id: "isolationsvarde", text: "Läs systemets isolationsvärde", krav: "matvarde" }, - { id: "temperaturer", text: "Läs batteriets temperaturgivare", krav: "matvarde" }, - ], - }, - { - id: "laddning", - rubrik: "Laddkedjan", - kontroller: [ - { id: "laddkabel", text: "Kontrollera laddkabel och laddkontakt — fotografera stiften", krav: "foto" }, - { id: "laddlogg", text: "Läs laddhistorik och avbrottsorsaker", krav: "kommentar" }, - { id: "extern", text: "Testa mot annan laddpunkt för att utesluta yttre orsak", krav: "kommentar" }, - ], - }, - ], -}; - -export const DIAGNOS_NATVERK: Metodik = { - id: "diagnos_natverk", - namn: "Felkoder och kommunikation", - omrade: "Diagnos", - nyckelord: ["felkod", "dtc", "can", "canbuss", "kommunikationsfel", "ingen kontakt", "styrenhet", "obd", "diagnos", "flera lampor"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "omfattning", - text: "Hur många system är berörda?", - svarstyp: "val", - val: ["En styrenhet", "Flera styrenheter", "Hela fordonet", "Endast diagnosverktyget"], - }, - { id: "nar", text: "Uppstod felet efter ingrepp, batteribyte eller uppdatering?", svarstyp: "text" }, - ], - }, - { - id: "grund", - rubrik: "Grundförutsättningar", - beskrivning: "Kommunikationsfel beror oftare på matning och jord än på styrenheten.", - kontroller: [ - { id: "batteri", text: "Mät batterispänning under avläsning", krav: "matvarde" }, - { id: "obd_matning", text: "Mät matning och jord i diagnosuttaget", krav: "matvarde" }, - { id: "sakringar", text: "Kontrollera säkringar till berörda styrenheter", krav: "kommentar" }, - ], - }, - { - id: "buss", - rubrik: "Bussmätning", - kontroller: [ - { id: "terminering", text: "Mät bussens termineringsresistans med systemet spänningslöst", krav: "matvarde" }, - { id: "bussignal", text: "Mät CAN-signalen med oscilloskop — fotografera kurvan", krav: "foto" }, - { id: "avdelning", text: "Dela av bussen för att lokalisera störande nod", krav: "kommentar" }, - ], - }, - { - id: "koder", - rubrik: "Felkoder i sammanhang", - beskrivning: "En felkod pekar på en krets, inte på en trasig komponent. Läs alla system innan någon kod raderas.", - kontroller: [ - { id: "helavlasning", text: "Läs samtliga styrenheter — fotografera listan", krav: "foto" }, - { id: "frysdata", text: "Dokumentera frysramsdata innan radering", krav: "kommentar" }, - { id: "efter_radering", text: "Radera, provkör och dokumentera vilka koder som återkom", krav: "kommentar" }, - ], - }, - ], -}; - -export const LACKAGE: Metodik = { - id: "lackage", - namn: "Läckage", - omrade: "Övrigt", - nyckelord: ["läck", "läckage", "oljefläck", "dropp", "vatten i bilen", "fukt", "blöt matta", "oljeförbrukning"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "vatska", - text: "Vilken vätska misstänks?", - svarstyp: "val", - val: ["Motorolja", "Kylvätska", "Växellådsolja", "Bromsvätska", "Bränsle", "Vatten/kondens", "Okänt"], - }, - { id: "mangd", text: "Hur mycket och hur ofta?", svarstyp: "text" }, - { id: "plats", text: "Var på marken syns fläcken i förhållande till fordonet?", svarstyp: "text" }, - ], - }, - { - id: "visuell", - rubrik: "Lokalisering", - beskrivning: "Rengör först. Ett gammalt läckage döljer var det nya kommer ifrån.", - kontroller: [ - { id: "foto_fore", text: "Fotografera området före rengöring", krav: "foto" }, - { id: "rengoring", text: "Rengör området och dokumentera att det är torrt", krav: "foto" }, - { id: "provkorning", text: "Provkör eller låt gå och fotografera var vätskan uppträder", krav: "foto" }, - ], - }, - { - id: "metod", - rubrik: "Läcksökning", - kontroller: [ - { id: "uv", text: "Läcksök med UV-spårämne där det är tillämpligt", krav: "kommentar" }, - { id: "trycktest", text: "Trycktesta systemet", krav: "matvarde" }, - { id: "niva", text: "Mät nivåförändring över tid", krav: "matvarde" }, - ], - }, - ], -}; - -export const MISSLJUD: Metodik = { - id: "missljud", - namn: "Missljud", - omrade: "Övrigt", - nyckelord: ["missljud", "ljud", "knack", "gnissel", "gnissl", "vinande", "brumm", "skram", "väsande", "tjut"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - beskrivning: "Ett missljud som inte reproducerats går inte att åtgärda. Spela in det.", - fragor: [ - { - id: "karaktar", - text: "Vilken karaktär har ljudet?", - svarstyp: "val", - val: ["Knack", "Gnissel", "Vinande", "Brummande", "Skrammel", "Väsande", "Tjut"], - }, - { - id: "nar", - text: "När hörs det?", - svarstyp: "val", - val: ["Vid start", "Vid tomgång", "Under körning", "Vid inbromsning", "Vid kurvtagning", "Över ojämnheter"], - }, - { id: "beroende", text: "Följer ljudet varvtal eller hastighet?", svarstyp: "val", val: ["Varvtal", "Hastighet", "Ingetdera"] }, - { id: "temperatur", text: "Skillnad kallt mot varmt?", svarstyp: "text" }, - ], - }, - { - id: "inspelning", - rubrik: "Dokumentera ljudet", - beskrivning: "Video med ljud är evidens. En beskrivning i text är det inte.", - kontroller: [ - { id: "video", text: "Spela in ljudet med video under de förhållanden där det uppträder", krav: "foto" }, - { id: "forhallanden", text: "Dokumentera hastighet, varvtal och underlag vid inspelningen", krav: "kommentar" }, - ], - }, - { - id: "lokalisering", - rubrik: "Lokalisering", - kontroller: [ - { id: "stetoskop", text: "Lokalisera med stetoskop eller ljudsond", krav: "kommentar" }, - { id: "avlastning", text: "Kontrollera om ljudet ändras vid avlastning eller belastning", krav: "kommentar" }, - { id: "uteslutning", text: "Dokumentera vilka källor som uteslutits och hur", krav: "kommentar" }, - ], - }, - ], -}; - -export const ADAS: Metodik = { - id: "adas", - namn: "Förarassistans och kalibrering", - omrade: "Diagnos", - nyckelord: ["adas", "kamera", "radar", "filhållning", "adaptiv farthållare", "kalibrer", "vindruta", "parkeringssensor", "döda vinkeln", "nödbroms"], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { - id: "funktion", - text: "Vilken funktion felar?", - svarstyp: "val", - val: ["Filhållning", "Adaptiv farthållare", "Automatisk nödbroms", "Döda vinkeln", "Parkeringshjälp", "Backkamera"], - }, - { id: "ingrepp", text: "Har vindruta, kaross eller fjädring åtgärdats nyligen?", svarstyp: "text" }, - { id: "forhallanden", text: "Uppträder felet i visst väder eller ljus?", svarstyp: "text" }, - ], - }, - { - id: "forutsattningar", - rubrik: "Förutsättningar för kalibrering", - beskrivning: - "En kalibrering på fel förutsättningar ger ett fel som ser åtgärdat ut. Dokumentera att kraven är uppfyllda innan den utförs.", - kontroller: [ - { id: "dtc", text: "Läs felkoder ur berörda styrenheter", krav: "foto" }, - { id: "hjulinstallning", text: "Kontrollera hjulinställning — bakaxelns spårning påverkar kameran", krav: "matvarde" }, - { id: "dacktryck", text: "Kontrollera däcktryck och körhöjd", krav: "matvarde" }, - { id: "sikt", text: "Kontrollera kamerans och radarns siktfält för smuts eller skada", krav: "foto" }, - { id: "yta", text: "Dokumentera att kalibreringsytan uppfyller kraven på planhet och ljus", krav: "foto" }, - ], - }, - { - id: "kalibrering", - rubrik: "Kalibrering", - kontroller: [ - { id: "utford", text: "Utför kalibrering enligt tillverkarens metod — fotografera resultatet", krav: "foto" }, - { id: "provkorning", text: "Provkör och verifiera funktionen under de förhållanden felet uppträdde", krav: "kommentar" }, - ], - }, - ], -}; - -export const GENERISK: Metodik = { - id: "generisk", - namn: "Generell strukturerad felsökning", - omrade: "Övrigt", - nyckelord: [], - steg: [ - { - id: "symptom", - rubrik: "Verifiera symptom", - fragor: [ - { id: "reproducerbart", text: "Går felet att reproducera?", svarstyp: "janej" }, - { - id: "nar", - text: "När uppträder felet?", - svarstyp: "val", - val: ["Alltid", "Ibland", "Endast under drift", "Endast vid start"], - }, - { - id: "var", - text: "Var upplevs felet?", - svarstyp: "val", - val: ["Fram", "Bak", "Höger", "Vänster", "Motor/drivkälla", "Kupé/förarplats", "Okänt"], - }, - { - id: "hur", - text: "Hur upplevs felet?", - svarstyp: "val", - val: ["Skakar/vibrerar", "Missljud (knack/gnissel/vinande)", "Rycker", "Tappar kraft", "Stannar helt", "Annat"], - }, - { id: "forlopp", text: "Beskriv förloppet när felet uppstår", svarstyp: "text" }, - { id: "forandring", text: "Har något förändrats nyligen (service, reparation, miljö)?", svarstyp: "text" }, - ], - }, - { - id: "visuell", - rubrik: "Visuell kontroll", - kontroller: [ - { id: "foto_objekt", text: "Fotografera objektet och det berörda området", krav: "foto" }, - { id: "skador", text: "Kontrollera synliga skador, läckage eller lösa anslutningar", krav: "kommentar" }, - { id: "typskylt", text: "Dokumentera typskylt/märkning", krav: "foto" }, - ], - }, - { - id: "grundkontroller", - rubrik: "Grundkontroller", - kontroller: [ - { - id: "matning", - text: "Utför grundläggande mätningar (spänning, tryck, temperatur — det som är relevant)", - krav: "matvarde", - }, - { id: "sakringar", text: "Kontrollera säkringar/skydd", krav: "kommentar" }, - { id: "anslutningar", text: "Kontrollera kontaktdon och anslutningar", krav: "kommentar" }, - ], - }, - { - id: "funktionstest", - rubrik: "Funktionstest", - kontroller: [{ id: "test", text: "Genomför funktionstest och dokumentera resultatet", krav: "kommentar" }], - }, - ], -}; - -// Ordningen styr bara hur de visas. Valet görs på nyckelord, inte på -// position — GENERISK ligger sist eftersom den är fallback. -export const ALLA_METODIKER: Metodik[] = [ - VIBRATION, - BROMSAR, - STYRNING_FJADRING, - ELSYSTEM, - START_LADDNING, - MOTOR_DRIFT, - KYLSYSTEM, - DRIVLINA, - AVGAS_EMISSION, - KLIMAT, - HOGVOLT, - DIAGNOS_NATVERK, - LACKAGE, - MISSLJUD, - ADAS, - GENERISK, -]; +export const ALLA_METODIKER = delade.ALLA_METODIKER as Metodik[]; diff --git a/felsokning/app/src/pages/felsokning/ArendeSida.tsx b/felsokning/app/src/pages/felsokning/ArendeSida.tsx index 6e260a1..18d7245 100644 --- a/felsokning/app/src/pages/felsokning/ArendeSida.tsx +++ b/felsokning/app/src/pages/felsokning/ArendeSida.tsx @@ -1295,6 +1295,19 @@ function GuideFlik({

)} + ) : steg.sparr ? ( + // Säkerhetsspärr. Metodiken går inte vidare — svaret är ett + // hinder, inte en varning. Enda vägen framåt är att ändra + // förutsättningen, inte att klicka förbi. +
+

Arbetet får inte fortsätta

+

{steg.sparr.orsak}

+

{steg.sparr.atgard}

+

+ Svaret är dokumenterat i loggen. Metodiken öppnas när förutsättningen är uppfylld och frågan besvaras + jakande. +

+
) : steg.fraga ? ( ) : steg.kontroll ? ( diff --git a/felsokning/services/gemensam/grind.mjs b/felsokning/services/gemensam/grind.mjs new file mode 100644 index 0000000..f5fe782 --- /dev/null +++ b/felsokning/services/gemensam/grind.mjs @@ -0,0 +1,188 @@ +// Kvalitetsgrinden på serversidan. +// +// Revisionen (docs/QUALITY-AUDIT.md, C-2) fann att grinden bara fanns i +// webbläsaren. Ett ärende kunde därmed avslutas utan evidens genom ett +// anrop till API:t, och rapporten presenterade det som komplett — trots +// att dokumentationen påstod att systemet aldrig kan skriva en slutsats +// ECM inte godkänt. +// +// Modulen utvärderar de obligatoriska raderna direkt mot händelseloggen. +// Klientens ecm.ts har fler rader (råd, rekommendationer, härledd +// evidensnivå per påstående) och fortsätter vara vägledningen i +// gränssnittet. Det som blockerar ett avslut bor här. +// +// Avsiktligt: reglerna är rena funktioner av (händelser, metodik). Ingen +// databasåtkomst, ingen tid, inget slumpmässigt — samma logg ger alltid +// samma utfall, vilket är vad som gör en spärr granskbar. + +const UNDANTAG_MOTIVERAT = (h) => typeof h.undantag === "string" && h.undantag.trim().length > 0; + +// Frågor som måste besvaras jakande för att arbetet ska få avslutas. +// Speglar SPARRFRAGOR i app/src/felsokning/metodik.ts; ett test jämför +// listorna så de inte kan glida isär. +export const SPARRFRAGOR = { + "hogvolt/sakerhet/behorighet": "Behörighet för högvoltsarbete bekräftad", + "hogvolt/sakerhet/avstangt": "Fordonet spänningslöst enligt tillverkarens rutin", +}; + +/** Händelser av en viss typ. */ +const av = (handelser, typ) => handelser.filter((h) => h.typ === typ); + +/** + * Utvärderar avslutsgrinden. + * + * @returns lista med hinder; tom lista betyder att ärendet får avslutas. + */ +export function grinda(handelser, metodik) { + const hinder = []; + const krav = (id, rubrik, ok, detalj) => { + if (!ok) hinder.push(detalj ? { id, rubrik, detalj } : { id, rubrik }); + }; + + krav("objekt", "Fordons-/objektidentifiering verifierad", handelser.some((h) => h.typ === "objekt_identifierat")); + + // Historiken får besvaras nekande, men då krävs en motivering — annars + // vore "Nej" ett sätt att hoppa över kontrollen utan att det syns. + const historik = av(handelser, "historik_kontrollerad").at(-1); + krav( + "historik", + "Fordonshistorik kontrollerad eller motiverad", + Boolean(historik) && (historik.kontrollerad || (historik.kommentar ?? "").trim().length > 0), + historik ? "Nekad historikkontroll kräver en motivering." : "Ingen historikkontroll dokumenterad.", + ); + + for (const [lage, rubrik] of [ + ["ingaende", "Ingående mätarställning dokumenterad"], + ["utgaende", "Utgående mätarställning dokumenterad"], + ]) { + krav(`matarstallning_${lage}`, rubrik, av(handelser, "matarstallning").some((h) => h.lage === lage)); + } + + krav( + "reproducering", + "Symptomverifiering: reproducerat eller dokumenterat ej reproducerbart", + handelser.some((h) => h.typ === "reproducering"), + ); + + krav("felorsak", "Felorsaksanalys dokumenterad", handelser.some((h) => h.typ === "felorsak")); + + // Åtgärdskedjan. En utebliven åtgärd är ett giltigt utfall — men bara + // när skälet står i loggen. + const atgarder = av(handelser, "atgard_utford"); + const utfordArbete = atgarder.some((h) => (h.text ?? "").trim().length > 0); + krav( + "atgard", + "Åtgärd dokumenterad eller motiverad", + atgarder.length > 0, + "Varken utförd åtgärd eller skäl till att den uteblev finns dokumenterat.", + ); + + if (utfordArbete) { + const beslut = av(handelser, "kundbeslut"); + krav("kundbeslut", "Kundens besked på åtgärdsförslaget registrerat", beslut.length > 0); + + // Hårt fel, inte en varning: arbete utfört trots att kunden avböjt. + krav( + "avbojt_men_utfort", + "Utfört arbete trots avböjt åtgärdsförslag", + !beslut.some((h) => h.utfall === "avbojt"), + "Kunden avböjde förslaget men arbete har dokumenterats som utfört.", + ); + + krav( + "kvalitetskontroll", + "Kvalitetskontroll genomförd — symptomet verifierat", + handelser.some((h) => h.typ === "kvalitetskontroll"), + ); + } + + // Metodikens kontroller: varje kontroll ska bära evidens eller ett + // dokumenterat undantag. Det är den rad som gör metodiken bindande. + const utforda = new Map(); + for (const h of av(handelser, "kontroll_utford")) utforda.set(`${h.stegId}/${h.kontrollId}`, h); + + const saknade = []; + const utanFoto = []; + const foton = av(handelser, "foto").length; + for (const steg of metodik?.steg ?? []) { + for (const kontroll of steg.kontroller ?? []) { + const h = utforda.get(`${steg.id}/${kontroll.id}`); + if (!h) { + saknade.push(`${steg.rubrik} · ${kontroll.text}`); + continue; + } + if (UNDANTAG_MOTIVERAT(h)) continue; + if (!(h.resultat ?? "").trim()) saknade.push(`${steg.rubrik} · ${kontroll.text} (utan resultat)`); + else if (kontroll.krav === "foto") utanFoto.push(kontroll.text); + } + } + krav( + "metodik_kontroller", + "Metodikens kontroller: evidens eller dokumenterat undantag", + saknade.length === 0, + saknade.slice(0, 5).join(" · "), + ); + krav("foton", "Foton finns för fotokrävande kontroller", utanFoto.length === 0 || foton >= utanFoto.length); + + // Säkerhetsspärrar. Ett nekande svar på en spärrfråga får aldrig + // passera som "besvarad" — se SPARRFRAGOR i klientens metodik.ts. + // Klientens spärr är vägledning; den här är hindret (QUALITET M-2). + for (const [nyckel, text] of Object.entries(SPARRFRAGOR)) { + const [metodikId, stegId, frageId] = nyckel.split("/"); + if (metodik?.id !== metodikId) continue; + const svaret = av(handelser, "fraga_besvarad").findLast( + (h) => h.stegId === stegId && h.frageId === frageId, + ); + krav(`sparr_${frageId}`, text, svaret?.svar === "Ja", "Säkerhetskravet är inte uppfyllt."); + } + + // Evidensnivån måste överstiga E0 — annars avslutas ett ärende utan + // att någonting alls har dokumenterats. + krav( + "evidens", + "Evidensnivå över E0", + handelser.some((h) => ["foto", "video", "matvarde", "observation", "kontroll_utford"].includes(h.typ)), + "Ingen evidens av något slag finns i loggen.", + ); + + return hinder; +} + +/** + * Extra krav som följer av ärendetypen. Reglerna kommer från regelpaketet + * (ECM Knowledge Library) och är alltså data, inte kod. + */ +export function grindaArendetyp(handelser, regelpaket) { + const typ = av(handelser, "arendetyp_satt").at(-1)?.arendetyp; + const regler = regelpaket?.arendetyper?.[typ]?.krav ?? []; + const hinder = []; + + const uppfyllt = { + miltal: () => av(handelser, "matarstallning").length > 0, + historik: () => av(handelser, "historik_kontrollerad").some((h) => h.kontrollerad), + foto: () => av(handelser, "foto").length > 0, + claim: () => harReferens(handelser, ["ao_claim", "claim"]), + skadenummer: () => harReferens(handelser, ["ao_skadenummer", "skadenummer"]), + }; + + for (const regel of regler) { + const kontroll = uppfyllt[regel.typ ?? regel]; + // En okänd kravtyp får aldrig tolkas som uppfylld. Ett regelpaket som + // inte går att utvärdera ska stoppa avslutet, inte släppa igenom det. + if (!kontroll) { + hinder.push({ id: `arendetyp_${regel.typ ?? regel}`, rubrik: `Okänt krav i regelpaketet: ${regel.typ ?? regel}` }); + } else if (!kontroll()) { + hinder.push({ id: `arendetyp_${regel.typ ?? regel}`, rubrik: regel.rubrik ?? `Krav för ärendetypen: ${regel.typ ?? regel}` }); + } + } + return hinder; +} + +function harReferens(handelser, nycklar) { + for (const h of av(handelser, "arbetsorder_skannad")) { + for (const f of h.falt ?? []) { + if (nycklar.includes(f.id) && String(f.varde ?? "").trim()) return true; + } + } + return false; +} diff --git a/felsokning/services/gemensam/handelser.mjs b/felsokning/services/gemensam/handelser.mjs new file mode 100644 index 0000000..8bfcb9b --- /dev/null +++ b/felsokning/services/gemensam/handelser.mjs @@ -0,0 +1,127 @@ +// Händelsevalidering vid API-gränsen. +// +// Bakgrund: revisionen (docs/QUALITY-AUDIT.md, C-1 och M-3) fann att servern +// bara kontrollerade att `anvandare` var *en sträng* och `tidpunkt` *sanningsvärd* +// — inte att de var sanna — och att `handelse` togs emot som godtycklig JSON. +// +// Två skilda problem med samma botemedel: +// +// Härkomst Vem som utförde arbetet och när är det som gör loggen till en +// bevis­kedja. Sätts de av anroparen är loggen en signerad behållare +// för overifierade påståenden. Båda härleds nu ur den verifierade +// token respektive serverns klocka. +// +// Form Loggen är append-only. En felaktig post kan aldrig rättas, bara +// kommenteras. Därför måste formen kontrolleras *innan* skrivning — +// efteråt är det för sent för alltid. +// +// Klientens klocka kastas inte bort. Vid offline-arbete är den det enda som +// finns, så den bevaras som `registrerad_tidpunkt` bredvid serverns +// mottagningstid. Glappet blir synligt i stället för osynligt. + +/** Fält som aldrig får komma från klienten. */ +export const SERVERÄGDA_FÄLT = ["anvandare", "tidpunkt"]; + +// Varje händelsetyp med sina obligatoriska fält. Listan är avsiktligt +// fullständig: en typ som inte står här avvisas, eftersom en okänd typ i en +// append-only-logg är permanent skräp. +export const HÄNDELSESCHEMA = { + objekt_identifierat: { objekt: "objekt" }, + arbetsorder_skannad: { falt: "lista" }, + felbeskrivning: { text: "text" }, + arendetyp_satt: { arendetyp: "text" }, + fraga_besvarad: { stegId: "text", frageId: "text", fraga: "text", svar: "text" }, + kontroll_utford: { stegId: "text", kontrollId: "text", text: "text" }, + observation: { text: "text" }, + matvarde: { beskrivning: "text", varde: "text" }, + hypotes: { text: "text", niva: ["medel", "lag"] }, + foto: { beskrivning: "text" }, + video: { beskrivning: "text" }, + matarstallning: { lage: ["ingaende", "utgaende"], varde: "text" }, + historik_kontrollerad: { kontrollerad: "boolean" }, + reproducering: { status: ["ja", "delvis", "nej"], beskrivning: "text" }, + felorsak: { avvikelse: "text", orsaker: "lista", underlag: "lista", sakerhet: ["hog", "medel", "lag"] }, + atgardsforslag: { text: "text" }, + kundbeslut: { utfall: ["godkant", "avbojt", "delvis"], kanal: "text" }, + atgard_utford: { text: "text" }, + kvalitetskontroll: { utfall: ["borta", "kvarstar", "delvis", "ej_verifierat"], beskrivning: "text" }, + kommentar: { text: "text" }, + kategori_byte: { kategori: "text" }, + inaktivitet_forklarad: { text: "text", minuter: "tal" }, + overlamning: { fran: "text" }, + ansvarig_satt: { ansvarig: "text" }, + ai_svar: { rader: "lista" }, + export_skapad: { format: "text", version: "tal" }, + arende_avslutat: {}, +}; + +// Hypotesen får aldrig anta hög tillförlitlighet — samma regel som +// typsystemet upprätthåller i klienten, upprepad här därför att servern inte +// kan lita på att klienten är vår. +const GILTIG = { + text: (v) => typeof v === "string" && v.length > 0 && v.length <= 20000, + tal: (v) => typeof v === "number" && Number.isFinite(v), + boolean: (v) => typeof v === "boolean", + lista: (v) => Array.isArray(v) && v.length <= 500, + objekt: (v) => v !== null && typeof v === "object" && !Array.isArray(v), +}; + +function fältFel(typ, nyckel, regel, värde) { + if (Array.isArray(regel)) { + return regel.includes(värde) ? null : `${typ}.${nyckel} måste vara en av: ${regel.join(", ")}`; + } + return GILTIG[regel](värde) ? null : `${typ}.${nyckel} har fel form (väntade ${regel})`; +} + +/** + * Validerar en händelses form. Returnerar null när den duger, annars en + * förklaring avsedd att läsas av en människa som felsöker en integration. + */ +export function granskaHändelse(handelse) { + if (handelse === null || typeof handelse !== "object" || Array.isArray(handelse)) { + return "handelse måste vara ett objekt"; + } + const schema = HÄNDELSESCHEMA[handelse.typ]; + if (!schema) { + return `okänd händelsetyp: ${JSON.stringify(handelse.typ)}`; + } + for (const [nyckel, regel] of Object.entries(schema)) { + const fel = fältFel(handelse.typ, nyckel, regel, handelse[nyckel]); + if (fel) return fel; + } + return null; +} + +/** + * Bygger den post som faktiskt skrivs. + * + * Härkomsten kommer härifrån och ingen annanstans: `anvandare` ur den + * verifierade token, `tidpunkt` ur serverns klocka. Klientens tidsstämpel + * bevaras separat när den finns, eftersom den är det enda som finns vid + * offline-arbete — men den avgör aldrig när något anses ha skett. + */ +export function tillPost(post, anspr, nu = new Date()) { + const fel = granskaHändelse(post?.handelse); + if (fel) return { fel }; + if (typeof post.id !== "string" || !/^[A-Za-z0-9_:.-]{1,128}$/.test(post.id)) { + return { fel: "id saknas eller har otillåtna tecken" }; + } + + const klient = typeof post.tidpunkt === "string" ? post.tidpunkt : null; + const klientTid = klient && !Number.isNaN(Date.parse(klient)) ? klient : null; + + return { + post: { + id: post.id, + tidpunkt: nu.toISOString(), + anvandare: anspr.namn, + handelse: { + ...post.handelse, + // Fälten nedan går inte att sätta utifrån; skrivs de över här är det + // just poängen. + anvandarId: anspr.sub, + ...(klientTid && klientTid !== nu.toISOString() ? { registrerad_tidpunkt: klientTid } : {}), + }, + }, + }; +} diff --git a/felsokning/services/gemensam/metodiker.mjs b/felsokning/services/gemensam/metodiker.mjs new file mode 100644 index 0000000..bfe2649 --- /dev/null +++ b/felsokning/services/gemensam/metodiker.mjs @@ -0,0 +1,902 @@ +// Metodikbiblioteket — delad kod. +// +// Låg tidigare bara i klienten, vilket gjorde att servern inte kunde +// utvärdera kvalitetsgrindens metodikrader (QUALITET C-2). Datan bor nu +// här; app/src/felsokning/metodiker.ts återexporterar den med typer, så +// det finns fortfarande exakt en sanning. +// +// En metodik är inte en checklista över vad som brukar vara fel — det +// vore att gissa åt teknikern. Den är en ordning för att utesluta: +// symptomet verifieras först, sedan det billiga och synliga, sedan det +// som kräver mätning, sist det som kräver demontering. +// +// Tre regler gäller alla metodiker här: +// +// 1. Varje kontroll har ett minimikrav (mätvärde, foto eller +// observation). En kryssruta är inte evidens. +// 2. Symptomsteget frågar aldrig "vad är fel" utan "vad händer, när, +// var". Kundens ord blir ett verifierat symptom först när det +// reproducerats. +// 3. Där arbetet kan skada någon kommer säkerhetssteget först och kan +// inte hoppas över — se HÖGVOLT. +// +// Att "täcka allt" går inte att lova. Det som går är att täcka fordonets +// system systematiskt och låta GENERISK vara ett strukturellt komplett +// skyddsnät: den fungerar även för det ingen förutsett. + +export const VIBRATION = { + id: "vibration", + namn: "Vibration under körning", + omrade: "Hjul och balans", + nyckelord: ["vibration", "vibrer", "skak", "obalans", "ojämn gång", "rattvibration"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { id: "hastighetsberoende", text: "Är vibrationen hastighetsberoende?", svarstyp: "janej" }, + { id: "var", text: "Var känns vibrationen?", svarstyp: "val", val: ["I ratten", "I stolen", "I hela bilen"] }, + { + id: "nar", + text: "När uppstår vibrationen?", + svarstyp: "val", + val: ["Vid acceleration", "Vid jämn fart", "Vid inbromsning", "Alltid under körning"], + }, + { id: "intervall", text: "Försvinner den över eller under ett visst hastighetsintervall?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + beskrivning: "Fotografera samtliga hjul. Skilj på observation och slutsats.", + kontroller: [ + { id: "foto_vf", text: "Fotografera vänster framhjul", krav: "foto" }, + { id: "foto_hf", text: "Fotografera höger framhjul", krav: "foto" }, + { id: "foto_vb", text: "Fotografera vänster bakhjul", krav: "foto" }, + { id: "foto_hb", text: "Fotografera höger bakhjul", krav: "foto" }, + { id: "dot", text: "Dokumentera DOT-/tillverkningsdatum", krav: "kommentar" }, + { id: "slitage", text: "Kontrollera däckslitage och slitagemönster", krav: "kommentar" }, + ], + }, + { + id: "kontroller", + rubrik: "Mätningar", + kontroller: [ + { id: "lufttryck", text: "Kontrollera lufttryck", krav: "matvarde" }, + { id: "hjulmoment", text: "Kontrollera hjulmoment", krav: "matvarde" }, + { id: "kast", text: "Kontrollera radial- och sidokast", krav: "matvarde" }, + { id: "balansering", text: "Kontrollera hjulbalansering", krav: "kommentar" }, + { id: "bussningar", text: "Kontrollera bussningar och leder", krav: "kommentar" }, + ], + }, + { + id: "provkorning", + rubrik: "Provkörning", + beskrivning: "Verifiera under provkörning:", + kontroller: [ + { id: "pk_hastighet", text: "Hastighet där vibration uppstår", krav: "kommentar" }, + { id: "pk_acc", text: "Förändring vid acceleration", krav: "kommentar" }, + { id: "pk_motorbroms", text: "Förändring vid motorbroms", krav: "kommentar" }, + { id: "pk_kurva", text: "Förändring vid kurvtagning", krav: "kommentar" }, + { id: "pk_var", text: "Om vibration känns i ratt eller kaross", krav: "kommentar" }, + ], + }, + ], +}; + +export const ELSYSTEM = { + id: "elsystem", + namn: "Elsystem och strömförsörjning", + omrade: "El", + nyckelord: ["relä", "rela", "säkring", "elektr", "spänning", "volt", "lampa", "belysning", "ström", "kortslut", "kabel", "kabla", "kontaktdon", "jordfel"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { id: "reproducerbart", text: "Går felet att reproducera?", svarstyp: "janej" }, + { + id: "omfattning", + text: "Är funktionen helt död eller delvis fungerande?", + svarstyp: "val", + val: ["Helt död", "Delvis fungerande", "Intermittent"], + }, + { id: "funktion", text: "Vilken funktion saknas eller felar?", svarstyp: "text" }, + { id: "forandring", text: "Har något åtgärdats eller förändrats nyligen?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + kontroller: [ + { id: "foto_komponent", text: "Fotografera komponenten och dess anslutningar", krav: "foto" }, + { id: "skador", text: "Kontrollera synliga skador, korrosion och lösa kontaktdon", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Mätningar", + beskrivning: "Mät under belastning. En spänning utan last säger nästan ingenting om en dålig förbindelse.", + kontroller: [ + { id: "batterispanning", text: "Mät batterispänning", krav: "matvarde" }, + { id: "sakringar", text: "Kontrollera berörda säkringar", krav: "kommentar" }, + { id: "matningsspanning", text: "Mät matningsspänning vid komponenten under last", krav: "matvarde" }, + { id: "jord", text: "Mät spänningsfall mot jord under last", krav: "matvarde" }, + ], + }, + { + id: "rela", + rubrik: "Relä och styrsignal", + kontroller: [ + { id: "rela_klick", text: "Kontrollera om reläet klickar vid aktivering", krav: "kommentar" }, + { id: "styrsignal", text: "Mät styrsignal till reläet", krav: "matvarde" }, + ], + }, + { + id: "funktionstest", + rubrik: "Funktionstest", + kontroller: [{ id: "test", text: "Genomför funktionstest och dokumentera resultatet", krav: "kommentar" }], + }, + ], +}; + +export const START_LADDNING = { + id: "start_laddning", + namn: "Start- och laddningssystem", + omrade: "El", + nyckelord: ["startar inte", "startproblem", "startmotor", "generator", "laddning", "batteriet dör", "urladdat", "batterilampa", "drar inte runt", "klick vid start"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "beteende", + text: "Vad händer vid startförsök?", + svarstyp: "val", + val: ["Ingenting alls", "Ett klick", "Drar runt långsamt", "Drar runt normalt men tänder inte", "Startar ibland"], + }, + { id: "kallt_varmt", text: "Uppträder felet kallt, varmt eller alltid?", svarstyp: "val", val: ["Kallt", "Varmt", "Alltid"] }, + { id: "stillestand", text: "Hur länge har fordonet stått mellan försöken?", svarstyp: "text" }, + { id: "laddat", text: "Har batteriet laddats eller bytts nyligen?", svarstyp: "text" }, + ], + }, + { + id: "batteri", + rubrik: "Batteriets kondition", + beskrivning: "Ett svagt batteri ger följdfel i hela fordonet. Uteslut det först.", + kontroller: [ + { id: "vilospanning", text: "Mät vilospänning efter minst en timmes vila", krav: "matvarde" }, + { id: "batteritest", text: "Belastningstest — fotografera testarens resultat", krav: "foto" }, + { id: "poler", text: "Kontrollera polskor och jordflätor", krav: "kommentar" }, + ], + }, + { + id: "start", + rubrik: "Startsystem", + kontroller: [ + { id: "startspanning", text: "Mät spänning vid startmotorn under startförsök", krav: "matvarde" }, + { id: "spanningsfall_plus", text: "Mät spänningsfall i plusledningen under start", krav: "matvarde" }, + { id: "spanningsfall_jord", text: "Mät spänningsfall i jordledningen under start", krav: "matvarde" }, + { id: "startsignal", text: "Kontrollera styrsignal till startrelä", krav: "matvarde" }, + ], + }, + { + id: "laddning", + rubrik: "Laddningssystem", + kontroller: [ + { id: "laddspanning", text: "Mät laddspänning vid tomgång och vid förhöjt varvtal", krav: "matvarde" }, + { id: "rippel", text: "Mät rippel från generatorn", krav: "matvarde" }, + { id: "rem", text: "Kontrollera drivrem och spänning", krav: "kommentar" }, + ], + }, + { + id: "krypstrom", + rubrik: "Krypström", + beskrivning: "Endast relevant när batteriet laddar ur under stillestånd.", + kontroller: [ + { id: "vilostrom", text: "Mät viloström efter att fordonet somnat", krav: "matvarde" }, + { id: "sakringsdrag", text: "Lokalisera krets genom successiv säkringsurtagning", krav: "kommentar" }, + ], + }, + ], +}; + +export const MOTOR_DRIFT = { + id: "motor_drift", + namn: "Motorgång och effekt", + omrade: "Motor", + nyckelord: ["ryck", "tappar kraft", "effektbrist", "misständ", "ojämn tomgång", "tomgång", "motorlampa", "kraftlös", "hack", "går på tre"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "nar", + text: "När uppträder felet?", + svarstyp: "val", + val: ["Vid kallstart", "Vid tomgång", "Under belastning", "Vid visst varvtal", "Alltid"], + }, + { id: "varvtal", text: "Vid vilket varvtal eller vilken last?", svarstyp: "text" }, + { id: "varningslampa", text: "Lyser eller blinkar motorlampan?", svarstyp: "val", val: ["Lyser fast", "Blinkar", "Slocknad"] }, + { id: "brankslebyte", text: "Har bränsle tankats eller service utförts strax innan?", svarstyp: "text" }, + ], + }, + { + id: "felkoder", + rubrik: "Felkoder och mätdata", + beskrivning: "Läs innan något demonteras. En felkod pekar på en krets, inte på en trasig komponent.", + kontroller: [ + { id: "dtc", text: "Läs felkoder — fotografera diagnosskärmen", krav: "foto" }, + { id: "frysdata", text: "Dokumentera frysramsdata för koden", krav: "kommentar" }, + { id: "misstandning", text: "Läs misständningsräknare per cylinder", krav: "matvarde" }, + { id: "branslekorrigering", text: "Läs kort- och långtidsbränslekorrigering", krav: "matvarde" }, + ], + }, + { + id: "mekanik", + rubrik: "Motorns grundkondition", + beskrivning: "Utan grundkondition är alla givarvärden svåra att tolka.", + kontroller: [ + { id: "kompression", text: "Mät kompression eller relativ kompression", krav: "matvarde" }, + { id: "vevhustryck", text: "Kontrollera vevhustryck", krav: "kommentar" }, + { id: "insugslackage", text: "Kontrollera insugssystemet för falskluft", krav: "kommentar" }, + ], + }, + { + id: "tandning_bransle", + rubrik: "Tändning och bränsle", + kontroller: [ + { id: "tandstift", text: "Kontrollera tändstift — fotografera samtliga", krav: "foto" }, + { id: "spolar", text: "Mät tändspolarnas resistans eller byt mellan cylindrar", krav: "matvarde" }, + { id: "bransletryck", text: "Mät bränsletryck under belastning", krav: "matvarde" }, + { id: "insprutare", text: "Kontrollera insprutarnas returmängd eller styrning", krav: "matvarde" }, + ], + }, + { + id: "provkorning", + rubrik: "Provkörning med loggning", + kontroller: [ + { id: "logg", text: "Logga mätdata under körning där felet uppträder", krav: "foto" }, + { id: "reproducerat", text: "Dokumentera under vilka förhållanden felet uppträdde", krav: "kommentar" }, + ], + }, + ], +}; + +export const BROMSAR = { + id: "bromsar", + namn: "Bromssystem", + omrade: "Chassi", + nyckelord: ["broms", "bromspedal", "skevhet", "gnisslar vid inbromsning", "abs", "bromsvätska", "handbroms", "drar snett vid inbromsning"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "karaktar", + text: "Hur yttrar sig felet?", + svarstyp: "val", + val: ["Skevhet/pulsering", "Missljud", "Lång pedalväg", "Mjuk pedal", "Drar åt sidan", "Bromsen ligger på"], + }, + { id: "nar", text: "Vid vilken hastighet och bromskraft?", svarstyp: "text" }, + { id: "varningslampa", text: "Lyser broms- eller ABS-varning?", svarstyp: "janej" }, + { id: "senaste", text: "När byttes bromskomponenter senast?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + kontroller: [ + { id: "foto_belagg", text: "Fotografera belägg och skivor på samtliga hjul", krav: "foto" }, + { id: "belaggtjocklek", text: "Mät beläggtjocklek", krav: "matvarde" }, + { id: "skivtjocklek", text: "Mät skivtjocklek och jämför med minmått", krav: "matvarde" }, + { id: "lackage", text: "Kontrollera slangar, rör och ok för läckage", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Mätningar", + kontroller: [ + { id: "skivkast", text: "Mät skivkast med mätklocka", krav: "matvarde" }, + { id: "tjockleksvariation", text: "Mät tjockleksvariation på skivan", krav: "matvarde" }, + { id: "vatska", text: "Mät bromsvätskans kokpunkt eller vatteninnehåll", krav: "matvarde" }, + { id: "okens_rorlighet", text: "Kontrollera okens och styrbultarnas rörlighet", krav: "kommentar" }, + ], + }, + { + id: "system", + rubrik: "System och ABS", + kontroller: [ + { id: "dtc", text: "Läs felkoder ur ABS-styrenheten", krav: "foto" }, + { id: "hjulgivare", text: "Kontrollera hjulhastighetsgivarnas signal", krav: "matvarde" }, + { id: "bromsprov", text: "Utför bromsprov i provbänk — fotografera protokollet", krav: "foto" }, + ], + }, + ], +}; + +export const STYRNING_FJADRING = { + id: "styrning_fjadring", + namn: "Styrning och fjädring", + omrade: "Chassi", + nyckelord: ["drar åt", "styrning", "ratt", "glapp", "stötdämpar", "fjäder", "fjädr", "hjulinställning", "spårar", "gungar", "knäpper i kurva", "servostyrning"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "karaktar", + text: "Hur yttrar sig felet?", + svarstyp: "val", + val: ["Drar åt sidan", "Glapp i ratten", "Missljud", "Dålig vägkänsla", "Ojämnt däckslitage", "Gungar/studsar"], + }, + { id: "underlag", text: "Uppträder det på visst underlag eller i viss kurva?", svarstyp: "text" }, + { id: "handelse", text: "Har fordonet kört på något eller varit i kontakt med trottoarkant?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + kontroller: [ + { id: "foto_slitage", text: "Fotografera däckens slitagemönster", krav: "foto" }, + { id: "dampare", text: "Kontrollera dämpare för läckage", krav: "kommentar" }, + { id: "damask", text: "Kontrollera damasker på styrleder och drivknutar", krav: "kommentar" }, + ], + }, + { + id: "glapp", + rubrik: "Glappkontroll", + beskrivning: "Utförs med hjulet avlastat. Dokumentera var glappet finns, inte bara att det finns.", + kontroller: [ + { id: "hjullager", text: "Kontrollera hjullagerspel", krav: "kommentar" }, + { id: "styrleder", text: "Kontrollera styr- och bärleder", krav: "kommentar" }, + { id: "bussningar", text: "Kontrollera bussningar", krav: "kommentar" }, + { id: "kuggstang", text: "Kontrollera kuggstång och styrstagsändar", krav: "kommentar" }, + ], + }, + { + id: "installning", + rubrik: "Hjulinställning", + kontroller: [ + { id: "matning", text: "Mät hjulinställning — fotografera protokollet före justering", krav: "foto" }, + { id: "avvikelse", text: "Dokumentera avvikelser mot tillverkarens värden", krav: "matvarde" }, + ], + }, + ], +}; + +export const KYLSYSTEM = { + id: "kylsystem", + namn: "Kylsystem och överhettning", + omrade: "Motor", + nyckelord: ["överhett", "koka", "kylvätska", "temperatur", "termostat", "kylare", "vattenpump", "värme i kupén", "expansionskärl"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "nar", + text: "När stiger temperaturen?", + svarstyp: "val", + val: ["Vid tomgång", "I låg fart", "I hög fart", "Vid belastning/backe", "Alltid"], + }, + { id: "kylvatskeforlust", text: "Förloras kylvätska? Hur mycket och hur ofta?", svarstyp: "text" }, + { id: "varme", text: "Fungerar kupévärmen?", svarstyp: "janej" }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + kontroller: [ + { id: "niva", text: "Kontrollera kylvätskenivå kall — fotografera expansionskärlet", krav: "foto" }, + { id: "lackage", text: "Kontrollera synligt läckage vid slangar, kylare och pump", krav: "kommentar" }, + { id: "kylarpaket", text: "Kontrollera kylarpaketet för igensättning", krav: "foto" }, + ], + }, + { + id: "matningar", + rubrik: "Mätningar", + kontroller: [ + { id: "trycktest", text: "Trycktesta kylsystemet", krav: "matvarde" }, + { id: "lock", text: "Trycktesta expansionskärlets lock", krav: "matvarde" }, + { id: "temperatur", text: "Mät temperatur in och ut ur kylaren med IR-termometer", krav: "matvarde" }, + { id: "flakt", text: "Kontrollera att kylfläkten startar vid rätt temperatur", krav: "kommentar" }, + { id: "givare", text: "Jämför styrenhetens temperaturvärde med uppmätt", krav: "matvarde" }, + ], + }, + { + id: "packning", + rubrik: "Topplockspackning", + beskrivning: "Görs när kylvätska förloras utan yttre läckage.", + kontroller: [ + { id: "avgastest", text: "Avgastest i kylvätskan (CO2-test)", krav: "foto" }, + { id: "kompression_kyl", text: "Kontrollera trycksättning av kylsystemet vid motorgång", krav: "kommentar" }, + ], + }, + ], +}; + +export const DRIVLINA = { + id: "drivlina", + namn: "Växellåda och drivlina", + omrade: "Drivlina", + nyckelord: ["växellåda", "växlar", "koppling", "automatlåda", "rycker vid växling", "slir", "drivknut", "kardan", "differential", "kärvar i växeln"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "karaktar", + text: "Hur yttrar sig felet?", + svarstyp: "val", + val: ["Slirar", "Rycker vid växling", "Går inte i växel", "Missljud", "Vibration vid acceleration", "Läckage"], + }, + { id: "vaxel", text: "Vilken växel eller vilket varvtal?", svarstyp: "text" }, + { id: "kall_varm", text: "Kall eller varm låda?", svarstyp: "val", val: ["Kall", "Varm", "Ingen skillnad"] }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + kontroller: [ + { id: "olja", text: "Kontrollera oljenivå och oljans skick — fotografera", krav: "foto" }, + { id: "lackage", text: "Kontrollera läckage vid tätningar och sump", krav: "kommentar" }, + { id: "fasten", text: "Kontrollera växellådsfästen", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Mätningar och avläsning", + kontroller: [ + { id: "dtc", text: "Läs felkoder ur växellådsstyrenheten", krav: "foto" }, + { id: "adaptioner", text: "Dokumentera adaptionsvärden och kopplingsslitage", krav: "matvarde" }, + { id: "oljetemp", text: "Mät oljetemperatur under provkörning", krav: "matvarde" }, + ], + }, + { + id: "provkorning", + rubrik: "Provkörning", + kontroller: [ + { id: "vaxlingspunkter", text: "Dokumentera växlingspunkter och beteende", krav: "kommentar" }, + { id: "belastning", text: "Provkör under belastning där felet uppträder", krav: "kommentar" }, + ], + }, + ], +}; + +export const AVGAS_EMISSION = { + id: "avgas_emission", + namn: "Avgassystem och emissioner", + omrade: "Motor", + nyckelord: ["partikelfilt", "dpf", "adblue", "katalysator", "lambda", "avgas", "emission", "rökutveckling", "svart rök", "blå rök", "os", "egr", "regenerer", "utsläpp"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "karaktar", + text: "Hur yttrar sig felet?", + svarstyp: "val", + val: ["Varningslampa", "Rök", "Effektbegränsning", "Lukt", "Underkänd besiktning"], + }, + { id: "rokfarg", text: "Om rök — vilken färg och när?", svarstyp: "text" }, + { id: "korprofil", text: "Beskriv fordonets körprofil (mest kort- eller långkörning)?", svarstyp: "text" }, + ], + }, + { + id: "avlasning", + rubrik: "Avläsning", + kontroller: [ + { id: "dtc", text: "Läs felkoder — fotografera diagnosskärmen", krav: "foto" }, + { id: "sotmangd", text: "Läs beräknad och uppmätt sotmängd i partikelfiltret", krav: "matvarde" }, + { id: "regenerering", text: "Dokumentera antal och utfall av senaste regenereringar", krav: "kommentar" }, + { id: "lambda", text: "Läs lambdagivarnas signaler före och efter katalysator", krav: "matvarde" }, + ], + }, + { + id: "matningar", + rubrik: "Mätningar", + kontroller: [ + { id: "mottryck", text: "Mät avgasmottryck", krav: "matvarde" }, + { id: "differenstryck", text: "Mät differenstryck över partikelfiltret", krav: "matvarde" }, + { id: "adblue_niva", text: "Kontrollera AdBlue-nivå och kvalitet där sådant finns", krav: "matvarde" }, + { id: "egr", text: "Kontrollera EGR-ventilens funktion och läge", krav: "matvarde" }, + ], + }, + { + id: "orsak", + rubrik: "Bakomliggande orsak", + beskrivning: "Ett igensatt filter är oftast en följd, inte orsaken. Dokumentera vad som skapade sotet.", + kontroller: [ + { id: "grundorsak", text: "Kontrollera insprutning, turbo och luftflöde", krav: "kommentar" }, + { id: "korstrackor", text: "Bedöm om körprofilen tillåter regenerering", krav: "kommentar" }, + ], + }, + ], +}; + +export const KLIMAT = { + id: "klimat", + namn: "Klimatanläggning", + omrade: "Komfort", + nyckelord: ["ac", "luftkonditionering", "klimatanläggning", "kyler inte", "kompressor", "köldmedi", "imma", "immig", "fläkt", "lukt i kupén", "värmepaket"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "karaktar", + text: "Hur yttrar sig felet?", + svarstyp: "val", + val: ["Kyler inte alls", "Kyler dåligt", "Kyler ibland", "Missljud", "Lukt", "Imma på rutorna"], + }, + { id: "sidor", text: "Skiljer det mellan höger och vänster sida?", svarstyp: "janej" }, + { id: "service", text: "När utfördes AC-service senast?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + kontroller: [ + { id: "kondensor", text: "Fotografera kondensorn — kontrollera igensättning och skador", krav: "foto" }, + { id: "kupefilter", text: "Kontrollera kupéfiltret", krav: "foto" }, + { id: "kompressor_rem", text: "Kontrollera kompressorns drivning och magnetkoppling", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Mätningar", + kontroller: [ + { id: "tryck", text: "Mät hög- och lågtryck vid känd omgivningstemperatur", krav: "matvarde" }, + { id: "utblastemp", text: "Mät utblåstemperatur vid mittre munstycke", krav: "matvarde" }, + { id: "mangd", text: "Väg upptagen köldmediemängd vid tömning", krav: "matvarde" }, + { id: "lackagesok", text: "Läcksök med spårgas eller UV", krav: "kommentar" }, + ], + }, + { + id: "styrning", + rubrik: "Styrning", + kontroller: [ + { id: "dtc", text: "Läs felkoder ur klimatstyrenheten", krav: "foto" }, + { id: "spjall", text: "Kontrollera spjällmotorernas rörelse", krav: "kommentar" }, + ], + }, + ], +}; + +export const HOGVOLT = { + id: "hogvolt", + namn: "Högvoltsystem — elbil och hybrid", + omrade: "Högvolt", + nyckelord: ["högvolt", "elbil", "hybrid", "traktionsbatteri", "hv", "laddning av bil", "räckvidd", "laddbox", "orange kabel", "sköldpadda"], + steg: [ + { + id: "sakerhet", + rubrik: "Säkerhet — utförs först", + beskrivning: + "Högvoltsystem kan vara livsfarliga. Inget arbete på systemet påbörjas innan stegen nedan är dokumenterade. Kräver behörighet för arbete på högvoltsystem.", + fragor: [ + { id: "behorighet", text: "Har du behörighet för arbete på högvoltsystem?", svarstyp: "janej" }, + { id: "avstangt", text: "Är fordonet spänningslöst enligt tillverkarens rutin?", svarstyp: "janej" }, + ], + kontroller: [ + { id: "service_disconnect", text: "Dokumentera att servicebrytaren är urtagen och säkrad", krav: "foto" }, + { id: "vantetid", text: "Dokumentera väntetid enligt tillverkarens anvisning", krav: "kommentar" }, + { id: "spanningsfrihet", text: "Mät och dokumentera spänningsfrihet före ingrepp", krav: "matvarde" }, + { id: "skyddsutrustning", text: "Dokumentera använd skyddsutrustning och avspärrning", krav: "foto" }, + ], + }, + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "karaktar", + text: "Hur yttrar sig felet?", + svarstyp: "val", + val: ["Laddar inte", "Kortare räckvidd", "Effektbegränsning", "Varningsmeddelande", "Fordonet startar inte"], + }, + { id: "laddtyp", text: "Vid vilken laddning uppträder felet (AC, DC, vilken laddpunkt)?", svarstyp: "text" }, + { id: "temperatur", text: "Vid vilken omgivnings- och batteritemperatur?", svarstyp: "text" }, + ], + }, + { + id: "avlasning", + rubrik: "Avläsning", + beskrivning: "Utförs med diagnosverktyg — inga ingrepp i systemet krävs.", + kontroller: [ + { id: "dtc", text: "Läs felkoder ur samtliga högvoltstyrenheter", krav: "foto" }, + { id: "soh", text: "Läs batteriets hälsa (SOH) och laddnivå (SOC)", krav: "matvarde" }, + { id: "cellspanning", text: "Läs cellspänningar och dokumentera största avvikelse", krav: "matvarde" }, + { id: "isolationsvarde", text: "Läs systemets isolationsvärde", krav: "matvarde" }, + { id: "temperaturer", text: "Läs batteriets temperaturgivare", krav: "matvarde" }, + ], + }, + { + id: "laddning", + rubrik: "Laddkedjan", + kontroller: [ + { id: "laddkabel", text: "Kontrollera laddkabel och laddkontakt — fotografera stiften", krav: "foto" }, + { id: "laddlogg", text: "Läs laddhistorik och avbrottsorsaker", krav: "kommentar" }, + { id: "extern", text: "Testa mot annan laddpunkt för att utesluta yttre orsak", krav: "kommentar" }, + ], + }, + ], +}; + +export const DIAGNOS_NATVERK = { + id: "diagnos_natverk", + namn: "Felkoder och kommunikation", + omrade: "Diagnos", + nyckelord: ["felkod", "dtc", "can", "canbuss", "kommunikationsfel", "ingen kontakt", "styrenhet", "obd", "diagnos", "flera lampor"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "omfattning", + text: "Hur många system är berörda?", + svarstyp: "val", + val: ["En styrenhet", "Flera styrenheter", "Hela fordonet", "Endast diagnosverktyget"], + }, + { id: "nar", text: "Uppstod felet efter ingrepp, batteribyte eller uppdatering?", svarstyp: "text" }, + ], + }, + { + id: "grund", + rubrik: "Grundförutsättningar", + beskrivning: "Kommunikationsfel beror oftare på matning och jord än på styrenheten.", + kontroller: [ + { id: "batteri", text: "Mät batterispänning under avläsning", krav: "matvarde" }, + { id: "obd_matning", text: "Mät matning och jord i diagnosuttaget", krav: "matvarde" }, + { id: "sakringar", text: "Kontrollera säkringar till berörda styrenheter", krav: "kommentar" }, + ], + }, + { + id: "buss", + rubrik: "Bussmätning", + kontroller: [ + { id: "terminering", text: "Mät bussens termineringsresistans med systemet spänningslöst", krav: "matvarde" }, + { id: "bussignal", text: "Mät CAN-signalen med oscilloskop — fotografera kurvan", krav: "foto" }, + { id: "avdelning", text: "Dela av bussen för att lokalisera störande nod", krav: "kommentar" }, + ], + }, + { + id: "koder", + rubrik: "Felkoder i sammanhang", + beskrivning: "En felkod pekar på en krets, inte på en trasig komponent. Läs alla system innan någon kod raderas.", + kontroller: [ + { id: "helavlasning", text: "Läs samtliga styrenheter — fotografera listan", krav: "foto" }, + { id: "frysdata", text: "Dokumentera frysramsdata innan radering", krav: "kommentar" }, + { id: "efter_radering", text: "Radera, provkör och dokumentera vilka koder som återkom", krav: "kommentar" }, + ], + }, + ], +}; + +export const LACKAGE = { + id: "lackage", + namn: "Läckage", + omrade: "Övrigt", + nyckelord: ["läck", "läckage", "oljefläck", "dropp", "vatten i bilen", "fukt", "blöt matta", "oljeförbrukning"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "vatska", + text: "Vilken vätska misstänks?", + svarstyp: "val", + val: ["Motorolja", "Kylvätska", "Växellådsolja", "Bromsvätska", "Bränsle", "Vatten/kondens", "Okänt"], + }, + { id: "mangd", text: "Hur mycket och hur ofta?", svarstyp: "text" }, + { id: "plats", text: "Var på marken syns fläcken i förhållande till fordonet?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Lokalisering", + beskrivning: "Rengör först. Ett gammalt läckage döljer var det nya kommer ifrån.", + kontroller: [ + { id: "foto_fore", text: "Fotografera området före rengöring", krav: "foto" }, + { id: "rengoring", text: "Rengör området och dokumentera att det är torrt", krav: "foto" }, + { id: "provkorning", text: "Provkör eller låt gå och fotografera var vätskan uppträder", krav: "foto" }, + ], + }, + { + id: "metod", + rubrik: "Läcksökning", + kontroller: [ + { id: "uv", text: "Läcksök med UV-spårämne där det är tillämpligt", krav: "kommentar" }, + { id: "trycktest", text: "Trycktesta systemet", krav: "matvarde" }, + { id: "niva", text: "Mät nivåförändring över tid", krav: "matvarde" }, + ], + }, + ], +}; + +export const MISSLJUD = { + id: "missljud", + namn: "Missljud", + omrade: "Övrigt", + nyckelord: ["missljud", "ljud", "knack", "gnissel", "gnissl", "vinande", "brumm", "skram", "väsande", "tjut"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + beskrivning: "Ett missljud som inte reproducerats går inte att åtgärda. Spela in det.", + fragor: [ + { + id: "karaktar", + text: "Vilken karaktär har ljudet?", + svarstyp: "val", + val: ["Knack", "Gnissel", "Vinande", "Brummande", "Skrammel", "Väsande", "Tjut"], + }, + { + id: "nar", + text: "När hörs det?", + svarstyp: "val", + val: ["Vid start", "Vid tomgång", "Under körning", "Vid inbromsning", "Vid kurvtagning", "Över ojämnheter"], + }, + { id: "beroende", text: "Följer ljudet varvtal eller hastighet?", svarstyp: "val", val: ["Varvtal", "Hastighet", "Ingetdera"] }, + { id: "temperatur", text: "Skillnad kallt mot varmt?", svarstyp: "text" }, + ], + }, + { + id: "inspelning", + rubrik: "Dokumentera ljudet", + beskrivning: "Video med ljud är evidens. En beskrivning i text är det inte.", + kontroller: [ + { id: "video", text: "Spela in ljudet med video under de förhållanden där det uppträder", krav: "foto" }, + { id: "forhallanden", text: "Dokumentera hastighet, varvtal och underlag vid inspelningen", krav: "kommentar" }, + ], + }, + { + id: "lokalisering", + rubrik: "Lokalisering", + kontroller: [ + { id: "stetoskop", text: "Lokalisera med stetoskop eller ljudsond", krav: "kommentar" }, + { id: "avlastning", text: "Kontrollera om ljudet ändras vid avlastning eller belastning", krav: "kommentar" }, + { id: "uteslutning", text: "Dokumentera vilka källor som uteslutits och hur", krav: "kommentar" }, + ], + }, + ], +}; + +export const ADAS = { + id: "adas", + namn: "Förarassistans och kalibrering", + omrade: "Diagnos", + nyckelord: ["adas", "kamera", "radar", "filhållning", "adaptiv farthållare", "kalibrer", "vindruta", "parkeringssensor", "döda vinkeln", "nödbroms"], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { + id: "funktion", + text: "Vilken funktion felar?", + svarstyp: "val", + val: ["Filhållning", "Adaptiv farthållare", "Automatisk nödbroms", "Döda vinkeln", "Parkeringshjälp", "Backkamera"], + }, + { id: "ingrepp", text: "Har vindruta, kaross eller fjädring åtgärdats nyligen?", svarstyp: "text" }, + { id: "forhallanden", text: "Uppträder felet i visst väder eller ljus?", svarstyp: "text" }, + ], + }, + { + id: "forutsattningar", + rubrik: "Förutsättningar för kalibrering", + beskrivning: + "En kalibrering på fel förutsättningar ger ett fel som ser åtgärdat ut. Dokumentera att kraven är uppfyllda innan den utförs.", + kontroller: [ + { id: "dtc", text: "Läs felkoder ur berörda styrenheter", krav: "foto" }, + { id: "hjulinstallning", text: "Kontrollera hjulinställning — bakaxelns spårning påverkar kameran", krav: "matvarde" }, + { id: "dacktryck", text: "Kontrollera däcktryck och körhöjd", krav: "matvarde" }, + { id: "sikt", text: "Kontrollera kamerans och radarns siktfält för smuts eller skada", krav: "foto" }, + { id: "yta", text: "Dokumentera att kalibreringsytan uppfyller kraven på planhet och ljus", krav: "foto" }, + ], + }, + { + id: "kalibrering", + rubrik: "Kalibrering", + kontroller: [ + { id: "utford", text: "Utför kalibrering enligt tillverkarens metod — fotografera resultatet", krav: "foto" }, + { id: "provkorning", text: "Provkör och verifiera funktionen under de förhållanden felet uppträdde", krav: "kommentar" }, + ], + }, + ], +}; + +export const GENERISK = { + id: "generisk", + namn: "Generell strukturerad felsökning", + omrade: "Övrigt", + nyckelord: [], + steg: [ + { + id: "symptom", + rubrik: "Verifiera symptom", + fragor: [ + { id: "reproducerbart", text: "Går felet att reproducera?", svarstyp: "janej" }, + { + id: "nar", + text: "När uppträder felet?", + svarstyp: "val", + val: ["Alltid", "Ibland", "Endast under drift", "Endast vid start"], + }, + { + id: "var", + text: "Var upplevs felet?", + svarstyp: "val", + val: ["Fram", "Bak", "Höger", "Vänster", "Motor/drivkälla", "Kupé/förarplats", "Okänt"], + }, + { + id: "hur", + text: "Hur upplevs felet?", + svarstyp: "val", + val: ["Skakar/vibrerar", "Missljud (knack/gnissel/vinande)", "Rycker", "Tappar kraft", "Stannar helt", "Annat"], + }, + { id: "forlopp", text: "Beskriv förloppet när felet uppstår", svarstyp: "text" }, + { id: "forandring", text: "Har något förändrats nyligen (service, reparation, miljö)?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visuell kontroll", + kontroller: [ + { id: "foto_objekt", text: "Fotografera objektet och det berörda området", krav: "foto" }, + { id: "skador", text: "Kontrollera synliga skador, läckage eller lösa anslutningar", krav: "kommentar" }, + { id: "typskylt", text: "Dokumentera typskylt/märkning", krav: "foto" }, + ], + }, + { + id: "grundkontroller", + rubrik: "Grundkontroller", + kontroller: [ + { + id: "matning", + text: "Utför grundläggande mätningar (spänning, tryck, temperatur — det som är relevant)", + krav: "matvarde", + }, + { id: "sakringar", text: "Kontrollera säkringar/skydd", krav: "kommentar" }, + { id: "anslutningar", text: "Kontrollera kontaktdon och anslutningar", krav: "kommentar" }, + ], + }, + { + id: "funktionstest", + rubrik: "Funktionstest", + kontroller: [{ id: "test", text: "Genomför funktionstest och dokumentera resultatet", krav: "kommentar" }], + }, + ], +}; + +// Ordningen styr bara hur de visas. Valet görs på nyckelord, inte på +// position — GENERISK ligger sist eftersom den är fallback. +export const ALLA_METODIKER = [ + VIBRATION, + BROMSAR, + STYRNING_FJADRING, + ELSYSTEM, + START_LADDNING, + MOTOR_DRIFT, + KYLSYSTEM, + DRIVLINA, + AVGAS_EMISSION, + KLIMAT, + HOGVOLT, + DIAGNOS_NATVERK, + LACKAGE, + MISSLJUD, + ADAS, + GENERISK, +]; diff --git a/felsokning/services/plattform/grind.mjs b/felsokning/services/plattform/grind.mjs new file mode 120000 index 0000000..01b094d --- /dev/null +++ b/felsokning/services/plattform/grind.mjs @@ -0,0 +1 @@ +../gemensam/grind.mjs \ No newline at end of file diff --git a/felsokning/services/plattform/handelser.mjs b/felsokning/services/plattform/handelser.mjs new file mode 120000 index 0000000..27bc120 --- /dev/null +++ b/felsokning/services/plattform/handelser.mjs @@ -0,0 +1 @@ +../gemensam/handelser.mjs \ No newline at end of file diff --git a/felsokning/services/plattform/metodiker.mjs b/felsokning/services/plattform/metodiker.mjs new file mode 120000 index 0000000..4ce4115 --- /dev/null +++ b/felsokning/services/plattform/metodiker.mjs @@ -0,0 +1 @@ +../gemensam/metodiker.mjs \ No newline at end of file diff --git a/felsokning/services/plattform/server.mjs b/felsokning/services/plattform/server.mjs index fbb86ee..50bc0d9 100644 --- a/felsokning/services/plattform/server.mjs +++ b/felsokning/services/plattform/server.mjs @@ -27,6 +27,9 @@ import { dirname, join } from "node:path"; import pg from "pg"; import { innehallsHash, mediatypGiltig, valjLager } from "./bilagor.mjs"; import { avsluta, logga, mätvärde, spårFrån, starta, traceparent } from "./observation.mjs"; +import { tillPost } from "./handelser.mjs"; +import { grinda, grindaArendetyp } from "./grind.mjs"; +import { ALLA_METODIKER } from "./metodiker.mjs"; // API-first: OpenAPI-specen är en versionerad artefakt och serveras live. const OPENAPI = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "openapi.yaml"), "utf8"); @@ -296,10 +299,46 @@ async function lasBinart(req, tak) { return Buffer.concat(bitar); } -function nyKod() { +// Delningskod. Förkastningsurval i stället för modulo: 256 är inte jämnt +// delbart med 36, så `b % 36` gör de fyra första tecknen ~14 % vanligare. +// Entropiförlusten är liten, men en snedfördelad kod i en säkerhetsväg är +// inte något man vill behöva förklara i en granskning (QUALITET m-1). +function nyKod(langd = 16) { const tecken = "abcdefghijklmnopqrstuvwxyz0123456789"; - const { randomBytes } = crypto; - return Array.from(randomBytes(16), (b) => tecken[b % 36]).join(""); + const tak = 256 - (256 % tecken.length); // 252 — värden däröver kastas + const ut = []; + while (ut.length < langd) { + for (const b of crypto.randomBytes(langd)) { + if (b < tak && ut.length < langd) ut.push(tecken[b % tecken.length]); + } + } + return ut.join(""); +} + +/** + * Avslutsspärren. Läser hela loggen ur databasen, lägger de inkommande + * händelserna ovanpå och utvärderar grinden mot det samlade underlaget — + * annars skulle ett avslut i samma anrop som evidensen felaktigt nekas. + */ +async function grindHinder(pool, arendeId, nya) { + const rader = await pool.query( + `select h.handelse, a.metodik_id from felsokning_handelser h + join felsokning_arenden a on a.id = h.arende_id + where h.arende_id = $1 order by h.tidpunkt, h.id`, + [arendeId], + ); + const handelser = [...rader.rows.map((r) => r.handelse), ...nya.map((p) => p.handelse)]; + const metodik = + ALLA_METODIKER.find((m) => m.id === rader.rows[0]?.metodik_id) ?? ALLA_METODIKER.at(-1); + + let regelpaket; + try { + regelpaket = JSON.parse(ECM_REGLER); + } catch { + // Ett trasigt regelpaket får aldrig tolkas som "inga extra krav". + return [{ id: "regelpaket", rubrik: "Regelpaketet gick inte att läsa — avslut spärrat." }]; + } + return [...grinda(handelser, metodik), ...grindaArendetyp(handelser, regelpaket)]; } function kravAuth(req, hemlighet) { @@ -1172,17 +1211,41 @@ export function skapaServer() { return svara(res, 400, { error: "Ogiltig händelselista." }); } res.spann.spår.antalHandelser = handelser.length; + + // Härkomst och form avgörs här, inte av anroparen. Se + // services/gemensam/handelser.mjs och QUALITET C-1/M-3. + const attSkriva = []; for (const post of handelser) { - if (typeof post?.id !== "string" || !post.tidpunkt || typeof post.anvandare !== "string" || !post.handelse) { - return svara(res, 400, { error: "Ogiltig händelse." }); + const { post: giltig, fel } = tillPost(post, anspr); + if (fel) return svara(res, 400, { error: `Ogiltig händelse: ${fel}` }); + attSkriva.push(giltig); + } + + // Avslut passerar kvalitetsgrinden på servern. Grinden fanns + // tidigare bara i webbläsaren, vilket gjorde hela ECM till ett + // råd i stället för en spärr (QUALITET C-2). + if (attSkriva.some((p) => p.handelse.typ === "arende_avslutat")) { + const hinder = await grindHinder(pool, handelserVag[1], attSkriva); + if (hinder.length > 0) { + return svara(res, 409, { + error: "Ärendet kan inte avslutas — kvalitetsgrinden är inte passerad.", + hinder, + }); } + } + + for (const p of attSkriva) { // Append-only: on conflict do nothing — en befintlig händelse // skrivs aldrig över, och databastriggern stoppar allt annat. - await pool.query( + // Kollisionen räknas: ett id som redan finns är antingen en + // ofarlig omsändning eller ett försök att blockera en framtida + // post, och skillnaden syns bara om den mäts (QUALITET m-2). + const skrivet = await pool.query( `insert into felsokning_handelser (id, arende_id, tidpunkt, anvandare, handelse) values ($1, $2, $3, $4, $5) on conflict (id) do nothing`, - [post.id, handelserVag[1], post.tidpunkt, post.anvandare, post.handelse], + [p.id, handelserVag[1], p.tidpunkt, p.anvandare, p.handelse], ); + if (skrivet.rowCount === 0) res.spann.spår.kollisioner = (res.spann.spår.kollisioner ?? 0) + 1; } return svara(res, 200, { ok: true }); }