From cd6b2caca55126c3c3b6dc42225c52274e4df3d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 15:55:31 +0000 Subject: [PATCH] Sammanfattning och slutsats i rapport och Live Share; API dokumenterat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sammanfattningen ligger först i både kundrapporten och Live Share. Mottagaren är oftast inte tekniker — det är kunden, en försäkringshandläggare eller en flottansvarig — och de ska få bilden på fem sekunder och sedan kunna gå djupare, inte tvärtom. I Live Share härleds den ur det NIVÅFILTRERADE underlaget. Det betyder att sammanfattningen aldrig kan avslöja något som delningsnivån döljer: filtret ligger före projektionen, inte efter. Slutsatsen visas som ett eget avsnitt före underlaget. En handläggare läser skälet först och kontrollerar det sedan — den ordningen är hela poängen med att fältet finns. Sju nya vägar dokumenterade i OpenAPI och låsta av paritetstestet: sammanfattning, protokollinläsning, statistik, integrationskategorier, prenumerationer, radering och mätdon. Ett API som inte är dokumenterat är inte ett API någon kan koppla in sig mot — och specen valideras maskinellt så dokumentationen inte kan glida från servern. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt --- .../app/src/felsokning/DelatArendeVy.tsx | 36 +++ .../app/src/felsokning/__tests__/ai.test.ts | 9 + .../app/src/pages/felsokning/ArendeSida.tsx | 28 +++ felsokning/services/plattform/openapi.yaml | 234 ++++++++++++++++++ 4 files changed, 307 insertions(+) diff --git a/felsokning/app/src/felsokning/DelatArendeVy.tsx b/felsokning/app/src/felsokning/DelatArendeVy.tsx index d7ba0d5..dd6bf9a 100644 --- a/felsokning/app/src/felsokning/DelatArendeVy.tsx +++ b/felsokning/app/src/felsokning/DelatArendeVy.tsx @@ -10,6 +10,7 @@ import { KUNDBESLUT_LABEL, handelseRubrik } from "./domain"; import { arAvslutat, arendeidentitet, brief, foton, tidsfordelningsRader, videor } from "./projektioner"; import { metodikForArende } from "./store"; import { Bild, Klipp } from "./Bilagevisning"; +import { sammanfatta } from "../../../services/gemensam/sammanfattning.mjs"; import { tidDatum, tidKlockslag } from "./format"; import { FelsokningSkal, Panel, StorKnapp } from "./ui"; import { IkonCheck, IkonKlocka, IkonUppdatera } from "./ikoner"; @@ -157,6 +158,41 @@ export function DelatArendeVy({ ärende vyn avser. Härledd ur det nivåfiltrerade underlaget. */} + {/* ALVA-PROC-0030 · Sammanfattningen först. Mottagaren är oftast + inte tekniker: kunden, en försäkringshandläggare, en + flottansvarig. De ska få bilden på fem sekunder och sedan kunna + gå djupare — inte tvärtom. Texten härleds ur det nivåfiltrerade + underlaget, så den kan aldrig avslöja något som nivån döljer. */} + +

{sammanfatta(arende).text}

+
+ + {/* ALVA-RULE-200 · Teknikerns varför. Den enda rad en + försäkringsbedömare egentligen behöver, kunddelbar med avsikt. */} + {(() => { + const post = [...arende.handelser].reverse().find((p) => p.handelse.typ === "slutsats"); + if (!post || post.handelse.typ !== "slutsats") return null; + const h = post.handelse; + const rader: [string, string][] = [ + [h.orsakFastställd === false ? "Skäl till att orsaken inte fastställts" : "Motivering", h.motivering], + ["Uteslutna alternativ", h.uteslutet], + ...(h.atgardsval ? ([["Val av åtgärd", h.atgardsval]] as [string, string][]) : []), + ["Kvarstående osäkerhet", h.kvarstaende], + ]; + return ( + +
+ {rader.map(([etikett, text]) => ( +
+
{etikett}
+
{text}
+
+ ))} +
+
+ ); + })()} +

{notis}

diff --git a/felsokning/app/src/felsokning/__tests__/ai.test.ts b/felsokning/app/src/felsokning/__tests__/ai.test.ts index 060b4d0..c05d9af 100644 --- a/felsokning/app/src/felsokning/__tests__/ai.test.ts +++ b/felsokning/app/src/felsokning/__tests__/ai.test.ts @@ -101,6 +101,15 @@ describe("AI-orkestern", () => { ["/api/arenden/{arendeId}/bilagor", "laddaUppVag"], ["/api/bilagor/{bilagaId}", "bilagaVag"], ["/api/delad/{delningskod}/bilagor/{bilagaId}", "delatBilaga"], + // ALVA: nya vägar. Ett API som inte är dokumenterat är inte ett API + // någon kan koppla in sig mot. + ["/api/arenden/{arendeId}/sammanfattning", "sammanfattningVag"], + ["/api/arenden/{arendeId}/protokoll", "protokollVag"], + ["/api/statistik/oversikt", "/api/statistik/oversikt"], + ["/api/integration/kategorier", "/api/integration/kategorier"], + ["/api/integration/prenumerationer", "/api/integration/prenumerationer"], + ["/api/radering", "/api/radering"], + ["/api/matdon", "/api/matdon"], ]; for (const [iSpec, iServer] of vagar) { expect(spec).toContain(`${iSpec}:`); diff --git a/felsokning/app/src/pages/felsokning/ArendeSida.tsx b/felsokning/app/src/pages/felsokning/ArendeSida.tsx index 898bf9b..829214e 100644 --- a/felsokning/app/src/pages/felsokning/ArendeSida.tsx +++ b/felsokning/app/src/pages/felsokning/ArendeSida.tsx @@ -7,6 +7,7 @@ import { nastaSteg } from "@/felsokning/metodik"; import { fasFor, klaraFaser } from "../../../../services/gemensam/faser.mjs"; import { Fasrad } from "@/alva/komponenter"; import { Slutsatspanel } from "@/felsokning/Slutsats"; +import { sammanfatta } from "../../../../services/gemensam/sammanfattning.mjs"; import { arendebeteckning, fasDefinition } from "@/alva/system"; import { arAvslutat, @@ -2317,6 +2318,33 @@ function RapportFlik({ Delningslänken kräver att ärendet är synkat mot molnet (inloggad användare).

+ {/* ALVA-PROC-0030 · Sammanfattningen först. Den som öppnar + rapporten — kund, handläggare, nästa tekniker — ska få bilden + på fem sekunder och sedan kunna gå djupare. Texten är härledd + ur loggen, så den kan inte säga något som inte står där. */} + +

{sammanfatta(arende).text}

+

ALVA-PROC-0030 · härledd ur händelseloggen

+
+ + {/* ALVA-RULE-200 · Teknikerns varför. Placerad före underlaget: + en handläggare läser skälet först och kontrollerar det sedan. */} + {(() => { + const s = [...arende.handelser].reverse().find((p) => p.handelse.typ === "slutsats"); + if (!s || s.handelse.typ !== "slutsats") return null; + const h = s.handelse; + return ( + + {identitetsRader([ + [h.orsakFastställd === false ? "Skäl till att orsaken inte fastställts" : "Motivering", h.motivering], + ["Uteslutna alternativ", h.uteslutet], + ["Val av åtgärd", h.atgardsval ?? "—"], + ["Kvarstående osäkerhet", h.kvarstaende], + ])} + + ); + })()} + {/* Rapportens första sida: ärendeidentiteten — registrerad en gång, återanvänd här automatiskt. */} diff --git a/felsokning/services/plattform/openapi.yaml b/felsokning/services/plattform/openapi.yaml index 2969251..6639e0b 100644 --- a/felsokning/services/plattform/openapi.yaml +++ b/felsokning/services/plattform/openapi.yaml @@ -952,6 +952,240 @@ paths: "502": { $ref: "#/components/responses/Fel" } "503": { $ref: "#/components/responses/Fel" } + /api/arenden/{arendeId}/sammanfattning: + get: + summary: Härledd sammanfattning av ärendet (ALVA-PROC-0030) + description: > + Några få meningar som ger vem som helst en bild av ärendet. + Sammanfattningen är HÄRLEDD ur händelseloggen, inte genererad: + samma ärende ger samma text i dag och om två år, den innehåller + inget som inte står i loggen, och den säger uttryckligen när något + saknas i stället för att utelämna det. Avsedd för handläggare, + kundtjänst och integrationer. + security: [{ bearerAuth: [] }] + parameters: + - { name: arendeId, in: path, required: true, schema: { type: string } } + responses: + "200": + description: Sammanfattning + content: + application/json: + schema: + type: object + properties: + version: { type: string, example: ALVA-PROC-0030 } + text: { type: string } + meningar: { type: array, items: { type: string } } + enrading: { type: string } + fullstandig: + type: boolean + description: Sant när inget obligatoriskt underlag saknas. + saknas: { type: array, items: { type: string } } + "404": { description: Ärendet är inte tillgängligt } + + /api/arenden/{arendeId}/protokoll: + post: + summary: Läs in ett diagnosprotokoll som evidens (ALVA-PROC-0020) + description: > + Avläsningar blir händelser i loggen, inte en bilaga. Härkomsten + bevaras i varje post, så ett värde som kommit utifrån aldrig ser ut + som något teknikern själv mätt. Lämnas instrumentets identitet + behålls den; utan den graderas värdet E1 i stället för E4. + Profilen beskriver var värdena ligger i leverantörens format — + ALVA antar aldrig ett visst leverantörsformat. + security: [{ bearerAuth: [] }] + parameters: + - { name: arendeId, in: path, required: true, schema: { type: string } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [kalla, profil, protokoll] + properties: + kalla: + type: string + description: Vad som producerade protokollet. Följer med varje händelse. + example: Diagnosinstrument, plats 3 + profil: + type: object + properties: + felkoder: + type: object + properties: + vag: { type: string, example: dtcs } + kod: { type: string, example: code } + text: { type: string, example: description } + matvarden: + type: object + properties: + vag: { type: string, example: liveData } + beskrivning: { type: string, example: name } + varde: { type: string, example: value } + enhet: { type: string, example: unit } + instrumentId: { type: string, example: toolSerial } + protokoll: + type: object + description: Leverantörens nyttolast, oförändrad. + responses: + "200": + description: Antal skrivna händelser + content: + application/json: + schema: + type: object + properties: + handelser: { type: integer } + kalla: { type: string } + "422": { description: Profilen gav inga händelser ur protokollet } + + /api/statistik/oversikt: + get: + summary: Driftmått för organisationen (ALVA-REP-0100) + description: > + Samma underlag som portalens analysvy och kvartalsrapporten, så att + skärm och rapport aldrig visar olika siffror för samma period. + Ett mått utan underlag returneras som null, aldrig som noll. + Kräver arbetsledare eller administratör. + security: [{ bearerAuth: [] }] + responses: + "200": + description: Driftmått + content: + application/json: + schema: + type: object + properties: + version: { type: string, example: ALVA-REP-0100 } + verifiering: + type: object + description: Andel avslut med fastställd orsak. + properties: + antal: { type: integer } + fastställda: { type: integer } + andel: { type: number, nullable: true } + reproduktion: { type: object } + omarbetning: + type: object + description: Samma fordon tillbaka med samma orsakskategori. + undantag: + type: array + description: Kontroller som oftast hoppas över, per steg och ALVA-fas. + items: { type: object } + evidens: { type: object } + faser: { type: object } + orsaker: { type: array, items: { type: object } } + "403": { description: Kräver arbetsledare eller administratör } + + /api/integration/kategorier: + get: + summary: Systemkategorier och utgående händelser (ALVA-SPEC-020) + description: > + ALVA är leverantörsoberoende. Kategorierna beskriver vilka slags + system som kan kopplas in och åt vilket håll data går; en profil + märks validated först efter att den körts mot leverantörens + faktiska gränssnitt. + security: [{ bearerAuth: [] }] + responses: + "200": + description: Kategorier och händelsetyper + content: + application/json: + schema: + type: object + properties: + kategorier: { type: object } + handelser: + type: object + description: Utgående händelsetyper med beskrivning. + + /api/integration/prenumerationer: + get: + summary: Organisationens utgående prenumerationer (ALVA-SPEC-021) + security: [{ bearerAuth: [] }] + responses: + "200": { description: Prenumerationer } + "403": { description: Kräver administratörsbehörighet } + post: + summary: Registrera en mottagare för utgående händelser + description: > + Leveranser signeras med HMAC över tidsstämpel och kropp i huvudet + `alva-signatur` (`t=,v1=`). Tidsstämpeln ligger inne i + signaturen, så en fångad leverans inte går att spela upp senare; + toleransen är fem minuter. Adressen kontrolleras mot samma + SSRF-gräns som leverantörsuppslagen. + security: [{ bearerAuth: [] }] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [namn, url, handelser] + properties: + namn: { type: string } + url: { type: string, format: uri } + handelser: + type: array + items: + type: string + enum: + - arende.skapat + - arende.fas + - arende.slutsats + - arende.avslutat + - media.tillagt + - atgardsforslag.lamnat + - kundbeslut.registrerat + hemlighet: + type: string + description: Utelämnas för att låta servern generera en. + responses: + "200": { description: Skapad } + "400": { description: Ogiltig adress eller okänd händelsetyp } + + /api/radering: + post: + summary: Radera personuppgifter för ett fordon (krypto-shredding) + description: > + Nyckeln förstörs; loggen står kvar. Det som raderas är + identifieringen, inte protokollet över vad som kontrollerades — + den enda konstruktion där bevisvärdet överlever en + raderingsbegäran. Kräver en bekräftelse som upprepar subjektet + exakt; åtgärden går inte att ångra. + security: [{ bearerAuth: [] }] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [subjekt, bekraftelse] + properties: + subjekt: { type: string, description: Registreringsnummer eller ärende-id. } + bekraftelse: { type: string, description: Måste vara identisk med subjekt. } + responses: + "200": { description: Verkställd } + "400": { description: Bekräftelsen matchar inte } + "404": { description: Inget skyddat underlag finns } + + /api/matdon: + get: + summary: Organisationens mätdon med kalibreringsstatus (ALVA-SPEC-004) + description: > + Ett mätvärde graderas E4 endast med spårbart, kalibrerat + instrument. Utan register är påståendet inte kontrollerbart. + security: [{ bearerAuth: [] }] + responses: + "200": { description: Mätdon } + post: + summary: Registrera eller uppdatera ett mätdon + security: [{ bearerAuth: [] }] + responses: + "200": { description: Sparat } + "403": { description: Kräver arbetsledare eller administratör } + components: securitySchemes: bearerAuth: