diff --git a/felsokning/docs/MVP.md b/felsokning/docs/MVP.md index 2a0cb3a..4b6d185 100644 --- a/felsokning/docs/MVP.md +++ b/felsokning/docs/MVP.md @@ -21,6 +21,8 @@ VITE_HASH_ROUTER=1 npm run build I det läget är `/` Guidad Felsökning i stället för värdapplikationens startsida, och sidan fungerar oavsett vilken sökväg den ligger på. +Fullständig systembeskrivning i ett dokument (för granskning eller resonemang utanför repot): [SYSTEMBESKRIVNING.md](SYSTEMBESKRIVNING.md). + Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på startsidan lägger in ett komplett vibrationsärende med 1 tim 35 min historik. ## Vad som ingår diff --git a/felsokning/docs/SYSTEMBESKRIVNING.md b/felsokning/docs/SYSTEMBESKRIVNING.md new file mode 100644 index 0000000..776783d --- /dev/null +++ b/felsokning/docs/SYSTEMBESKRIVNING.md @@ -0,0 +1,883 @@ +# Guidad Felsökning — fullständig systembeskrivning + +> Ett självständigt referensdokument. Allt nedan är hämtat ur koden i +> `felsokning/` (branch `claude/guidad-felsokning-vision-1mnx7f`), inte ur +> planer eller avsikter. Där något **inte** finns står det uttryckligen. +> +> Syftet är att kunna resonera om systemet utan tillgång till repot. +> +> Senast synkat mot kod: commit `1bb4031`, 2026-08-04. + +--- + +## 0. Sammanfattning på trettio sekunder + +Guidad Felsökning är en SaaS-plattform för **fordonsverkstäder**. Den leder en +tekniker genom en strukturerad felsökning, kräver bevis för varje påstående, och +producerar ett spårbart underlag som kan delas med kund, försäkringsbolag eller +nästa tekniker. + +Den bärande idén är negativ snarare än positiv: **systemet presenterar aldrig en +hypotes som ett konstaterat fel.** Det är inte en policy i en dokumentfil — det +är kodat, testat och blockerar flöden. När evidens saknas står det "Evidens +saknas", inte en kvalificerad gissning. + +Tekniskt: en **append-only händelselogg** är enda sanningskällan. Allt annat — +ärendevyn, briefen, kundrapporten, kvalitetsgrinden, statistiken — är rena +projektioner av loggen och kan alltid regenereras. + +| | | +|---|---| +| Klient | React 18 + TypeScript + Vite + Tailwind + zustand + react-router | +| Backend | Två Node-tjänster (`plattform`, `ai-orkester`), rena `node:http`, minimala beroenden | +| Databas | PostgreSQL (Aurora Serverless v2), append-only via databastriggrar | +| Modell | Claude, serverägd routing per uppgift | +| Infra | AWS + EKS, 126 Terraform-resurser i två lager | +| Git & CI | **Självhostad Gitea + Actions-runners på egen EKS** — inget GitHub i driftvägen | +| Tester | 120 vitest-tester + integrationstest mot riktig Postgres | +| Språk i kod | Svenska (identifierare, kommentarer, commit-meddelanden) | + +--- + +## 1. Produktprinciper + +Dessa fem är inte riktlinjer utan invarianter. Var och en har en motsvarighet i +kod och i test. + +### 1.1 Ingen hypotes presenteras som ett konstaterat fel + +Hypoteser är en egen händelsetyp (`hypotes`) med obligatorisk +tillförlitlighetsnivå, och kan **aldrig** anta nivån `hog` +(`niva: Exclude` — typsystemet förbjuder det). +I kundrapporten märks de uttryckligen som ej verifierade. Kvalitetsgrinden har +en egen rad för detta. + +Formuleringen vid misslyckad reproduktion är +*"kunde inte reproduceras under de förhållanden som rådde"* — aldrig +*"felet konstaterat"* eller *"inget fel hittat"*. Detta är kodat i både +projektionerna och orkesterns grundprompt. + +### 1.2 En kryssruta är inte evidens + +Varje kontrollpunkt i varje metodik har ett **minimikrav**: +`matvarde | kommentar | foto`. En mätning kan inte markeras utförd utan värde; +en fotokontroll kan inte markeras utförd utan bild. Vill teknikern hoppa över +något krävs ett **dokumenterat undantag** med orsak ur en fast lista. + +Låst av test: *"varje kontroll kräver bevis — en kryssruta är inte evidens"*. + +### 1.3 Loggen är append-only, hela vägen ner + +Inga update- eller delete-operationer finns i API:t, och databasen har triggrar +som avvisar dem även om någon kringgår applikationen. Ett test söker aktivt +efter `update`/`delete` mot händelsetabellen i serverkoden och faller om de dyker +upp. + +Konsekvens: en felaktig uppgift *rättas genom en ny händelse*, aldrig genom att +den gamla försvinner. Historiken är det som ger underlaget värde i en tvist. + +### 1.4 Terminologi + +I UI och kundkommunikation används **systemet, analysen, bedömningen, +beslutsstödet** — inte "AI", om det inte är tekniskt nödvändigt. Produkten +beskrivs som *evidensbaserat diagnossystem* / *intelligent beslutsstöd*. + +Skälet är kommersiellt och epistemiskt: en verkstadskund som hör "AI" hör +"gissning". En försäkringshandläggare som läser "AI-bedömning" i ett underlag +väger det lägre. + +### 1.5 Delningsgränsen är en tillåtelselista + +Vad som får lämna organisationen räknas upp **positivt**, per nivå. En ny +händelsetyp är därmed intern tills någon aktivt släpper fram den. Ett test +kräver att varje typ i domänmodellen är klassificerad — glöms en bort faller +bygget, i stället för att den läcker. + +--- + +## 2. Domänmodellen — händelseloggen + +`app/src/felsokning/domain.ts` (281 rader). + +Ett ärende är: identitet + metadata + en **ordnad lista av loggposter**. Varje +loggpost bär `id`, `tidpunkt`, `tekniker` och en `handelse`. + +### 2.1 Samtliga händelsetyper + +| Typ | Innehåll | Roll | +|---|---|---| +| `objekt_identifierat` | `objekt` (regnr/VIN, märke, modell, motor …) | Vad ärendet gäller | +| `arbetsorder_skannad` | `falt[]` + bilaga | Tolkad arbetsorder (**intern**) | +| `felbeskrivning` | `text` | Kundens ord, ordagrant | +| `arendetyp_satt` | `arendetyp` | Garanti / försäkring / kund … styr regelpaket | +| `fraga_besvarad` | `stegId`, `frageId`, `fraga`, `svar` | Metodikens symptomfrågor | +| `kontroll_utford` | `stegId`, `kontrollId`, `text`, `resultat?`, `undantag?` | Verifierad checklistpunkt | +| `observation` | `text` | Vad teknikern såg — inte vad hen tror | +| `matvarde` | `beskrivning`, `varde`, `enhet?` | Mätning (E4) | +| `hypotes` | `text`, `niva` (aldrig `hog`) | Arbetshypotes (**intern**) | +| `foto` | `beskrivning` + bilaga | Bildbevis (E2) | +| `video` | `beskrivning` + bilaga | Rörligt bevis (E3) | +| `matarstallning` | `lage` (ingående/utgående), `varde` + bilaga | Miltal in/ut | +| `historik_kontrollerad` | `kontrollerad`, `kommentar?` | Servicehistorik | +| `reproducering` | `status` (ja/delvis/nej), `beskrivning` | **Symptomverifiering** | +| `felorsak` | strukturerad felorsaksanalys | Orsak, kategori, underlag | +| `atgardsforslag` | förslag med motivering | Vad som bör göras | +| `kundbeslut` | godkänt/avböjt, kanal | Kundens besked | +| `atgard_utford` | utfört arbete | Vad som faktiskt gjordes | +| `kvalitetskontroll` | verifiering efter åtgärd | Är symptomet borta? | +| `kommentar` | `text` | Fri anteckning | +| `kategori_byte` | `kategori` | Tidredovisning (**intern**) | +| `inaktivitet_forklarad` | `text`, `minuter` | Varför det stod stilla | +| `overlamning` | `fran`, `till?` | Skiftbyte | +| `ansvarig_satt` | `ansvarig` | Arbetsledarens omfördelning (**intern**) | +| `ai_svar` | `rader[]` klassificerade, modellnamn | Beslutsstödets svar (**intern**) | +| `export_skapad` | `format`, `version` | Exporten loggar sig själv | +| `arende_avslutat` | `signatur?` | Teknikerns signering | + +### 2.2 Bilagor är innehållsadresserade + +`foto`, `video`, `matarstallning` och `arbetsorder_skannad` är +*intersektionstyper* med `Bilaga`: + +```ts +export interface Bilaga { + bilagaId?: string; + bilagaHash?: string; // SHA-256 + dataUrl?: string; // finns kvar för alltid — loggen är append-only +} +``` + +Innehållet ligger utanför loggen (S3 eller databas), men **hashen ligger i +loggen**. Vid läsning verifieras hashen; stämmer den inte returneras `409`. +Innebörden: byter någon ut en bild i lagret upptäcks det, och loggen kan bevisa +att den ursprungliga bilden var en annan. + +`dataUrl` behålls i typen därför att gamla poster har den inbäddad — och loggen +kan inte skrivas om. + +--- + +## 3. Metodikmotorn + +Sedan senaste ändringen är **motor och innehåll åtskilda**: + +- `metodik.ts` (171 rader) — typer, val av metodik, härledning av nästa steg. +- `metodiker.ts` (899 rader) — de sexton metodikerna. + +Biblioteket kan växa utan att motorn ändras. + +### 3.1 Metodikbiblioteket + +| id | Namn | Område | Steg | Kontroller | +|---|---|---|---|---| +| `vibration` | Vibration under körning | Hjul och balans | symptom → visuell → kontroller → provkorning | 19 | +| `bromsar` | Bromssystem | Chassi | symptom → visuell → matningar → system | 14 | +| `styrning_fjadring` | Styrning och fjädring | Chassi | symptom → visuell → glapp → installning | 11 | +| `elsystem` | Elsystem och strömförsörjning | El | symptom → visuell → matningar → rela → funktionstest | 12 | +| `start_laddning` | Start- och laddningssystem | El | symptom → batteri → start → laddning → krypstrom | 15 | +| `motor_drift` | Motorgång och effekt | Motor | symptom → felkoder → mekanik → tandning_bransle → provkorning | 16 | +| `kylsystem` | Kylsystem och överhettning | Motor | symptom → visuell → matningar → packning | 12 | +| `drivlina` | Växellåda och drivlina | Drivlina | symptom → visuell → matningar → provkorning | 10 | +| `avgas_emission` | Avgassystem och emissioner | Motor | symptom → avlasning → matningar → orsak | 12 | +| `klimat` | Klimatanläggning | Komfort | symptom → visuell → matningar → styrning | 11 | +| `hogvolt` | Högvoltsystem — elbil och hybrid | Högvolt | **sakerhet** → symptom → avlasning → laddning | 16 | +| `diagnos_natverk` | Felkoder och kommunikation | Diagnos | symptom → grund → buss → koder | 10 | +| `lackage` | Läckage | Övrigt | symptom → visuell → metod | 8 | +| `missljud` | Missljud | Övrigt | symptom → inspelning → lokalisering | 7 | +| `adas` | Förarassistans och kalibrering | Diagnos | symptom → forutsattningar → kalibrering | 9 | +| `generisk` | Generell strukturerad felsökning | Övrigt | symptom → visuell → grundkontroller → funktionstest | 9 | + +### 3.2 Tre regler, låsta av test + +1. **Varje kontroll har ett minimikrav.** Mätvärde, foto eller observation. +2. **Varje metodik börjar med att verifiera symptomet**, aldrig med att åtgärda. + Kundens ord blir ett verifierat symptom först när det reproducerats. +3. **Där arbetet kan skada någon ligger säkerhetssteget först.** Endast + `sakerhet` får föregå `symptom` — testet tillåter exakt det undantaget och + inget annat. + +`hogvolt` är den enda metodiken med säkerhetssteg. Det kräver behörighet, +dokumenterad urtagen servicebrytare (foto), väntetid enligt tillverkaren, **mätt +spänningsfrihet** (mätvärde — inte ett ja på en fråga) och skyddsutrustning. +Testet kontrollerar att steget ligger först, att `spanningsfrihet` kräver +mätvärde, och att beskrivningen innehåller ordet "livsfarlig". + +Skälet är enkelt: det arbetet kan döda någon. Där duger ingen kryssruta. + +### 3.3 Val av metodik + +Tidigare en regexkedja med tre utfall. Nu **poängsatt nyckelordsmatchning**: + +```ts +export function metodikPoang(metodik: Metodik, text: string): number +export function valjMetodik(felbeskrivning: string): Metodik +``` + +- Poäng = summan av längden på de nyckelord som träffar. Ett längre — mer + specifikt — ord väger tyngre. `traktionsbatteri` (16) slår `batteri`. +- **Korta ord (≤3 tecken) matchas som helt ord, längre som ordstam.** Annars + hade `"ac"` träffat *acceleration* och en vibration hamnat i + klimatanläggningen. +- Vid lika poäng vinner den som står först i biblioteket → valet är **stabilt** + mellan körningar. +- Ingen träff → `generisk`. + +**Fallgrop som faktiskt bet under utvecklingen:** nyckelorden måste vara +*stammar*, inte färdigböjda ord. Svensk böjning kapar ofta ett `e`: +*filter → filtret*, så `"partikelfilter"` matchar aldrig texten teknikern +skriver. Samma gäller *regenerering → regenererar*, *misständning → misständer*, +*skrammel → skramlar*. Biblioteket använder därför `partikelfilt`, `regenerer`, +`misständ`, `skram`. + +**Valet är en frågeordning, inte en diagnos.** Det avgör var teknikern börjar +leta, inte vad som är fel. Träffar inget är `generisk` det ärliga svaret — +strukturellt komplett, och bättre än en gissning. + +### 3.4 Nästa steg + +```ts +export function nastaSteg(arende: Arende, metodik: Metodik): NastaSteg +``` + +Rent härlett ur loggen: första obesvarade frågan, därefter första ej utförda +kontrollen, i metodikens ordning. Ingen dold tillståndsmaskin — samma logg ger +alltid samma nästa steg. + +### 3.5 Om "täcka allt" + +Det går inte att lova ärligt, och dokumentationen påstår det inte. Det som går +är att täcka fordonets system systematiskt och låta `generisk` vara ett +strukturellt komplett skyddsnät för det ingen förutsett. + +--- + +## 4. ECM v2.0 — evidens- och regelmotorn + +`app/src/felsokning/ecm.ts` (749 rader). Sex motorer: + +### 4.1 Evidence Engine + +Evidensnivåer, härledda ur loggen: + +| Nivå | Betydelse | +|---|---| +| E0 | Inget underlag | +| E1 | Teknikerns observation | +| E2 | Foto | +| E3 | Video | +| E4 | Mätvärde | +| E5 | Diagnosdata / dokument | +| E6 | Flera oberoende källor | + +Ett ärendes evidensnivå är den högsta som underlaget bär. Den visas i UI och +följer med i exporten. + +**Innehållshash:** `innehallsHash()` är en deterministisk FNV-1a över +evidensinnehållet. Samma underlag ⇒ samma hash, oavsett maskin eller tidpunkt. +Det gör exporten verifierbar i efterhand. + +### 4.2 Rule Engine + +- `ORSAKSKATEGORIER` — fast lista för felorsaksanalys (ger jämförbar statistik + över flottan). +- `UNDANTAGSORSAKER` — fast lista för "varför gjordes inte detta". +- `UNDERLAGSKALLOR` — vad en slutsats vilar på. +- `INGEN_ATGARD_ORSAKER`, `KUNDKANALER`. +- `granskaAvvikelse()` — flaggar text som formulerats som konstaterande utan + täckning. + +Fasta listor i stället för fritext är ett medvetet val: fritext går inte att +aggregera, och statistiken över flottan är en av produktens verkliga tillgångar. + +### 4.3 Compliance Engine + +`ARENDETYPER` styr vilket **regelpaket** som gäller. Garantiärende kräver +claim-nummer och servicehistorik; försäkringsärende kräver skadenummer och +bildbevis; kundärende kräver mindre. Paketen är data (`ecm-regler.json`, +serverbart via `/api/ecm/regler`) — nya krav kräver ingen ny release. + +### 4.4 Validation Engine — pre-diagnostik + +Innan felsökningen får börja: objektidentifiering verifierad, arbetsorder +inläst, fordonshistorik kontrollerad **eller motiverad**, ingående mätarställning +dokumenterad, kundens felbeskrivning verifierad, tidiga observationer hanterade. + +### 4.5 Completion Engine — kvalitetsgrinden + +Den största enskilda funktionen (`kvalitetsgrind`, ~240 rader). Ärendet kan inte +avslutas förrän varje rad är grön eller motiverad: + +- Fordonshistorik kontrollerad eller motiverad +- Ingående/utgående mätarställning dokumenterad +- Kundens felbeskrivning verifierad +- **Symptomverifiering:** reproducerat, eller dokumenterat ej reproducerbart +- Felorsaksanalys dokumenterad +- Åtgärd dokumenterad eller motiverad +- Kundens besked på åtgärdsförslaget registrerat +- Utfört arbete trots avböjt åtgärdsförslag (om tillämpligt) +- Kvalitetskontroll genomförd — symptomet verifierat +- Metodikens kontroller: evidens eller dokumenterat undantag +- Foton finns för fotokrävande kontroller +- Teknikerns slutsats signerad +- Hypoteser redovisas som ej verifierade +- Ärendetypens regelpaket uppfyllt (claim / skadenummer / miltal / historik) + +### 4.6 Traceability Engine + +`sparbarhetspaket()` — hela beviskedjan i ett strukturerat objekt: vad som +påstås, vad det vilar på, vem som dokumenterade det och när. + +--- + +## 5. Symptomverifiering (SVP) + +Egen princip därför att den är produktens skarpaste kant mot verkligheten. + +**Kundens beskrivning ≠ konstaterat fel.** + +1. Beskrivningen dokumenteras **ordagrant** (`felbeskrivning`). +2. Den förtydligas via metodikens symptomfrågor — *när, var, hur*, aldrig + "vad är fel". +3. Den **reproduceras**, med tre möjliga utfall: + - **Ja** — med dokumenterade förhållanden. + - **Delvis** — vad som kunde och inte kunde återskapas. + - **Nej** — obligatorisk motivering. + +Rapportens beviskedja skiljer fyra saker som annars blandas ihop: *kundens +beskrivning*, *verifierad observation*, *felorsaksanalys* och *rekommenderad +åtgärd*. + +--- + +## 6. Klienten + +`app/src/felsokning/` + `app/src/pages/felsokning/`. + +| Modul | Rader | Ansvar | +|---|---|---| +| `ArendeSida.tsx` | 2433 | Ärendevyn. Trekolumnslayout på skrivbord | +| `metodiker.ts` | 899 | Metodikbiblioteket | +| `ecm.ts` | 749 | Regel- och evidensmotorn | +| `NyttArende.tsx` | 497 | Ärendestart, arbetsorderskanning | +| `Arendelista.tsx` | 399 | Dashboard: räknare, filter | +| `projektioner.ts` | 356 | Alla vyer som rena funktioner av loggen | +| `ai.ts` | 305 | Klientsidan av orkestern, promptbygge, svarstolkning | +| `plattform.ts` | 296 | API-klient mot självhostad plattform | +| `DelatArendeVy.tsx` | 283 | Delad vy (kund/partner/intern) | +| `domain.ts` | 281 | Händelsetyper | +| `Installningar.tsx` | 281 | Organisation, användare, integrationer | +| `Oversikt.tsx` | 238 | Arbetsledarvy | +| `demo.ts` | 200 | Demoärende med 1 tim 35 min historik | +| `ui.tsx` | 174 | Industriellt verkstads-UI | +| `metodik.ts` | 171 | Metodikmotorn | +| `synk.ts` | 141 | Konfliktfri ihopflätning av händelser | +| `ikoner.tsx` | 132 | Egna SVG-linjeikoner (inga emojis) | +| `streckkod.ts` | 131 | Streckkods-/VIN-avläsning | +| `store.ts` | 106 | zustand-store | +| `bilagor.ts` | 96 | Uppladdning + blob-URL-cache | +| `installningar.ts` | 86 | Organisationsinställningar | +| `Bilagevisning.tsx` | 69 | `` / `` | +| `Mikrofon.tsx` / `rost.ts` | 66 / 65 | Taligenkänning | +| `format.ts` | 48 | Fotoskalning m.m. | + +### 6.1 Projektionerna + +``` +objekt · felbeskrivning · ansvarig · arendeidentitet · arAvslutat +lokalFordonshistorik · utfordaKontroller · ejKontrollerat +observationer · hypoteser · foton · videor +tidsfordelning · formateraTid · tillforlitlighet +brief · overlamningstext · tidsfordelningsRader · sistaAktivitet +``` + +Alla rena funktioner av `Arende`. `ejKontrollerat` är den som sparar mest tid i +verkligheten: *det som orsakar dubbelarbete vid skiftbyte är det ingen skrivit +ner att ingen gjort.* + +### 6.2 UI-språk + +ETKA-inspirerat verkstads-UI: plana ljusgrå ytor (#ECECEC/#F7F7F7), skarpa +kanter, djup marinblå primärfärg, tät typografi (11–15 px), rektangulära knappar +(max 4 px radie), verktygsrad ~44 px. Egna linjeikoner i stället för emojis; +status som färgpunkter. + +Motivet: teknikern har handskar på sig, står i ett bullrigt utrymme och har inte +tid med ett luftigt konsument-UI. + +### 6.3 Lokalt läge + +Utan inloggning fungerar appen mot `localStorage`. Metodiken guidar ensam, +orkestern är avstängd. Status visas i ärendehuvudet. Vid inloggning flätas +lokala händelser ihop med serverns — konfliktfritt per händelse-id, testat. + +--- + +## 7. Backend + +### 7.1 `services/plattform` (1210 rader) + +Ren `node:http`. Enda beroendet är `pg`. + +**API-vägar:** + +``` +GET /halsa +GET /api/openapi.yaml +POST /api/auth/registrera skapar organisation + systemadministratör +POST /api/auth/logga-in +POST /api/auth/logga-ut-alla höjer token_version → alla sessioner dör +GET /api/anvandare admin +POST /api/anvandare +POST /api/anvandare/{id}/avaktivera | /aktivera +GET /api/organisation +GET/PUT /api/organisation/installningar +GET /api/ecm/regler regelpaket som data +GET/POST /api/arenden +POST /api/arenden/{id}/handelser append-only +POST /api/arenden/{id}/bilagor +GET /api/bilagor/{id} hash verifieras vid läsning +GET /api/fordon/{identifierare}/historik +GET /api/statistik/felorsaker +GET /api/oversikt arbetsledarvy +GET /api/delad/{kod} filtrerad enligt nivå +POST /api/delad/{kod}/beslut kundens besked utan inloggning +GET /api/delad/{kod}/bilagor/{id} nivåfiltrerad +GET /api/integrationer/leverantorer +GET/PUT/DELETE /api/integrationer/{leverantor} +POST /api/integrationer/{leverantor}/uppslag +``` + +Inga update- eller delete-vägar mot ärendedata. Med avsikt. + +**Säkerhetsfunktioner i tjänsten:** + +``` +ursprungFor · forTataForsok · kallaFor · inloggningSparrad · loggaForsok +skapaJwt · verifieraJwt · kontoGiltigt · kravAuth · arendeIOrg +integrationsNyckel · kryptera · dekryptera · maskera +arPrivatAdress · pekarInat · gorUppslag · skickaBilaga · synligaTyper +``` + +### 7.2 `services/ai-orkester` (400 rader) + +Äger Claude-nyckeln. Klienten har den **aldrig**. Routing per uppgift: + +| Uppgift | Modell | Effort | Vision | +|---|---|---|---| +| `handledning` | `claude-sonnet-5` | medium | — | +| `granskning` | `claude-opus-5` | **high** | — | +| `sammanfattning` | `claude-sonnet-5` | low | — | +| `metodikval` | `claude-haiku-4-5` | *(ingen — modellen tar inte parametern)* | — | +| `instrumentavlasning` | `claude-sonnet-5` | low | ✔ | +| `dokumenttolkning` | `claude-sonnet-5` | low | ✔ | + +Samtliga svar är **schema-bundna** (`json_schema`). Grundprompten kodar +AI-reglerna: *"Hitta aldrig på fakta"*, *"aldrig en hypotes som ett konstaterat +fel"*, *"KRÄVER verifiering"*. Vid avböjd förfrågan sker automatisk fallback +till reservmodell. **Modellen som svarade loggas i varje `ai_svar`-händelse** — +underlaget ska gå att granska i efterhand. + +Metodikkatalogen byggs ur en enda lista (`METODIK_KATALOG`), som genererar både +schemats `enum` och promptens punktlista. Ett test jämför den mot klientens +bibliotek: glider listorna isär returnerar klassificeraren ett id klienten inte +känner igen, och valet skulle falla *tyst* tillbaka på generisk. Nu faller +testet i stället. + +### 7.3 `services/gemensam/observation.mjs` (166 rader) + +Spårning och mätvärden **utan nya beroenden**. Tjänsterna har medvetet nästan +inga beroenden; att dra in ett OpenTelemetry-SDK med trettio paket för att mäta +fyra saker vore fel avvägning. I stället två standarder som båda bara är text på +stdout: + +- **W3C Trace Context** — `traceparent` följer med genom hela kedjan + (klient → plattform → orkester). +- **CloudWatch EMF** — strukturerad JSON som CloudWatch själv extraherar + mätvärden ur. Ingen agent, ingen SDK, inget som kan sluta fungera tyst. + +``` +spårFrån(huvud) · traceparent(spår) · starta(namn, spår) + → .mät(delnamn, arbete) · .ms() · .delar() +logga(nivå, meddelande, fält) · mätvärde(namn, värde, enhet, dim, extra) +avsluta(spann, { status, väg, extra }) +``` + +Det som mäts valdes utifrån en fråga: *vad vill man veta klockan tre på natten +när något är långsamt?* Svaret är **var tiden gick** — inte hur många anrop som +skett. Därför `delar()`: databasen, modellanropet, objektlagringen, kundens +leverantör, med antal och summa per del i samma loggrad. + +`mät()` mäter även när arbetet kastar — annars ser fel ut som noll tid. + +**Dimensioner hålls medvetet få.** Varje unik kombination är en egen tidsserie +som kostar pengar, så organisation, ärende och spår-id får aldrig bli +dimensioner — de ligger som vanliga fält. Låst av test som explicit förbjuder +`org`, `organisation`, `arende`, `spårId`, `anvandare` bland dimensionerna. + +--- + +## 8. Säkerhet + +| Skydd | Implementation | +|---|---| +| **Multi-tenant-isolering** | Alla ärendefrågor är organisationsknutna (`arendeIOrg`); integrationstestat mot riktig Postgres | +| **Roller** | `tekniker` / `arbetsledare` / `admin`, i JWT och databas-check | +| **JWT-anspråk** | `{ sub, namn, org, roll, tv }` — `tv` = token_version | +| **Omedelbar återkallelse** | `kontoGiltigt()` kontrollerar `aktiv` + `token_version` vid *varje* autentiserat anrop. En giltig signatur räcker inte | +| **Global utloggning** | `/api/auth/logga-ut-alla` höjer `token_version` → alla utfärdade tokens dör direkt | +| **Lösenord** | bcrypt via `gen_salt('bf')` i databasen | +| **Inloggningsspärr** | 15 min-fönster; max 10 försök per konto, 30 per källa. Städas probabilistiskt (2 % chans per skrivning) för att slippa ett cron-jobb | +| **Kryptering i vila** | AES-256-GCM för kundernas integrationsuppgifter; hemliga fält maskeras alltid i API-svar | +| **SSRF-försvar** | `arPrivatAdress()` + `pekarInat()`: 10/8, 127/8, 169.254/16, 172.16–31, 192.168/16, 100.64/10, ::1, fc/fd, fe80, ::ffff:. **DNS slås upp** innan anrop; `.local`/`.internal` blockeras. Escape-hatch `TILLAT_INTERNA_UPPSLAG` för testmiljö | +| **CORS** | `TILLATNA_URSPRUNG`-allowlist, ursprung sätts en gång per anrop | +| **Bilageintegritet** | SHA-256 i loggen, verifieras vid läsning → `409` vid avvikelse | +| **Append-only i databasen** | Triggrar `before update or delete` på både `felsokning_handelser` och `felsokning_arenden` | +| **Podd-härdning** | IMDSv2 obligatoriskt, hoppgräns 1 → podar kan inte låna nodens IAM-roll | +| **IRSA** | Varje tjänstekonto har egen roll; noderna delar inga rättigheter | +| **Delade IAM-roller** | `bygg` får publicera till ECR men inte röra klustret; `drift` får röra klustret men inte publicera bilder | +| **Nätverkspolicy** | Default deny in; explicita `_ut`-regler per tjänst | +| **Databasåtkomst** | Endast från klustrets noder, i ett subnätlager **utan routing ut** | + +### 8.1 Databasschema + +``` +organisationer · anvandare · inloggningsforsok +felsokning_arenden · felsokning_handelser +bilagor · bilage_innehall +delningar · integrationer +``` + +--- + +## 9. Live Share — delningsnivåer + +Tre nivåer, serverstyrd filtrering: + +| Nivå | Ser | +|---|---| +| **kund** | 22 händelsetyper: objekt, felbeskrivning, frågor, kontroller, observationer, mätvärden, foton, videor, kommentarer, överlämningar, åtgärder, kvalitetskontroll … | +| **partner** | Allt kunden ser **+ `hypotes`** (märkta ej verifierade) | +| **intern** | Full insyn — ingen filtrering | + +**Aldrig utanför organisationen:** +`kategori_byte`, `hypotes`, `ai_svar`, `ansvarig_satt`, `arbetsorder_skannad`. + +```js +export function synligaTyper(niva) { + if (niva === "intern") return null; // full insyn + return niva === "partner" ? DELBART_PARTNER : DELBART_KUND; +} +``` + +Länkar är återkallbara. Publik delningssida (`/felsokning/delad/:kod`) kräver +ingen inloggning och pollar för liveuppdatering. Kunden kan lämna besked direkt +i vyn (`POST /api/delad/{kod}/beslut`). + +**Varför tillåtelselista:** en neka-lista måste uppdateras när en ny +händelsetyp läggs till — och det är precis det man glömmer. En tillåtelselista +gör "glömt" till "intern", vilket är det säkra utfallet. + +--- + +## 10. Märkesspecifika kopplingar + +Leverantörer är **data, inte kod** (`integrationer.json`, monterbar som +ConfigMap via `INTEGRATIONER_FIL`). Nya märken kräver ingen ombyggnad. + +| id | Leverantör | +|---|---| +| `generisk_vin` | Valfri VIN-tjänst över HTTP | +| `vag_erwin` | Volkswagen Group erWin (VW, Audi, Škoda, SEAT) | +| `volvo_vida` | Volvo VIDA | +| `fordonsregister` | Regnr → fordon | + +Varje leverantör deklarerar sina fält, vilka som är hemliga (krypteras + maskeras), +och hur svaret mappas till domänens fält (`marke`, `modell`, `arsmodell`, +`motor`, `vaxellada`). + +Uppslag går genom SSRF-skyddet — en kund kan alltså inte peka en "leverantör" +mot klustrets interna adresser. + +--- + +## 11. Visual-first + +Kameran **är** integrationslagret. Det som syns på en skärm eller ett instrument +fotograferas och tolkas, i stället för att integreras. + +- **Arbetsorderskanning** är primärvägen vid ärendestart. Sonnet 5 (vision) + läser kund-, fordons- och verkstadsuppgifter oavsett layout, med konfidens per + fält: + - 🟢 ≥95 % godkänns automatiskt + - 🟡 80–95 % markeras för genomläsning + - 🔴 <80 % kräver aktiv bekräftelse + + Teknikern granskar alltså bara osäkra fält. Visuell granskning med dokumentet + bredvid fälten; klick markerar ungefärlig position. + +- **Instrumentavläsning** — foto av diagnosskärm eller instrument → strukturerade + värden. + +Motivet är kommersiellt: en integration per verkstadssystem är en säljcykel per +kund. En kamera fungerar mot allt, direkt. + +--- + +## 12. Infrastruktur + +Två Terraform-lager. Basen körs sällan, arbetslastlagret ofta. + +### 12.1 `infra/aws` — basen (91 resurser) + +| Område | Innehåll | +|---|---| +| **Nät** | 1 VPC, 3 subnätlager × 3 zoner: publikt (bara ALB + NAT), privat (noder, inga publika adresser), data (Aurora, **ingen väg ut alls**). VPC-endpoints: S3 (gateway); ECR, loggar, Secrets Manager, STS, ELB (gränssnitt) → trafiken lämnar aldrig nätet | +| **Kluster** | EKS, arm64-noder, IRSA via OIDC-provider, IMDSv2 hoppgräns 1, alla fem kontrollplansloggar på | +| **Data** | Aurora PostgreSQL Serverless v2, PITR ned till sekunden, KMS med egen nyckel, `sslmode=require` | +| **Objektlagring** | S3 för bilagor: SSE-KMS, publik åtkomst blockerad, TLS obligatoriskt, versionshantering på. Plattformens roll får läsa och skriva — **men aldrig radera** | +| **Register** | ECR med **oföränderliga taggar** + sårbarhetsskanning | +| **Hemligheter** | Secrets Manager; läses endast av plattformsrollen via IRSA | +| **Roller** | 9 IAM-roller, bl.a. delade `bygg` / `drift` | +| **Domän** | Route 53 + ACM med DNS-validering | +| **Observation** | 7 larm, 1 instrumentpanel, 3 loggrupper, SNS-topic | + +**Larmen** — få, men de som finns betyder något. *Ett larm som ingen agerar på +lär folk att ignorera larm.* + +- Aurora CPU > 85 % i tre perioder (skalningstaket kan vara nått) +- Aurora fri lokal lagring < 5 GiB +- **Backup-ålder** — `treat_missing_data = "breaching"`. Saknas mätvärdet finns + ingen säkerhetskopiering. *En backup man tror finns är värre än ingen.* +- Färre noder än minsta önskade +- Svarstid **p95** > 3 s i tre perioder — inte medelvärde, som döljer att var + tjugonde tekniker väntar orimligt länge +- Serverfel (summa > 5) +- Modellen avböjer (tyder på oväntat underlag, inte driftfel) + +### 12.2 `infra/terraform` — arbetslasten (35 resurser) + +Läser basen via `terraform_remote_state`, upprepar ingenting. + +``` +kubernetes_namespace_v1 denna, gitea +kubernetes_service_account_v1 plattform (IRSA), drift, runner +kubernetes_deployment_v1 plattform, orkester, web +kubernetes_service_v1 plattform, orkester, web +kubernetes_horizontal_pod_autoscaler_v2 × 3 +kubernetes_pod_disruption_budget_v1 × 3 +kubernetes_ingress_v1 denna, gitea +kubernetes_network_policy_v1 neka_allt_in, tjanster_in, dns_ut, + plattform_ut, orkester_ut, web_ut +kubernetes_manifest hemlighetskalla, hemligheter (External Secrets) +helm_release lastbalanserare (ALB), external_secrets, + cloudwatch, metrics, gitea +aws_route53_record denna +``` + +`karta.tf` (117 rader) producerar en läsbar karta över hela driftbilden: +`terraform output karta`. + +### 12.3 Git och CI — helt egenhostat + +Uttrycklig produktbeslut: **inget GitHub i driftvägen.** Gitea + Actions-runners +kör på egen EKS. `.gitea/workflows/felsokning.yml`: + +| Jobb | Innehåll | +|---|---| +| `test-och-bygg` | `vitest run`, `typkontroll`, eslint, `vite build` | +| `tjanster` | eslint på tjänsterna, **integrationstest mot riktig Postgres**, `swagger-cli validate` | +| `terraform` | `fmt -check -recursive`, `init -backend=false`, `validate` | +| `publicera` | Endast på `main`, endast om ovanstående gått igenom. Bygger tre bilder, taggar med commit-SHA, pushar till eget ECR. **OIDC, ingen statisk nyckel** | +| `driftsatt` | **Manuellt** (`workflow_dispatch`) med explicit bildtagg | + +Driftsättning är ett eget steg med avsikt: *en bild i registret är inte samma +sak som en bild som kör.* Rollback = kör igen med tidigare tagg. + +Klientens API-adress bakas in vid bygget (Vite), så bilden är miljöbunden. +Byggkontexten för tjänsterna är `felsokning/services` så att båda når den delade +observationsmodulen utan att den dupliceras. + +--- + +## 13. Testning + +**120 vitest-tester** i 13 filer: + +| Fil | Antal | Låser | +|---|---|---| +| `ecm.test.ts` | 32 | Evidensnivåer, regelpaket, kvalitetsgrind, pre-diagnostik | +| `metodiker.test.ts` | 14 | Bibliotekets struktur, metodikval, katalogparitet mot orkestern | +| `projektioner.test.ts` | 13 | Vyer som rena funktioner, nästa steg | +| `ai.test.ts` | 11 | Orkesterparitet, OpenAPI ↔ server, append-only, promptregler | +| `observation.test.ts` | 10 | Spårning, EMF-format, **förbjudna dimensioner** | +| `bilagor.test.ts` | 9 | Innehållshash, SigV4, lagerval | +| `delning.test.ts` | 7 | Tillåtelselistan täcker varje händelsetyp | +| `integrationer.test.ts` | 7 | Leverantörsuppslag, SSRF-guard | +| `demo.test.ts` | 4 | Demoärendet är rikt nog att visa | +| `installningar.test.ts` | 4 | Organisationsinställningar | +| `streckkod.test.ts` | 4 | VIN/streckkod | +| `synk.test.ts` | 4 | Konfliktfri ihopflätning | +| `example.test.ts` | 1 | — | + +**Utöver enhetstesterna:** + +- `integrationstest.sh` — hela flödet mot **riktig Postgres**: organisationer, + roller, append-only-triggern, isolering, delning, bilagor. +- SigV4 **korsverifierad bit-för-bit mot botocore** (`sigv4-referens.json`). +- `swagger-cli validate` på OpenAPI-specen. +- Paritetstester som jämför spec ↔ server, klient ↔ orkester (× 2 kopior), + domänmodell ↔ delningslista. + +### 13.1 Verifieringsloopen före varje commit + +``` +npx vitest run # 120 tester +npm run typkontroll # tsc --noEmit (vite build typkontrollerar INTE) +npx eslint src/felsokning src/pages/felsokning +cd ../services && npx eslint . +npm run build +terraform fmt -check -recursive +# rotens CI: lint · format:check · typecheck · test +``` + +`typkontroll` lades till efter att två latenta krascher (`TextFalt` och +`UNDANTAGSORSAKER` använda utan import) tagit sig förbi `vite build` — som +transpilerar men inte typkontrollerar. + +--- + +## 14. Repostruktur + +`main` är en npm-workspaces-monorepo som heter **Semantika** och äger roten. När +de två produkterna slogs ihop behölls båda, med verktygskedjorna **skilda åt per +träd** — inte genom att försvaga någons regler. + +``` +/ Semantika (workspaces-rot) +├── apps/mobile/ Semantika +├── services/api/ Semantika +├── infra/ Semantika +├── .github/workflows/ci.yml Semantika — rörs inte +│ +├── .gitea/workflows/felsokning.yml Guidad Felsökning (självhostad CI) +└── felsokning/ + ├── app/ klient (egen package.json, eslint, vitest, tsconfig) + ├── services/ + │ ├── plattform/ + │ ├── ai-orkester/ + │ └── gemensam/ observation.mjs (delad) + ├── infra/ + │ ├── aws/ basen, 91 resurser + │ ├── terraform/ arbetslasten, 35 resurser + │ └── postgres-init.sql + ├── docs/ + └── supabase/ migrationer + edge-funktion (äldre driftväg) +``` + +**Två driftvägar finns parallellt:** den självhostade AWS-stacken (den som +gäller) och en äldre Supabase-baserad (edge-funktionen `felsokning-ai`, +migrationer). Orkestern finns därför i **två kopior** som hålls i synk av test. + +--- + +## 15. Dokumentation i repot + +``` +docs/VISION.md produktvisionen +docs/MASTER-PROMPT.md grundinstruktionen +docs/MVP.md vad som är byggt, funktion för funktion +docs/DEMO.md demomanus för visning +docs/DRIFT.md drift +docs/SYSTEMBESKRIVNING.md detta dokument +docs/exempel/vibration-vid-88-km-h.md genomgående exempelflöde +docs/moduler/ + arbetslogg-och-tidredovisning.md + arendebrief.md + evidensmotor.md + kommunikationsmodell.md + kundrapport.md + live-share.md + markesspecifika-kopplingar.md + verifierade-checklistor.md +``` + +--- + +## 16. Designbeslut och deras motiv + +Sammanställda därför att motivet ofta är viktigare än beslutet. + +| Beslut | Motiv | +|---|---| +| Event sourcing | Underlaget måste hålla i en tvist. Historiken *är* värdet | +| Append-only även i databasen | Applikationslagret kan kringgås; triggern kan det inte | +| Tillåtelselista för delning | Glömd händelsetyp blir intern, inte läckt | +| Fasta orsakskategorier | Fritext går inte att aggregera; flottstatistiken är en tillgång | +| Serverägd modellrouting | Klienten ska aldrig ha nyckeln, och routing ska kunna ändras utan release | +| Loggad modell per svar | Underlaget ska gå att granska i efterhand | +| Undvik ordet "AI" | Kunden hör "gissning"; handläggaren väger det lägre | +| Visual-first | En integration per verkstadssystem = en säljcykel per kund. Kameran fungerar direkt | +| Leverantörer som data | Nytt märke ska inte kräva release | +| Egen observation, noll beroenden | 30 paket för att mäta 4 saker är fel avvägning | +| Få dimensioner i EMF | Varje kombination är en betald tidsserie | +| p95 i larmet, inte medel | Medelvärdet döljer att var tjugonde tekniker väntar | +| Larm på *saknad* backupdata | En backup man tror finns är värre än ingen | +| Delade bygg-/driftroller | Ett komprometterat bygge ska inte kunna röra klustret | +| Manuell driftsättning | En bild i registret ≠ en bild som kör | +| Oföränderliga ECR-taggar | En tagg ska betyda samma sak imorgon | +| Innehållsadresserade bilagor | Utbytt bild ska upptäckas, inte antas | +| Hash-verifiering vid läsning | Det räcker inte att hasha vid skrivning | +| S3-rollen får inte radera | Append-only måste gälla även lagret | +| Motor skild från innehåll | Biblioteket växer; motorn ska inte behöva ändras | +| Poängsatt metodikval | Regexkedjor blir ogenomskådliga vid 16 alternativ | +| Nyckelord som stammar | Svensk böjning kapar ett `e` — annars matchar inget | +| `generisk` som fallback | Ett ärligt "vi vet inte" slår en gissning | +| Säkerhetssteg först i högvolt | Det arbetet kan döda | +| Svenska i koden | Domänen är svensk; översättning fram och tillbaka tappar precision | + +--- + +## 17. Kända begränsningar och öppna punkter + +Uttryckligen inte färdigt: + +- **Två orkesterkopior** (Supabase edge-funktion + K8s-tjänst) hålls i synk av + test, inte av delad kod. Supabase-vägen är den äldre och bör avvecklas. +- **`ArendeSida.tsx` är 2433 rader.** Fungerar, men är den fil som kostar mest + att ändra i. +- **`terraform validate` kan inte köras lokalt** i utvecklingsmiljön + (utgående nätverkspolicy blockerar providernedladdning). Ersatt av + `terraform fmt` + en egen statisk referenskontroll; den riktiga valideringen + sker i CI. +- **Claude-nyckeln fylls i för hand** efter första `apply` — den ligger inte i + Terraform-state, med avsikt. +- **`postgres-init.sql` körs manuellt** mot databasen efter basens `apply`. +- **Ingen automatisk återställningstest av backup.** Larmet säger att backup + *sker*, inte att den *går att återställa*. +- **Metodikbiblioteket täcker inte allt** — och påstår det inte. `generisk` är + skyddsnätet. +- Rotens eslint har 20 förbefintliga fel i Semantikas egna sidor (`no-explicit-any`) + som inte hör till Guidad Felsökning. + +--- + +## 18. Ordlista + +| Svenska | Betydelse | +|---|---| +| Ärende | Ett felsökningsuppdrag | +| Händelse / loggpost | Odelbar post i append-only-loggen | +| Metodik | Strukturerat felsökningsflöde | +| Steg | Fas i en metodik (symptom, visuell, mätningar …) | +| Kontroll | Enskild checklistpunkt med minimikrav | +| Krav | `matvarde` / `kommentar` / `foto` | +| Undantag | Dokumenterad orsak till att en kontroll hoppades över | +| Brief | Sammanställd ärendebild — projektion | +| Kvalitetsgrind | Regeluppsättning som måste passeras före avslut | +| Evidensnivå | E0–E6, bevisvärdet i underlaget | +| Reproducering | Symptomverifiering: ja / delvis / nej | +| Felorsak | Strukturerad orsaksanalys med kategori och underlag | +| Delning | Extern länk med behörighetsnivå | +| Orkester | Tjänsten som äger modellroutingen | +| Spann / spår | Tidsmätning respektive W3C-spårning | +| Bilaga | Innehållsadresserat foto/video/dokument |