Ärendestart via arbetsorderskanning med konfidensgranskning
Primärvägen när ett ärende startas: teknikern fotar arbetsorderns framsida, orkesterns nya dokumenttolkningsuppgift (Sonnet 5, vision) läser dokumentet layoutoberoende och returnerar strukturerade fält (kund, fordon, verkstad, felbeskrivning) med konfidens per värde. - Konfidensvalidering: ≥95 % godkänns automatiskt, 80–95 % markeras för genomläsning, <80 % kräver aktiv bekräftelse — teknikern granskar bara osäkra fält - Visuell granskning: dokumentet bredvid fälten; klick på ett fält markerar ungefärlig position i bilden - "Starta diagnos" skapar hela ärendet: objekt ur fordonsfälten, metodikval ur felbeskrivningen, tolkningen loggad som ny organisationsintern händelse arbetsorder_skannad (filtreras ur kund- och partnerdelningar i server, RPC och klient) - Schema-bunden vision-uppgift i båda orkestertjänsterna; bild som data-URL, validerad på servern - Inloggade användare tillfrågas aldrig om namn — kontot används - Manuell inmatning kvar som andrahandsväg; tydligt märkt demo-tolkning i lokalt läge - 37 vitest-tester; integrationstestet verifierar filtreringen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -63,6 +63,60 @@ const METODIK_SCHEMA = {
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
// Fält som kan tolkas ur en skannad arbetsorder. Klienten har samma
|
||||
// katalog (src/felsokning/ai.ts) och filtrerar dessutom okända id:n.
|
||||
const ARBETSORDER_FALT_ID = [
|
||||
"kund_namn", "kund_foretag", "kund_telefon", "kund_epost",
|
||||
"fordon_regnr", "fordon_vin", "fordon_marke", "fordon_modell",
|
||||
"fordon_motor", "fordon_arsmodell", "fordon_matarstallning",
|
||||
"fordon_motorkod", "fordon_vaxellada",
|
||||
"ao_nummer", "ao_referens", "ao_serviceradgivare", "ao_bokningsdatum",
|
||||
"felbeskrivning",
|
||||
];
|
||||
|
||||
const ARBETSORDER_SCHEMA = {
|
||||
type: "object",
|
||||
properties: {
|
||||
falt: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", enum: ARBETSORDER_FALT_ID },
|
||||
varde: { type: "string" },
|
||||
konfidens: { type: "number", minimum: 0, maximum: 1 },
|
||||
omrade: {
|
||||
type: "object",
|
||||
properties: {
|
||||
x: { type: "number" },
|
||||
y: { type: "number" },
|
||||
bredd: { type: "number" },
|
||||
hojd: { type: "number" },
|
||||
},
|
||||
required: ["x", "y", "bredd", "hojd"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
required: ["id", "varde", "konfidens"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["falt"],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
const DOKUMENT_REGLER = `Du tolkar ett foto av en arbetsorder från en fordonsverkstad (OCR + layoutförståelse). Layouter varierar mellan verkstäder — identifiera fälten oavsett var de står, i tabeller såväl som fritext.
|
||||
|
||||
Regler:
|
||||
- Returnera ENDAST fält som faktiskt går att läsa i dokumentet. Hitta aldrig på värden.
|
||||
- "konfidens" är din läs-säkerhet 0–1: 1.0 endast vid helt entydig läsning; sänk vid oskarp text, handstil eller tvetydig layout.
|
||||
- "omrade" anger ungefär var värdet står i bilden, normaliserat 0–1 (x, y = övre vänstra hörnet).
|
||||
- Normalisera: registreringsnummer i versaler utan mellanslag, VIN med 17 tecken, datum som ÅÅÅÅ-MM-DD, mätarställning med enhet.
|
||||
- "felbeskrivning" är kundens beskrivna problem/arbetsbegäran om en sådan finns i dokumentet.`;
|
||||
|
||||
const MAX_BILD_LANGD = 3_000_000;
|
||||
|
||||
interface UppgiftKonfig {
|
||||
modell: string;
|
||||
// effort utelämnas för modeller som saknar parametern (Haiku 4.5).
|
||||
@@ -70,6 +124,8 @@ interface UppgiftKonfig {
|
||||
maxTokens: number;
|
||||
system: string;
|
||||
schema: object;
|
||||
// Uppgiften tar en bifogad bild (data-URL) i stället för ren text.
|
||||
bild?: boolean;
|
||||
}
|
||||
|
||||
// Orkestern: en modell per uppgiftstyp.
|
||||
@@ -117,6 +173,16 @@ Uppgift: En tekniker lämnar över ärendet. Komplettera överlämningen med det
|
||||
Gissa inte: välj "generisk" om beskrivningen inte tydligt hör till en specifik metodik.`,
|
||||
schema: METODIK_SCHEMA,
|
||||
},
|
||||
// Ärendestart: teknikern fotograferar arbetsordern; vi läser dokumentet
|
||||
// och returnerar strukturerade fält med konfidens per värde.
|
||||
dokumenttolkning: {
|
||||
modell: "claude-sonnet-5",
|
||||
effort: "low",
|
||||
maxTokens: 2048,
|
||||
system: DOKUMENT_REGLER,
|
||||
schema: ARBETSORDER_SCHEMA,
|
||||
bild: true,
|
||||
},
|
||||
};
|
||||
|
||||
serve(async (req: Request): Promise<Response> => {
|
||||
@@ -128,7 +194,7 @@ serve(async (req: Request): Promise<Response> => {
|
||||
if (!apiNyckel) {
|
||||
return svara(503, { error: "AI-tjänsten är inte konfigurerad." });
|
||||
}
|
||||
const { uppgift, prompt } = await req.json();
|
||||
const { uppgift, prompt, bild } = await req.json();
|
||||
const konfig = ORKESTER[uppgift as string];
|
||||
if (!konfig) {
|
||||
return svara(400, { error: "Okänd uppgift." });
|
||||
@@ -140,6 +206,21 @@ serve(async (req: Request): Promise<Response> => {
|
||||
return svara(400, { error: "prompt är för lång." });
|
||||
}
|
||||
|
||||
// Bilduppgifter: bilden skickas som data-URL och blir ett image-block
|
||||
// före textprompten i samma användarmeddelande.
|
||||
let innehall: unknown = prompt;
|
||||
if (konfig.bild) {
|
||||
const matchning =
|
||||
typeof bild === "string" && bild.length <= MAX_BILD_LANGD
|
||||
? bild.match(/^data:(image\/(?:jpeg|png|webp));base64,([A-Za-z0-9+/=]+)$/)
|
||||
: null;
|
||||
if (!matchning) return svara(400, { error: "bild saknas eller har fel format." });
|
||||
innehall = [
|
||||
{ type: "image", source: { type: "base64", media_type: matchning[1], data: matchning[2] } },
|
||||
{ type: "text", text: prompt },
|
||||
];
|
||||
}
|
||||
|
||||
const klient = new Anthropic({ apiKey: apiNyckel });
|
||||
const svar = await klient.beta.messages.create({
|
||||
model: konfig.modell,
|
||||
@@ -153,7 +234,7 @@ serve(async (req: Request): Promise<Response> => {
|
||||
betas: ["server-side-fallback-2026-07-01"],
|
||||
fallbacks: "default",
|
||||
system: [{ type: "text", text: konfig.system, cache_control: { type: "ephemeral" } }],
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
messages: [{ role: "user", content: innehall }],
|
||||
// deno-lint-ignore no-explicit-any -- fallbacks/output_config ligger före SDK-typerna
|
||||
} as any);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user