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} +

+