From 01189ae42a6f75ebcc5ae709b195f2db625575ae Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 15:20:27 +0000 Subject: [PATCH] =?UTF-8?q?ALVA-RULE-200:=20ett=20=C3=A4rende=20st=C3=A4ng?= =?UTF-8?q?s=20aldrig=20utan=20ett=20varf=C3=B6r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kravet är enkelt att formulera och lätt att bygga fel. En obligatorisk fritextruta blir "klart" på tredje ärendet, och då har vi bara gjort dokumentationen långsammare utan att göra den bättre. Därför fyra frågor med olika adressat i stället för ett fält: MOTIVERING Varför följer slutsatsen av evidensen? Detta är raden som saknas i varje verkstadsprotokoll. Underlaget säger vad som mättes, slutsatsen vad som är fel — ingenting säger varför det ena medför det andra, och det är precis det steget en försäkringsbedömare granskar. UTESLUTET Vad övervägdes och varför föll det bort? ÅTGÄRDSVAL Varför denna åtgärd och inte en annan? KVARSTÅENDE Vad är fortfarande osäkert? Får vara "inget" — men aktivt. Kvalitetsgranskningen är riktad mot hur någon med bråttom faktiskt skriver: en katalog över icke-svar (klart, åtgärdat, trasig, se ovan, vet ej), minsta längd, och kravet att texten bär ett orsakssamband eller refererar konkret evidens. Den som skriver "12,4 V vid stift 14" hänvisar till en mätning utan att säga ordet — regeln får inte tvinga fram ett språkbruk som inte är teknikerns. Den regel som gör underlaget användbart för ett försäkringsbolag härleds ur loggen: en hypotes som dokumenterats och inte blivit slutsatsen MÅSTE bemötas. Utan den är en felsökning en gissning som råkade stämma. Den ärliga vägen finns: orsaken kunde inte fastställas är ett giltigt utfall, ofta mer användbart än en påhittad orsak — men varför den inte kunde det är fortfarande ett varför. Slutsatsen är kunddelbar. Den besvarar "varför kostade det här vad det kostade" och är den enda rad en bedömare behöver. Att bygga funktionen och sedan hålla den intern vore att bygga den förgäves. I gränssnittet granskas fälten medan man skriver, inte efter Spara, och obemötta hypoteser listas — teknikern ska aldrig behöva gissa vad som fattas. Det är skillnaden mellan ett krav som respekteras och ett som kringgås. --- Typkontrollen avslöjade under arbetet en riktig bugg i mitt eget grindarbete: schemat och grinden skrevs mot antagna fältnamn i stället för mot domänmodellen. atgard_utford heter beskrivning och utford, inte text. Följden var värre än ett typfel — grinden såg utförd åtgärd som utebliven och krävde därför aldrig kundbesked eller kvalitetskontroll. Det syntes inte, eftersom testfixturerna hade samma antagande. Rättat i schema, grind och fixturer, och låst av två nya test: varje fält i schemat måste finnas i domänmodellen, och varje händelse demoärendet producerar måste passera serverns validering. Ett schema som avvisar riktig trafik är värre än inget schema. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt --- felsokning/app/src/felsokning/Slutsats.tsx | 205 +++++++++++++++ .../src/felsokning/__tests__/grind.test.ts | 59 ++++- .../src/felsokning/__tests__/slutsats.test.ts | 180 +++++++++++++ felsokning/app/src/felsokning/domain.ts | 24 ++ .../app/src/pages/felsokning/ArendeSida.tsx | 6 + felsokning/services/gemensam/grind.mjs | 19 +- felsokning/services/gemensam/handelser.mjs | 24 +- felsokning/services/gemensam/motivering.mjs | 242 ++++++++++++++++++ felsokning/services/plattform/motivering.mjs | 1 + felsokning/services/plattform/server.mjs | 11 + 10 files changed, 759 insertions(+), 12 deletions(-) create mode 100644 felsokning/app/src/felsokning/Slutsats.tsx create mode 100644 felsokning/app/src/felsokning/__tests__/slutsats.test.ts create mode 100644 felsokning/services/gemensam/motivering.mjs create mode 120000 felsokning/services/plattform/motivering.mjs diff --git a/felsokning/app/src/felsokning/Slutsats.tsx b/felsokning/app/src/felsokning/Slutsats.tsx new file mode 100644 index 0000000..6b16dd9 --- /dev/null +++ b/felsokning/app/src/felsokning/Slutsats.tsx @@ -0,0 +1,205 @@ +// ALVA-RULE-200 · Slutsatspanelen. +// +// Här möter teknikern kravet, och här avgörs om regeln blir älskad eller +// hatad. Tre val styr det: +// +// Fyra fält, inte ett. En stor ruta ger "klart". Fyra namngivna +// frågor med olika adressat ger fyra svar, +// eftersom var och en är konkret nog att +// besvara utan att först fundera på vad som +// efterfrågas. +// +// Granskning medan man Bristerna visas under fältet direkt, inte som +// skriver. ett felmeddelande efter Spara. Att bli nekad +// efter att ha skrivit klart är det som gör +// obligatoriska fält förhatliga. +// +// Hypoteserna listas. Systemet visar vilka misstankar som ligger i +// loggen och behöver bemötas. Teknikern ska +// aldrig behöva gissa vad som fattas. + +import { useMemo, useState } from "react"; +import type { Arende, Handelse } from "./domain"; +import { granskaSlutsats, obemottaHypoteser } from "../../../services/gemensam/motivering.mjs"; +import { FARG, Etikett, Knapp, Statusmärke } from "@/alva/komponenter"; + +interface Brist { + falt: string; + text: string; +} + +const FALT = [ + { + id: "motivering" as const, + etikett: "Motivering", + fraga: "Varför följer slutsatsen av underlaget?", + hjalp: "Knyt mätvärden, foton och kontroller till slutsatsen. Vad i evidensen gör att just detta följer?", + rader: 4, + }, + { + id: "uteslutet" as const, + etikett: "Uteslutna alternativ", + fraga: "Vad övervägdes, och varför föll det bort?", + hjalp: "En felsökning utan uteslutna alternativ är en gissning som råkade stämma.", + rader: 3, + }, + { + id: "atgardsval" as const, + etikett: "Val av åtgärd", + fraga: "Varför denna åtgärd och inte en annan?", + hjalp: "Byte eller justering? Hela enheten eller en del? Skälet läses av den som granskar kostnaden.", + rader: 2, + }, + { + id: "kvarstaende" as const, + etikett: "Kvarstående osäkerhet", + fraga: "Vad är fortfarande osäkert?", + hjalp: 'Får vara "inget" — men det måste sägas aktivt.', + rader: 2, + }, +]; + +export function Slutsatspanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) { + const handelser = useMemo(() => arende.handelser.map((p) => p.handelse), [arende]); + const befintlig = useMemo( + () => [...handelser].reverse().find((h) => h.typ === "slutsats"), + [handelser], + ) as Extract | undefined; + + const [fastställd, setFastställd] = useState(befintlig?.orsakFastställd !== false); + const [utkast, setUtkast] = useState({ + motivering: befintlig?.motivering ?? "", + uteslutet: befintlig?.uteslutet ?? "", + atgardsval: befintlig?.atgardsval ?? "", + kvarstaende: befintlig?.kvarstaende ?? "", + }); + + const förslag = { typ: "slutsats" as const, ...utkast, orsakFastställd: fastställd }; + const brister: Brist[] = useMemo( + () => granskaSlutsats(förslag, handelser), + // eslint-disable-next-line react-hooks/exhaustive-deps + [utkast, fastställd, handelser], + ); + const obemötta: string[] = useMemo(() => obemottaHypoteser(handelser, förslag), [handelser, förslag]); + + const brist = (falt: string) => brister.find((b) => b.falt === falt && !b.text.includes("bemöts inte")); + const klar = brister.length === 0; + + const arbeteUtfört = handelser.some((h) => h.typ === "atgard_utford" && h.utford); + + return ( +
+
+

