Sammanfattning och slutsats i rapport och Live Share; API dokumenterat
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -10,6 +10,7 @@ import { KUNDBESLUT_LABEL, handelseRubrik } from "./domain";
|
|||||||
import { arAvslutat, arendeidentitet, brief, foton, tidsfordelningsRader, videor } from "./projektioner";
|
import { arAvslutat, arendeidentitet, brief, foton, tidsfordelningsRader, videor } from "./projektioner";
|
||||||
import { metodikForArende } from "./store";
|
import { metodikForArende } from "./store";
|
||||||
import { Bild, Klipp } from "./Bilagevisning";
|
import { Bild, Klipp } from "./Bilagevisning";
|
||||||
|
import { sammanfatta } from "../../../services/gemensam/sammanfattning.mjs";
|
||||||
import { tidDatum, tidKlockslag } from "./format";
|
import { tidDatum, tidKlockslag } from "./format";
|
||||||
import { FelsokningSkal, Panel, StorKnapp } from "./ui";
|
import { FelsokningSkal, Panel, StorKnapp } from "./ui";
|
||||||
import { IkonCheck, IkonKlocka, IkonUppdatera } from "./ikoner";
|
import { IkonCheck, IkonKlocka, IkonUppdatera } from "./ikoner";
|
||||||
@@ -157,6 +158,41 @@ export function DelatArendeVy({
|
|||||||
ärende vyn avser. Härledd ur det nivåfiltrerade underlaget. */}
|
ärende vyn avser. Härledd ur det nivåfiltrerade underlaget. */}
|
||||||
<IdentitetsPanel arende={arende} avslutat={avslutat} />
|
<IdentitetsPanel arende={arende} avslutat={avslutat} />
|
||||||
|
|
||||||
|
{/* 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. */}
|
||||||
|
<Panel rubrik="Sammanfattning">
|
||||||
|
<p className="text-[15px] leading-[24px] text-[#1B1E22]">{sammanfatta(arende).text}</p>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
{/* 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 (
|
||||||
|
<Panel rubrik="Slutsats och motivering">
|
||||||
|
<dl className="text-[14px] leading-[22px]">
|
||||||
|
{rader.map(([etikett, text]) => (
|
||||||
|
<div key={etikett} className="border-t border-[#D7DCE2] py-2 first:border-t-0">
|
||||||
|
<dt className="text-[11px] font-semibold uppercase tracking-[0.12em] text-[#4D5662]">{etikett}</dt>
|
||||||
|
<dd className="mt-1 text-[#1B1E22]">{text}</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
<p className="mb-4 rounded border border-[#C6C6C6] bg-[#F7F7F7] p-3 text-[12px] text-[#4A5560] print:hidden">
|
<p className="mb-4 rounded border border-[#C6C6C6] bg-[#F7F7F7] p-3 text-[12px] text-[#4A5560] print:hidden">
|
||||||
{notis}
|
{notis}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -101,6 +101,15 @@ describe("AI-orkestern", () => {
|
|||||||
["/api/arenden/{arendeId}/bilagor", "laddaUppVag"],
|
["/api/arenden/{arendeId}/bilagor", "laddaUppVag"],
|
||||||
["/api/bilagor/{bilagaId}", "bilagaVag"],
|
["/api/bilagor/{bilagaId}", "bilagaVag"],
|
||||||
["/api/delad/{delningskod}/bilagor/{bilagaId}", "delatBilaga"],
|
["/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) {
|
for (const [iSpec, iServer] of vagar) {
|
||||||
expect(spec).toContain(`${iSpec}:`);
|
expect(spec).toContain(`${iSpec}:`);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { nastaSteg } from "@/felsokning/metodik";
|
|||||||
import { fasFor, klaraFaser } from "../../../../services/gemensam/faser.mjs";
|
import { fasFor, klaraFaser } from "../../../../services/gemensam/faser.mjs";
|
||||||
import { Fasrad } from "@/alva/komponenter";
|
import { Fasrad } from "@/alva/komponenter";
|
||||||
import { Slutsatspanel } from "@/felsokning/Slutsats";
|
import { Slutsatspanel } from "@/felsokning/Slutsats";
|
||||||
|
import { sammanfatta } from "../../../../services/gemensam/sammanfattning.mjs";
|
||||||
import { arendebeteckning, fasDefinition } from "@/alva/system";
|
import { arendebeteckning, fasDefinition } from "@/alva/system";
|
||||||
import {
|
import {
|
||||||
arAvslutat,
|
arAvslutat,
|
||||||
@@ -2317,6 +2318,33 @@ function RapportFlik({
|
|||||||
Delningslänken kräver att ärendet är synkat mot molnet (inloggad användare).
|
Delningslänken kräver att ärendet är synkat mot molnet (inloggad användare).
|
||||||
</p>
|
</p>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
{/* 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. */}
|
||||||
|
<Panel rubrik="Sammanfattning">
|
||||||
|
<p className="text-[15px] leading-[24px] text-[#1B1E22]">{sammanfatta(arende).text}</p>
|
||||||
|
<p className="mt-2 font-mono text-[11px] text-[#4D5662]">ALVA-PROC-0030 · härledd ur händelseloggen</p>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
{/* 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 (
|
||||||
|
<Panel rubrik="Slutsats och motivering">
|
||||||
|
{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],
|
||||||
|
])}
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* Rapportens första sida: ärendeidentiteten — registrerad en gång,
|
{/* Rapportens första sida: ärendeidentiteten — registrerad en gång,
|
||||||
återanvänd här automatiskt. */}
|
återanvänd här automatiskt. */}
|
||||||
<Panel rubrik="Ärendeinformation">
|
<Panel rubrik="Ärendeinformation">
|
||||||
|
|||||||
@@ -952,6 +952,240 @@ paths:
|
|||||||
"502": { $ref: "#/components/responses/Fel" }
|
"502": { $ref: "#/components/responses/Fel" }
|
||||||
"503": { $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=<unix>,v1=<hex>`). 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:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearerAuth:
|
bearerAuth:
|
||||||
|
|||||||
Reference in New Issue
Block a user