diff --git a/felsokning/docs/MVP.md b/felsokning/docs/MVP.md index 4b6d185..b6db031 100644 --- a/felsokning/docs/MVP.md +++ b/felsokning/docs/MVP.md @@ -21,7 +21,7 @@ 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). +Fullständig systembeskrivning i ett dokument (för granskning eller resonemang utanför repot): [SYSTEMBESKRIVNING.md](SYSTEMBESKRIVNING.md) — även på [engelska](SYSTEMBESKRIVNING.en.md), [tyska](SYSTEMBESKRIVNING.de.md), [danska](SYSTEMBESKRIVNING.da.md) och [norska](SYSTEMBESKRIVNING.no.md). Svenska versionen är källan; översättningarna behåller kodidentifierarna oöversatta. 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. diff --git a/felsokning/docs/SYSTEMBESKRIVNING.da.md b/felsokning/docs/SYSTEMBESKRIVNING.da.md new file mode 100644 index 0000000..6a4cc12 --- /dev/null +++ b/felsokning/docs/SYSTEMBESKRIVNING.da.md @@ -0,0 +1,933 @@ +# Guidad Felsökning (Guidet Fejlfinding) — fuldstændig systembeskrivelse + +> **Dansk oversættelse.** Kilden er [SYSTEMBESKRIVNING.md](SYSTEMBESKRIVNING.md) (svensk). +> Ved uoverensstemmelse gælder det svenske dokument. +> +> **Kodeidentifikatorer oversættes ikke.** Hændelsestyper, funktions- og +> feltnavne, filstier og konfigurationsnøgler er svenske *i selve koden*. En +> oversættelse ville gøre dokumentet ubrugeligt over for repositoriet, så de står +> ordret, med dansk forklaring hvor betydningen ikke er indlysende. +> +> Et selvstændigt referencedokument. Alt nedenfor er hentet fra koden i +> `felsokning/` (branch `claude/guidad-felsokning-vision-1mnx7f`), ikke fra +> planer eller hensigter. Hvor noget **ikke** findes, står det udtrykkeligt. +> +> Senest synkroniseret med koden: commit `1bb4031`, 04-08-2026. + +--- + +## 0. Sammenfatning på tredive sekunder + +Guidad Felsökning er en SaaS-platform til **autoværksteder**. Den fører en +tekniker gennem en struktureret fejlfinding, kræver bevis for hver eneste +påstand og producerer et sporbart grundlag, der kan deles med kunden, et +forsikringsselskab eller den næste tekniker. + +Den bærende idé er negativ snarere end positiv: **systemet fremstiller aldrig en +hypotese som en konstateret fejl.** Det er ikke en politik i et dokument — det er +kodet, testet og blokerer forløb. Når beviset mangler, står der „Evidens saknas" +(bevis mangler), ikke et kvalificeret gæt. + +Teknisk: en **append-only hændelseslog** er eneste sandhedskilde. Alt andet — +sagsvisningen, briefen, kunderapporten, kvalitetsporten, statistikken — er rene +projektioner af loggen og kan altid gendannes. + +| | | +|---|---| +| Klient | React 18 + TypeScript + Vite + Tailwind + zustand + react-router | +| Backend | To Node-tjenester (`plattform`, `ai-orkester`), rent `node:http`, minimale afhængigheder | +| Database | PostgreSQL (Aurora Serverless v2), append-only håndhævet af databasetriggere | +| Model | Claude, serverejet routing pr. opgave | +| Infrastruktur | AWS + EKS, 126 Terraform-ressourcer i to lag | +| Git & CI | **Selvhostet Gitea + Actions-runnere på eget EKS** — ingen GitHub i driftvejen | +| Test | 120 vitest-test + integrationstest mod rigtig Postgres | +| Sprog i koden | Svensk (identifikatorer, kommentarer, commit-beskeder) | + +--- + +## 1. Produktprincipper + +Disse fem er invarianter, ikke retningslinjer. Hver enkelt har en modsvarighed i +kode og i en test. + +### 1.1 Ingen hypotese fremstilles som en konstateret fejl + +Hypoteser er deres egen hændelsestype (`hypotes`) med obligatorisk +pålidelighedsniveau og kan **aldrig** antage niveauet `hog` (høj) — +`niva: Exclude`; typesystemet forbyder det. I +kunderapporten er de udtrykkeligt markeret som ikke verificerede. +Kvalitetsporten har en egen række til dette. + +Formuleringen ved mislykket reproduktion er *„kunde inte reproduceras under de +förhållanden som rådde"* („kunne ikke reproduceres under de forhold, der +herskede") — aldrig „fejlen konstateret" eller „ingen fejl fundet". Det er kodet +både i projektionerne og i orkestrets grundprompt. + +### 1.2 Et flueben er ikke bevis + +Hvert kontrolpunkt i hver metodik bærer et **minimumskrav**: `matvarde` +(måleværdi) | `kommentar` (observation) | `foto`. En måling kan ikke markeres +udført uden værdi; en fotokontrol ikke uden billede. Vil teknikeren springe +noget over, kræves en **dokumenteret undtagelse** med en begrundelse fra en fast +liste. + +Låst af testen *„varje kontroll kräver bevis — en kryssruta är inte evidens"*. + +### 1.3 Loggen er append-only, hele vejen ned + +Der findes ingen update- eller delete-operationer i API'et, og databasen har +triggere, som afviser dem, selv hvis nogen omgår applikationen. En test søger +aktivt i serverkoden efter `update`/`delete` mod hændelsestabellen og fejler, +hvis de dukker op. + +Konsekvens: en forkert oplysning rettes *ved en ny hændelse*, aldrig ved at den +gamle forsvinder. Historikken er det, der giver grundlaget værdi i en tvist. + +### 1.4 Terminologi + +I brugerfladen og i kundekommunikationen bruges **systemet, analysen, +vurderingen, beslutningsstøtten** — ikke „AI", medmindre det er teknisk +nødvendigt. Produktet beskrives som et *evidensbaseret diagnosesystem* / +*intelligent beslutningsstøtte*. + +Grunden er både kommerciel og erkendelsesmæssig: en værkstedskunde, der hører +„AI", hører „gæt". En forsikringssagsbehandler, der læser „AI-vurdering" i et +grundlag, vægter det lavere. + +### 1.5 Delingsgrænsen er en tilladelsesliste + +Hvad der må forlade organisationen, opregnes **positivt**, pr. niveau. En ny +hændelsestype er dermed intern, indtil nogen aktivt slipper den fri. En test +kræver, at hver type i domænemodellen er klassificeret — glemmes en, fejler +bygget, i stedet for at den lækker. + +--- + +## 2. Domænemodellen — hændelsesloggen + +`app/src/felsokning/domain.ts` (281 linjer). + +En sag er: identitet + metadata + en **ordnet liste af logposter**. Hver post +bærer `id`, `tidpunkt` (tidspunkt), `tekniker` og en `handelse` (hændelse). + +### 2.1 Samtlige hændelsestyper + +| Type | Indhold | Rolle | +|---|---|---| +| `objekt_identifierat` | `objekt` (nummerplade/VIN, mærke, model, motor …) | Hvad sagen angår | +| `arbetsorder_skannad` | `falt[]` + bilag | Aflæst arbejdsordre (**intern**) | +| `felbeskrivning` | `text` | Kundens ord, ordret | +| `arendetyp_satt` | `arendetyp` | Garanti / forsikring / kunde — vælger regelpakke | +| `fraga_besvarad` | `stegId`, `frageId`, `fraga`, `svar` | Metodikkens symptomspørgsmål | +| `kontroll_utford` | `stegId`, `kontrollId`, `text`, `resultat?`, `undantag?` | Verificeret tjeklistepunkt | +| `observation` | `text` | Hvad teknikeren så — ikke hvad hen tror | +| `matvarde` | `beskrivning`, `varde`, `enhet?` | Måling (E4) | +| `hypotes` | `text`, `niva` (aldrig `hog`) | Arbejdshypotese (**intern**) | +| `foto` | `beskrivning` + bilag | Billedbevis (E2) | +| `video` | `beskrivning` + bilag | Levende bevis (E3) | +| `matarstallning` | `lage` (ind/ud), `varde` + bilag | Kilometerstand ind/ud | +| `historik_kontrollerad` | `kontrollerad`, `kommentar?` | Servicehistorik | +| `reproducering` | `status` (ja/delvis/nej), `beskrivning` | **Symptomverifikation** | +| `felorsak` | struktureret årsagsanalyse | Årsag, kategori, grundlag | +| `atgardsforslag` | forslag med begrundelse | Hvad der bør gøres | +| `kundbeslut` | godkendt/afvist, kanal | Kundens beslutning | +| `atgard_utford` | udført arbejde | Hvad der faktisk blev gjort | +| `kvalitetskontroll` | verifikation efter reparation | Er symptomet væk? | +| `kommentar` | `text` | Fri note | +| `kategori_byte` | `kategori` | Tidsregistrering (**intern**) | +| `inaktivitet_forklarad` | `text`, `minuter` | Hvorfor det stod stille | +| `overlamning` | `fran`, `till?` | Vagtoverdragelse | +| `ansvarig_satt` | `ansvarig` | Værkførerens omfordeling (**intern**) | +| `ai_svar` | klassificerede `rader[]`, modelnavn | Beslutningsstøttens svar (**intern**) | +| `export_skapad` | `format`, `version` | Eksporten logger sig selv | +| `arende_avslutat` | `signatur?` | Teknikerens underskrift | + +### 2.2 Bilag er indholdsadresserede + +`foto`, `video`, `matarstallning` og `arbetsorder_skannad` er *snittyper* med +`Bilaga` (bilag): + +```ts +export interface Bilaga { + bilagaId?: string; + bilagaHash?: string; // SHA-256 + dataUrl?: string; // bliver for altid — loggen er append-only +} +``` + +Indholdet ligger uden for loggen (S3 eller database), men **hashen ligger i +loggen**. Ved læsning verificeres hashen; passer den ikke, returneres `409`. +Betydningen: bytter nogen et billede ud i lageret, opdages det, og loggen kan +bevise, at det oprindelige billede var et andet. + +`dataUrl` beholdes i typen, fordi ældre poster har den indlejret — og loggen kan +ikke skrives om. + +--- + +## 3. Metodikmotoren + +Siden den seneste ændring er **motor og indhold adskilt**: + +- `metodik.ts` (171 linjer) — typer, valg af metodik, udledning af næste trin. +- `metodiker.ts` (899 linjer) — de seksten metodikker. + +Biblioteket kan vokse, uden at motoren ændres. + +### 3.1 Metodikbiblioteket + +Trin-id'er er kode og forbliver svenske. `symptom` = symptom, `visuell` = +visuel kontrol, `matningar` = målinger, `provkorning` = prøvekørsel, `sakerhet` += sikkerhed, `avlasning` = aflæsning, `glapp` = slør, `packning` = pakning. + +| id | Navn (i koden) | Område | Trin | Kontroller | +|---|---|---|---|---| +| `vibration` | Vibration under körning | Hjul og afbalancering | symptom → visuell → kontroller → provkorning | 19 | +| `bromsar` | Bromssystem | Undervogn | symptom → visuell → matningar → system | 14 | +| `styrning_fjadring` | Styrning och fjädring | Undervogn | 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 | Transmission | 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øjvolt | **sakerhet** → symptom → avlasning → laddning | 16 | +| `diagnos_natverk` | Felkoder och kommunikation | Diagnose | 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 | Diagnose | symptom → forutsattningar → kalibrering | 9 | +| `generisk` | Generell strukturerad felsökning | Øvrigt | symptom → visuell → grundkontroller → funktionstest | 9 | + +Danske navne: vibration under kørsel · bremsesystem · styretøj og affjedring · +elsystem og strømforsyning · start- og ladesystem · motorgang og ydelse · +kølesystem og overophedning · gearkasse og transmission · udstødning og +emissioner · klimaanlæg · højvoltsystem (elbil/hybrid) · fejlkoder og +kommunikation · lækage · unormal støj · førerassistance og kalibrering · +generisk struktureret fejlfinding. + +### 3.2 Tre regler, låst af test + +1. **Hver kontrol har et minimumskrav.** Måleværdi, foto eller observation. +2. **Hver metodik begynder med at verificere symptomet**, aldrig med at + reparere. Kundens ord bliver først et verificeret symptom, når det er + reproduceret. +3. **Hvor arbejdet kan skade nogen, kommer sikkerhedstrinnet først.** Kun + `sakerhet` må gå forud for `symptom` — testen tillader præcis den undtagelse + og ingen anden. + +`hogvolt` er den eneste metodik med et sikkerhedstrin. Det kræver +kvalifikation, dokumenteret udtaget serviceafbryder (foto), ventetid efter +fabrikantens anvisning, **målt spændingsfrihed** (en måleværdi — ikke et ja til +et spørgsmål) og værnemidler. Testen kontrollerer, at trinnet ligger først, at +`spanningsfrihet` kræver en måleværdi, og at beskrivelsen indeholder ordet +„livsfarlig". + +Grunden er enkel: det arbejde kan dræbe nogen. Der dur et flueben ikke. + +### 3.3 Valg af metodik + +Tidligere en regex-kæde med tre udfald. Nu **pointsat nøgleordsmatchning**: + +```ts +export function metodikPoang(metodik: Metodik, text: string): number +export function valjMetodik(felbeskrivning: string): Metodik +``` + +- Point = summen af længden på de nøgleord, der rammer. Et længere — mere + specifikt — ord vejer tungere. `traktionsbatteri` (16) slår `batteri`. +- **Korte ord (≤3 tegn) matches som helt ord, længere som ordstamme.** Ellers + ville `"ac"` have ramt *acceleration*, og en vibration var havnet i + klimaanlægget. +- Ved lige point vinder den, der står først i biblioteket → valget er **stabilt** + mellem kørsler. +- Ingen træffer → `generisk`. + +**En faldgrube, der faktisk bed under udviklingen:** nøgleordene skal være +*stammer*, ikke færdigbøjede ord. Svensk bøjning fjerner ofte et `e`: +*filter → filtret*, så `"partikelfilter"` rammer aldrig den tekst, en tekniker +rent faktisk skriver. Det samme gælder *regenerering → regenererar*, +*misständning → misständer*, *skrammel → skramlar*. Biblioteket bruger derfor +`partikelfilt`, `regenerer`, `misständ`, `skram`. + +*(Til en lokalisering: dette er en egenskab ved svensk morfologi. Dansk har den +samme klasse af problem i bestemt form og sammensætninger — `filter` bliver +`filteret`, og `bremse` optræder inde i `håndbremse`, hvor en ordstammematchning +med krav om ordstart ikke rammer. Et lokaliseret nøgleordssæt skal valideres mod +den samme test, ikke oversættes ord for ord.)* + +**Valget er en spørgsmålsrækkefølge, ikke en diagnose.** Det afgør, hvor +teknikeren begynder at lede, ikke hvad der er galt. Rammer intet, er `generisk` +det ærlige svar — strukturelt komplet og bedre end et gæt. + +### 3.4 Næste trin + +```ts +export function nastaSteg(arende: Arende, metodik: Metodik): NastaSteg +``` + +Rent udledt af loggen: første ubesvarede spørgsmål, dernæst første ikke-udførte +kontrol, i metodikkens rækkefølge. Ingen skjult tilstandsmaskine — den samme log +giver altid det samme næste trin. + +### 3.5 Om at „dække alt" + +Det kan ikke loves ærligt, og dokumentationen påstår det ikke. Det, der kan +lade sig gøre, er at dække køretøjets systemer systematisk og lade `generisk` +være et strukturelt komplet sikkerhedsnet for det, ingen har forudset. + +--- + +## 4. ECM v2.0 — bevis- og regelmotoren + +`app/src/felsokning/ecm.ts` (749 linjer). Seks motorer: + +### 4.1 Evidence Engine + +Bevisniveauer, udledt af loggen: + +| Niveau | Betydning | +|---|---| +| E0 | Intet grundlag | +| E1 | Teknikerens observation | +| E2 | Foto | +| E3 | Video | +| E4 | Måleværdi | +| E5 | Diagnosedata / dokument | +| E6 | Flere uafhængige kilder | + +En sags bevisniveau er det højeste, grundlaget bærer. Det vises i brugerfladen +og følger med eksporten. + +**Indholdshash:** `innehallsHash()` er en deterministisk FNV-1a over +bevisindholdet. Samme grundlag ⇒ samme hash, uanset maskine eller tidspunkt. Det +gør eksporten verificerbar bagefter. + +### 4.2 Rule Engine + +- `ORSAKSKATEGORIER` — fast liste over årsagskategorier (giver sammenlignelig + statistik på tværs af flåden). +- `UNDANTAGSORSAKER` — fast liste til „hvorfor dette ikke blev gjort". +- `UNDERLAGSKALLOR` — hvad en konklusion hviler på. +- `INGEN_ATGARD_ORSAKER`, `KUNDKANALER` (kundekanaler). +- `granskaAvvikelse()` — markerer tekst, der er formuleret som en konstatering + uden dækning. + +Faste lister frem for fritekst er et bevidst valg: fritekst kan ikke aggregeres, +og flådestatistikken er et af produktets reelle aktiver. + +### 4.3 Compliance Engine + +`ARENDETYPER` (sagstyper) bestemmer, hvilken **regelpakke** der gælder. En +garantisag kræver claim-nummer og servicehistorik; en forsikringssag kræver +skadenummer og billedbevis; en kundesag kræver mindre. Pakkerne er data +(`ecm-regler.json`, kan serveres via `/api/ecm/regler`) — nye krav kræver ingen +ny udgivelse. + +### 4.4 Validation Engine — prædiagnostik + +Før fejlfindingen må begynde: objektidentifikation verificeret, arbejdsordre +indlæst, køretøjshistorik kontrolleret **eller begrundet**, indgående +kilometerstand dokumenteret, kundens fejlbeskrivelse verificeret, tidlige +observationer håndteret. + +### 4.5 Completion Engine — kvalitetsporten + +Den største enkeltfunktion (`kvalitetsgrind`, ~240 linjer). Sagen kan ikke +afsluttes, før hver række er grøn eller begrundet: + +- Køretøjshistorik kontrolleret eller begrundet +- Indgående/udgående kilometerstand dokumenteret +- Kundens fejlbeskrivelse verificeret +- **Symptomverifikation:** reproduceret eller dokumenteret som ikke + reproducerbar +- Årsagsanalyse dokumenteret +- Udbedring dokumenteret eller begrundet +- Kundens svar på forslaget registreret +- Arbejde udført trods afvist forslag (hvor det er relevant) +- Kvalitetskontrol gennemført — symptomet verificeret +- Metodikkens kontroller: bevis eller dokumenteret undtagelse +- Fotos findes til fotokrævende kontroller +- Teknikerens konklusion underskrevet +- Hypoteser fremstillet som ikke verificerede +- Sagstypens regelpakke opfyldt (claim / skadenummer / kilometerstand / + historik) + +### 4.6 Traceability Engine + +`sparbarhetspaket()` — hele beviskæden i ét struktureret objekt: hvad der +påstås, hvad det hviler på, hvem der dokumenterede det og hvornår. + +--- + +## 5. Symptomverifikation (SVP) + +Et selvstændigt princip, fordi det er produktets skarpeste kant mod +virkeligheden. + +**Kundens beskrivelse ≠ en konstateret fejl.** + +1. Beskrivelsen dokumenteres **ordret** (`felbeskrivning`). +2. Den præciseres gennem metodikkens symptomspørgsmål — *hvornår, hvor, + hvordan*, aldrig „hvad er der galt". +3. Den **reproduceres**, med tre mulige udfald: + - **Ja** — med dokumenterede forhold. + - **Delvis** — hvad der kunne og ikke kunne genskabes. + - **Nej** — obligatorisk begrundelse. + +Rapportens beviskæde adskiller fire ting, der ellers blandes sammen: *kundens +beskrivelse*, *verificeret observation*, *årsagsanalyse* og *anbefalet +udbedring*. + +--- + +## 6. Klienten + +`app/src/felsokning/` + `app/src/pages/felsokning/`. + +| Modul | Linjer | Ansvar | +|---|---|---| +| `ArendeSida.tsx` | 2433 | Sagsvisningen. Trekolonnelayout på skrivebord | +| `metodiker.ts` | 899 | Metodikbiblioteket | +| `ecm.ts` | 749 | Regel- og bevismotor | +| `NyttArende.tsx` | 497 | Sagsoprettelse, scanning af arbejdsordre | +| `Arendelista.tsx` | 399 | Dashboard: tællere, filtre | +| `projektioner.ts` | 356 | Alle visninger som rene funktioner af loggen | +| `ai.ts` | 305 | Klientsiden af orkestret, promptopbygning, svarfortolkning | +| `plattform.ts` | 296 | API-klient mod den selvhostede platform | +| `DelatArendeVy.tsx` | 283 | Delt visning (kunde/partner/intern) | +| `domain.ts` | 281 | Hændelsestyper | +| `Installningar.tsx` | 281 | Organisation, brugere, integrationer | +| `Oversikt.tsx` | 238 | Værkførervisning | +| `demo.ts` | 200 | Demosag med 1 t 35 min historik | +| `ui.tsx` | 174 | Industriel værkstedsflade | +| `metodik.ts` | 171 | Metodikmotoren | +| `synk.ts` | 141 | Konfliktfri sammenfletning af hændelser | +| `ikoner.tsx` | 132 | Egne SVG-linjeikoner (ingen emojis) | +| `streckkod.ts` | 131 | Stregkode-/VIN-aflæsning | +| `store.ts` | 106 | zustand-store | +| `bilagor.ts` | 96 | Upload + blob-URL-cache | +| `installningar.ts` | 86 | Organisationsindstillinger | +| `Bilagevisning.tsx` | 69 | `` / `` | +| `Mikrofon.tsx` / `rost.ts` | 66 / 65 | Talegenkendelse | +| `format.ts` | 48 | Fotoskalering m.m. | + +### 6.1 Projektionerne + +``` +objekt · felbeskrivning · ansvarig · arendeidentitet · arAvslutat +lokalFordonshistorik · utfordaKontroller · ejKontrollerat +observationer · hypoteser · foton · videor +tidsfordelning · formateraTid · tillforlitlighet +brief · overlamningstext · tidsfordelningsRader · sistaAktivitet +``` + +Alle rene funktioner af `Arende`. `ejKontrollerat` („ikke kontrolleret") er den, +der sparer mest tid i praksis: *det, der giver dobbeltarbejde ved vagtskifte, er +det, ingen har skrevet ned, at ingen har gjort.* + +### 6.2 Designsprog + +En ETKA-inspireret værkstedsflade: flade lysegrå flader (#ECECEC/#F7F7F7), +skarpe kanter, dyb marineblå som primærfarve, tæt typografi (11–15 px), +rektangulære knapper (maks. 4 px radius), værktøjslinje ~44 px. Egne +linjeikoner i stedet for emojis; status som farveprikker. + +Motivet: teknikeren har handsker på, står i et støjende rum og har ikke tid til +en luftig forbrugerflade. + +### 6.3 Lokal tilstand + +Uden login arbejder appen mod `localStorage`. Metodikken guider alene; +orkestret er slukket. Status vises i sagshovedet. Ved login flettes lokale +hændelser sammen med serverens — konfliktfrit pr. hændelses-id, testet. + +--- + +## 7. Backend + +### 7.1 `services/plattform` (1210 linjer) + +Rent `node:http`. Eneste afhængighed er `pg`. + +**API-stier:** + +``` +GET /halsa sundhedstjek +GET /api/openapi.yaml +POST /api/auth/registrera opretter organisation + systemadministrator +POST /api/auth/logga-in login +POST /api/auth/logga-ut-alla hæver token_version → alle sessioner dør +GET /api/anvandare brugere; kun admin +POST /api/anvandare +POST /api/anvandare/{id}/avaktivera | /aktivera +GET /api/organisation +GET/PUT /api/organisation/installningar +GET /api/ecm/regler regelpakker som data +GET/POST /api/arenden sager +POST /api/arenden/{id}/handelser append-only +POST /api/arenden/{id}/bilagor bilag +GET /api/bilagor/{id} hash verificeres ved læsning +GET /api/fordon/{identifierare}/historik +GET /api/statistik/felorsaker årsagsstatistik +GET /api/oversikt værkførervisning +GET /api/delad/{kod} delt, filtreret efter niveau +POST /api/delad/{kod}/beslut kundens beslutning uden login +GET /api/delad/{kod}/bilagor/{id} niveaufiltreret +GET /api/integrationer/leverantorer +GET/PUT/DELETE /api/integrationer/{leverantor} +POST /api/integrationer/{leverantor}/uppslag +``` + +Der findes ingen update- eller delete-stier mod sagsdata. Med vilje. + +**Sikkerhedsfunktioner i tjenesten:** + +``` +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 linjer) + +Ejer Claude-nøglen. Klienten har den **aldrig**. Routing pr. opgave: + +| Opgave | Model | Effort | Vision | +|---|---|---|---| +| `handledning` (vejledning i realtid) | `claude-sonnet-5` | medium | — | +| `granskning` (dybdegennemgang) | `claude-opus-5` | **high** | — | +| `sammanfattning` (overdragelsesresumé) | `claude-sonnet-5` | low | — | +| `metodikval` (klassificering) | `claude-haiku-4-5` | *(ingen — modellen tager ikke parameteren)* | — | +| `instrumentavlasning` (instrumentaflæsning) | `claude-sonnet-5` | low | ✔ | +| `dokumenttolkning` (dokumentfortolkning) | `claude-sonnet-5` | low | ✔ | + +Alle svar er **skemabundne** (`json_schema`). Grundprompten koder reglerne: +*„Find aldrig på fakta"*, *„aldrig en hypotese som en konstateret fejl"*, +*„KRÆVER verifikation"*. Ved afvist forespørgsel sker der automatisk fallback +til en reservemodel. **Den model, der svarede, logges i hver `ai_svar`-hændelse** +— grundlaget skal kunne granskes bagefter. + +Metodikkataloget bygges ud fra én liste (`METODIK_KATALOG`), som genererer både +skemaets `enum` og promptens punktliste. En test sammenligner det med klientens +bibliotek: driver listerne fra hinanden, returnerer klassifikatoren et id, +klienten ikke kender, og valget ville falde *stiltiende* tilbage på generisk. Nu +fejler testen i stedet. + +### 7.3 `services/gemensam/observation.mjs` (166 linjer) + +Sporing og måleværdier **uden nye afhængigheder**. Tjenesterne har bevidst +næsten ingen afhængigheder; at trække et OpenTelemetry-SDK med tredive pakker +ind for at måle fire ting ville være den forkerte afvejning. I stedet to +standarder, der begge blot er tekst på stdout: + +- **W3C Trace Context** — `traceparent` følger med gennem hele kæden + (klient → platform → orkester). +- **CloudWatch EMF** — struktureret JSON, som CloudWatch selv trækker + måleværdier ud af. Ingen agent, intet SDK, intet der kan holde op med at + virke i stilhed. + +``` +spårFrån(header) · traceparent(spor) · starta(navn, spor) + → .mät(delnavn, arbejde) · .ms() · .delar() +logga(niveau, besked, felter) · mätvärde(navn, værdi, enhed, dim, ekstra) +avsluta(span, { status, väg, extra }) +``` + +Det, der måles, blev valgt ud fra ét spørgsmål: *hvad vil man vide klokken tre +om natten, når noget er langsomt?* Svaret er **hvor tiden blev af** — ikke hvor +mange kald der er sket. Derfor `delar()` („dele"): databasen, modelkaldet, +objektlageret, kundens leverandør, med antal og sum pr. del i samme loglinje. + +`mät()` måler også, når arbejdet kaster — ellers ser fejl ud som nul tid. + +**Dimensioner holdes bevidst få.** Hver unik kombination er sin egen tidsserie, +der koster penge, så organisation, sag og spor-id må aldrig blive dimensioner — +de ligger som almindelige felter. Låst af en test, der udtrykkeligt forbyder +`org`, `organisation`, `arende`, `spårId`, `anvandare` blandt dimensionerne. + +--- + +## 8. Sikkerhed + +| Beskyttelse | Implementering | +|---|---| +| **Multi-tenant-isolation** | Alle sagsforespørgsler er organisationsbundne (`arendeIOrg`); integrationstestet mod rigtig Postgres | +| **Roller** | `tekniker` / `arbetsledare` / `admin` (tekniker / værkfører / admin), i JWT'et og som databasetjek | +| **JWT-claims** | `{ sub, namn, org, roll, tv }` — `tv` = token_version | +| **Øjeblikkelig tilbagekaldelse** | `kontoGiltigt()` kontrollerer `aktiv` + `token_version` ved *hver* autentificeret forespørgsel. En gyldig signatur er ikke nok | +| **Global udlogning** | `/api/auth/logga-ut-alla` hæver `token_version` → alle udstedte tokens dør straks | +| **Adgangskoder** | bcrypt via `gen_salt('bf')` i databasen | +| **Login-spærring** | 15-minutters vindue; maks. 10 forsøg pr. konto, 30 pr. kilde. Ryddes probabilistisk (2 % pr. skrivning) for at undgå et cron-job | +| **Kryptering i hvile** | AES-256-GCM til kundernes integrationsoplysninger; hemmelige felter maskeres altid i API-svar | +| **SSRF-forsvar** | `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 op**, før kaldet sker; `.local`/`.internal` blokeret. Nødudgang `TILLAT_INTERNA_UPPSLAG` til testmiljø | +| **CORS** | `TILLATNA_URSPRUNG`-tilladelsesliste; oprindelsen sættes én gang pr. forespørgsel | +| **Bilagsintegritet** | SHA-256 i loggen, verificeres ved læsning → `409` ved afvigelse | +| **Append-only i databasen** | Triggere `before update or delete` på både `felsokning_handelser` og `felsokning_arenden` | +| **Pod-hærdning** | IMDSv2 obligatorisk, hop-grænse 1 → pods kan ikke låne nodens IAM-rolle | +| **IRSA** | Hver servicekonto har sin egen rolle; noderne deler ingen rettigheder | +| **Delte IAM-roller** | `bygg` (build) må publicere til ECR, men ikke røre klyngen; `drift` må røre klyngen, men ikke publicere images | +| **Netværkspolitik** | Indgående nægtes som standard; eksplicitte `_ut`-regler (udgående) pr. tjeneste | +| **Databaseadgang** | Kun fra klyngens noder, i et undernetslag **uden rute ud** | + +### 8.1 Databaseskema + +``` +organisationer · anvandare · inloggningsforsok +felsokning_arenden · felsokning_handelser +bilagor · bilage_innehall +delningar · integrationer +``` + +(organisationer · brugere · loginforsøg · sager · hændelser · bilag · +bilagsindhold · delinger · integrationer) + +--- + +## 9. Live Share — delingsniveauer + +Tre niveauer, serverstyret filtrering: + +| Niveau | Ser | +|---|---| +| **kund** (kunde) | 22 hændelsestyper: objekt, fejlbeskrivelse, spørgsmål, kontroller, observationer, måleværdier, fotos, videoer, kommentarer, overdragelser, udbedringer, kvalitetskontrol … | +| **partner** | Alt, hvad kunden ser **+ `hypotes`** (markeret som ikke verificeret) | +| **intern** | Fuld indsigt — ingen filtrering | + +**Aldrig uden for organisationen:** +`kategori_byte`, `hypotes`, `ai_svar`, `ansvarig_satt`, `arbetsorder_skannad`. + +```js +export function synligaTyper(niva) { + if (niva === "intern") return null; // fuld indsigt + return niva === "partner" ? DELBART_PARTNER : DELBART_KUND; +} +``` + +Links kan tilbagekaldes. Den offentlige delingsside +(`/felsokning/delad/:kod`) kræver ikke login og poller for liveopdatering. +Kunden kan give sit svar direkte i visningen +(`POST /api/delad/{kod}/beslut`). + +**Hvorfor en tilladelsesliste:** en nægtelsesliste skal opdateres, hver gang en +ny hændelsestype tilføjes — og det er præcis det, man glemmer. En +tilladelsesliste gør „glemt" til „intern", og det er det sikre udfald. + +--- + +## 10. Mærkespecifikke koblinger + +Leverandører er **data, ikke kode** (`integrationer.json`, kan monteres som +ConfigMap via `INTEGRATIONER_FIL`). Nye mærker kræver ingen ombygning. + +| id | Leverandør | +|---|---| +| `generisk_vin` | Vilkårlig VIN-tjeneste over HTTP | +| `vag_erwin` | Volkswagen Group erWin (VW, Audi, Škoda, SEAT) | +| `volvo_vida` | Volvo VIDA | +| `fordonsregister` | Nummerplade → køretøj | + +Hver leverandør deklarerer sine felter, hvilke der er hemmelige (krypteres + +maskeres), og hvordan svaret mappes til domænets felter (`marke`, `modell`, +`arsmodell`, `motor`, `vaxellada` — mærke, model, årgang, motor, gearkasse). + +Opslag går gennem SSRF-beskyttelsen — en kunde kan altså ikke pege en +„leverandør" mod klyngens interne adresser. + +--- + +## 11. Visual-first + +Kameraet **er** integrationslaget. Det, der står på en skærm eller et +instrument, fotograferes og fortolkes i stedet for at blive integreret. + +- **Scanning af arbejdsordren** er hovedvejen ved sagsoprettelse. Sonnet 5 + (vision) læser kunde-, køretøjs- og værkstedsoplysninger uanset layout, med + en konfidens pr. felt: + - 🟢 ≥95 % godkendes automatisk + - 🟡 80–95 % markeres til gennemlæsning + - 🔴 <80 % kræver aktiv bekræftelse + + Teknikeren gennemgår altså kun de usikre felter. Visuel kontrol med + dokumentet ved siden af felterne; et klik markerer den omtrentlige position. + +- **Instrumentaflæsning** — foto af en diagnoseskærm eller et instrument → + strukturerede værdier. + +Motivet er kommercielt: én integration pr. værkstedssystem er én salgscyklus +pr. kunde. Et kamera virker mod alt, med det samme. + +--- + +## 12. Infrastruktur + +To Terraform-lag. Basen kører sjældent, arbejdsbyrdelaget ofte. + +### 12.1 `infra/aws` — basen (91 ressourcer) + +| Område | Indhold | +|---|---| +| **Netværk** | 1 VPC, 3 undernetslag × 3 zoner: offentligt (kun ALB + NAT), privat (noder, ingen offentlige adresser), data (Aurora, **slet ingen rute ud**). VPC-endpoints: S3 (gateway); ECR, logs, Secrets Manager, STS, ELB (interface) → trafikken forlader aldrig netværket | +| **Klynge** | EKS, arm64-noder, IRSA via OIDC-provider, IMDSv2 hop-grænse 1, alle fem control plane-logs slået til | +| **Data** | Aurora PostgreSQL Serverless v2, PITR ned til sekundet, KMS med egen nøgle, `sslmode=require` | +| **Objektlager** | S3 til bilag: SSE-KMS, offentlig adgang blokeret, TLS obligatorisk, versionering slået til. Platformsrollen må læse og skrive — **men aldrig slette** | +| **Register** | ECR med **uforanderlige tags** + sårbarhedsscanning | +| **Hemmeligheder** | Secrets Manager; kan kun læses af platformsrollen via IRSA | +| **Roller** | 9 IAM-roller, heriblandt de delte `bygg` / `drift` | +| **Domæne** | Route 53 + ACM med DNS-validering | +| **Observerbarhed** | 7 alarmer, 1 dashboard, 3 loggrupper, SNS-topic | + +**Alarmerne** — få, men de, der findes, betyder noget. *En alarm, ingen +reagerer på, lærer folk at ignorere alarmer.* + +- Aurora-CPU > 85 % i tre perioder (skaleringsloftet kan være nået) +- Aurora fri lokal lagring < 5 GiB +- **Backup-alder** — `treat_missing_data = "breaching"`. Mangler måleværdien, + findes der ingen sikkerhedskopiering. *En backup, man tror findes, er værre + end ingen.* +- Færre noder end det ønskede minimum +- Svartid **p95** > 3 s i tre perioder — ikke gennemsnittet, som skjuler, at + hver tyvende tekniker venter urimeligt længe +- Serverfejl (sum > 5) +- Modellen afviser (tyder på uventet input, ikke på en driftsfejl) + +### 12.2 `infra/terraform` — arbejdsbyrden (35 ressourcer) + +Læser basen via `terraform_remote_state`; gentager intet. + +``` +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 linjer) producerer et læsbart kort over hele driftbilledet: +`terraform output karta`. + +### 12.3 Git og CI — fuldt selvhostet + +En udtrykkelig produktbeslutning: **ingen GitHub i driftvejen.** Gitea + +Actions-runnere kører på eget EKS. `.gitea/workflows/felsokning.yml`: + +| Job | Indhold | +|---|---| +| `test-och-bygg` | `vitest run`, `typkontroll`, eslint, `vite build` | +| `tjanster` | eslint på tjenesterne, **integrationstest mod rigtig Postgres**, `swagger-cli validate` | +| `terraform` | `fmt -check -recursive`, `init -backend=false`, `validate` | +| `publicera` | Kun på `main`, kun hvis ovenstående gik igennem. Bygger tre images, tagger med commit-SHA'en, skubber til vores eget ECR. **OIDC, ingen statisk nøgle** | +| `driftsatt` | **Manuelt** (`workflow_dispatch`) med eksplicit image-tag | + +Idriftsættelse er et selvstændigt trin med vilje: *et image i registret er ikke +det samme som et image, der kører.* Rollback = kør igen med det tidligere tag. + +Klientens API-adresse bages ind ved bygget (Vite), så imaget er miljøbundet. +Byggekonteksten for tjenesterne er `felsokning/services`, så begge når det +fælles observationsmodul uden at duplikere det. + +--- + +## 13. Test + +**120 vitest-test** i 13 filer: + +| Fil | Antal | Låser | +|---|---|---| +| `ecm.test.ts` | 32 | Bevisniveauer, regelpakker, kvalitetsport, prædiagnostik | +| `metodiker.test.ts` | 14 | Bibliotekets struktur, metodikvalg, katalogparitet med orkestret | +| `projektioner.test.ts` | 13 | Visninger som rene funktioner, næste trin | +| `ai.test.ts` | 11 | Orkesterparitet, OpenAPI ↔ server, append-only, promptregler | +| `observation.test.ts` | 10 | Sporing, EMF-format, **forbudte dimensioner** | +| `bilagor.test.ts` | 9 | Indholdshash, SigV4, valg af lagerlag | +| `delning.test.ts` | 7 | Tilladelseslisten dækker hver hændelsestype | +| `integrationer.test.ts` | 7 | Leverandøropslag, SSRF-værn | +| `demo.test.ts` | 4 | Demosagen er rig nok til at vise | +| `installningar.test.ts` | 4 | Organisationsindstillinger | +| `streckkod.test.ts` | 4 | VIN/stregkode | +| `synk.test.ts` | 4 | Konfliktfri sammenfletning | +| `example.test.ts` | 1 | — | + +**Ud over enhedstestene:** + +- `integrationstest.sh` — hele forløbet mod **rigtig Postgres**: organisationer, + roller, append-only-triggeren, isolation, deling, bilag. +- SigV4 **krydsverificeret bit for bit mod botocore** (`sigv4-referens.json`). +- `swagger-cli validate` på OpenAPI-specifikationen. +- Paritetstest mellem specifikation ↔ server, klient ↔ orkester (× 2 kopier), + domænemodel ↔ delingsliste. + +### 13.1 Verifikationssløjfen før hver commit + +``` +npx vitest run # 120 test +npm run typkontroll # tsc --noEmit (vite build typetjekker IKKE) +npx eslint src/felsokning src/pages/felsokning +cd ../services && npx eslint . +npm run build +terraform fmt -check -recursive +# rodens CI: lint · format:check · typecheck · test +``` + +`typkontroll` kom til, efter at to latente nedbrud (`TextFalt` og +`UNDANTAGSORSAKER` brugt uden import) var sluppet forbi `vite build` — som +transpilerer, men ikke typetjekker. + +--- + +## 14. Repositoriestruktur + +`main` er et npm-workspaces-monorepo ved navn **Semantika**, som ejer roden. Da +de to produkter blev slået sammen, blev begge bevaret, med værktøjskæderne +**adskilt pr. træ** — ikke ved at svække nogens regler. + +``` +/ Semantika (workspaces-rod) +├── apps/mobile/ Semantika +├── services/api/ Semantika +├── infra/ Semantika +├── .github/workflows/ci.yml Semantika — urørt +│ +├── .gitea/workflows/felsokning.yml Guidad Felsökning (egen CI) +└── felsokning/ + ├── app/ klient (egen package.json, eslint, vitest, tsconfig) + ├── services/ + │ ├── plattform/ + │ ├── ai-orkester/ + │ └── gemensam/ observation.mjs (fælles) + ├── infra/ + │ ├── aws/ basen, 91 ressourcer + │ ├── terraform/ arbejdsbyrden, 35 ressourcer + │ └── postgres-init.sql + ├── docs/ + └── supabase/ migrationer + edge-funktion (ældre vej) +``` + +**To driftveje findes parallelt:** den selvhostede AWS-stak (den, der gælder) og +en ældre Supabase-baseret (edge-funktionen `felsokning-ai`, migrationer). +Orkestret findes derfor i **to kopier**, holdt synkrone af test. + +--- + +## 15. Dokumentation i repositoriet + +``` +docs/VISION.md produktvisionen +docs/MASTER-PROMPT.md grundinstruktionen +docs/MVP.md hvad der er bygget, funktion for funktion +docs/DEMO.md demomanuskript til fremvisning +docs/DRIFT.md drift +docs/SYSTEMBESKRIVNING.md det svenske original af dette dokument +docs/SYSTEMBESKRIVNING.en.md engelsk +docs/SYSTEMBESKRIVNING.de.md tysk +docs/SYSTEMBESKRIVNING.da.md dette dokument +docs/SYSTEMBESKRIVNING.no.md norsk (bokmål) +docs/exempel/vibration-vid-88-km-h.md gennemgående eksempel +docs/moduler/ otte moduldokumenter +``` + +--- + +## 16. Designbeslutninger og deres begrundelser + +Samlet, fordi begrundelsen ofte er vigtigere end beslutningen. + +| Beslutning | Begrundelse | +|---|---| +| Event sourcing | Grundlaget skal holde i en tvist. Historikken *er* værdien | +| Append-only også i databasen | Applikationslaget kan omgås; triggeren kan ikke | +| Tilladelsesliste til deling | En glemt hændelsestype bliver intern, ikke lækket | +| Faste årsagskategorier | Fritekst kan ikke aggregeres; flådestatistikken er et aktiv | +| Serverejet modelrouting | Klienten må aldrig have nøglen, og routing skal kunne ændres uden en udgivelse | +| Model logget pr. svar | Grundlaget skal kunne granskes bagefter | +| Undgå ordet „AI" | Kunden hører „gæt"; sagsbehandleren vægter det lavere | +| Visual-first | Én integration pr. værkstedssystem = én salgscyklus pr. kunde. Kameraet virker med det samme | +| Leverandører som data | Et nyt mærke bør ikke kræve en udgivelse | +| Egen observerbarhed, nul afhængigheder | 30 pakker for at måle 4 ting er den forkerte afvejning | +| Få EMF-dimensioner | Hver kombination er en betalt tidsserie | +| p95 i alarmen, ikke gennemsnittet | Gennemsnittet skjuler, at hver tyvende tekniker venter | +| Alarm på *manglende* backupdata | En backup, man tror findes, er værre end ingen | +| Delte build-/driftroller | Et kompromitteret build må ikke kunne røre klyngen | +| Manuel idriftsættelse | Et image i registret ≠ et image, der kører | +| Uforanderlige ECR-tags | Et tag skal betyde det samme i morgen | +| Indholdsadresserede bilag | Et udskiftet billede skal opdages, ikke antages | +| Hashverifikation ved læsning | Det er ikke nok at hashe ved skrivning | +| S3-rollen må ikke slette | Append-only skal også gælde lageret | +| Motor adskilt fra indhold | Biblioteket vokser; motoren skal ikke behøve at ændres | +| Pointsat metodikvalg | Regex-kæder bliver uigennemskuelige ved 16 alternativer | +| Nøgleord som ordstammer | Svensk bøjning fjerner et `e` — ellers rammer intet | +| `generisk` som fallback | Et ærligt „vi ved det ikke" slår et gæt | +| Sikkerhedstrin først i højvolt | Det arbejde kan dræbe | +| Svensk i koden | Domænet er svensk; oversættelse frem og tilbage taber præcision | + +--- + +## 17. Kendte begrænsninger og åbne punkter + +Udtrykkeligt ikke færdigt: + +- **To orkesterkopier** (Supabase-edge-funktion + K8s-tjeneste) holdes + synkrone af test, ikke af fælles kode. Supabase-vejen er den ældre og bør + udfases. +- **`ArendeSida.tsx` er på 2433 linjer.** Den virker, men er den fil, der koster + mest at ændre i. +- **`terraform validate` kan ikke køres lokalt** i udviklingsmiljøet (den + udgående netværkspolitik blokerer provider-downloads). Erstattet af + `terraform fmt` plus en egen statisk referencekontrol; den rigtige validering + sker i CI. +- **Claude-nøglen udfyldes i hånden** efter den første `apply` — den ligger med + vilje ikke i Terraform-state. +- **`postgres-init.sql` køres manuelt** mod databasen, efter at basen er + anvendt. +- **Ingen automatisk gendannelsestest af backuppen.** Alarmen siger, at der + *tages* backup, ikke at den *kan gendannes*. +- **Metodikbiblioteket dækker ikke alt** — og påstår det ikke. `generisk` er + sikkerhedsnettet. +- Rodens eslint har 20 allerede eksisterende fejl i Semantikas egne sider + (`no-explicit-any`), som ikke vedrører Guidad Felsökning. + +--- + +## 18. Ordliste + +Venstre kolonne er begrebet, som det står i kode og brugerflade. + +| Svensk | Dansk | +|---|---| +| Ärende | Sag — én fejlfindingsopgave | +| Händelse / loggpost | Hændelse / logpost — udelelig post i append-only-loggen | +| Metodik | Metodik — struktureret fejlfindingsforløb | +| Steg | Trin — fase i en metodik (symptom, visuel kontrol, målinger …) | +| Kontroll | Kontrol — enkelt tjeklistepunkt med minimumskrav | +| Krav | Krav — `matvarde` / `kommentar` / `foto` | +| Undantag | Undtagelse — dokumenteret grund til, at en kontrol blev sprunget over | +| Brief | Brief — sammenstillet sagsbillede; en projektion | +| Kvalitetsgrind | Kvalitetsport — regelsæt, der skal passeres før afslutning | +| Evidensnivå | Bevisniveau — E0–E6, grundlagets bevisværdi | +| Reproducering | Reproduktion — symptomverifikation: ja / delvis / nej | +| Felorsak | Fejlårsag — struktureret analyse med kategori og grundlag | +| Delning | Deling — eksternt link med rettighedsniveau | +| Orkester | Orkester — tjenesten, der ejer modelroutingen | +| Spann / spår | Span / spor — tidsmåling henholdsvis W3C-sporing | +| Bilaga | Bilag — indholdsadresseret foto/video/dokument | +| Tekniker | Tekniker, mekaniker | +| Arbetsledare | Værkfører | +| Fordon | Køretøj | +| Mätvärde | Måleværdi | +| Felbeskrivning | Fejlbeskrivelse (kundens ord) | +| Arbetsorder | Arbejdsordre | +| Mätarställning | Kilometerstand | +| Överlämning | Overdragelse | +| Åtgärd | Udbedring, indgreb | +| Kundbeslut | Kundens beslutning | +| Säkerhet | Sikkerhed | +| Högvolt | Højvolt | diff --git a/felsokning/docs/SYSTEMBESKRIVNING.de.md b/felsokning/docs/SYSTEMBESKRIVNING.de.md new file mode 100644 index 0000000..d9fbe66 --- /dev/null +++ b/felsokning/docs/SYSTEMBESKRIVNING.de.md @@ -0,0 +1,966 @@ +# Guidad Felsökning (Geführte Fehlersuche) — vollständige Systembeschreibung + +> **Deutsche Übersetzung.** Maßgeblich ist [SYSTEMBESKRIVNING.md](SYSTEMBESKRIVNING.md) (Schwedisch). +> Bei Widersprüchen gilt das schwedische Dokument. +> +> **Bezeichner im Code werden nicht übersetzt.** Ereignistypen, Funktions- und +> Feldnamen, Dateipfade und Konfigurationsschlüssel sind *im Code selbst* +> schwedisch. Eine Übersetzung würde dieses Dokument gegenüber dem Repository +> unbrauchbar machen; sie stehen daher wörtlich, mit deutscher Erläuterung, wo +> die Bedeutung nicht offensichtlich ist. +> +> Ein eigenständiges Referenzdokument. Alles Folgende stammt aus dem Code in +> `felsokning/` (Branch `claude/guidad-felsokning-vision-1mnx7f`), nicht aus +> Plänen oder Absichten. Wo etwas **nicht** existiert, wird das ausdrücklich +> gesagt. +> +> Zuletzt mit dem Code abgeglichen: Commit `1bb4031`, 04.08.2026. + +--- + +## 0. Zusammenfassung in dreißig Sekunden + +Guidad Felsökning ist eine SaaS-Plattform für **Kfz-Werkstätten**. Sie führt +eine Technikerin oder einen Techniker durch eine strukturierte Fehlersuche, +verlangt für jede Aussage einen Nachweis und erzeugt eine nachvollziehbare +Dokumentation, die mit der Kundschaft, einer Versicherung oder der nächsten +Schicht geteilt werden kann. + +Der tragende Gedanke ist negativ, nicht positiv formuliert: **Das System stellt +eine Hypothese niemals als festgestellten Fehler dar.** Das ist keine Richtlinie +in einem Dokument — es ist codiert, getestet, und es blockiert Abläufe. Fehlt +der Nachweis, steht dort „Evidens saknas" (Nachweis fehlt), nicht eine +qualifizierte Vermutung. + +Technisch: ein **ausschließlich anfügendes Ereignisprotokoll** (append-only) ist +die einzige Wahrheitsquelle. Alles andere — die Fallansicht, das Briefing, der +Kundenbericht, das Qualitätstor, die Statistik — ist eine reine Projektion des +Protokolls und lässt sich jederzeit neu erzeugen. + +| | | +|---|---| +| Client | React 18 + TypeScript + Vite + Tailwind + zustand + react-router | +| Backend | Zwei Node-Dienste (`plattform`, `ai-orkester`), reines `node:http`, minimale Abhängigkeiten | +| Datenbank | PostgreSQL (Aurora Serverless v2), append-only durch Datenbank-Trigger erzwungen | +| Modell | Claude, serverseitiges Routing pro Aufgabe | +| Infrastruktur | AWS + EKS, 126 Terraform-Ressourcen in zwei Schichten | +| Git & CI | **Selbst gehostetes Gitea + Actions-Runner auf eigenem EKS** — kein GitHub im Betriebspfad | +| Tests | 120 vitest-Tests + Integrationstest gegen echtes Postgres | +| Sprache im Code | Schwedisch (Bezeichner, Kommentare, Commit-Nachrichten) | + +--- + +## 1. Produktprinzipien + +Diese fünf sind Invarianten, keine Empfehlungen. Jedes hat eine Entsprechung im +Code und in einem Test. + +### 1.1 Keine Hypothese wird als festgestellter Fehler dargestellt + +Hypothesen sind ein eigener Ereignistyp (`hypotes`) mit verpflichtender +Zuverlässigkeitsstufe und können **niemals** die Stufe `hog` (hoch) annehmen — +`niva: Exclude`; das Typsystem verbietet es. Im +Kundenbericht sind sie ausdrücklich als nicht verifiziert gekennzeichnet. Das +Qualitätstor hat dafür eine eigene Zeile. + +Die Formulierung bei fehlgeschlagener Reproduktion lautet *„kunde inte +reproduceras under de förhållanden som rådde"* („konnte unter den herrschenden +Bedingungen nicht reproduziert werden") — niemals „Fehler festgestellt" oder +„kein Fehler gefunden". Das ist sowohl in den Projektionen als auch im +Basis-Prompt des Orchesters codiert. + +### 1.2 Ein Häkchen ist kein Nachweis + +Jeder Prüfpunkt jeder Methodik trägt eine **Mindestanforderung**: `matvarde` +(Messwert) | `kommentar` (Beobachtung) | `foto` (Foto). Eine Messung kann ohne +Wert nicht als erledigt markiert werden; eine Fotoprüfung nicht ohne Bild. Will +die Technikerin etwas überspringen, ist eine **dokumentierte Ausnahme** mit +einem Grund aus einer festen Liste erforderlich. + +Festgeschrieben durch den Test *„varje kontroll kräver bevis — en kryssruta är +inte evidens"*. + +### 1.3 Das Protokoll ist append-only, bis ganz nach unten + +Es gibt keine Update- oder Delete-Operationen in der API, und die Datenbank hat +Trigger, die sie auch dann abweisen, wenn jemand die Anwendung umgeht. Ein Test +sucht im Servercode aktiv nach `update`/`delete` gegen die Ereignistabelle und +schlägt fehl, wenn sie auftauchen. + +Folge: Eine falsche Angabe wird *durch ein neues Ereignis korrigiert*, niemals +dadurch, dass die alte verschwindet. Die Historie ist das, was der Dokumentation +im Streitfall ihren Wert gibt. + +### 1.4 Terminologie + +In der Oberfläche und in der Kundenkommunikation heißt es **das System, die +Analyse, die Bewertung, die Entscheidungsunterstützung** — nicht „KI", sofern +nicht technisch nötig. Das Produkt wird als *evidenzbasiertes Diagnosesystem* / +*intelligente Entscheidungsunterstützung* beschrieben. + +Der Grund ist kommerziell und erkenntnistheoretisch zugleich: Wer als +Werkstattkunde „KI" hört, hört „Vermutung". Wer als Sachbearbeiterin einer +Versicherung „KI-Bewertung" in einer Dokumentation liest, gewichtet sie +niedriger. + +### 1.5 Die Freigabegrenze ist eine Positivliste + +Was die Organisation verlassen darf, wird **positiv** aufgezählt, je Stufe. Ein +neuer Ereignistyp ist damit intern, bis ihn jemand aktiv freigibt. Ein Test +verlangt, dass jeder Typ des Domänenmodells eingestuft ist — wird einer +vergessen, schlägt der Build fehl, statt dass er nach außen gelangt. + +--- + +## 2. Das Domänenmodell — das Ereignisprotokoll + +`app/src/felsokning/domain.ts` (281 Zeilen). + +Ein Fall besteht aus: Identität + Metadaten + einer **geordneten Liste von +Protokolleinträgen**. Jeder Eintrag trägt `id`, `tidpunkt` (Zeitpunkt), +`tekniker` (Techniker) und ein `handelse` (Ereignis). + +### 2.1 Sämtliche Ereignistypen + +| Typ | Inhalt | Rolle | +|---|---|---| +| `objekt_identifierat` | `objekt` (Kennzeichen/FIN, Marke, Modell, Motor …) | Worum es geht | +| `arbetsorder_skannad` | `falt[]` + Anhang | Ausgelesener Auftrag (**intern**) | +| `felbeskrivning` | `text` | Die Worte der Kundschaft, wörtlich | +| `arendetyp_satt` | `arendetyp` | Garantie / Versicherung / Kunde — wählt Regelpaket | +| `fraga_besvarad` | `stegId`, `frageId`, `fraga`, `svar` | Symptomfragen der Methodik | +| `kontroll_utford` | `stegId`, `kontrollId`, `text`, `resultat?`, `undantag?` | Verifizierter Prüfpunkt | +| `observation` | `text` | Was beobachtet wurde — nicht was vermutet wird | +| `matvarde` | `beskrivning`, `varde`, `enhet?` | Messung (E4) | +| `hypotes` | `text`, `niva` (nie `hog`) | Arbeitshypothese (**intern**) | +| `foto` | `beskrivning` + Anhang | Bildnachweis (E2) | +| `video` | `beskrivning` + Anhang | Bewegtbildnachweis (E3) | +| `matarstallning` | `lage` (ein/aus), `varde` + Anhang | Kilometerstand ein/aus | +| `historik_kontrollerad` | `kontrollerad`, `kommentar?` | Servicehistorie | +| `reproducering` | `status` (ja/delvis/nej), `beskrivning` | **Symptomverifizierung** | +| `felorsak` | strukturierte Ursachenanalyse | Ursache, Kategorie, Grundlage | +| `atgardsforslag` | Vorschlag mit Begründung | Was getan werden sollte | +| `kundbeslut` | genehmigt/abgelehnt, Kanal | Entscheidung der Kundschaft | +| `atgard_utford` | ausgeführte Arbeit | Was tatsächlich getan wurde | +| `kvalitetskontroll` | Prüfung nach der Reparatur | Ist das Symptom weg? | +| `kommentar` | `text` | Freie Notiz | +| `kategori_byte` | `kategori` | Zeiterfassung (**intern**) | +| `inaktivitet_forklarad` | `text`, `minuter` | Warum es stillstand | +| `overlamning` | `fran`, `till?` | Schichtübergabe | +| `ansvarig_satt` | `ansvarig` | Umverteilung durch die Leitung (**intern**) | +| `ai_svar` | klassifizierte `rader[]`, Modellname | Antwort der Entscheidungsunterstützung (**intern**) | +| `export_skapad` | `format`, `version` | Der Export protokolliert sich selbst | +| `arende_avslutat` | `signatur?` | Unterschrift der Technikerin | + +### 2.2 Anhänge sind inhaltsadressiert + +`foto`, `video`, `matarstallning` und `arbetsorder_skannad` sind +*Schnittmengentypen* mit `Bilaga` (Anhang): + +```ts +export interface Bilaga { + bilagaId?: string; + bilagaHash?: string; // SHA-256 + dataUrl?: string; // bleibt für immer — das Protokoll ist append-only +} +``` + +Der Inhalt liegt außerhalb des Protokolls (S3 oder Datenbank), **der Hash aber +im Protokoll**. Beim Lesen wird der Hash geprüft; stimmt er nicht, wird `409` +zurückgegeben. Die Bedeutung: Wird ein Bild im Speicher ausgetauscht, fällt das +auf, und das Protokoll kann belegen, dass das ursprüngliche Bild ein anderes +war. + +`dataUrl` bleibt im Typ, weil ältere Einträge es eingebettet enthalten — und das +Protokoll lässt sich nicht umschreiben. + +--- + +## 3. Die Methodik-Engine + +Seit der jüngsten Änderung sind **Engine und Inhalt getrennt**: + +- `metodik.ts` (171 Zeilen) — Typen, Methodikauswahl, Ableitung des nächsten + Schritts. +- `metodiker.ts` (899 Zeilen) — die sechzehn Methodiken. + +Die Bibliothek kann wachsen, ohne dass die Engine sich ändert. + +### 3.1 Die Methodikbibliothek + +Schritt-IDs sind Code und bleiben schwedisch. `symptom` = Symptom, `visuell` = +Sichtprüfung, `matningar` = Messungen, `provkorning` = Probefahrt, `sakerhet` = +Sicherheit, `avlasning` = Auslesen, `glapp` = Spiel, `packning` = Dichtung. + +| id | Name (im Code) | Bereich | Schritte | Prüfungen | +|---|---|---|---|---| +| `vibration` | Vibration under körning | Räder und Auswuchtung | symptom → visuell → kontroller → provkorning | 19 | +| `bromsar` | Bromssystem | Fahrwerk | symptom → visuell → matningar → system | 14 | +| `styrning_fjadring` | Styrning och fjädring | Fahrwerk | symptom → visuell → glapp → installning | 11 | +| `elsystem` | Elsystem och strömförsörjning | Elektrik | symptom → visuell → matningar → rela → funktionstest | 12 | +| `start_laddning` | Start- och laddningssystem | Elektrik | 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 | Antrieb | 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 | Hochvolt | **sakerhet** → symptom → avlasning → laddning | 16 | +| `diagnos_natverk` | Felkoder och kommunikation | Diagnose | symptom → grund → buss → koder | 10 | +| `lackage` | Läckage | Sonstiges | symptom → visuell → metod | 8 | +| `missljud` | Missljud | Sonstiges | symptom → inspelning → lokalisering | 7 | +| `adas` | Förarassistans och kalibrering | Diagnose | symptom → forutsattningar → kalibrering | 9 | +| `generisk` | Generell strukturerad felsökning | Sonstiges | symptom → visuell → grundkontroller → funktionstest | 9 | + +Deutsche Namen: Vibration während der Fahrt · Bremsanlage · Lenkung und +Federung · Elektrik und Stromversorgung · Start- und Ladesystem · Motorlauf und +Leistung · Kühlsystem und Überhitzung · Getriebe und Antriebsstrang · Abgas und +Emissionen · Klimaanlage · Hochvoltsystem (E-Auto/Hybrid) · Fehlercodes und +Kommunikation · Leckage · Störgeräusche · Fahrerassistenz und Kalibrierung · +generische strukturierte Fehlersuche. + +### 3.2 Drei Regeln, durch Tests festgeschrieben + +1. **Jede Prüfung hat eine Mindestanforderung.** Messwert, Foto oder + Beobachtung. +2. **Jede Methodik beginnt damit, das Symptom zu verifizieren**, nie mit der + Instandsetzung. Die Worte der Kundschaft werden erst dann zum verifizierten + Symptom, wenn sie reproduziert wurden. +3. **Wo die Arbeit jemanden verletzen kann, steht der Sicherheitsschritt an + erster Stelle.** Nur `sakerhet` darf `symptom` vorausgehen — der Test lässt + genau diese Ausnahme zu und keine andere. + +`hogvolt` ist die einzige Methodik mit Sicherheitsschritt. Sie verlangt die +Befähigung, den dokumentiert entnommenen Servicestecker (Foto), die Wartezeit +nach Herstellerangabe, die **gemessene Spannungsfreiheit** (ein Messwert — kein +Ja auf eine Frage) und die Schutzausrüstung. Der Test prüft, dass der Schritt an +erster Stelle steht, dass `spanningsfrihet` einen Messwert verlangt und dass die +Beschreibung das Wort „livsfarlig" (lebensgefährlich) enthält. + +Der Grund ist einfach: Diese Arbeit kann tödlich sein. Ein Häkchen genügt dort +nicht. + +### 3.3 Auswahl der Methodik + +Früher eine Regex-Kette mit drei Ausgängen. Jetzt **punktbewertetes +Stichwort-Matching**: + +```ts +export function metodikPoang(metodik: Metodik, text: string): number +export function valjMetodik(felbeskrivning: string): Metodik +``` + +- Punktzahl = Summe der Längen der treffenden Stichwörter. Ein längeres — also + spezifischeres — Wort wiegt schwerer. `traktionsbatteri` (16) schlägt + `batteri`. +- **Kurze Wörter (≤3 Zeichen) treffen als ganzes Wort, längere als Wortstamm.** + Sonst hätte `"ac"` das Wort *acceleration* getroffen und eine Vibration wäre + in der Klimaanlage gelandet. +- Bei Gleichstand gewinnt das in der Bibliothek zuerst stehende Element → die + Auswahl ist **stabil** über Läufe hinweg. +- Kein Treffer → `generisk`. + +**Eine Falle, die in der Entwicklung tatsächlich zugeschlagen hat:** Die +Stichwörter müssen *Wortstämme* sein, keine fertig gebeugten Wörter. Die +schwedische Flexion tilgt oft ein `e`: *filter → filtret*, weshalb +`"partikelfilter"` den Text, den eine Technikerin wirklich schreibt, nie trifft. +Dasselbe gilt für *regenerering → regenererar*, *misständning → misständer*, +*skrammel → skramlar*. Die Bibliothek verwendet daher `partikelfilt`, +`regenerer`, `misständ`, `skram`. + +*(Für eine Lokalisierung: Das ist eine Eigenschaft der schwedischen Morphologie. +Im Deutschen entsteht dieselbe Problemklasse durch Komposita — +`Partikelfilter` steckt in `Dieselpartikelfilter`, aber `Bremse` nicht am +Wortanfang von `Feststellbremse`. Ein lokalisierter Stichwortsatz muss gegen +denselben Test validiert und nicht Wort für Wort übersetzt werden.)* + +**Die Auswahl ist eine Fragenreihenfolge, keine Diagnose.** Sie entscheidet, wo +gesucht wird, nicht was defekt ist. Trifft nichts zu, ist `generisk` die +ehrliche Antwort — strukturell vollständig und besser als eine Vermutung. + +### 3.4 Nächster Schritt + +```ts +export function nastaSteg(arende: Arende, metodik: Metodik): NastaSteg +``` + +Rein aus dem Protokoll abgeleitet: die erste unbeantwortete Frage, danach die +erste nicht ausgeführte Prüfung, in der Reihenfolge der Methodik. Keine +verborgene Zustandsmaschine — dasselbe Protokoll ergibt immer denselben nächsten +Schritt. + +### 3.5 Zum Anspruch „alles abdecken" + +Das lässt sich nicht ehrlich versprechen, und die Dokumentation behauptet es +auch nicht. Möglich ist, die Systeme des Fahrzeugs systematisch abzudecken und +`generisk` als strukturell vollständiges Auffangnetz für das Unvorhergesehene +zu belassen. + +--- + +## 4. ECM v2.0 — die Evidenz- und Regel-Engine + +`app/src/felsokning/ecm.ts` (749 Zeilen). Sechs Engines: + +### 4.1 Evidence Engine + +Evidenzstufen, aus dem Protokoll abgeleitet: + +| Stufe | Bedeutung | +|---|---| +| E0 | Keine Grundlage | +| E1 | Beobachtung der Technikerin | +| E2 | Foto | +| E3 | Video | +| E4 | Messwert | +| E5 | Diagnosedaten / Dokument | +| E6 | Mehrere unabhängige Quellen | + +Die Evidenzstufe eines Falls ist die höchste, die die Grundlage trägt. Sie wird +in der Oberfläche angezeigt und wandert mit dem Export mit. + +**Inhalts-Hash:** `innehallsHash()` ist ein deterministischer FNV-1a über den +Evidenzinhalt. Dieselbe Grundlage ⇒ derselbe Hash, unabhängig von Maschine oder +Zeitpunkt. Damit ist der Export nachträglich überprüfbar. + +### 4.2 Rule Engine + +- `ORSAKSKATEGORIER` — feste Liste von Ursachenkategorien (ergibt vergleichbare + Statistik über den Fuhrpark). +- `UNDANTAGSORSAKER` — feste Liste für „warum dies nicht getan wurde". +- `UNDERLAGSKALLOR` — worauf eine Schlussfolgerung beruht. +- `INGEN_ATGARD_ORSAKER`, `KUNDKANALER` (Kundenkanäle). +- `granskaAvvikelse()` — markiert Text, der als Feststellung formuliert ist, + ohne gedeckt zu sein. + +Feste Listen statt Freitext sind eine bewusste Entscheidung: Freitext lässt sich +nicht aggregieren, und die Fuhrparkstatistik ist eines der echten Aktiva des +Produkts. + +### 4.3 Compliance Engine + +`ARENDETYPER` (Fallarten) bestimmt, welches **Regelpaket** gilt. Ein Garantiefall +verlangt Claim-Nummer und Servicehistorie; ein Versicherungsfall Schadennummer +und Bildnachweis; ein Kundenfall weniger. Die Pakete sind Daten +(`ecm-regler.json`, ausliefbar über `/api/ecm/regler`) — neue Anforderungen +brauchen kein neues Release. + +### 4.4 Validation Engine — Vordiagnostik + +Bevor die Fehlersuche beginnen darf: Objektidentifizierung verifiziert, Auftrag +eingelesen, Fahrzeughistorie geprüft **oder begründet**, eingehender +Kilometerstand dokumentiert, Fehlerbeschreibung der Kundschaft verifiziert, +frühe Beobachtungen bearbeitet. + +### 4.5 Completion Engine — das Qualitätstor + +Die größte Einzelfunktion (`kvalitetsgrind`, ~240 Zeilen). Der Fall kann nicht +abgeschlossen werden, bevor jede Zeile grün oder begründet ist: + +- Fahrzeughistorie geprüft oder begründet +- Eingehender/ausgehender Kilometerstand dokumentiert +- Fehlerbeschreibung der Kundschaft verifiziert +- **Symptomverifizierung:** reproduziert oder als nicht reproduzierbar + dokumentiert +- Ursachenanalyse dokumentiert +- Maßnahme dokumentiert oder begründet +- Entscheidung der Kundschaft zum Vorschlag erfasst +- Arbeit trotz abgelehntem Vorschlag ausgeführt (sofern zutreffend) +- Qualitätskontrolle durchgeführt — Symptom verifiziert +- Prüfungen der Methodik: Nachweis oder dokumentierte Ausnahme +- Fotos vorhanden für fotopflichtige Prüfungen +- Schlussfolgerung der Technikerin unterschrieben +- Hypothesen als nicht verifiziert ausgewiesen +- Regelpaket der Fallart erfüllt (Claim / Schadennummer / Kilometerstand / + Historie) + +### 4.6 Traceability Engine + +`sparbarhetspaket()` — die gesamte Beweiskette in einem strukturierten Objekt: +was behauptet wird, worauf es beruht, wer es dokumentiert hat und wann. + +--- + +## 5. Symptomverifizierung (SVP) + +Ein eigenes Prinzip, weil es die schärfste Kante des Produkts zur Wirklichkeit +ist. + +**Die Beschreibung der Kundschaft ≠ ein festgestellter Fehler.** + +1. Die Beschreibung wird **wörtlich** dokumentiert (`felbeskrivning`). +2. Sie wird über die Symptomfragen der Methodik präzisiert — *wann, wo, wie*, + nie „was ist kaputt". +3. Sie wird **reproduziert**, mit drei möglichen Ausgängen: + - **Ja** — mit dokumentierten Bedingungen. + - **Teilweise** — was sich nachstellen ließ und was nicht. + - **Nein** — verpflichtende Begründung. + +Die Beweiskette des Berichts trennt vier Dinge, die sonst vermischt werden: *die +Beschreibung der Kundschaft*, *die verifizierte Beobachtung*, *die +Ursachenanalyse* und *die empfohlene Maßnahme*. + +--- + +## 6. Der Client + +`app/src/felsokning/` + `app/src/pages/felsokning/`. + +| Modul | Zeilen | Verantwortung | +|---|---|---| +| `ArendeSida.tsx` | 2433 | Die Fallansicht. Dreispaltiges Layout am Schreibtisch | +| `metodiker.ts` | 899 | Die Methodikbibliothek | +| `ecm.ts` | 749 | Regel- und Evidenz-Engine | +| `NyttArende.tsx` | 497 | Fallanlage, Auftragsscan | +| `Arendelista.tsx` | 399 | Dashboard: Zähler, Filter | +| `projektioner.ts` | 356 | Alle Ansichten als reine Funktionen des Protokolls | +| `ai.ts` | 305 | Clientseite des Orchesters, Prompt-Bau, Antwort-Parsing | +| `plattform.ts` | 296 | API-Client gegen die selbst gehostete Plattform | +| `DelatArendeVy.tsx` | 283 | Geteilte Ansicht (Kunde/Partner/intern) | +| `domain.ts` | 281 | Ereignistypen | +| `Installningar.tsx` | 281 | Organisation, Benutzer, Integrationen | +| `Oversikt.tsx` | 238 | Ansicht für die Werkstattleitung | +| `demo.ts` | 200 | Demofall mit 1 Std. 35 Min. Historie | +| `ui.tsx` | 174 | Industrielle Werkstattoberfläche | +| `metodik.ts` | 171 | Die Methodik-Engine | +| `synk.ts` | 141 | Konfliktfreies Zusammenführen von Ereignissen | +| `ikoner.tsx` | 132 | Eigene SVG-Linienicons (keine Emojis) | +| `streckkod.ts` | 131 | Barcode-/FIN-Erfassung | +| `store.ts` | 106 | zustand-Store | +| `bilagor.ts` | 96 | Upload + Blob-URL-Cache | +| `installningar.ts` | 86 | Organisationseinstellungen | +| `Bilagevisning.tsx` | 69 | `` / `` | +| `Mikrofon.tsx` / `rost.ts` | 66 / 65 | Spracherkennung | +| `format.ts` | 48 | Fotoskalierung u. a. | + +### 6.1 Die Projektionen + +``` +objekt · felbeskrivning · ansvarig · arendeidentitet · arAvslutat +lokalFordonshistorik · utfordaKontroller · ejKontrollerat +observationer · hypoteser · foton · videor +tidsfordelning · formateraTid · tillforlitlighet +brief · overlamningstext · tidsfordelningsRader · sistaAktivitet +``` + +Alles reine Funktionen von `Arende`. `ejKontrollerat` („noch nicht geprüft") ist +die, die in der Praxis am meisten Zeit spart: *Was beim Schichtwechsel +Doppelarbeit verursacht, ist das, wovon niemand aufgeschrieben hat, dass es +niemand getan hat.* + +### 6.2 Gestaltungssprache + +Eine an ETKA angelehnte Werkstattoberfläche: flache hellgraue Flächen +(#ECECEC/#F7F7F7), scharfe Kanten, tiefes Marineblau als Primärfarbe, dichte +Typografie (11–15 px), rechteckige Schaltflächen (max. 4 px Radius), +Werkzeugleiste ~44 px. Eigene Linienicons statt Emojis; Status als Farbpunkte. + +Das Motiv: Die Technikerin trägt Handschuhe, steht in einem lauten Raum und hat +keine Zeit für eine luftige Consumer-Oberfläche. + +### 6.3 Lokaler Betrieb + +Ohne Anmeldung arbeitet die App gegen `localStorage`. Die Methodik führt allein, +das Orchester ist aus. Der Status steht im Fallkopf. Bei der Anmeldung werden +lokale Ereignisse mit denen des Servers zusammengeführt — konfliktfrei je +Ereignis-ID, getestet. + +--- + +## 7. Backend + +### 7.1 `services/plattform` (1210 Zeilen) + +Reines `node:http`. Einzige Abhängigkeit ist `pg`. + +**API-Pfade:** + +``` +GET /halsa Gesundheitsprüfung +GET /api/openapi.yaml +POST /api/auth/registrera legt Organisation + Systemadministrator an +POST /api/auth/logga-in Anmeldung +POST /api/auth/logga-ut-alla erhöht token_version → alle Sitzungen enden +GET /api/anvandare Benutzer; nur Admin +POST /api/anvandare +POST /api/anvandare/{id}/avaktivera | /aktivera +GET /api/organisation +GET/PUT /api/organisation/installningar +GET /api/ecm/regler Regelpakete als Daten +GET/POST /api/arenden Fälle +POST /api/arenden/{id}/handelser append-only +POST /api/arenden/{id}/bilagor Anhänge +GET /api/bilagor/{id} Hash beim Lesen geprüft +GET /api/fordon/{identifierare}/historik +GET /api/statistik/felorsaker Ursachenstatistik +GET /api/oversikt Leitungsansicht +GET /api/delad/{kod} geteilt, nach Stufe gefiltert +POST /api/delad/{kod}/beslut Kundenentscheidung ohne Anmeldung +GET /api/delad/{kod}/bilagor/{id} stufengefiltert +GET /api/integrationer/leverantorer +GET/PUT/DELETE /api/integrationer/{leverantor} +POST /api/integrationer/{leverantor}/uppslag +``` + +Es gibt keine Update- oder Delete-Pfade auf Falldaten. Mit Absicht. + +**Sicherheitsfunktionen im Dienst:** + +``` +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 Zeilen) + +Besitzt den Claude-Schlüssel. Der Client hat ihn **nie**. Routing pro Aufgabe: + +| Aufgabe | Modell | Effort | Vision | +|---|---|---|---| +| `handledning` (Begleitung in Echtzeit) | `claude-sonnet-5` | medium | — | +| `granskning` (Tiefenprüfung) | `claude-opus-5` | **high** | — | +| `sammanfattning` (Übergabezusammenfassung) | `claude-sonnet-5` | low | — | +| `metodikval` (Klassifizierung) | `claude-haiku-4-5` | *(keiner — das Modell nimmt den Parameter nicht)* | — | +| `instrumentavlasning` (Instrumentenablesung) | `claude-sonnet-5` | low | ✔ | +| `dokumenttolkning` (Dokumentenauswertung) | `claude-sonnet-5` | low | ✔ | + +Alle Antworten sind **schemagebunden** (`json_schema`). Der Basis-Prompt codiert +die Regeln: *„Erfinde niemals Fakten"*, *„niemals eine Hypothese als +festgestellten Fehler"*, *„ERFORDERT Verifizierung"*. Bei abgelehnter Anfrage +erfolgt automatischer Rückfall auf ein Reservemodell. **Das antwortende Modell +wird in jedem `ai_svar`-Ereignis protokolliert** — die Grundlage muss +nachträglich prüfbar sein. + +Der Methodikkatalog wird aus einer einzigen Liste (`METODIK_KATALOG`) gebaut, +die sowohl das `enum` des Schemas als auch die Aufzählung im Prompt erzeugt. Ein +Test vergleicht ihn mit der Bibliothek des Clients: Driften die Listen +auseinander, liefert der Klassifikator eine ID, die der Client nicht kennt, und +die Auswahl fiele *stillschweigend* auf generisch zurück. Jetzt schlägt +stattdessen der Test fehl. + +### 7.3 `services/gemensam/observation.mjs` (166 Zeilen) + +Tracing und Metriken **ohne neue Abhängigkeiten**. Die Dienste haben bewusst +fast keine Abhängigkeiten; ein OpenTelemetry-SDK mit dreißig Paketen +hereinzuholen, um vier Dinge zu messen, wäre die falsche Abwägung. Stattdessen +zwei Standards, die beide nur Text auf stdout sind: + +- **W3C Trace Context** — `traceparent` läuft durch die ganze Kette mit + (Client → Plattform → Orchester). +- **CloudWatch EMF** — strukturiertes JSON, aus dem CloudWatch selbst Metriken + zieht. Kein Agent, kein SDK, nichts, was still ausfallen kann. + +``` +spårFrån(Header) · traceparent(Spur) · starta(Name, Spur) + → .mät(Teilname, Arbeit) · .ms() · .delar() +logga(Stufe, Meldung, Felder) · mätvärde(Name, Wert, Einheit, Dim, Extra) +avsluta(Span, { status, väg, extra }) +``` + +Was gemessen wird, ergab sich aus einer Frage: *Was will man um drei Uhr nachts +wissen, wenn etwas langsam ist?* Die Antwort ist **wohin die Zeit ging** — nicht +wie viele Aufrufe erfolgten. Daher `delar()` („Teile"): die Datenbank, der +Modellaufruf, der Objektspeicher, der Anbieter der Kundschaft, mit Anzahl und +Summe je Teil in derselben Protokollzeile. + +`mät()` misst auch, wenn die Arbeit eine Ausnahme wirft — sonst sähen Fehler wie +null Zeit aus. + +**Dimensionen werden bewusst knapp gehalten.** Jede eindeutige Kombination ist +eine eigene, kostenpflichtige Zeitreihe; Organisation, Fall und Spur-ID dürfen +daher nie zu Dimensionen werden — sie sind gewöhnliche Felder. Festgeschrieben +durch einen Test, der `org`, `organisation`, `arende`, `spårId`, `anvandare` +unter den Dimensionen ausdrücklich verbietet. + +--- + +## 8. Sicherheit + +| Schutz | Umsetzung | +|---|---| +| **Mandantentrennung** | Alle Fallabfragen sind organisationsgebunden (`arendeIOrg`); integrationsgetestet gegen echtes Postgres | +| **Rollen** | `tekniker` / `arbetsledare` / `admin` (Techniker / Werkstattleitung / Admin), im JWT und als Datenbank-Check | +| **JWT-Claims** | `{ sub, namn, org, roll, tv }` — `tv` = token_version | +| **Sofortiger Entzug** | `kontoGiltigt()` prüft `aktiv` + `token_version` bei *jeder* authentifizierten Anfrage. Eine gültige Signatur genügt nicht | +| **Globale Abmeldung** | `/api/auth/logga-ut-alla` erhöht `token_version` → alle ausgestellten Tokens verfallen sofort | +| **Passwörter** | bcrypt über `gen_salt('bf')` in der Datenbank | +| **Anmeldesperre** | 15-Minuten-Fenster; max. 10 Versuche je Konto, 30 je Quelle. Probabilistisch aufgeräumt (2 % je Schreibvorgang), um einen Cron-Job zu vermeiden | +| **Verschlüsselung im Ruhezustand** | AES-256-GCM für die Integrationszugänge der Kundschaft; geheime Felder werden in API-Antworten immer maskiert | +| **SSRF-Abwehr** | `arPrivatAdress()` + `pekarInat()`: 10/8, 127/8, 169.254/16, 172.16–31, 192.168/16, 100.64/10, ::1, fc/fd, fe80, ::ffff:. **DNS wird aufgelöst**, bevor der Aufruf erfolgt; `.local`/`.internal` blockiert. Notausgang `TILLAT_INTERNA_UPPSLAG` für Testumgebungen | +| **CORS** | `TILLATNA_URSPRUNG`-Positivliste; der Ursprung wird einmal je Anfrage gesetzt | +| **Integrität von Anhängen** | SHA-256 im Protokoll, beim Lesen geprüft → `409` bei Abweichung | +| **Append-only in der Datenbank** | Trigger `before update or delete` auf `felsokning_handelser` und `felsokning_arenden` | +| **Pod-Härtung** | IMDSv2 verpflichtend, Hop-Limit 1 → Pods können die IAM-Rolle des Knotens nicht ausleihen | +| **IRSA** | Jedes Service-Konto hat eine eigene Rolle; die Knoten teilen keine Rechte | +| **Getrennte IAM-Rollen** | `bygg` (Build) darf nach ECR veröffentlichen, aber den Cluster nicht anfassen; `drift` (Betrieb) darf den Cluster anfassen, aber keine Images veröffentlichen | +| **Netzwerkrichtlinie** | Eingehend standardmäßig verboten; explizite `_ut`-Regeln (ausgehend) je Dienst | +| **Datenbankzugriff** | Nur von den Cluster-Knoten, in einer Subnetzschicht **ohne Route nach draußen** | + +### 8.1 Datenbankschema + +``` +organisationer · anvandare · inloggningsforsok +felsokning_arenden · felsokning_handelser +bilagor · bilage_innehall +delningar · integrationer +``` + +(Organisationen · Benutzer · Anmeldeversuche · Fälle · Ereignisse · Anhänge · +Anhangsinhalte · Freigaben · Integrationen) + +--- + +## 9. Live Share — Freigabestufen + +Drei Stufen, serverseitige Filterung: + +| Stufe | Sieht | +|---|---| +| **kund** (Kundschaft) | 22 Ereignistypen: Objekt, Fehlerbeschreibung, Fragen, Prüfungen, Beobachtungen, Messwerte, Fotos, Videos, Kommentare, Übergaben, Maßnahmen, Qualitätskontrolle … | +| **partner** | Alles, was die Kundschaft sieht **+ `hypotes`** (als nicht verifiziert gekennzeichnet) | +| **intern** | Volle Einsicht — keine Filterung | + +**Niemals außerhalb der Organisation:** +`kategori_byte`, `hypotes`, `ai_svar`, `ansvarig_satt`, `arbetsorder_skannad`. + +```js +export function synligaTyper(niva) { + if (niva === "intern") return null; // volle Einsicht + return niva === "partner" ? DELBART_PARTNER : DELBART_KUND; +} +``` + +Links sind widerrufbar. Die öffentliche Freigabeseite +(`/felsokning/delad/:kod`) verlangt keine Anmeldung und pollt für +Live-Aktualisierung. Die Kundschaft kann ihre Entscheidung direkt in der Ansicht +abgeben (`POST /api/delad/{kod}/beslut`). + +**Warum eine Positivliste:** Eine Negativliste muss aktualisiert werden, sobald +ein neuer Ereignistyp hinzukommt — und genau das wird vergessen. Eine +Positivliste macht aus „vergessen" ein „intern", und das ist der sichere +Ausgang. + +--- + +## 10. Markenspezifische Anbindungen + +Anbieter sind **Daten, kein Code** (`integrationer.json`, als ConfigMap über +`INTEGRATIONER_FIL` einhängbar). Neue Marken erfordern keinen Neubau. + +| id | Anbieter | +|---|---| +| `generisk_vin` | Beliebiger FIN-Dienst über HTTP | +| `vag_erwin` | Volkswagen Group erWin (VW, Audi, Škoda, SEAT) | +| `volvo_vida` | Volvo VIDA | +| `fordonsregister` | Kennzeichen → Fahrzeug | + +Jeder Anbieter deklariert seine Felder, welche geheim sind (verschlüsselt + +maskiert) und wie die Antwort auf die Domänenfelder abgebildet wird (`marke`, +`modell`, `arsmodell`, `motor`, `vaxellada` — Marke, Modell, Baujahr, Motor, +Getriebe). + +Abfragen laufen durch den SSRF-Schutz — eine Kundin kann einen „Anbieter" also +nicht auf interne Adressen des Clusters richten. + +--- + +## 11. Visual-first + +Die Kamera **ist** die Integrationsschicht. Was auf einem Bildschirm oder einem +Instrument steht, wird fotografiert und ausgewertet, statt angebunden zu werden. + +- **Auftragsscan** ist der Hauptweg bei der Fallanlage. Sonnet 5 (Vision) liest + Kunden-, Fahrzeug- und Werkstattangaben unabhängig vom Layout, mit einer + Konfidenz je Feld: + - 🟢 ≥95 % automatisch übernommen + - 🟡 80–95 % zum Durchlesen markiert + - 🔴 <80 % erfordert aktive Bestätigung + + Die Technikerin prüft also nur die unsicheren Felder. Visuelle Kontrolle mit + dem Dokument neben den Feldern; ein Klick markiert die ungefähre Position. + +- **Instrumentenablesung** — Foto eines Diagnosebildschirms oder Instruments → + strukturierte Werte. + +Das Motiv ist kommerziell: Eine Anbindung je Werkstattsystem bedeutet einen +Vertriebszyklus je Kunde. Eine Kamera funktioniert sofort gegen alles. + +--- + +## 12. Infrastruktur + +Zwei Terraform-Schichten. Die Basis läuft selten, die Arbeitslastschicht oft. + +### 12.1 `infra/aws` — die Basis (91 Ressourcen) + +| Bereich | Inhalt | +|---|---| +| **Netz** | 1 VPC, 3 Subnetzschichten × 3 Zonen: öffentlich (nur ALB + NAT), privat (Knoten, keine öffentlichen Adressen), Daten (Aurora, **überhaupt keine Route nach draußen**). VPC-Endpunkte: S3 (Gateway); ECR, Logs, Secrets Manager, STS, ELB (Interface) → der Verkehr verlässt das Netz nie | +| **Cluster** | EKS, arm64-Knoten, IRSA über OIDC-Provider, IMDSv2 Hop-Limit 1, alle fünf Control-Plane-Logs an | +| **Daten** | Aurora PostgreSQL Serverless v2, PITR sekundengenau, KMS mit eigenem Schlüssel, `sslmode=require` | +| **Objektspeicher** | S3 für Anhänge: SSE-KMS, öffentlicher Zugriff blockiert, TLS verpflichtend, Versionierung an. Die Plattformrolle darf lesen und schreiben — **aber nie löschen** | +| **Registry** | ECR mit **unveränderlichen Tags** + Schwachstellen-Scan | +| **Geheimnisse** | Secrets Manager; nur von der Plattformrolle über IRSA lesbar | +| **Rollen** | 9 IAM-Rollen, darunter die getrennten `bygg` / `drift` | +| **Domäne** | Route 53 + ACM mit DNS-Validierung | +| **Beobachtbarkeit** | 7 Alarme, 1 Dashboard, 3 Log-Gruppen, SNS-Topic | + +**Die Alarme** — wenige, aber die vorhandenen bedeuten etwas. *Ein Alarm, auf +den niemand reagiert, bringt Menschen bei, Alarme zu ignorieren.* + +- Aurora-CPU > 85 % über drei Perioden (die Skalierungsgrenze könnte erreicht + sein) +- Aurora freier lokaler Speicher < 5 GiB +- **Backup-Alter** — `treat_missing_data = "breaching"`. Fehlt der Messwert, + gibt es keine Sicherung. *Ein Backup, von dem man glaubt, es gäbe es, ist + schlimmer als keines.* +- Weniger Knoten als das gewünschte Minimum +- Antwortzeit **p95** > 3 s über drei Perioden — nicht der Mittelwert, der + verdeckt, dass jede zwanzigste Technikerin unzumutbar lange wartet +- Serverfehler (Summe > 5) +- Das Modell lehnt ab (deutet auf unerwartete Eingaben hin, nicht auf einen + Betriebsfehler) + +### 12.2 `infra/terraform` — die Arbeitslast (35 Ressourcen) + +Liest die Basis über `terraform_remote_state`; wiederholt nichts. + +``` +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 Zeilen) erzeugt eine lesbare Karte des gesamten Betriebsbildes: +`terraform output karta`. + +### 12.3 Git und CI — vollständig selbst gehostet + +Eine ausdrückliche Produktentscheidung: **kein GitHub im Betriebspfad.** Gitea + +Actions-Runner laufen auf eigenem EKS. `.gitea/workflows/felsokning.yml`: + +| Job | Inhalt | +|---|---| +| `test-och-bygg` | `vitest run`, `typkontroll`, eslint, `vite build` | +| `tjanster` | eslint auf den Diensten, **Integrationstest gegen echtes Postgres**, `swagger-cli validate` | +| `terraform` | `fmt -check -recursive`, `init -backend=false`, `validate` | +| `publicera` | Nur auf `main`, nur wenn das Obige durchlief. Baut drei Images, taggt mit dem Commit-SHA, schiebt in die eigene ECR. **OIDC, kein statischer Schlüssel** | +| `driftsatt` | **Manuell** (`workflow_dispatch`) mit explizitem Image-Tag | + +Die Inbetriebnahme ist bewusst ein eigener Schritt: *Ein Image in der Registry +ist nicht dasselbe wie ein laufendes Image.* Rollback = erneut mit dem +vorherigen Tag ausführen. + +Die API-Adresse des Clients wird beim Build eingebacken (Vite), das Image ist +also umgebungsgebunden. Der Build-Kontext der Dienste ist `felsokning/services`, +damit beide das gemeinsame Beobachtungsmodul erreichen, ohne es zu duplizieren. + +--- + +## 13. Testen + +**120 vitest-Tests** in 13 Dateien: + +| Datei | Anzahl | Schreibt fest | +|---|---|---| +| `ecm.test.ts` | 32 | Evidenzstufen, Regelpakete, Qualitätstor, Vordiagnostik | +| `metodiker.test.ts` | 14 | Bibliotheksstruktur, Methodikauswahl, Katalogparität mit dem Orchester | +| `projektioner.test.ts` | 13 | Ansichten als reine Funktionen, nächster Schritt | +| `ai.test.ts` | 11 | Orchesterparität, OpenAPI ↔ Server, append-only, Prompt-Regeln | +| `observation.test.ts` | 10 | Tracing, EMF-Format, **verbotene Dimensionen** | +| `bilagor.test.ts` | 9 | Inhalts-Hash, SigV4, Speicherschichtwahl | +| `delning.test.ts` | 7 | Die Positivliste deckt jeden Ereignistyp ab | +| `integrationer.test.ts` | 7 | Anbieterabfragen, SSRF-Schutz | +| `demo.test.ts` | 4 | Der Demofall ist reichhaltig genug zum Zeigen | +| `installningar.test.ts` | 4 | Organisationseinstellungen | +| `streckkod.test.ts` | 4 | FIN/Barcode | +| `synk.test.ts` | 4 | Konfliktfreies Zusammenführen | +| `example.test.ts` | 1 | — | + +**Über die Unit-Tests hinaus:** + +- `integrationstest.sh` — der gesamte Ablauf gegen **echtes Postgres**: + Organisationen, Rollen, der Append-only-Trigger, Trennung, Freigabe, Anhänge. +- SigV4 **bitgenau gegen botocore quergeprüft** (`sigv4-referens.json`). +- `swagger-cli validate` auf der OpenAPI-Spezifikation. +- Paritätstests zwischen Spezifikation ↔ Server, Client ↔ Orchester (× 2 + Kopien), Domänenmodell ↔ Freigabeliste. + +### 13.1 Die Prüfschleife vor jedem Commit + +``` +npx vitest run # 120 Tests +npm run typkontroll # tsc --noEmit (vite build prüft KEINE Typen) +npx eslint src/felsokning src/pages/felsokning +cd ../services && npx eslint . +npm run build +terraform fmt -check -recursive +# CI der Wurzel: lint · format:check · typecheck · test +``` + +`typkontroll` kam hinzu, nachdem zwei latente Abstürze (`TextFalt` und +`UNDANTAGSORSAKER` ohne Import verwendet) an `vite build` vorbeigekommen waren — +das transpiliert, prüft aber keine Typen. + +--- + +## 14. Repository-Struktur + +`main` ist ein npm-workspaces-Monorepo namens **Semantika**, das die Wurzel +besitzt. Bei der Zusammenführung der beiden Produkte wurden beide behalten, mit +**je Baum getrennten Werkzeugketten** — nicht dadurch, dass die Regeln einer +Seite aufgeweicht wurden. + +``` +/ Semantika (Workspaces-Wurzel) +├── apps/mobile/ Semantika +├── services/api/ Semantika +├── infra/ Semantika +├── .github/workflows/ci.yml Semantika — unangetastet +│ +├── .gitea/workflows/felsokning.yml Guidad Felsökning (eigene CI) +└── felsokning/ + ├── app/ Client (eigene package.json, eslint, vitest, tsconfig) + ├── services/ + │ ├── plattform/ + │ ├── ai-orkester/ + │ └── gemensam/ observation.mjs (gemeinsam) + ├── infra/ + │ ├── aws/ die Basis, 91 Ressourcen + │ ├── terraform/ die Arbeitslast, 35 Ressourcen + │ └── postgres-init.sql + ├── docs/ + └── supabase/ Migrationen + Edge-Funktion (älterer Weg) +``` + +**Zwei Betriebswege bestehen parallel:** der selbst gehostete AWS-Stack (der +gilt) und ein älterer Supabase-basierter (die Edge-Funktion `felsokning-ai`, +Migrationen). Das Orchester existiert daher in **zwei Kopien**, durch Tests +synchron gehalten. + +--- + +## 15. Dokumentation im Repository + +``` +docs/VISION.md die Produktvision +docs/MASTER-PROMPT.md die Gründungsinstruktion +docs/MVP.md was gebaut ist, Funktion für Funktion +docs/DEMO.md Demoskript für Vorführungen +docs/DRIFT.md Betrieb +docs/SYSTEMBESKRIVNING.md das schwedische Original dieses Dokuments +docs/SYSTEMBESKRIVNING.en.md Englisch +docs/SYSTEMBESKRIVNING.de.md dieses Dokument +docs/SYSTEMBESKRIVNING.da.md Dänisch +docs/SYSTEMBESKRIVNING.no.md Norwegisch (Bokmål) +docs/exempel/vibration-vid-88-km-h.md durchgerechnetes Beispiel +docs/moduler/ acht Moduldokumente +``` + +--- + +## 16. Entwurfsentscheidungen und ihre Beweggründe + +Zusammengestellt, weil der Beweggrund oft wichtiger ist als die Entscheidung. + +| Entscheidung | Beweggrund | +|---|---| +| Event Sourcing | Die Dokumentation muss im Streitfall halten. Die Historie *ist* der Wert | +| Append-only auch in der Datenbank | Die Anwendungsschicht lässt sich umgehen, der Trigger nicht | +| Positivliste für Freigaben | Ein vergessener Ereignistyp wird intern, nicht geleakt | +| Feste Ursachenkategorien | Freitext lässt sich nicht aggregieren; die Fuhrparkstatistik ist ein Aktivum | +| Serverseitiges Modell-Routing | Der Client darf den Schlüssel nie halten, und Routing muss sich ohne Release ändern lassen | +| Modell je Antwort protokolliert | Die Grundlage muss nachträglich prüfbar sein | +| Das Wort „KI" vermeiden | Die Kundschaft hört „Vermutung"; die Sachbearbeitung gewichtet niedriger | +| Visual-first | Eine Anbindung je Werkstattsystem = ein Vertriebszyklus je Kunde. Die Kamera funktioniert sofort | +| Anbieter als Daten | Eine neue Marke soll kein Release erfordern | +| Eigene Beobachtbarkeit, null Abhängigkeiten | 30 Pakete, um 4 Dinge zu messen, ist die falsche Abwägung | +| Wenige EMF-Dimensionen | Jede Kombination ist eine kostenpflichtige Zeitreihe | +| p95 im Alarm, nicht der Mittelwert | Der Mittelwert verdeckt, dass jede zwanzigste Technikerin wartet | +| Alarm auf *fehlende* Backup-Daten | Ein geglaubtes Backup ist schlimmer als keines | +| Getrennte Build-/Betriebsrollen | Ein kompromittierter Build darf den Cluster nicht anfassen können | +| Manuelle Inbetriebnahme | Ein Image in der Registry ≠ ein laufendes Image | +| Unveränderliche ECR-Tags | Ein Tag muss morgen dasselbe bedeuten | +| Inhaltsadressierte Anhänge | Ein ausgetauschtes Bild muss auffallen, nicht unterstellt werden | +| Hash-Prüfung beim Lesen | Hashen beim Schreiben genügt nicht | +| Die S3-Rolle darf nicht löschen | Append-only muss auch für den Speicher gelten | +| Engine vom Inhalt getrennt | Die Bibliothek wächst; die Engine soll sich nicht ändern müssen | +| Punktbewertete Methodikauswahl | Regex-Ketten werden bei 16 Alternativen undurchschaubar | +| Stichwörter als Wortstämme | Die schwedische Flexion tilgt ein `e` — sonst trifft nichts | +| `generisk` als Rückfall | Ein ehrliches „wir wissen es nicht" schlägt eine Vermutung | +| Sicherheitsschritt zuerst bei Hochvolt | Diese Arbeit kann tödlich sein | +| Schwedisch im Code | Die Domäne ist schwedisch; Hin- und Herübersetzen verliert Präzision | + +--- + +## 17. Bekannte Grenzen und offene Punkte + +Ausdrücklich nicht fertig: + +- **Zwei Orchester-Kopien** (Supabase-Edge-Funktion + K8s-Dienst) werden durch + Tests synchron gehalten, nicht durch gemeinsamen Code. Der Supabase-Weg ist + der ältere und sollte abgelöst werden. +- **`ArendeSida.tsx` hat 2433 Zeilen.** Es funktioniert, ist aber die Datei, die + am meisten kostet, wenn man sie ändert. +- **`terraform validate` lässt sich lokal nicht ausführen** (die ausgehende + Netzwerkrichtlinie blockiert Provider-Downloads). Ersetzt durch + `terraform fmt` plus eine eigene statische Referenzprüfung; die echte + Validierung erfolgt in der CI. +- **Der Claude-Schlüssel wird von Hand eingetragen**, nach dem ersten `apply` — + er liegt bewusst nicht im Terraform-State. +- **`postgres-init.sql` wird manuell** gegen die Datenbank ausgeführt, nachdem + die Basis angewandt wurde. +- **Kein automatischer Wiederherstellungstest des Backups.** Der Alarm sagt, + dass gesichert *wird*, nicht dass sich *wiederherstellen lässt*. +- **Die Methodikbibliothek deckt nicht alles ab** — und behauptet es auch nicht. + `generisk` ist das Auffangnetz. +- Das eslint der Wurzel hat 20 vorbestehende Fehler in Semantikas eigenen Seiten + (`no-explicit-any`), die nichts mit Guidad Felsökning zu tun haben. + +--- + +## 18. Glossar + +Die linke Spalte ist der Begriff, wie er in Code und Oberfläche erscheint. + +| Schwedisch | Deutsch | +|---|---| +| Ärende | Fall — ein Fehlersuchauftrag | +| Händelse / loggpost | Ereignis / Protokolleintrag — unteilbarer Eintrag im Append-only-Protokoll | +| Metodik | Methodik — strukturierter Fehlersuchablauf | +| Steg | Schritt — Phase einer Methodik (Symptom, Sichtprüfung, Messungen …) | +| Kontroll | Prüfung — einzelner Checklistenpunkt mit Mindestanforderung | +| Krav | Anforderung — `matvarde` / `kommentar` / `foto` | +| Undantag | Ausnahme — dokumentierter Grund, warum eine Prüfung entfiel | +| Brief | Briefing — verdichtetes Fallbild; eine Projektion | +| Kvalitetsgrind | Qualitätstor — Regelwerk, das vor dem Abschluss passiert werden muss | +| Evidensnivå | Evidenzstufe — E0–E6, die Beweiskraft der Grundlage | +| Reproducering | Reproduktion — Symptomverifizierung: ja / teilweise / nein | +| Felorsak | Fehlerursache — strukturierte Analyse mit Kategorie und Grundlage | +| Delning | Freigabe — externer Link mit Berechtigungsstufe | +| Orkester | Orchester — der Dienst, dem das Modell-Routing gehört | +| Spann / spår | Span / Spur — Zeitmessung bzw. W3C-Tracing | +| Bilaga | Anhang — inhaltsadressiertes Foto/Video/Dokument | +| Tekniker | Technikerin, Techniker | +| Arbetsledare | Werkstattleitung, Meister | +| Fordon | Fahrzeug | +| Mätvärde | Messwert | +| Felbeskrivning | Fehlerbeschreibung (die Worte der Kundschaft) | +| Arbetsorder | Werkstattauftrag | +| Mätarställning | Kilometerstand | +| Överlämning | Übergabe | +| Åtgärd | Maßnahme, Instandsetzung | +| Kundbeslut | Kundenentscheidung | +| Säkerhet | Sicherheit | +| Högvolt | Hochvolt | diff --git a/felsokning/docs/SYSTEMBESKRIVNING.en.md b/felsokning/docs/SYSTEMBESKRIVNING.en.md new file mode 100644 index 0000000..e393cef --- /dev/null +++ b/felsokning/docs/SYSTEMBESKRIVNING.en.md @@ -0,0 +1,939 @@ +# Guidad Felsökning (Guided Diagnostics) — complete system description + +> **English translation.** Source of truth: [SYSTEMBESKRIVNING.md](SYSTEMBESKRIVNING.md) (Swedish). +> If the two disagree, the Swedish document is correct. +> +> **Code identifiers are not translated.** Event types, function names, field +> names, file paths and configuration keys are Swedish *in the code itself*. +> Translating them would make this document useless against the repository, so +> they appear verbatim, with an English gloss where the meaning is not obvious. +> +> A self-contained reference document. Everything below is taken from the code +> in `felsokning/` (branch `claude/guidad-felsokning-vision-1mnx7f`), not from +> plans or intentions. Where something does **not** exist, this is stated +> explicitly. +> +> Last synchronised against code: commit `1bb4031`, 2026-08-04. + +--- + +## 0. Summary in thirty seconds + +Guidad Felsökning is a SaaS platform for **vehicle workshops**. It guides a +technician through a structured diagnostic process, requires evidence for every +claim, and produces a traceable record that can be shared with the customer, an +insurer, or the next technician. + +The load-bearing idea is negative rather than positive: **the system never +presents a hypothesis as a confirmed fault.** This is not a policy in a document +— it is coded, tested, and it blocks flows. When evidence is missing the system +says "Evidens saknas" (evidence missing), not a qualified guess. + +Technically: an **append-only event log** is the single source of truth. +Everything else — the case view, the brief, the customer report, the quality +gate, the statistics — is a pure projection of the log and can always be +regenerated. + +| | | +|---|---| +| Client | React 18 + TypeScript + Vite + Tailwind + zustand + react-router | +| Backend | Two Node services (`plattform`, `ai-orkester`), plain `node:http`, minimal dependencies | +| Database | PostgreSQL (Aurora Serverless v2), append-only enforced by database triggers | +| Model | Claude, server-owned routing per task | +| Infrastructure | AWS + EKS, 126 Terraform resources across two layers | +| Git & CI | **Self-hosted Gitea + Actions runners on our own EKS** — no GitHub in the operational path | +| Tests | 120 vitest tests + an integration test against real Postgres | +| Language in code | Swedish (identifiers, comments, commit messages) | + +--- + +## 1. Product principles + +These five are invariants, not guidelines. Each has a counterpart in code and in +a test. + +### 1.1 No hypothesis is presented as a confirmed fault + +Hypotheses are their own event type (`hypotes`) with a mandatory confidence +level, and can **never** take the level `hog` (high) — +`niva: Exclude`; the type system forbids it. In the +customer report they are explicitly marked as unverified. The quality gate has a +dedicated row for this. + +The wording on failed reproduction is *"kunde inte reproduceras under de +förhållanden som rådde"* ("could not be reproduced under the conditions that +prevailed") — never "fault confirmed" or "no fault found". This is coded in both +the projections and the orchestrator's base prompt. + +### 1.2 A checkbox is not evidence + +Every check item in every methodology carries a **minimum requirement**: +`matvarde` (measured value) | `kommentar` (observation) | `foto` (photo). A +measurement cannot be marked complete without a value; a photo check cannot be +marked complete without an image. If the technician wants to skip something, a +**documented exemption** with a reason from a fixed list is required. + +Locked by the test *"varje kontroll kräver bevis — en kryssruta är inte +evidens"*. + +### 1.3 The log is append-only, all the way down + +There are no update or delete operations in the API, and the database has +triggers that reject them even if someone bypasses the application. A test +actively searches the server code for `update`/`delete` against the event table +and fails if they appear. + +Consequence: an incorrect entry is *corrected by a new event*, never by making +the old one disappear. The history is what gives the record its value in a +dispute. + +### 1.4 Terminology + +In the UI and in customer communication the words used are **the system, the +analysis, the assessment, the decision support** — not "AI", unless technically +necessary. The product is described as an *evidence-based diagnostic system* / +*intelligent decision support*. + +The reason is both commercial and epistemic: a workshop customer who hears "AI" +hears "guess". An insurance assessor who reads "AI assessment" in a record +weights it lower. + +### 1.5 The sharing boundary is an allowlist + +What may leave the organisation is enumerated **positively**, per level. A new +event type is therefore internal until someone actively releases it. A test +requires every type in the domain model to be classified — if one is forgotten +the build fails, instead of it leaking. + +--- + +## 2. The domain model — the event log + +`app/src/felsokning/domain.ts` (281 lines). + +A case is: identity + metadata + an **ordered list of log entries**. Each entry +carries `id`, `tidpunkt` (timestamp), `tekniker` (technician) and a `handelse` +(event). + +### 2.1 All event types + +| Type | Contents | Role | +|---|---|---| +| `objekt_identifierat` | `objekt` (plate/VIN, make, model, engine …) | What the case concerns | +| `arbetsorder_skannad` | `falt[]` + attachment | Interpreted work order (**internal**) | +| `felbeskrivning` | `text` | The customer's words, verbatim | +| `arendetyp_satt` | `arendetyp` | Warranty / insurance / customer — selects rule pack | +| `fraga_besvarad` | `stegId`, `frageId`, `fraga`, `svar` | Methodology symptom questions | +| `kontroll_utford` | `stegId`, `kontrollId`, `text`, `resultat?`, `undantag?` | Verified checklist item | +| `observation` | `text` | What the technician saw — not what they believe | +| `matvarde` | `beskrivning`, `varde`, `enhet?` | Measurement (E4) | +| `hypotes` | `text`, `niva` (never `hog`) | Working hypothesis (**internal**) | +| `foto` | `beskrivning` + attachment | Photographic evidence (E2) | +| `video` | `beskrivning` + attachment | Moving-image evidence (E3) | +| `matarstallning` | `lage` (in/out), `varde` + attachment | Odometer in/out | +| `historik_kontrollerad` | `kontrollerad`, `kommentar?` | Service history | +| `reproducering` | `status` (ja/delvis/nej), `beskrivning` | **Symptom verification** | +| `felorsak` | structured root-cause analysis | Cause, category, supporting evidence | +| `atgardsforslag` | proposal with justification | What should be done | +| `kundbeslut` | approved/declined, channel | The customer's decision | +| `atgard_utford` | work performed | What was actually done | +| `kvalitetskontroll` | verification after repair | Is the symptom gone? | +| `kommentar` | `text` | Free note | +| `kategori_byte` | `kategori` | Time accounting (**internal**) | +| `inaktivitet_forklarad` | `text`, `minuter` | Why work stood still | +| `overlamning` | `fran`, `till?` | Shift handover | +| `ansvarig_satt` | `ansvarig` | Supervisor reassignment (**internal**) | +| `ai_svar` | classified `rader[]`, model name | Decision-support response (**internal**) | +| `export_skapad` | `format`, `version` | The export logs itself | +| `arende_avslutat` | `signatur?` | Technician's sign-off | + +### 2.2 Attachments are content-addressed + +`foto`, `video`, `matarstallning` and `arbetsorder_skannad` are *intersection +types* with `Bilaga` (attachment): + +```ts +export interface Bilaga { + bilagaId?: string; + bilagaHash?: string; // SHA-256 + dataUrl?: string; // stays forever — the log is append-only +} +``` + +The content lives outside the log (S3 or database), but **the hash lives in the +log**. On read the hash is verified; if it does not match, `409` is returned. +The meaning: if someone swaps an image in storage it is detected, and the log +can prove the original image was a different one. + +`dataUrl` is kept in the type because older entries have it embedded — and the +log cannot be rewritten. + +--- + +## 3. The methodology engine + +Since the most recent change, **engine and content are separated**: + +- `metodik.ts` (171 lines) — types, methodology selection, derivation of the + next step. +- `metodiker.ts` (899 lines) — the sixteen methodologies. + +The library can grow without the engine changing. + +### 3.1 The methodology library + +Step ids are code and stay Swedish; an English gloss follows each table row +where useful. `symptom` = symptom, `visuell` = visual, `matningar` = +measurements, `provkorning` = road test, `sakerhet` = safety, `avlasning` = +readout. + +| id | Name (in code) | Area | Steps | Checks | +|---|---|---|---|---| +| `vibration` | Vibration under körning | Wheels and balance | symptom → visuell → kontroller → provkorning | 19 | +| `bromsar` | Bromssystem | Chassis | symptom → visuell → matningar → system | 14 | +| `styrning_fjadring` | Styrning och fjädring | Chassis | symptom → visuell → glapp → installning | 11 | +| `elsystem` | Elsystem och strömförsörjning | Electrical | symptom → visuell → matningar → rela → funktionstest | 12 | +| `start_laddning` | Start- och laddningssystem | Electrical | symptom → batteri → start → laddning → krypstrom | 15 | +| `motor_drift` | Motorgång och effekt | Engine | symptom → felkoder → mekanik → tandning_bransle → provkorning | 16 | +| `kylsystem` | Kylsystem och överhettning | Engine | symptom → visuell → matningar → packning | 12 | +| `drivlina` | Växellåda och drivlina | Drivetrain | symptom → visuell → matningar → provkorning | 10 | +| `avgas_emission` | Avgassystem och emissioner | Engine | symptom → avlasning → matningar → orsak | 12 | +| `klimat` | Klimatanläggning | Comfort | symptom → visuell → matningar → styrning | 11 | +| `hogvolt` | Högvoltsystem — elbil och hybrid | High voltage | **sakerhet** → symptom → avlasning → laddning | 16 | +| `diagnos_natverk` | Felkoder och kommunikation | Diagnostics | symptom → grund → buss → koder | 10 | +| `lackage` | Läckage | Other | symptom → visuell → metod | 8 | +| `missljud` | Missljud | Other | symptom → inspelning → lokalisering | 7 | +| `adas` | Förarassistans och kalibrering | Diagnostics | symptom → forutsattningar → kalibrering | 9 | +| `generisk` | Generell strukturerad felsökning | Other | symptom → visuell → grundkontroller → funktionstest | 9 | + +English names: vibration while driving · braking system · steering and +suspension · electrical system and power supply · starting and charging · +engine running and power · cooling and overheating · gearbox and drivetrain · +exhaust and emissions · climate control · high-voltage system (EV/hybrid) · +fault codes and communication · leakage · abnormal noise · driver assistance and +calibration · generic structured diagnosis. + +### 3.2 Three rules, locked by tests + +1. **Every check has a minimum requirement.** Measured value, photo or + observation. +2. **Every methodology begins by verifying the symptom**, never by repairing. + The customer's words become a verified symptom only once reproduced. +3. **Where the work can injure someone, the safety step comes first.** Only + `sakerhet` may precede `symptom` — the test permits exactly that exception + and no other. + +`hogvolt` is the only methodology with a safety step. It requires +authorisation, a documented removed service disconnect (photo), the +manufacturer's waiting time, **measured absence of voltage** (a measured value — +not a yes to a question), and protective equipment. The test checks that the +step comes first, that `spanningsfrihet` requires a measured value, and that the +description contains the word "livsfarlig" (potentially lethal). + +The reason is simple: that work can kill someone. A checkbox will not do there. + +### 3.3 Methodology selection + +Previously a regex chain with three outcomes. Now **scored keyword matching**: + +```ts +export function metodikPoang(metodik: Metodik, text: string): number +export function valjMetodik(felbeskrivning: string): Metodik +``` + +- Score = the sum of the lengths of the keywords that match. A longer — more + specific — word weighs more. `traktionsbatteri` (16) beats `batteri`. +- **Short words (≤3 characters) match as whole words, longer ones as stems.** + Otherwise `"ac"` would have matched *acceleration* and a vibration would have + ended up in the climate-control methodology. +- On a tie the one listed first in the library wins → the selection is **stable** + across runs. +- No match → `generisk`. + +**A pitfall that actually bit during development:** the keywords must be +*stems*, not fully inflected words. Swedish inflection often drops an `e`: +*filter → filtret*, so `"partikelfilter"` never matches the text a technician +actually writes. The same applies to *regenerering → regenererar*, +*misständning → misständer*, *skrammel → skramlar*. The library therefore uses +`partikelfilt`, `regenerer`, `misständ`, `skram`. + +*(For readers translating this to another market: this is a property of Swedish +morphology, and the same class of problem exists in German compounding and +Danish/Norwegian definite forms. A localised keyword set has to be validated +against the same test, not translated word-for-word.)* + +**The selection is a question order, not a diagnosis.** It decides where the +technician starts looking, not what is wrong. If nothing matches, `generisk` is +the honest answer — structurally complete, and better than a guess. + +### 3.4 Next step + +```ts +export function nastaSteg(arende: Arende, metodik: Metodik): NastaSteg +``` + +Purely derived from the log: the first unanswered question, then the first +un-performed check, in the methodology's order. No hidden state machine — the +same log always yields the same next step. + +### 3.5 On "covering everything" + +That cannot be promised honestly, and the documentation does not claim it. What +is possible is to cover the vehicle's systems systematically and let `generisk` +be a structurally complete safety net for what nobody anticipated. + +--- + +## 4. ECM v2.0 — the evidence and rule engine + +`app/src/felsokning/ecm.ts` (749 lines). Six engines: + +### 4.1 Evidence Engine + +Evidence levels, derived from the log: + +| Level | Meaning | +|---|---| +| E0 | No supporting evidence | +| E1 | Technician's observation | +| E2 | Photo | +| E3 | Video | +| E4 | Measured value | +| E5 | Diagnostic data / document | +| E6 | Multiple independent sources | + +A case's evidence level is the highest the record supports. It is shown in the +UI and travels with the export. + +**Content hash:** `innehallsHash()` is a deterministic FNV-1a over the evidence +content. The same record ⇒ the same hash, regardless of machine or moment. That +makes the export verifiable after the fact. + +### 4.2 Rule Engine + +- `ORSAKSKATEGORIER` — fixed list of root-cause categories (gives comparable + statistics across the fleet). +- `UNDANTAGSORSAKER` — fixed list of reasons for "why this was not done". +- `UNDERLAGSKALLOR` — what a conclusion rests on. +- `INGEN_ATGARD_ORSAKER`, `KUNDKANALER` (customer channels). +- `granskaAvvikelse()` — flags text phrased as a statement of fact without + cover. + +Fixed lists instead of free text is a deliberate choice: free text cannot be +aggregated, and fleet statistics are one of the product's real assets. + +### 4.3 Compliance Engine + +`ARENDETYPER` (case types) selects which **rule pack** applies. A warranty case +requires a claim number and service history; an insurance case requires a claim +reference and photographic evidence; a customer-paid case requires less. The +packs are data (`ecm-regler.json`, servable via `/api/ecm/regler`) — new +requirements need no new release. + +### 4.4 Validation Engine — pre-diagnostics + +Before diagnosis may begin: object identification verified, work order read in, +vehicle history checked **or justified**, incoming odometer documented, +customer's fault description verified, early observations handled. + +### 4.5 Completion Engine — the quality gate + +The largest single function (`kvalitetsgrind`, ~240 lines). The case cannot be +closed until every row is green or justified: + +- Vehicle history checked or justified +- Incoming/outgoing odometer documented +- Customer's fault description verified +- **Symptom verification:** reproduced, or documented as non-reproducible +- Root-cause analysis documented +- Repair documented or justified +- Customer's decision on the proposal recorded +- Work performed despite a declined proposal (where applicable) +- Quality check performed — symptom verified +- Methodology checks: evidence or documented exemption +- Photos present for photo-requiring checks +- Technician's conclusion signed +- Hypotheses presented as unverified +- The case type's rule pack satisfied (claim / insurance reference / odometer / + history) + +### 4.6 Traceability Engine + +`sparbarhetspaket()` — the whole chain of evidence in one structured object: +what is claimed, what it rests on, who documented it and when. + +--- + +## 5. Symptom verification (SVP) + +Its own principle, because it is the product's sharpest edge against reality. + +**The customer's description ≠ a confirmed fault.** + +1. The description is documented **verbatim** (`felbeskrivning`). +2. It is clarified through the methodology's symptom questions — *when, where, + how*, never "what is wrong". +3. It is **reproduced**, with three possible outcomes: + - **Yes** — with documented conditions. + - **Partly** — what could and could not be recreated. + - **No** — mandatory justification. + +The report's chain of evidence separates four things that otherwise get mixed +together: *the customer's description*, *verified observation*, *root-cause +analysis* and *recommended action*. + +--- + +## 6. The client + +`app/src/felsokning/` + `app/src/pages/felsokning/`. + +| Module | Lines | Responsibility | +|---|---|---| +| `ArendeSida.tsx` | 2433 | The case view. Three-column layout on desktop | +| `metodiker.ts` | 899 | The methodology library | +| `ecm.ts` | 749 | Rule and evidence engine | +| `NyttArende.tsx` | 497 | Case start, work-order scanning | +| `Arendelista.tsx` | 399 | Dashboard: counters, filters | +| `projektioner.ts` | 356 | All views as pure functions of the log | +| `ai.ts` | 305 | Client side of the orchestrator, prompt building, response parsing | +| `plattform.ts` | 296 | API client against the self-hosted platform | +| `DelatArendeVy.tsx` | 283 | Shared view (customer/partner/internal) | +| `domain.ts` | 281 | Event types | +| `Installningar.tsx` | 281 | Organisation, users, integrations | +| `Oversikt.tsx` | 238 | Supervisor view | +| `demo.ts` | 200 | Demo case with 1 h 35 min of history | +| `ui.tsx` | 174 | Industrial workshop UI | +| `metodik.ts` | 171 | The methodology engine | +| `synk.ts` | 141 | Conflict-free merging of events | +| `ikoner.tsx` | 132 | Own SVG line icons (no emojis) | +| `streckkod.ts` | 131 | Barcode/VIN reading | +| `store.ts` | 106 | zustand store | +| `bilagor.ts` | 96 | Upload + blob-URL cache | +| `installningar.ts` | 86 | Organisation settings | +| `Bilagevisning.tsx` | 69 | `` / `` | +| `Mikrofon.tsx` / `rost.ts` | 66 / 65 | Speech recognition | +| `format.ts` | 48 | Photo scaling etc. | + +### 6.1 The projections + +``` +objekt · felbeskrivning · ansvarig · arendeidentitet · arAvslutat +lokalFordonshistorik · utfordaKontroller · ejKontrollerat +observationer · hypoteser · foton · videor +tidsfordelning · formateraTid · tillforlitlighet +brief · overlamningstext · tidsfordelningsRader · sistaAktivitet +``` + +All pure functions of `Arende`. `ejKontrollerat` ("not yet checked") is the one +that saves the most time in practice: *what causes duplicated work at shift +change is the thing nobody wrote down that nobody did.* + +### 6.2 UI language + +An ETKA-inspired workshop UI: flat light-grey surfaces (#ECECEC/#F7F7F7), sharp +edges, deep navy as the primary colour, dense typography (11–15 px), rectangular +buttons (max 4 px radius), toolbar ~44 px. Own line icons instead of emojis; +status as colour dots. + +The motive: the technician is wearing gloves, standing in a noisy space, and has +no time for an airy consumer UI. + +### 6.3 Local mode + +Without login the app works against `localStorage`. The methodology guides +alone; the orchestrator is off. Status is shown in the case header. On login, +local events are merged with the server's — conflict-free per event id, tested. + +--- + +## 7. Backend + +### 7.1 `services/plattform` (1210 lines) + +Plain `node:http`. The only dependency is `pg`. + +**API routes:** + +``` +GET /halsa health +GET /api/openapi.yaml +POST /api/auth/registrera creates organisation + system administrator +POST /api/auth/logga-in log in +POST /api/auth/logga-ut-alla raises token_version → every session dies +GET /api/anvandare users; admin only +POST /api/anvandare +POST /api/anvandare/{id}/avaktivera | /aktivera +GET /api/organisation +GET/PUT /api/organisation/installningar +GET /api/ecm/regler rule packs as data +GET/POST /api/arenden cases +POST /api/arenden/{id}/handelser append-only +POST /api/arenden/{id}/bilagor attachments +GET /api/bilagor/{id} hash verified on read +GET /api/fordon/{identifierare}/historik +GET /api/statistik/felorsaker root-cause statistics +GET /api/oversikt supervisor view +GET /api/delad/{kod} shared, filtered by level +POST /api/delad/{kod}/beslut customer decision without login +GET /api/delad/{kod}/bilagor/{id} level-filtered +GET /api/integrationer/leverantorer +GET/PUT/DELETE /api/integrationer/{leverantor} +POST /api/integrationer/{leverantor}/uppslag +``` + +There are no update or delete routes against case data. By design. + +**Security functions in the service:** + +``` +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 lines) + +Owns the Claude key. The client **never** has it. Routing per task: + +| Task | Model | Effort | Vision | +|---|---|---|---| +| `handledning` (live guidance) | `claude-sonnet-5` | medium | — | +| `granskning` (deep review) | `claude-opus-5` | **high** | — | +| `sammanfattning` (handover summary) | `claude-sonnet-5` | low | — | +| `metodikval` (classification) | `claude-haiku-4-5` | *(none — the model does not take the parameter)* | — | +| `instrumentavlasning` (instrument reading) | `claude-sonnet-5` | low | ✔ | +| `dokumenttolkning` (document reading) | `claude-sonnet-5` | low | ✔ | + +All responses are **schema-bound** (`json_schema`). The base prompt encodes the +rules: *"Never invent facts"*, *"never a hypothesis as a confirmed fault"*, +*"REQUIRES verification"*. On a declined request there is automatic fallback to +a reserve model. **The model that answered is logged in every `ai_svar` +event** — the record must be auditable after the fact. + +The methodology catalogue is built from a single list (`METODIK_KATALOG`) that +generates both the schema's `enum` and the prompt's bullet list. A test compares +it against the client's library: if the lists drift apart, the classifier +returns an id the client does not recognise, and the selection would fall back +*silently* to generic. Now the test fails instead. + +### 7.3 `services/gemensam/observation.mjs` (166 lines) + +Tracing and metrics **with no new dependencies**. The services deliberately have +almost no dependencies; pulling in an OpenTelemetry SDK with thirty packages to +measure four things would be the wrong trade. Instead, two standards that are +both just text on stdout: + +- **W3C Trace Context** — `traceparent` travels through the whole chain + (client → platform → orchestrator). +- **CloudWatch EMF** — structured JSON from which CloudWatch itself extracts + metrics. No agent, no SDK, nothing that can silently stop working. + +``` +spårFrån(header) · traceparent(trace) · starta(name, trace) + → .mät(partName, work) · .ms() · .delar() +logga(level, message, fields) · mätvärde(name, value, unit, dims, extra) +avsluta(span, { status, väg, extra }) +``` + +What is measured was chosen from one question: *what do you want to know at +three in the morning when something is slow?* The answer is **where the time +went** — not how many calls were made. Hence `delar()` ("parts"): the database, +the model call, object storage, the customer's vendor, with count and sum per +part in the same log line. + +`mät()` measures even when the work throws — otherwise errors look like zero +time. + +**Dimensions are deliberately few.** Every unique combination is its own time +series that costs money, so organisation, case and trace id must never become +dimensions — they are ordinary fields. Locked by a test that explicitly forbids +`org`, `organisation`, `arende`, `spårId`, `anvandare` among the dimensions. + +--- + +## 8. Security + +| Protection | Implementation | +|---|---| +| **Multi-tenant isolation** | All case queries are organisation-scoped (`arendeIOrg`); integration-tested against real Postgres | +| **Roles** | `tekniker` / `arbetsledare` / `admin` (technician / supervisor / admin), in the JWT and as a database check | +| **JWT claims** | `{ sub, namn, org, roll, tv }` — `tv` = token_version | +| **Immediate revocation** | `kontoGiltigt()` checks `aktiv` + `token_version` on *every* authenticated request. A valid signature is not enough | +| **Global logout** | `/api/auth/logga-ut-alla` raises `token_version` → every issued token dies immediately | +| **Passwords** | bcrypt via `gen_salt('bf')` in the database | +| **Login throttling** | 15-minute window; max 10 attempts per account, 30 per source. Cleaned probabilistically (2 % chance per write) to avoid needing a cron job | +| **Encryption at rest** | AES-256-GCM for customers' integration credentials; secret fields are always masked in API responses | +| **SSRF defence** | `arPrivatAdress()` + `pekarInat()`: 10/8, 127/8, 169.254/16, 172.16–31, 192.168/16, 100.64/10, ::1, fc/fd, fe80, ::ffff:. **DNS is resolved** before the call; `.local`/`.internal` blocked. Escape hatch `TILLAT_INTERNA_UPPSLAG` for test environments | +| **CORS** | `TILLATNA_URSPRUNG` allowlist; the origin is set once per request | +| **Attachment integrity** | SHA-256 in the log, verified on read → `409` on mismatch | +| **Append-only in the database** | Triggers `before update or delete` on both `felsokning_handelser` and `felsokning_arenden` | +| **Pod hardening** | IMDSv2 mandatory, hop limit 1 → pods cannot borrow the node's IAM role | +| **IRSA** | Every service account has its own role; nodes share no permissions | +| **Split IAM roles** | `bygg` (build) may publish to ECR but not touch the cluster; `drift` (operate) may touch the cluster but not publish images | +| **Network policy** | Default deny inbound; explicit `_ut` (outbound) rules per service | +| **Database access** | Only from the cluster's nodes, in a subnet layer **with no route out** | + +### 8.1 Database schema + +``` +organisationer · anvandare · inloggningsforsok +felsokning_arenden · felsokning_handelser +bilagor · bilage_innehall +delningar · integrationer +``` + +(organisations · users · login attempts · cases · events · attachments · +attachment content · shares · integrations) + +--- + +## 9. Live Share — sharing levels + +Three levels, server-side filtering: + +| Level | Sees | +|---|---| +| **kund** (customer) | 22 event types: object, fault description, questions, checks, observations, measurements, photos, videos, comments, handovers, repairs, quality check … | +| **partner** | Everything the customer sees **+ `hypotes`** (marked unverified) | +| **intern** (internal) | Full visibility — no filtering | + +**Never outside the organisation:** +`kategori_byte`, `hypotes`, `ai_svar`, `ansvarig_satt`, `arbetsorder_skannad`. + +```js +export function synligaTyper(niva) { + if (niva === "intern") return null; // full visibility + return niva === "partner" ? DELBART_PARTNER : DELBART_KUND; +} +``` + +Links are revocable. The public share page (`/felsokning/delad/:kod`) requires +no login and polls for live updates. The customer can give their decision +directly in the view (`POST /api/delad/{kod}/beslut`). + +**Why an allowlist:** a denylist must be updated whenever a new event type is +added — and that is exactly what gets forgotten. An allowlist turns "forgotten" +into "internal", which is the safe outcome. + +--- + +## 10. Brand-specific integrations + +Vendors are **data, not code** (`integrationer.json`, mountable as a ConfigMap +via `INTEGRATIONER_FIL`). New brands require no rebuild. + +| id | Vendor | +|---|---| +| `generisk_vin` | Any VIN service over HTTP | +| `vag_erwin` | Volkswagen Group erWin (VW, Audi, Škoda, SEAT) | +| `volvo_vida` | Volvo VIDA | +| `fordonsregister` | Registration number → vehicle | + +Each vendor declares its fields, which are secret (encrypted + masked), and how +the response maps to the domain's fields (`marke`, `modell`, `arsmodell`, +`motor`, `vaxellada` — make, model, year, engine, transmission). + +Lookups go through the SSRF protection — a customer therefore cannot point a +"vendor" at the cluster's internal addresses. + +--- + +## 11. Visual-first + +The camera **is** the integration layer. What appears on a screen or an +instrument is photographed and interpreted, rather than integrated. + +- **Work-order scanning** is the primary path at case start. Sonnet 5 (vision) + reads customer, vehicle and workshop details regardless of layout, with a + confidence per field: + - 🟢 ≥95 % accepted automatically + - 🟡 80–95 % flagged for reading + - 🔴 <80 % requires active confirmation + + The technician therefore reviews only the uncertain fields. Visual review with + the document beside the fields; clicking marks the approximate position. + +- **Instrument reading** — a photo of a diagnostic screen or instrument → + structured values. + +The motive is commercial: one integration per workshop system is one sales cycle +per customer. A camera works against everything, immediately. + +--- + +## 12. Infrastructure + +Two Terraform layers. The base runs rarely, the workload layer often. + +### 12.1 `infra/aws` — the base (91 resources) + +| Area | Contents | +|---|---| +| **Network** | 1 VPC, 3 subnet layers × 3 zones: public (ALB + NAT only), private (nodes, no public addresses), data (Aurora, **no route out at all**). VPC endpoints: S3 (gateway); ECR, logs, Secrets Manager, STS, ELB (interface) → traffic never leaves the network | +| **Cluster** | EKS, arm64 nodes, IRSA via OIDC provider, IMDSv2 hop limit 1, all five control-plane logs on | +| **Data** | Aurora PostgreSQL Serverless v2, PITR to the second, KMS with its own key, `sslmode=require` | +| **Object storage** | S3 for attachments: SSE-KMS, public access blocked, TLS mandatory, versioning on. The platform role may read and write — **but never delete** | +| **Registry** | ECR with **immutable tags** + vulnerability scanning | +| **Secrets** | Secrets Manager; readable only by the platform role via IRSA | +| **Roles** | 9 IAM roles, including the split `bygg` / `drift` | +| **Domain** | Route 53 + ACM with DNS validation | +| **Observability** | 7 alarms, 1 dashboard, 3 log groups, SNS topic | + +**The alarms** — few, but the ones that exist mean something. *An alarm nobody +acts on teaches people to ignore alarms.* + +- Aurora CPU > 85 % for three periods (the scaling ceiling may have been hit) +- Aurora free local storage < 5 GiB +- **Backup age** — `treat_missing_data = "breaching"`. If the metric is missing, + there is no backup. *A backup you believe exists is worse than none.* +- Fewer nodes than the desired minimum +- Response time **p95** > 3 s for three periods — not the mean, which hides that + every twentieth technician waits unreasonably long +- Server errors (sum > 5) +- The model declines (indicates unexpected input, not an operational fault) + +### 12.2 `infra/terraform` — the workload (35 resources) + +Reads the base via `terraform_remote_state`; repeats nothing. + +``` +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 lines) produces a readable map of the whole operational picture: +`terraform output karta`. + +### 12.3 Git and CI — fully self-hosted + +An explicit product decision: **no GitHub in the operational path.** Gitea + +Actions runners run on our own EKS. `.gitea/workflows/felsokning.yml`: + +| Job | Contents | +|---|---| +| `test-och-bygg` | `vitest run`, `typkontroll`, eslint, `vite build` | +| `tjanster` | eslint on the services, **integration test against real Postgres**, `swagger-cli validate` | +| `terraform` | `fmt -check -recursive`, `init -backend=false`, `validate` | +| `publicera` | Only on `main`, only if the above passed. Builds three images, tags with the commit SHA, pushes to our own ECR. **OIDC, no static key** | +| `driftsatt` | **Manual** (`workflow_dispatch`) with an explicit image tag | + +Deployment is a separate step by design: *an image in the registry is not the +same thing as an image that is running.* Rollback = run again with the previous +tag. + +The client's API address is baked in at build time (Vite), so the image is +environment-bound. The build context for the services is `felsokning/services` +so that both reach the shared observability module without duplicating it. + +--- + +## 13. Testing + +**120 vitest tests** across 13 files: + +| File | Count | Locks | +|---|---|---| +| `ecm.test.ts` | 32 | Evidence levels, rule packs, quality gate, pre-diagnostics | +| `metodiker.test.ts` | 14 | Library structure, methodology selection, catalogue parity with the orchestrator | +| `projektioner.test.ts` | 13 | Views as pure functions, next step | +| `ai.test.ts` | 11 | Orchestrator parity, OpenAPI ↔ server, append-only, prompt rules | +| `observation.test.ts` | 10 | Tracing, EMF format, **forbidden dimensions** | +| `bilagor.test.ts` | 9 | Content hash, SigV4, storage-layer selection | +| `delning.test.ts` | 7 | The allowlist covers every event type | +| `integrationer.test.ts` | 7 | Vendor lookups, SSRF guard | +| `demo.test.ts` | 4 | The demo case is rich enough to show | +| `installningar.test.ts` | 4 | Organisation settings | +| `streckkod.test.ts` | 4 | VIN/barcode | +| `synk.test.ts` | 4 | Conflict-free merging | +| `example.test.ts` | 1 | — | + +**Beyond the unit tests:** + +- `integrationstest.sh` — the whole flow against **real Postgres**: + organisations, roles, the append-only trigger, isolation, sharing, + attachments. +- SigV4 **cross-verified bit-for-bit against botocore** + (`sigv4-referens.json`). +- `swagger-cli validate` on the OpenAPI spec. +- Parity tests comparing spec ↔ server, client ↔ orchestrator (× 2 copies), + domain model ↔ sharing list. + +### 13.1 The verification loop before every commit + +``` +npx vitest run # 120 tests +npm run typkontroll # tsc --noEmit (vite build does NOT typecheck) +npx eslint src/felsokning src/pages/felsokning +cd ../services && npx eslint . +npm run build +terraform fmt -check -recursive +# root CI: lint · format:check · typecheck · test +``` + +`typkontroll` was added after two latent crashes (`TextFalt` and +`UNDANTAGSORSAKER` used without import) got past `vite build` — which +transpiles but does not typecheck. + +--- + +## 14. Repository structure + +`main` is an npm-workspaces monorepo called **Semantika** which owns the root. +When the two products were merged, both were kept, with the toolchains **kept +separate per tree** — not by weakening anyone's rules. + +``` +/ Semantika (workspaces root) +├── apps/mobile/ Semantika +├── services/api/ Semantika +├── infra/ Semantika +├── .github/workflows/ci.yml Semantika — untouched +│ +├── .gitea/workflows/felsokning.yml Guidad Felsökning (self-hosted CI) +└── felsokning/ + ├── app/ client (own package.json, eslint, vitest, tsconfig) + ├── services/ + │ ├── plattform/ + │ ├── ai-orkester/ + │ └── gemensam/ observation.mjs (shared) + ├── infra/ + │ ├── aws/ the base, 91 resources + │ ├── terraform/ the workload, 35 resources + │ └── postgres-init.sql + ├── docs/ + └── supabase/ migrations + edge function (older path) +``` + +**Two operational paths exist in parallel:** the self-hosted AWS stack (the one +that counts) and an older Supabase-based one (the edge function +`felsokning-ai`, migrations). The orchestrator therefore exists in **two +copies**, kept in sync by tests. + +--- + +## 15. Documentation in the repository + +``` +docs/VISION.md the product vision +docs/MASTER-PROMPT.md the founding instruction +docs/MVP.md what is built, feature by feature +docs/DEMO.md demo script for presentations +docs/DRIFT.md operations +docs/SYSTEMBESKRIVNING.md the Swedish original of this document +docs/SYSTEMBESKRIVNING.en.md this document +docs/SYSTEMBESKRIVNING.de.md German +docs/SYSTEMBESKRIVNING.da.md Danish +docs/SYSTEMBESKRIVNING.no.md Norwegian (Bokmål) +docs/exempel/vibration-vid-88-km-h.md worked example +docs/moduler/ eight module documents +``` + +--- + +## 16. Design decisions and their motives + +Collected because the motive is often more important than the decision. + +| Decision | Motive | +|---|---| +| Event sourcing | The record has to hold up in a dispute. The history *is* the value | +| Append-only in the database too | The application layer can be bypassed; the trigger cannot | +| Allowlist for sharing | A forgotten event type becomes internal, not leaked | +| Fixed cause categories | Free text cannot be aggregated; fleet statistics are an asset | +| Server-owned model routing | The client must never hold the key, and routing must change without a release | +| Model logged per response | The record must be auditable after the fact | +| Avoid the word "AI" | The customer hears "guess"; the assessor weights it lower | +| Visual-first | One integration per workshop system = one sales cycle per customer. The camera works immediately | +| Vendors as data | A new brand should not require a release | +| Own observability, zero dependencies | 30 packages to measure 4 things is the wrong trade | +| Few EMF dimensions | Every combination is a paid time series | +| p95 in the alarm, not the mean | The mean hides that every twentieth technician waits | +| Alarm on *missing* backup data | A backup you believe exists is worse than none | +| Split build/operate roles | A compromised build must not be able to touch the cluster | +| Manual deployment | An image in the registry ≠ an image that is running | +| Immutable ECR tags | A tag must mean the same thing tomorrow | +| Content-addressed attachments | A swapped image must be detected, not assumed | +| Hash verification on read | Hashing on write is not enough | +| The S3 role may not delete | Append-only must apply to storage too | +| Engine separated from content | The library grows; the engine should not have to change | +| Scored methodology selection | Regex chains become opaque at 16 alternatives | +| Keywords as stems | Swedish inflection drops an `e` — otherwise nothing matches | +| `generisk` as fallback | An honest "we don't know" beats a guess | +| Safety step first in high voltage | That work can kill | +| Swedish in the code | The domain is Swedish; translating back and forth loses precision | + +--- + +## 17. Known limitations and open items + +Explicitly not finished: + +- **Two orchestrator copies** (Supabase edge function + K8s service) are kept in + sync by tests, not by shared code. The Supabase path is the older one and + should be retired. +- **`ArendeSida.tsx` is 2433 lines.** It works, but it is the file that costs + the most to change. +- **`terraform validate` cannot be run locally** in the development environment + (the outbound network policy blocks provider downloads). Replaced by + `terraform fmt` plus a bespoke static reference check; real validation happens + in CI. +- **The Claude key is filled in by hand** after the first `apply` — it is not in + Terraform state, by design. +- **`postgres-init.sql` is run manually** against the database after the base + `apply`. +- **No automatic restore test of the backup.** The alarm says backup *happens*, + not that it *can be restored*. +- **The methodology library does not cover everything** — and does not claim to. + `generisk` is the safety net. +- The root eslint has 20 pre-existing errors in Semantika's own pages + (`no-explicit-any`) unrelated to Guidad Felsökning. + +--- + +## 18. Glossary + +The left column is the term as it appears in code and UI. + +| Swedish | English | +|---|---| +| Ärende | Case — one diagnostic job | +| Händelse / loggpost | Event / log entry — atomic entry in the append-only log | +| Metodik | Methodology — structured diagnostic flow | +| Steg | Step — phase in a methodology (symptom, visual, measurements …) | +| Kontroll | Check — individual checklist item with a minimum requirement | +| Krav | Requirement — `matvarde` / `kommentar` / `foto` | +| Undantag | Exemption — documented reason a check was skipped | +| Brief | Brief — compiled case picture; a projection | +| Kvalitetsgrind | Quality gate — rule set that must be passed before closing | +| Evidensnivå | Evidence level — E0–E6, the probative value of the record | +| Reproducering | Reproduction — symptom verification: yes / partly / no | +| Felorsak | Root cause — structured analysis with category and supporting evidence | +| Delning | Share — external link with a permission level | +| Orkester | Orchestrator — the service that owns model routing | +| Spann / spår | Span / trace — timing and W3C tracing respectively | +| Bilaga | Attachment — content-addressed photo/video/document | +| Tekniker | Technician | +| Arbetsledare | Supervisor / foreman | +| Fordon | Vehicle | +| Mätvärde | Measured value | +| Felbeskrivning | Fault description (the customer's words) | +| Arbetsorder | Work order | +| Mätarställning | Odometer reading | +| Överlämning | Handover | +| Åtgärd | Repair / action | +| Kundbeslut | Customer decision | +| Säkerhet | Safety | +| Högvolt | High voltage | diff --git a/felsokning/docs/SYSTEMBESKRIVNING.md b/felsokning/docs/SYSTEMBESKRIVNING.md index 776783d..daef751 100644 --- a/felsokning/docs/SYSTEMBESKRIVNING.md +++ b/felsokning/docs/SYSTEMBESKRIVNING.md @@ -788,7 +788,11 @@ 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/SYSTEMBESKRIVNING.md detta dokument (källan) +docs/SYSTEMBESKRIVNING.en.md engelska +docs/SYSTEMBESKRIVNING.de.md tyska +docs/SYSTEMBESKRIVNING.da.md danska +docs/SYSTEMBESKRIVNING.no.md norska (bokmål) docs/exempel/vibration-vid-88-km-h.md genomgående exempelflöde docs/moduler/ arbetslogg-och-tidredovisning.md diff --git a/felsokning/docs/SYSTEMBESKRIVNING.no.md b/felsokning/docs/SYSTEMBESKRIVNING.no.md new file mode 100644 index 0000000..842c448 --- /dev/null +++ b/felsokning/docs/SYSTEMBESKRIVNING.no.md @@ -0,0 +1,930 @@ +# Guidad Felsökning (Veiledet Feilsøking) — fullstendig systembeskrivelse + +> **Norsk oversettelse (bokmål).** Kilden er [SYSTEMBESKRIVNING.md](SYSTEMBESKRIVNING.md) (svensk). +> Ved uoverensstemmelse gjelder det svenske dokumentet. +> +> **Kodeidentifikatorer oversettes ikke.** Hendelsestyper, funksjons- og +> feltnavn, filstier og konfigurasjonsnøkler er svenske *i selve koden*. En +> oversettelse ville gjort dokumentet ubrukelig mot repositoriet, så de står +> ordrett, med norsk forklaring der betydningen ikke er åpenbar. +> +> Et selvstendig referansedokument. Alt nedenfor er hentet fra koden i +> `felsokning/` (branch `claude/guidad-felsokning-vision-1mnx7f`), ikke fra +> planer eller intensjoner. Der noe **ikke** finnes, står det uttrykkelig. +> +> Sist synkronisert mot koden: commit `1bb4031`, 04.08.2026. + +--- + +## 0. Sammendrag på tretti sekunder + +Guidad Felsökning er en SaaS-plattform for **bilverksteder**. Den fører en +tekniker gjennom en strukturert feilsøking, krever bevis for hver eneste +påstand, og produserer et sporbart grunnlag som kan deles med kunden, et +forsikringsselskap eller neste tekniker. + +Den bærende ideen er negativ snarere enn positiv: **systemet framstiller aldri +en hypotese som en konstatert feil.** Det er ikke en retningslinje i et dokument +— det er kodet, testet og blokkerer flyt. Når beviset mangler, står det „Evidens +saknas" (bevis mangler), ikke en kvalifisert gjetning. + +Teknisk: en **append-only hendelseslogg** er eneste sannhetskilde. Alt annet — +saksvisningen, briefen, kunderapporten, kvalitetsporten, statistikken — er rene +projeksjoner av loggen og kan alltid gjenskapes. + +| | | +|---|---| +| Klient | React 18 + TypeScript + Vite + Tailwind + zustand + react-router | +| Backend | To Node-tjenester (`plattform`, `ai-orkester`), rent `node:http`, minimale avhengigheter | +| Database | PostgreSQL (Aurora Serverless v2), append-only håndhevet av databasetriggere | +| Modell | Claude, servereid ruting per oppgave | +| Infrastruktur | AWS + EKS, 126 Terraform-ressurser i to lag | +| Git & CI | **Selvhostet Gitea + Actions-runnere på eget EKS** — ingen GitHub i driftsveien | +| Tester | 120 vitest-tester + integrasjonstest mot ekte Postgres | +| Språk i koden | Svensk (identifikatorer, kommentarer, commit-meldinger) | + +--- + +## 1. Produktprinsipper + +Disse fem er invarianter, ikke retningslinjer. Hver enkelt har en motsvarighet i +kode og i en test. + +### 1.1 Ingen hypotese framstilles som en konstatert feil + +Hypoteser er sin egen hendelsestype (`hypotes`) med obligatorisk +pålitelighetsnivå, og kan **aldri** anta nivået `hog` (høy) — +`niva: Exclude`; typesystemet forbyr det. I +kunderapporten er de uttrykkelig merket som ikke verifiserte. Kvalitetsporten +har en egen rad for dette. + +Formuleringen ved mislykket reproduksjon er *„kunde inte reproduceras under de +förhållanden som rådde"* („kunne ikke reproduseres under de forholdene som +rådde") — aldri „feilen konstatert" eller „ingen feil funnet". Det er kodet +både i projeksjonene og i orkesterets grunnprompt. + +### 1.2 En avkryssing er ikke bevis + +Hvert kontrollpunkt i hver metodikk bærer et **minimumskrav**: `matvarde` +(måleverdi) | `kommentar` (observasjon) | `foto`. En måling kan ikke merkes som +utført uten verdi; en fotokontroll ikke uten bilde. Vil teknikeren hoppe over +noe, kreves et **dokumentert unntak** med en begrunnelse fra en fast liste. + +Låst av testen *„varje kontroll kräver bevis — en kryssruta är inte evidens"*. + +### 1.3 Loggen er append-only, hele veien ned + +Det finnes ingen update- eller delete-operasjoner i API-et, og databasen har +triggere som avviser dem selv om noen omgår applikasjonen. En test søker aktivt +i serverkoden etter `update`/`delete` mot hendelsestabellen og feiler hvis de +dukker opp. + +Konsekvens: en feilaktig opplysning rettes *ved en ny hendelse*, aldri ved at +den gamle forsvinner. Historikken er det som gir grunnlaget verdi i en tvist. + +### 1.4 Terminologi + +I brukergrensesnittet og i kundekommunikasjonen brukes **systemet, analysen, +vurderingen, beslutningsstøtten** — ikke „KI", med mindre det er teknisk +nødvendig. Produktet beskrives som et *evidensbasert diagnosesystem* / +*intelligent beslutningsstøtte*. + +Grunnen er både kommersiell og erkjennelsesmessig: en verkstedkunde som hører +„KI", hører „gjetning". En forsikringssaksbehandler som leser „KI-vurdering" i +et grunnlag, vekter det lavere. + +### 1.5 Delingsgrensen er en tillatelsesliste + +Hva som får forlate organisasjonen, listes opp **positivt**, per nivå. En ny +hendelsestype er dermed intern til noen aktivt slipper den fram. En test krever +at hver type i domenemodellen er klassifisert — glemmes en, feiler bygget, i +stedet for at den lekker. + +--- + +## 2. Domenemodellen — hendelsesloggen + +`app/src/felsokning/domain.ts` (281 linjer). + +En sak er: identitet + metadata + en **ordnet liste av loggposter**. Hver post +bærer `id`, `tidpunkt` (tidspunkt), `tekniker` og en `handelse` (hendelse). + +### 2.1 Samtlige hendelsestyper + +| Type | Innhold | Rolle | +|---|---|---| +| `objekt_identifierat` | `objekt` (skiltnummer/VIN, merke, modell, motor …) | Hva saken gjelder | +| `arbetsorder_skannad` | `falt[]` + vedlegg | Avlest arbeidsordre (**intern**) | +| `felbeskrivning` | `text` | Kundens ord, ordrett | +| `arendetyp_satt` | `arendetyp` | Garanti / forsikring / kunde — velger regelpakke | +| `fraga_besvarad` | `stegId`, `frageId`, `fraga`, `svar` | Metodikkens symptomspørsmål | +| `kontroll_utford` | `stegId`, `kontrollId`, `text`, `resultat?`, `undantag?` | Verifisert sjekklistepunkt | +| `observation` | `text` | Hva teknikeren så — ikke hva hen tror | +| `matvarde` | `beskrivning`, `varde`, `enhet?` | Måling (E4) | +| `hypotes` | `text`, `niva` (aldri `hog`) | Arbeidshypotese (**intern**) | +| `foto` | `beskrivning` + vedlegg | Bildebevis (E2) | +| `video` | `beskrivning` + vedlegg | Levende bevis (E3) | +| `matarstallning` | `lage` (inn/ut), `varde` + vedlegg | Kilometerstand inn/ut | +| `historik_kontrollerad` | `kontrollerad`, `kommentar?` | Servicehistorikk | +| `reproducering` | `status` (ja/delvis/nej), `beskrivning` | **Symptomverifisering** | +| `felorsak` | strukturert årsaksanalyse | Årsak, kategori, grunnlag | +| `atgardsforslag` | forslag med begrunnelse | Hva som bør gjøres | +| `kundbeslut` | godkjent/avslått, kanal | Kundens beslutning | +| `atgard_utford` | utført arbeid | Hva som faktisk ble gjort | +| `kvalitetskontroll` | verifisering etter reparasjon | Er symptomet borte? | +| `kommentar` | `text` | Fri notat | +| `kategori_byte` | `kategori` | Tidsføring (**intern**) | +| `inaktivitet_forklarad` | `text`, `minuter` | Hvorfor det sto stille | +| `overlamning` | `fran`, `till?` | Skiftovergang | +| `ansvarig_satt` | `ansvarig` | Verksmesterens omfordeling (**intern**) | +| `ai_svar` | klassifiserte `rader[]`, modellnavn | Beslutningsstøttens svar (**intern**) | +| `export_skapad` | `format`, `version` | Eksporten logger seg selv | +| `arende_avslutat` | `signatur?` | Teknikerens signatur | + +### 2.2 Vedlegg er innholdsadresserte + +`foto`, `video`, `matarstallning` og `arbetsorder_skannad` er *snittyper* med +`Bilaga` (vedlegg): + +```ts +export interface Bilaga { + bilagaId?: string; + bilagaHash?: string; // SHA-256 + dataUrl?: string; // blir for alltid — loggen er append-only +} +``` + +Innholdet ligger utenfor loggen (S3 eller database), men **hashen ligger i +loggen**. Ved lesing verifiseres hashen; stemmer den ikke, returneres `409`. +Betydningen: byttes et bilde ut i lageret, oppdages det, og loggen kan bevise at +det opprinnelige bildet var et annet. + +`dataUrl` beholdes i typen fordi eldre poster har den innebygd — og loggen kan +ikke skrives om. + +--- + +## 3. Metodikkmotoren + +Siden siste endring er **motor og innhold atskilt**: + +- `metodik.ts` (171 linjer) — typer, valg av metodikk, utledning av neste steg. +- `metodiker.ts` (899 linjer) — de seksten metodikkene. + +Biblioteket kan vokse uten at motoren endres. + +### 3.1 Metodikkbiblioteket + +Steg-id-er er kode og forblir svenske. `symptom` = symptom, `visuell` = visuell +kontroll, `matningar` = målinger, `provkorning` = prøvekjøring, `sakerhet` = +sikkerhet, `avlasning` = avlesing, `glapp` = slark, `packning` = pakning. + +| id | Navn (i koden) | Område | Steg | Kontroller | +|---|---|---|---|---| +| `vibration` | Vibration under körning | Hjul og balansering | symptom → visuell → kontroller → provkorning | 19 | +| `bromsar` | Bromssystem | Understell | symptom → visuell → matningar → system | 14 | +| `styrning_fjadring` | Styrning och fjädring | Understell | 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 | Drivverk | 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øyvolt | **sakerhet** → symptom → avlasning → laddning | 16 | +| `diagnos_natverk` | Felkoder och kommunikation | Diagnose | symptom → grund → buss → koder | 10 | +| `lackage` | Läckage | Annet | symptom → visuell → metod | 8 | +| `missljud` | Missljud | Annet | symptom → inspelning → lokalisering | 7 | +| `adas` | Förarassistans och kalibrering | Diagnose | symptom → forutsattningar → kalibrering | 9 | +| `generisk` | Generell strukturerad felsökning | Annet | symptom → visuell → grundkontroller → funktionstest | 9 | + +Norske navn: vibrasjon under kjøring · bremsesystem · styring og fjæring · +elsystem og strømforsyning · start- og ladesystem · motorgang og effekt · +kjølesystem og overoppheting · girkasse og drivverk · eksos og utslipp · +klimaanlegg · høyvoltsystem (elbil/hybrid) · feilkoder og kommunikasjon · +lekkasje · ulyd · førerassistanse og kalibrering · generisk strukturert +feilsøking. + +### 3.2 Tre regler, låst av tester + +1. **Hver kontroll har et minimumskrav.** Måleverdi, foto eller observasjon. +2. **Hver metodikk begynner med å verifisere symptomet**, aldri med å utbedre. + Kundens ord blir først et verifisert symptom når det er reprodusert. +3. **Der arbeidet kan skade noen, kommer sikkerhetssteget først.** Bare + `sakerhet` får gå foran `symptom` — testen tillater akkurat det unntaket og + ingen andre. + +`hogvolt` er den eneste metodikken med et sikkerhetssteg. Det krever +kvalifikasjon, dokumentert uttatt servicebryter (foto), ventetid etter +produsentens anvisning, **målt spenningsfrihet** (en måleverdi — ikke et ja på +et spørsmål) og verneutstyr. Testen kontrollerer at steget ligger først, at +`spanningsfrihet` krever en måleverdi, og at beskrivelsen inneholder ordet +„livsfarlig". + +Grunnen er enkel: det arbeidet kan drepe noen. Der holder ikke en avkryssing. + +### 3.3 Valg av metodikk + +Tidligere en regex-kjede med tre utfall. Nå **poengsatt nøkkelordsmatching**: + +```ts +export function metodikPoang(metodik: Metodik, text: string): number +export function valjMetodik(felbeskrivning: string): Metodik +``` + +- Poeng = summen av lengden på nøkkelordene som treffer. Et lengre — mer + spesifikt — ord veier tyngre. `traktionsbatteri` (16) slår `batteri`. +- **Korte ord (≤3 tegn) matches som helt ord, lengre som ordstamme.** Ellers + ville `"ac"` truffet *acceleration*, og en vibrasjon havnet i klimaanlegget. +- Ved lik poengsum vinner den som står først i biblioteket → valget er + **stabilt** mellom kjøringer. +- Ingen treff → `generisk`. + +**En fallgruve som faktisk slo til under utviklingen:** nøkkelordene må være +*stammer*, ikke ferdigbøyde ord. Svensk bøyning kutter ofte en `e`: +*filter → filtret*, så `"partikelfilter"` treffer aldri teksten en tekniker +faktisk skriver. Det samme gjelder *regenerering → regenererar*, +*misständning → misständer*, *skrammel → skramlar*. Biblioteket bruker derfor +`partikelfilt`, `regenerer`, `misständ`, `skram`. + +*(For en lokalisering: dette er en egenskap ved svensk morfologi. Norsk har +samme klasse av problem i bestemt form og sammensetninger — `filter` blir +`filteret`, og `brems` opptrer inne i `håndbrems`, der en stammematching med +krav om ordstart ikke treffer. Et lokalisert nøkkelordssett må valideres mot +samme test, ikke oversettes ord for ord.)* + +**Valget er en spørsmålsrekkefølge, ikke en diagnose.** Det avgjør hvor +teknikeren begynner å lete, ikke hva som er galt. Treffer ingenting, er +`generisk` det ærlige svaret — strukturelt komplett og bedre enn en gjetning. + +### 3.4 Neste steg + +```ts +export function nastaSteg(arende: Arende, metodik: Metodik): NastaSteg +``` + +Rent utledet av loggen: første ubesvarte spørsmål, deretter første ikke-utførte +kontroll, i metodikkens rekkefølge. Ingen skjult tilstandsmaskin — samme logg +gir alltid samme neste steg. + +### 3.5 Om å „dekke alt" + +Det kan ikke loves ærlig, og dokumentasjonen påstår det ikke. Det som lar seg +gjøre, er å dekke kjøretøyets systemer systematisk og la `generisk` være et +strukturelt komplett sikkerhetsnett for det ingen har forutsett. + +--- + +## 4. ECM v2.0 — bevis- og regelmotoren + +`app/src/felsokning/ecm.ts` (749 linjer). Seks motorer: + +### 4.1 Evidence Engine + +Bevisnivåer, utledet av loggen: + +| Nivå | Betydning | +|---|---| +| E0 | Ingen grunnlag | +| E1 | Teknikerens observasjon | +| E2 | Foto | +| E3 | Video | +| E4 | Måleverdi | +| E5 | Diagnosedata / dokument | +| E6 | Flere uavhengige kilder | + +En saks bevisnivå er det høyeste grunnlaget bærer. Det vises i grensesnittet og +følger med eksporten. + +**Innholdshash:** `innehallsHash()` er en deterministisk FNV-1a over +bevisinnholdet. Samme grunnlag ⇒ samme hash, uansett maskin eller tidspunkt. Det +gjør eksporten verifiserbar i ettertid. + +### 4.2 Rule Engine + +- `ORSAKSKATEGORIER` — fast liste over årsakskategorier (gir sammenlignbar + statistikk på tvers av flåten). +- `UNDANTAGSORSAKER` — fast liste for „hvorfor dette ikke ble gjort". +- `UNDERLAGSKALLOR` — hva en konklusjon hviler på. +- `INGEN_ATGARD_ORSAKER`, `KUNDKANALER` (kundekanaler). +- `granskaAvvikelse()` — flagger tekst som er formulert som en konstatering uten + dekning. + +Faste lister framfor fritekst er et bevisst valg: fritekst kan ikke aggregeres, +og flåtestatistikken er en av produktets reelle verdier. + +### 4.3 Compliance Engine + +`ARENDETYPER` (sakstyper) bestemmer hvilken **regelpakke** som gjelder. En +garantisak krever claim-nummer og servicehistorikk; en forsikringssak krever +skadenummer og bildebevis; en kundesak krever mindre. Pakkene er data +(`ecm-regler.json`, kan serveres via `/api/ecm/regler`) — nye krav trenger ingen +ny utgivelse. + +### 4.4 Validation Engine — prediagnostikk + +Før feilsøkingen får begynne: objektidentifisering verifisert, arbeidsordre +lest inn, kjøretøyhistorikk kontrollert **eller begrunnet**, inngående +kilometerstand dokumentert, kundens feilbeskrivelse verifisert, tidlige +observasjoner håndtert. + +### 4.5 Completion Engine — kvalitetsporten + +Den største enkeltfunksjonen (`kvalitetsgrind`, ~240 linjer). Saken kan ikke +avsluttes før hver rad er grønn eller begrunnet: + +- Kjøretøyhistorikk kontrollert eller begrunnet +- Inngående/utgående kilometerstand dokumentert +- Kundens feilbeskrivelse verifisert +- **Symptomverifisering:** reprodusert, eller dokumentert som ikke + reproduserbar +- Årsaksanalyse dokumentert +- Utbedring dokumentert eller begrunnet +- Kundens svar på forslaget registrert +- Arbeid utført tross avslått forslag (der det er aktuelt) +- Kvalitetskontroll gjennomført — symptomet verifisert +- Metodikkens kontroller: bevis eller dokumentert unntak +- Foto finnes for fotokrevende kontroller +- Teknikerens konklusjon signert +- Hypoteser framstilt som ikke verifiserte +- Sakstypens regelpakke oppfylt (claim / skadenummer / kilometerstand / + historikk) + +### 4.6 Traceability Engine + +`sparbarhetspaket()` — hele beviskjeden i ett strukturert objekt: hva som +påstås, hva det hviler på, hvem som dokumenterte det og når. + +--- + +## 5. Symptomverifisering (SVP) + +Et eget prinsipp, fordi det er produktets skarpeste kant mot virkeligheten. + +**Kundens beskrivelse ≠ en konstatert feil.** + +1. Beskrivelsen dokumenteres **ordrett** (`felbeskrivning`). +2. Den presiseres gjennom metodikkens symptomspørsmål — *når, hvor, hvordan*, + aldri „hva er galt". +3. Den **reproduseres**, med tre mulige utfall: + - **Ja** — med dokumenterte forhold. + - **Delvis** — hva som lot seg og ikke lot seg gjenskape. + - **Nei** — obligatorisk begrunnelse. + +Rapportens beviskjede skiller fire ting som ellers blandes sammen: *kundens +beskrivelse*, *verifisert observasjon*, *årsaksanalyse* og *anbefalt +utbedring*. + +--- + +## 6. Klienten + +`app/src/felsokning/` + `app/src/pages/felsokning/`. + +| Modul | Linjer | Ansvar | +|---|---|---| +| `ArendeSida.tsx` | 2433 | Saksvisningen. Trekolonneoppsett på skrivebord | +| `metodiker.ts` | 899 | Metodikkbiblioteket | +| `ecm.ts` | 749 | Regel- og bevismotor | +| `NyttArende.tsx` | 497 | Saksoppretting, skanning av arbeidsordre | +| `Arendelista.tsx` | 399 | Dashbord: tellere, filtre | +| `projektioner.ts` | 356 | Alle visninger som rene funksjoner av loggen | +| `ai.ts` | 305 | Klientsiden av orkesteret, promptbygging, svartolkning | +| `plattform.ts` | 296 | API-klient mot den selvhostede plattformen | +| `DelatArendeVy.tsx` | 283 | Delt visning (kunde/partner/intern) | +| `domain.ts` | 281 | Hendelsestyper | +| `Installningar.tsx` | 281 | Organisasjon, brukere, integrasjoner | +| `Oversikt.tsx` | 238 | Verksmestervisning | +| `demo.ts` | 200 | Demosak med 1 t 35 min historikk | +| `ui.tsx` | 174 | Industrielt verkstedgrensesnitt | +| `metodik.ts` | 171 | Metodikkmotoren | +| `synk.ts` | 141 | Konfliktfri sammenfletting av hendelser | +| `ikoner.tsx` | 132 | Egne SVG-linjeikoner (ingen emojier) | +| `streckkod.ts` | 131 | Strekkode-/VIN-avlesing | +| `store.ts` | 106 | zustand-store | +| `bilagor.ts` | 96 | Opplasting + blob-URL-cache | +| `installningar.ts` | 86 | Organisasjonsinnstillinger | +| `Bilagevisning.tsx` | 69 | `` / `` | +| `Mikrofon.tsx` / `rost.ts` | 66 / 65 | Talegjenkjenning | +| `format.ts` | 48 | Fotoskalering m.m. | + +### 6.1 Projeksjonene + +``` +objekt · felbeskrivning · ansvarig · arendeidentitet · arAvslutat +lokalFordonshistorik · utfordaKontroller · ejKontrollerat +observationer · hypoteser · foton · videor +tidsfordelning · formateraTid · tillforlitlighet +brief · overlamningstext · tidsfordelningsRader · sistaAktivitet +``` + +Alle rene funksjoner av `Arende`. `ejKontrollerat` („ikke kontrollert") er den +som sparer mest tid i praksis: *det som gir dobbeltarbeid ved skiftbytte, er det +ingen har skrevet ned at ingen har gjort.* + +### 6.2 Designspråk + +Et ETKA-inspirert verkstedgrensesnitt: flate lysegrå flater (#ECECEC/#F7F7F7), +skarpe kanter, dyp marineblå som primærfarge, tett typografi (11–15 px), +rektangulære knapper (maks 4 px radius), verktøylinje ~44 px. Egne linjeikoner i +stedet for emojier; status som fargeprikker. + +Motivet: teknikeren har hansker på, står i et støyende rom og har ikke tid til +et luftig forbrukergrensesnitt. + +### 6.3 Lokal modus + +Uten innlogging arbeider appen mot `localStorage`. Metodikken veileder alene; +orkesteret er av. Status vises i sakshodet. Ved innlogging flettes lokale +hendelser sammen med serverens — konfliktfritt per hendelses-id, testet. + +--- + +## 7. Backend + +### 7.1 `services/plattform` (1210 linjer) + +Rent `node:http`. Eneste avhengighet er `pg`. + +**API-stier:** + +``` +GET /halsa helsesjekk +GET /api/openapi.yaml +POST /api/auth/registrera oppretter organisasjon + systemadministrator +POST /api/auth/logga-in innlogging +POST /api/auth/logga-ut-alla hever token_version → alle økter dør +GET /api/anvandare brukere; kun admin +POST /api/anvandare +POST /api/anvandare/{id}/avaktivera | /aktivera +GET /api/organisation +GET/PUT /api/organisation/installningar +GET /api/ecm/regler regelpakker som data +GET/POST /api/arenden saker +POST /api/arenden/{id}/handelser append-only +POST /api/arenden/{id}/bilagor vedlegg +GET /api/bilagor/{id} hash verifiseres ved lesing +GET /api/fordon/{identifierare}/historik +GET /api/statistik/felorsaker årsaksstatistikk +GET /api/oversikt verksmestervisning +GET /api/delad/{kod} delt, filtrert etter nivå +POST /api/delad/{kod}/beslut kundens beslutning uten innlogging +GET /api/delad/{kod}/bilagor/{id} nivåfiltrert +GET /api/integrationer/leverantorer +GET/PUT/DELETE /api/integrationer/{leverantor} +POST /api/integrationer/{leverantor}/uppslag +``` + +Det finnes ingen update- eller delete-stier mot saksdata. Med hensikt. + +**Sikkerhetsfunksjoner i tjenesten:** + +``` +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 linjer) + +Eier Claude-nøkkelen. Klienten har den **aldri**. Ruting per oppgave: + +| Oppgave | Modell | Effort | Vision | +|---|---|---|---| +| `handledning` (veiledning i sanntid) | `claude-sonnet-5` | medium | — | +| `granskning` (dybdegjennomgang) | `claude-opus-5` | **high** | — | +| `sammanfattning` (overleveringssammendrag) | `claude-sonnet-5` | low | — | +| `metodikval` (klassifisering) | `claude-haiku-4-5` | *(ingen — modellen tar ikke parameteren)* | — | +| `instrumentavlasning` (instrumentavlesing) | `claude-sonnet-5` | low | ✔ | +| `dokumenttolkning` (dokumenttolking) | `claude-sonnet-5` | low | ✔ | + +Alle svar er **skjemabundne** (`json_schema`). Grunnprompten koder reglene: +*„Finn aldri på fakta"*, *„aldri en hypotese som en konstatert feil"*, +*„KREVER verifisering"*. Ved avvist forespørsel skjer automatisk fallback til en +reservemodell. **Modellen som svarte, logges i hver `ai_svar`-hendelse** — +grunnlaget skal kunne granskes i ettertid. + +Metodikkatalogen bygges fra én liste (`METODIK_KATALOG`) som genererer både +skjemaets `enum` og promptens punktliste. En test sammenligner den med klientens +bibliotek: driver listene fra hverandre, returnerer klassifikatoren en id +klienten ikke kjenner, og valget ville falt *stilltiende* tilbake på generisk. +Nå feiler testen i stedet. + +### 7.3 `services/gemensam/observation.mjs` (166 linjer) + +Sporing og måleverdier **uten nye avhengigheter**. Tjenestene har bevisst nesten +ingen avhengigheter; å dra inn et OpenTelemetry-SDK med tretti pakker for å måle +fire ting ville vært feil avveining. I stedet to standarder som begge bare er +tekst på stdout: + +- **W3C Trace Context** — `traceparent` følger med gjennom hele kjeden + (klient → plattform → orkester). +- **CloudWatch EMF** — strukturert JSON som CloudWatch selv henter måleverdier + ut av. Ingen agent, ingen SDK, ingenting som kan slutte å virke i stillhet. + +``` +spårFrån(header) · traceparent(spor) · starta(navn, spor) + → .mät(delnavn, arbeid) · .ms() · .delar() +logga(nivå, melding, felt) · mätvärde(navn, verdi, enhet, dim, ekstra) +avsluta(span, { status, väg, extra }) +``` + +Det som måles, ble valgt ut fra ett spørsmål: *hva vil man vite klokka tre om +natta når noe er tregt?* Svaret er **hvor tiden ble av** — ikke hvor mange kall +som har skjedd. Derav `delar()` („deler"): databasen, modellkallet, +objektlagringen, kundens leverandør, med antall og sum per del i samme +logglinje. + +`mät()` måler også når arbeidet kaster — ellers ser feil ut som null tid. + +**Dimensjoner holdes bevisst få.** Hver unike kombinasjon er en egen tidsserie +som koster penger, så organisasjon, sak og spor-id må aldri bli dimensjoner — de +ligger som vanlige felt. Låst av en test som uttrykkelig forbyr `org`, +`organisation`, `arende`, `spårId`, `anvandare` blant dimensjonene. + +--- + +## 8. Sikkerhet + +| Beskyttelse | Implementasjon | +|---|---| +| **Multi-tenant-isolasjon** | Alle sakssøk er organisasjonsbundne (`arendeIOrg`); integrasjonstestet mot ekte Postgres | +| **Roller** | `tekniker` / `arbetsledare` / `admin` (tekniker / verksmester / admin), i JWT-en og som databasesjekk | +| **JWT-claims** | `{ sub, namn, org, roll, tv }` — `tv` = token_version | +| **Umiddelbar tilbakekalling** | `kontoGiltigt()` kontrollerer `aktiv` + `token_version` ved *hver* autentisert forespørsel. En gyldig signatur holder ikke | +| **Global utlogging** | `/api/auth/logga-ut-alla` hever `token_version` → alle utstedte tokens dør umiddelbart | +| **Passord** | bcrypt via `gen_salt('bf')` i databasen | +| **Innloggingssperre** | 15-minutters vindu; maks 10 forsøk per konto, 30 per kilde. Ryddes probabilistisk (2 % per skriving) for å slippe en cron-jobb | +| **Kryptering i hvile** | AES-256-GCM for kundenes integrasjonsopplysninger; hemmelige felt maskeres alltid i API-svar | +| **SSRF-forsvar** | `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 opp** før kallet; `.local`/`.internal` blokkert. Nødutgang `TILLAT_INTERNA_UPPSLAG` for testmiljø | +| **CORS** | `TILLATNA_URSPRUNG`-tillatelsesliste; opphavet settes én gang per forespørsel | +| **Vedleggsintegritet** | SHA-256 i loggen, verifiseres ved lesing → `409` ved avvik | +| **Append-only i databasen** | Triggere `before update or delete` på både `felsokning_handelser` og `felsokning_arenden` | +| **Pod-herding** | IMDSv2 obligatorisk, hoppgrense 1 → poder kan ikke låne nodens IAM-rolle | +| **IRSA** | Hver tjenestekonto har sin egen rolle; nodene deler ingen rettigheter | +| **Delte IAM-roller** | `bygg` (bygg) får publisere til ECR, men ikke røre klyngen; `drift` får røre klyngen, men ikke publisere images | +| **Nettverkspolicy** | Innkommende nektes som standard; eksplisitte `_ut`-regler (utgående) per tjeneste | +| **Databasetilgang** | Kun fra klyngens noder, i et subnettlag **uten rute ut** | + +### 8.1 Databaseskjema + +``` +organisationer · anvandare · inloggningsforsok +felsokning_arenden · felsokning_handelser +bilagor · bilage_innehall +delningar · integrationer +``` + +(organisasjoner · brukere · innloggingsforsøk · saker · hendelser · vedlegg · +vedleggsinnhold · delinger · integrasjoner) + +--- + +## 9. Live Share — delingsnivåer + +Tre nivåer, serverstyrt filtrering: + +| Nivå | Ser | +|---|---| +| **kund** (kunde) | 22 hendelsestyper: objekt, feilbeskrivelse, spørsmål, kontroller, observasjoner, måleverdier, foto, video, kommentarer, overleveringer, utbedringer, kvalitetskontroll … | +| **partner** | Alt kunden ser **+ `hypotes`** (merket som ikke verifisert) | +| **intern** | Full innsikt — ingen filtrering | + +**Aldri utenfor organisasjonen:** +`kategori_byte`, `hypotes`, `ai_svar`, `ansvarig_satt`, `arbetsorder_skannad`. + +```js +export function synligaTyper(niva) { + if (niva === "intern") return null; // full innsikt + return niva === "partner" ? DELBART_PARTNER : DELBART_KUND; +} +``` + +Lenker kan tilbakekalles. Den offentlige delingssiden +(`/felsokning/delad/:kod`) krever ingen innlogging og poller for +liveoppdatering. Kunden kan gi sitt svar direkte i visningen +(`POST /api/delad/{kod}/beslut`). + +**Hvorfor en tillatelsesliste:** en nektelsesliste må oppdateres hver gang en ny +hendelsestype legges til — og det er nettopp det man glemmer. En +tillatelsesliste gjør „glemt" til „intern", og det er det trygge utfallet. + +--- + +## 10. Merkespesifikke koblinger + +Leverandører er **data, ikke kode** (`integrationer.json`, kan monteres som +ConfigMap via `INTEGRATIONER_FIL`). Nye merker krever ingen ombygging. + +| id | Leverandør | +|---|---| +| `generisk_vin` | Vilkårlig VIN-tjeneste over HTTP | +| `vag_erwin` | Volkswagen Group erWin (VW, Audi, Škoda, SEAT) | +| `volvo_vida` | Volvo VIDA | +| `fordonsregister` | Skiltnummer → kjøretøy | + +Hver leverandør deklarerer sine felt, hvilke som er hemmelige (krypteres + +maskeres), og hvordan svaret mappes til domenets felt (`marke`, `modell`, +`arsmodell`, `motor`, `vaxellada` — merke, modell, årsmodell, motor, girkasse). + +Oppslag går gjennom SSRF-beskyttelsen — en kunde kan altså ikke peke en +„leverandør" mot klyngens interne adresser. + +--- + +## 11. Visual-first + +Kameraet **er** integrasjonslaget. Det som står på en skjerm eller et +instrument, fotograferes og tolkes i stedet for å integreres. + +- **Skanning av arbeidsordren** er hovedveien ved saksoppretting. Sonnet 5 + (vision) leser kunde-, kjøretøy- og verkstedopplysninger uansett oppsett, med + en konfidens per felt: + - 🟢 ≥95 % godkjennes automatisk + - 🟡 80–95 % merkes for gjennomlesing + - 🔴 <80 % krever aktiv bekreftelse + + Teknikeren går altså bare gjennom de usikre feltene. Visuell kontroll med + dokumentet ved siden av feltene; et klikk markerer omtrentlig posisjon. + +- **Instrumentavlesing** — foto av en diagnoseskjerm eller et instrument → + strukturerte verdier. + +Motivet er kommersielt: én integrasjon per verkstedsystem er én salgssyklus per +kunde. Et kamera virker mot alt, med én gang. + +--- + +## 12. Infrastruktur + +To Terraform-lag. Basen kjører sjelden, arbeidslastlaget ofte. + +### 12.1 `infra/aws` — basen (91 ressurser) + +| Område | Innhold | +|---|---| +| **Nettverk** | 1 VPC, 3 subnettlag × 3 soner: offentlig (kun ALB + NAT), privat (noder, ingen offentlige adresser), data (Aurora, **ingen vei ut i det hele tatt**). VPC-endepunkter: S3 (gateway); ECR, logger, Secrets Manager, STS, ELB (grensesnitt) → trafikken forlater aldri nettet | +| **Klynge** | EKS, arm64-noder, IRSA via OIDC-provider, IMDSv2 hoppgrense 1, alle fem control plane-logger på | +| **Data** | Aurora PostgreSQL Serverless v2, PITR ned til sekundet, KMS med egen nøkkel, `sslmode=require` | +| **Objektlagring** | S3 for vedlegg: SSE-KMS, offentlig tilgang blokkert, TLS obligatorisk, versjonering på. Plattformrollen får lese og skrive — **men aldri slette** | +| **Register** | ECR med **uforanderlige tagger** + sårbarhetsskanning | +| **Hemmeligheter** | Secrets Manager; kan bare leses av plattformrollen via IRSA | +| **Roller** | 9 IAM-roller, blant dem de delte `bygg` / `drift` | +| **Domene** | Route 53 + ACM med DNS-validering | +| **Observerbarhet** | 7 alarmer, 1 dashbord, 3 logggrupper, SNS-topic | + +**Alarmene** — få, men de som finnes, betyr noe. *En alarm ingen reagerer på, +lærer folk å ignorere alarmer.* + +- Aurora-CPU > 85 % i tre perioder (skaleringstaket kan være nådd) +- Aurora fri lokal lagring < 5 GiB +- **Backup-alder** — `treat_missing_data = "breaching"`. Mangler måleverdien, + finnes ingen sikkerhetskopiering. *En backup man tror finnes, er verre enn + ingen.* +- Færre noder enn ønsket minimum +- Svartid **p95** > 3 s i tre perioder — ikke gjennomsnittet, som skjuler at + hver tjuende tekniker venter urimelig lenge +- Serverfeil (sum > 5) +- Modellen avslår (tyder på uventet inndata, ikke på en driftsfeil) + +### 12.2 `infra/terraform` — arbeidslasten (35 ressurser) + +Leser basen via `terraform_remote_state`; gjentar 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 linjer) produserer et lesbart kart over hele driftsbildet: +`terraform output karta`. + +### 12.3 Git og CI — helt selvhostet + +En uttrykkelig produktbeslutning: **ingen GitHub i driftsveien.** Gitea + +Actions-runnere kjører på eget EKS. `.gitea/workflows/felsokning.yml`: + +| Jobb | Innhold | +|---|---| +| `test-och-bygg` | `vitest run`, `typkontroll`, eslint, `vite build` | +| `tjanster` | eslint på tjenestene, **integrasjonstest mot ekte Postgres**, `swagger-cli validate` | +| `terraform` | `fmt -check -recursive`, `init -backend=false`, `validate` | +| `publicera` | Kun på `main`, kun hvis det over gikk gjennom. Bygger tre images, tagger med commit-SHA-en, dytter til vårt eget ECR. **OIDC, ingen statisk nøkkel** | +| `driftsatt` | **Manuelt** (`workflow_dispatch`) med eksplisitt image-tagg | + +Idriftsetting er et eget steg med hensikt: *et image i registeret er ikke det +samme som et image som kjører.* Rollback = kjør igjen med forrige tagg. + +Klientens API-adresse bakes inn ved bygget (Vite), så imaget er miljøbundet. +Byggkonteksten for tjenestene er `felsokning/services`, slik at begge når den +felles observasjonsmodulen uten å duplisere den. + +--- + +## 13. Testing + +**120 vitest-tester** i 13 filer: + +| Fil | Antall | Låser | +|---|---|---| +| `ecm.test.ts` | 32 | Bevisnivåer, regelpakker, kvalitetsport, prediagnostikk | +| `metodiker.test.ts` | 14 | Bibliotekets struktur, metodikkvalg, katalogparitet med orkesteret | +| `projektioner.test.ts` | 13 | Visninger som rene funksjoner, neste steg | +| `ai.test.ts` | 11 | Orkesterparitet, OpenAPI ↔ server, append-only, promptregler | +| `observation.test.ts` | 10 | Sporing, EMF-format, **forbudte dimensjoner** | +| `bilagor.test.ts` | 9 | Innholdshash, SigV4, valg av lagringslag | +| `delning.test.ts` | 7 | Tillatelseslisten dekker hver hendelsestype | +| `integrationer.test.ts` | 7 | Leverandøroppslag, SSRF-vern | +| `demo.test.ts` | 4 | Demosaken er rik nok til å vises | +| `installningar.test.ts` | 4 | Organisasjonsinnstillinger | +| `streckkod.test.ts` | 4 | VIN/strekkode | +| `synk.test.ts` | 4 | Konfliktfri sammenfletting | +| `example.test.ts` | 1 | — | + +**Utover enhetstestene:** + +- `integrationstest.sh` — hele flyten mot **ekte Postgres**: organisasjoner, + roller, append-only-triggeren, isolasjon, deling, vedlegg. +- SigV4 **kryssverifisert bit for bit mot botocore** (`sigv4-referens.json`). +- `swagger-cli validate` på OpenAPI-spesifikasjonen. +- Paritetstester mellom spesifikasjon ↔ server, klient ↔ orkester (× 2 kopier), + domenemodell ↔ delingsliste. + +### 13.1 Verifiseringssløyfen før hver commit + +``` +npx vitest run # 120 tester +npm run typkontroll # tsc --noEmit (vite build typesjekker IKKE) +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` kom til etter at to latente krasj (`TextFalt` og +`UNDANTAGSORSAKER` brukt uten import) hadde sluppet forbi `vite build` — som +transpilerer, men ikke typesjekker. + +--- + +## 14. Repositoriestruktur + +`main` er et npm-workspaces-monorepo som heter **Semantika** og eier roten. Da +de to produktene ble slått sammen, ble begge beholdt, med verktøykjedene +**atskilt per tre** — ikke ved å svekke noens regler. + +``` +/ Semantika (workspaces-rot) +├── apps/mobile/ Semantika +├── services/api/ Semantika +├── infra/ Semantika +├── .github/workflows/ci.yml Semantika — urørt +│ +├── .gitea/workflows/felsokning.yml Guidad Felsökning (egen CI) +└── felsokning/ + ├── app/ klient (egen package.json, eslint, vitest, tsconfig) + ├── services/ + │ ├── plattform/ + │ ├── ai-orkester/ + │ └── gemensam/ observation.mjs (felles) + ├── infra/ + │ ├── aws/ basen, 91 ressurser + │ ├── terraform/ arbeidslasten, 35 ressurser + │ └── postgres-init.sql + ├── docs/ + └── supabase/ migrasjoner + edge-funksjon (eldre vei) +``` + +**To driftsveier finnes parallelt:** den selvhostede AWS-stakken (den som +gjelder) og en eldre Supabase-basert (edge-funksjonen `felsokning-ai`, +migrasjoner). Orkesteret finnes derfor i **to kopier**, holdt synkrone av +tester. + +--- + +## 15. Dokumentasjon i repositoriet + +``` +docs/VISION.md produktvisjonen +docs/MASTER-PROMPT.md grunninstruksjonen +docs/MVP.md hva som er bygget, funksjon for funksjon +docs/DEMO.md demomanus for framvisning +docs/DRIFT.md drift +docs/SYSTEMBESKRIVNING.md den svenske originalen av dette dokumentet +docs/SYSTEMBESKRIVNING.en.md engelsk +docs/SYSTEMBESKRIVNING.de.md tysk +docs/SYSTEMBESKRIVNING.da.md dansk +docs/SYSTEMBESKRIVNING.no.md dette dokumentet +docs/exempel/vibration-vid-88-km-h.md gjennomgående eksempel +docs/moduler/ åtte moduldokumenter +``` + +--- + +## 16. Designbeslutninger og deres begrunnelser + +Samlet fordi begrunnelsen ofte er viktigere enn beslutningen. + +| Beslutning | Begrunnelse | +|---|---| +| Event sourcing | Grunnlaget må holde i en tvist. Historikken *er* verdien | +| Append-only også i databasen | Applikasjonslaget kan omgås; triggeren kan ikke | +| Tillatelsesliste for deling | En glemt hendelsestype blir intern, ikke lekket | +| Faste årsakskategorier | Fritekst kan ikke aggregeres; flåtestatistikken er en verdi | +| Servereid modellruting | Klienten må aldri ha nøkkelen, og ruting må kunne endres uten en utgivelse | +| Modell logget per svar | Grunnlaget må kunne granskes i ettertid | +| Unngå ordet „KI" | Kunden hører „gjetning"; saksbehandleren vekter det lavere | +| Visual-first | Én integrasjon per verkstedsystem = én salgssyklus per kunde. Kameraet virker med én gang | +| Leverandører som data | Et nytt merke bør ikke kreve en utgivelse | +| Egen observerbarhet, null avhengigheter | 30 pakker for å måle 4 ting er feil avveining | +| Få EMF-dimensjoner | Hver kombinasjon er en betalt tidsserie | +| p95 i alarmen, ikke gjennomsnittet | Gjennomsnittet skjuler at hver tjuende tekniker venter | +| Alarm på *manglende* backupdata | En backup man tror finnes, er verre enn ingen | +| Delte bygg-/driftsroller | Et kompromittert bygg må ikke kunne røre klyngen | +| Manuell idriftsetting | Et image i registeret ≠ et image som kjører | +| Uforanderlige ECR-tagger | En tagg må bety det samme i morgen | +| Innholdsadresserte vedlegg | Et utbyttet bilde må oppdages, ikke antas | +| Hashverifisering ved lesing | Det holder ikke å hashe ved skriving | +| S3-rollen får ikke slette | Append-only må gjelde lagringen også | +| Motor atskilt fra innhold | Biblioteket vokser; motoren skal ikke måtte endres | +| Poengsatt metodikkvalg | Regex-kjeder blir ugjennomtrengelige ved 16 alternativer | +| Nøkkelord som ordstammer | Svensk bøyning kutter en `e` — ellers treffer ingenting | +| `generisk` som fallback | Et ærlig „vi vet ikke" slår en gjetning | +| Sikkerhetssteg først i høyvolt | Det arbeidet kan drepe | +| Svensk i koden | Domenet er svensk; oversetting fram og tilbake taper presisjon | + +--- + +## 17. Kjente begrensninger og åpne punkter + +Uttrykkelig ikke ferdig: + +- **To orkesterkopier** (Supabase-edge-funksjon + K8s-tjeneste) holdes + synkrone av tester, ikke av felles kode. Supabase-veien er den eldre og bør + fases ut. +- **`ArendeSida.tsx` er på 2433 linjer.** Den virker, men er filen som koster + mest å endre i. +- **`terraform validate` kan ikke kjøres lokalt** i utviklingsmiljøet (den + utgående nettverkspolicyen blokkerer nedlasting av providere). Erstattet av + `terraform fmt` pluss en egen statisk referansekontroll; den ekte valideringen + skjer i CI. +- **Claude-nøkkelen fylles inn for hånd** etter første `apply` — den ligger + bevisst ikke i Terraform-state. +- **`postgres-init.sql` kjøres manuelt** mot databasen etter at basen er + anvendt. +- **Ingen automatisk gjenopprettingstest av backupen.** Alarmen sier at det + *tas* backup, ikke at den *kan gjenopprettes*. +- **Metodikkbiblioteket dekker ikke alt** — og påstår det ikke. `generisk` er + sikkerhetsnettet. +- Rotens eslint har 20 allerede eksisterende feil i Semantikas egne sider + (`no-explicit-any`) som ikke gjelder Guidad Felsökning. + +--- + +## 18. Ordliste + +Venstre kolonne er begrepet slik det står i kode og grensesnitt. + +| Svensk | Norsk | +|---|---| +| Ärende | Sak — én feilsøkingsoppgave | +| Händelse / loggpost | Hendelse / loggpost — udelelig post i append-only-loggen | +| Metodik | Metodikk — strukturert feilsøkingsflyt | +| Steg | Steg — fase i en metodikk (symptom, visuell kontroll, målinger …) | +| Kontroll | Kontroll — enkelt sjekklistepunkt med minimumskrav | +| Krav | Krav — `matvarde` / `kommentar` / `foto` | +| Undantag | Unntak — dokumentert grunn til at en kontroll ble hoppet over | +| Brief | Brief — sammenstilt saksbilde; en projeksjon | +| Kvalitetsgrind | Kvalitetsport — regelsett som må passeres før avslutning | +| Evidensnivå | Bevisnivå — E0–E6, grunnlagets bevisverdi | +| Reproducering | Reproduksjon — symptomverifisering: ja / delvis / nei | +| Felorsak | Feilårsak — strukturert analyse med kategori og grunnlag | +| Delning | Deling — ekstern lenke med rettighetsnivå | +| Orkester | Orkester — tjenesten som eier modellrutingen | +| Spann / spår | Span / spor — tidsmåling henholdsvis W3C-sporing | +| Bilaga | Vedlegg — innholdsadressert foto/video/dokument | +| Tekniker | Tekniker, mekaniker | +| Arbetsledare | Verksmester, arbeidsleder | +| Fordon | Kjøretøy | +| Mätvärde | Måleverdi | +| Felbeskrivning | Feilbeskrivelse (kundens ord) | +| Arbetsorder | Arbeidsordre | +| Mätarställning | Kilometerstand | +| Överlämning | Overlevering | +| Åtgärd | Utbedring, tiltak | +| Kundbeslut | Kundens beslutning | +| Säkerhet | Sikkerhet | +| Högvolt | Høyvolt |