+ Closing statement +

+
+ + + ALVA-RULE-200 + +
+
+ +
+

+ Underlaget visar vad som mättes. Slutsatsen visar vad som är fel. Det här är raden som säger varför det + ena följer av det andra — och den enda en garantihandläggare eller försäkringsbedömare behöver granska. +

+ + {/* Den ärliga vägen. Att orsaken inte kunnat fastställas är ett + giltigt utfall, och ofta mer användbart än en påhittad orsak. + Men varför den inte kunde det är fortfarande ett varför. */} +
+ {[ + { värde: true, text: "Orsak fastställd" }, + { värde: false, text: "Orsak kunde inte fastställas" }, + ].map((val) => ( + + ))} +
+ + {obemötta.length > 0 && ( +
+ Hypoteser i loggen som behöver bemötas +
    + {obemötta.map((text) => ( +
  • + + {text} +
  • + ))} +
+

+ Ange under Uteslutna alternativ varför de föll bort — eller varför de kvarstår. +

+
+ )} + + {FALT.filter((f) => f.id !== "atgardsval" || arbeteUtfört).map((f) => { + const fel = brist(f.id); + const etikett = f.id === "motivering" && !fastställd ? "Skäl till att orsaken inte fastställts" : f.etikett; + return ( +
+ +

+ {f.fraga} +

+