From 3c5752898b73bf875c4ea504801eb538c37bca98 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 15:12:28 +0000 Subject: [PATCH] =?UTF-8?q?Systembeskrivningen=20p=C3=A5=20engelska,=20tys?= =?UTF-8?q?ka,=20danska=20och=20norska?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fyra översättningar av hela dokumentet — inte sammanfattningar. Alla 48 numrerade avsnitt finns i varje språkversion, verifierat maskinellt. Kodidentifierare är inte översatta. Händelsetyper, funktionsnamn, fältnamn, filsökvägar och konfigurationsnycklar är svenska i själva koden; ett dokument som döper om dem till engelska blir oanvändbart mot repot. De står därför ordagrant, med förklaring på målspråket där betydelsen inte är uppenbar. Ett skript kontrollerar att 38 sådana identifierare överlevde översättningen i alla fem filer. Varje översättning säger i ingressen att den svenska versionen är källan och gäller vid avvikelse. Två dokument som påstår sig vara lika auktoritativa blir i praktiken två sanningar. Avsnittet om nyckelordsstammar har fått ett tillägg per språk: att "partikelfilter" inte matchar "partikelfiltret" är en egenskap hos svensk böjning, och tyska sammansättningar respektive dansk och norsk bestämd form ger samma klass av problem. Ett lokaliserat nyckelordsset måste valideras mot samma test, inte översättas ord för ord. Ordlistan går från svenska till målspråket och är därmed mer användbar i översättning än i originalet — den blir nyckeln mellan koden och läsaren. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt --- felsokning/docs/MVP.md | 2 +- felsokning/docs/SYSTEMBESKRIVNING.da.md | 933 +++++++++++++++++++++++ felsokning/docs/SYSTEMBESKRIVNING.de.md | 966 ++++++++++++++++++++++++ felsokning/docs/SYSTEMBESKRIVNING.en.md | 939 +++++++++++++++++++++++ felsokning/docs/SYSTEMBESKRIVNING.md | 6 +- felsokning/docs/SYSTEMBESKRIVNING.no.md | 930 +++++++++++++++++++++++ 6 files changed, 3774 insertions(+), 2 deletions(-) create mode 100644 felsokning/docs/SYSTEMBESKRIVNING.da.md create mode 100644 felsokning/docs/SYSTEMBESKRIVNING.de.md create mode 100644 felsokning/docs/SYSTEMBESKRIVNING.en.md create mode 100644 felsokning/docs/SYSTEMBESKRIVNING.no.md 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 |