From 04e0a1e8ecbf39e96114c344709fe3148c19e9c4 Mon Sep 17 00:00:00 2001 From: Bernt Date: Sat, 8 Aug 2026 13:12:13 +0000 Subject: [PATCH] ALVA deployment config --- felsokning/app/Dockerfile | 1 + felsokning/app/index.html | 8 +- .../app/services/gemensam/abonnemang.mjs | 211 ++++ .../app/services/gemensam/fakturapdf.mjs | 200 ++++ .../app/services/gemensam/fakturering.mjs | 257 +++++ felsokning/app/services/gemensam/faser.mjs | 107 ++ .../services/gemensam/foretagsuppgifter.mjs | 198 ++++ .../app/services/gemensam/forsakring.mjs | 227 ++++ .../app/services/gemensam/garantier.mjs | 396 +++++++ felsokning/app/services/gemensam/grind.mjs | 313 ++++++ .../app/services/gemensam/handelser.mjs | 226 ++++ .../app/services/gemensam/integration.mjs | 188 ++++ felsokning/app/services/gemensam/kedja.mjs | 108 ++ .../app/services/gemensam/metodiker.mjs | 967 ++++++++++++++++++ .../app/services/gemensam/motivering.mjs | 178 ++++ .../app/services/gemensam/observation.mjs | 166 +++ .../app/services/gemensam/personuppgifter.mjs | 162 +++ felsokning/app/services/gemensam/sakerhet.mjs | 74 ++ .../app/services/gemensam/sammanfattning.mjs | 150 +++ felsokning/app/services/gemensam/sprak/de.mjs | 329 ++++++ felsokning/app/services/gemensam/sprak/en.mjs | 362 +++++++ felsokning/app/services/gemensam/sprak/es.mjs | 327 ++++++ felsokning/app/services/gemensam/sprak/fr.mjs | 328 ++++++ .../app/services/gemensam/sprak/index.mjs | 237 +++++ felsokning/app/services/gemensam/sprak/it.mjs | 328 ++++++ felsokning/app/services/gemensam/sprak/nl.mjs | 327 ++++++ .../app/services/gemensam/sprak/ord.mjs | 175 ++++ felsokning/app/services/gemensam/sprak/pl.mjs | 329 ++++++ felsokning/app/services/gemensam/sprak/pt.mjs | 327 ++++++ felsokning/app/services/gemensam/sprak/ro.mjs | 333 ++++++ felsokning/app/services/gemensam/sprak/sv.mjs | 325 ++++++ .../app/services/gemensam/statistik.mjs | 208 ++++ felsokning/app/services/gemensam/support.mjs | 108 ++ felsokning/app/services/gemensam/version.mjs | 201 ++++ .../services-backup/gemensam/abonnemang.mjs | 211 ++++ .../services-backup/gemensam/fakturapdf.mjs | 200 ++++ .../services-backup/gemensam/fakturering.mjs | 257 +++++ felsokning/services-backup/gemensam/faser.mjs | 107 ++ .../gemensam/foretagsuppgifter.mjs | 198 ++++ .../services-backup/gemensam/forsakring.mjs | 227 ++++ .../services-backup/gemensam/garantier.mjs | 396 +++++++ felsokning/services-backup/gemensam/grind.mjs | 313 ++++++ .../services-backup/gemensam/handelser.mjs | 226 ++++ .../services-backup/gemensam/integration.mjs | 188 ++++ felsokning/services-backup/gemensam/kedja.mjs | 108 ++ .../services-backup/gemensam/metodiker.mjs | 967 ++++++++++++++++++ .../services-backup/gemensam/motivering.mjs | 178 ++++ .../services-backup/gemensam/observation.mjs | 166 +++ .../gemensam/personuppgifter.mjs | 162 +++ .../services-backup/gemensam/sakerhet.mjs | 74 ++ .../gemensam/sammanfattning.mjs | 150 +++ .../services-backup/gemensam/sprak/de.mjs | 329 ++++++ .../services-backup/gemensam/sprak/en.mjs | 362 +++++++ .../services-backup/gemensam/sprak/es.mjs | 327 ++++++ .../services-backup/gemensam/sprak/fr.mjs | 328 ++++++ .../services-backup/gemensam/sprak/index.mjs | 237 +++++ .../services-backup/gemensam/sprak/it.mjs | 328 ++++++ .../services-backup/gemensam/sprak/nl.mjs | 327 ++++++ .../services-backup/gemensam/sprak/ord.mjs | 175 ++++ .../services-backup/gemensam/sprak/pl.mjs | 329 ++++++ .../services-backup/gemensam/sprak/pt.mjs | 327 ++++++ .../services-backup/gemensam/sprak/ro.mjs | 333 ++++++ .../services-backup/gemensam/sprak/sv.mjs | 325 ++++++ .../services-backup/gemensam/statistik.mjs | 208 ++++ .../services-backup/gemensam/support.mjs | 108 ++ .../services-backup/gemensam/version.mjs | 201 ++++ 66 files changed, 16749 insertions(+), 4 deletions(-) create mode 100644 felsokning/app/services/gemensam/abonnemang.mjs create mode 100644 felsokning/app/services/gemensam/fakturapdf.mjs create mode 100644 felsokning/app/services/gemensam/fakturering.mjs create mode 100644 felsokning/app/services/gemensam/faser.mjs create mode 100644 felsokning/app/services/gemensam/foretagsuppgifter.mjs create mode 100644 felsokning/app/services/gemensam/forsakring.mjs create mode 100644 felsokning/app/services/gemensam/garantier.mjs create mode 100644 felsokning/app/services/gemensam/grind.mjs create mode 100644 felsokning/app/services/gemensam/handelser.mjs create mode 100644 felsokning/app/services/gemensam/integration.mjs create mode 100644 felsokning/app/services/gemensam/kedja.mjs create mode 100644 felsokning/app/services/gemensam/metodiker.mjs create mode 100644 felsokning/app/services/gemensam/motivering.mjs create mode 100644 felsokning/app/services/gemensam/observation.mjs create mode 100644 felsokning/app/services/gemensam/personuppgifter.mjs create mode 100644 felsokning/app/services/gemensam/sakerhet.mjs create mode 100644 felsokning/app/services/gemensam/sammanfattning.mjs create mode 100644 felsokning/app/services/gemensam/sprak/de.mjs create mode 100644 felsokning/app/services/gemensam/sprak/en.mjs create mode 100644 felsokning/app/services/gemensam/sprak/es.mjs create mode 100644 felsokning/app/services/gemensam/sprak/fr.mjs create mode 100644 felsokning/app/services/gemensam/sprak/index.mjs create mode 100644 felsokning/app/services/gemensam/sprak/it.mjs create mode 100644 felsokning/app/services/gemensam/sprak/nl.mjs create mode 100644 felsokning/app/services/gemensam/sprak/ord.mjs create mode 100644 felsokning/app/services/gemensam/sprak/pl.mjs create mode 100644 felsokning/app/services/gemensam/sprak/pt.mjs create mode 100644 felsokning/app/services/gemensam/sprak/ro.mjs create mode 100644 felsokning/app/services/gemensam/sprak/sv.mjs create mode 100644 felsokning/app/services/gemensam/statistik.mjs create mode 100644 felsokning/app/services/gemensam/support.mjs create mode 100644 felsokning/app/services/gemensam/version.mjs create mode 100644 felsokning/services-backup/gemensam/abonnemang.mjs create mode 100644 felsokning/services-backup/gemensam/fakturapdf.mjs create mode 100644 felsokning/services-backup/gemensam/fakturering.mjs create mode 100644 felsokning/services-backup/gemensam/faser.mjs create mode 100644 felsokning/services-backup/gemensam/foretagsuppgifter.mjs create mode 100644 felsokning/services-backup/gemensam/forsakring.mjs create mode 100644 felsokning/services-backup/gemensam/garantier.mjs create mode 100644 felsokning/services-backup/gemensam/grind.mjs create mode 100644 felsokning/services-backup/gemensam/handelser.mjs create mode 100644 felsokning/services-backup/gemensam/integration.mjs create mode 100644 felsokning/services-backup/gemensam/kedja.mjs create mode 100644 felsokning/services-backup/gemensam/metodiker.mjs create mode 100644 felsokning/services-backup/gemensam/motivering.mjs create mode 100644 felsokning/services-backup/gemensam/observation.mjs create mode 100644 felsokning/services-backup/gemensam/personuppgifter.mjs create mode 100644 felsokning/services-backup/gemensam/sakerhet.mjs create mode 100644 felsokning/services-backup/gemensam/sammanfattning.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/de.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/en.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/es.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/fr.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/index.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/it.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/nl.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/ord.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/pl.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/pt.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/ro.mjs create mode 100644 felsokning/services-backup/gemensam/sprak/sv.mjs create mode 100644 felsokning/services-backup/gemensam/statistik.mjs create mode 100644 felsokning/services-backup/gemensam/support.mjs create mode 100644 felsokning/services-backup/gemensam/version.mjs diff --git a/felsokning/app/Dockerfile b/felsokning/app/Dockerfile index 3dee4a4..96320f1 100644 --- a/felsokning/app/Dockerfile +++ b/felsokning/app/Dockerfile @@ -6,6 +6,7 @@ WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --no-audit --no-fund COPY . . +COPY services /services ARG VITE_SUPABASE_URL ARG VITE_SUPABASE_PUBLISHABLE_KEY ARG VITE_SUPABASE_PROJECT_ID diff --git a/felsokning/app/index.html b/felsokning/app/index.html index 38a5fa7..bca7e98 100644 --- a/felsokning/app/index.html +++ b/felsokning/app/index.html @@ -4,13 +4,13 @@ - Lovable App - + ALVA - Guidad Felsökning + - - + + diff --git a/felsokning/app/services/gemensam/abonnemang.mjs b/felsokning/app/services/gemensam/abonnemang.mjs new file mode 100644 index 0000000..fb245bf --- /dev/null +++ b/felsokning/app/services/gemensam/abonnemang.mjs @@ -0,0 +1,211 @@ +// ALVA-PROC-0002 · Abonnemang. +// +// ---- Hållningen --------------------------------------------------------- +// +// Vi litar på kunden. Det är inte en artighet utan ett designbeslut med +// följder: inget kort krävs, ingen förskottsbetalning, ingen spärr innan +// någon fått veta att något är obetalt. Kunden är en verkstad med +// anställda och kunder i sin tur — den försvinner inte över natten, och +// att behandla den som om den kunde det gör produkten sämre för alla som +// betalar i tid. +// +// Vid utebliven betalning varnar vi, räknar ned synligt, och låser sedan. +// +// ---- Vad en låsning ALDRIG får göra ------------------------------------- +// +// Låsning stänger nya ärenden. Den rör inte historiken, och den får inte +// göra det. +// +// Skälet är inte generositet. Ärendeloggen är verkstadens underlag i en +// garanti- eller försäkringstvist som gäller DERAS kund — en tredje part +// som inte har någon del i vår obetalda faktura. Att göra det underlaget +// oåtkomligt vore att använda någon annans rättsliga ställning som +// påtryckningsmedel. Läsning och export förblir därför öppna i låst läge, +// och det är inte förhandlingsbart. +// +// ---- Gratis är en nivå, inte en provperiod ------------------------------ +// +// Kontot startar på Free och stannar där tills någon väljer annat. En +// provperiod som tyst börjar kosta är samma sorts fälla som produkten +// finns för att undvika. Fakturan skapas ändå vid registreringen — på +// noll kronor — därför att den etablerar serien och visar kunden exakt +// vad den inte betalar för. + +import { PRISLISTA } from "./fakturering.mjs"; + +/** Dagar efter förfallodagen innan låsning. Synlig nedräkning hela tiden. */ +export const RESPITDAGAR = 14; + +/** + * Nivåerna. + * + * `basavgift` och `anvandaravgift` är i öre, som resten av faktureringen. + * Värdena är PLATSHÅLLARE på samma villkor som PRISLISTA — mekanismen är + * oberoende av dem. + */ +export const NIVAER = [ + { + id: "free", + namn: "Free", + basavgift: 0, + anvandaravgift: 0, + maxAnvandare: 2, + text: "Full method, full evidence model, full report. Two accounts.", + ingar: [ + "The complete ALVA methodology and quality gate", + "Append-only case log and evidence report", + "Two active accounts", + ], + saknas: ["Fleet analytics", "Outgoing integrations", "Knowledge source connectors"], + }, + { + id: "standard", + namn: "Standard", + basavgift: PRISLISTA.plattform_ar / 12, + anvandaravgift: PRISLISTA.anvandare_manad, + maxAnvandare: null, + text: "Platform fee plus a fee per active account.", + ingar: [ + "Everything in Free, without the account limit", + "Fleet analytics and fault-cause statistics", + "Live Share with customer, partner and internal levels", + "Instrument register with calibration tracking", + ], + saknas: ["Outgoing integrations", "Knowledge source connectors"], + }, + { + id: "premium", + namn: "Premium", + basavgift: (PRISLISTA.plattform_ar / 12) * 2, + anvandaravgift: PRISLISTA.anvandare_manad, + maxAnvandare: null, + text: "For workshops that report into someone else's system.", + ingar: [ + "Everything in Standard", + "Outgoing integrations and event subscriptions", + "Protocol ingest from external test equipment", + "Knowledge source connectors", + ], + saknas: ["Dedicated instance", "Custom methodology authoring"], + }, + { + id: "enterprise", + namn: "Enterprise", + basavgift: null, // Avtalas. Inte "kontakta oss" som prissignal — det finns inget listpris. + anvandaravgift: null, + maxAnvandare: null, + text: "Chains and fleets. Priced per agreement because the scope is.", + ingar: [ + "Everything in Premium", + "Own methodologies and own rule package", + "Dedicated instance, own key management", + "Signed rule package and audit support", + ], + saknas: [], + }, +]; + +export const nivaFor = (id) => NIVAER.find((n) => n.id === id) ?? NIVAER[0]; + +/** Abonnemangets tillstånd. Härlett, aldrig lagrat. */ +export const TILLSTAND = ["aktiv", "varning", "last"]; + +/** + * Tillståndet, härlett ur fakturorna. + * + * Tre utfall och inget fjärde: + * + * aktiv Inget är förfallet. + * varning Något är förfallet men respiten löper. Nedräkningen visas. + * last Respiten är slut. + * + * En faktura på noll kronor kan inte försätta någon i varning. Free-nivån + * fakturerar noll, och en obetald nollfaktura är inte en skuld — den är + * en bokföringspost. Att låsa någon för den vore absurt, och det är + * precis den sortens absurditet som uppstår när tillståndet räknas fram + * utan att någon tänkt på specialfallet. + * + * @param fakturor [{ forfaller, totalt, status }] + * @param nu tidpunkt; injicerad för testbarhet + */ +export function abonnemangstillstand(fakturor = [], nu = new Date()) { + const obetalda = fakturor.filter( + (f) => f.status === "utfardad" && Number(f.totalt) > 0 && new Date(f.forfaller) < nu, + ); + if (obetalda.length === 0) return { tillstand: "aktiv", dagarKvar: null, aldsta: null }; + + // Den äldsta förfallna styr. Betalar kunden den senaste men inte den + // första har respiten ändå löpt sedan den första. + const aldsta = obetalda.reduce((a, b) => (new Date(a.forfaller) < new Date(b.forfaller) ? a : b)); + const forfalloDygn = Math.floor((nu - new Date(aldsta.forfaller)) / 86_400_000); + const dagarKvar = RESPITDAGAR - forfalloDygn; + + return { + tillstand: dagarKvar > 0 ? "varning" : "last", + dagarKvar: Math.max(0, dagarKvar), + aldsta: aldsta.beteckning ?? null, + belopp: obetalda.reduce((s, f) => s + Number(f.totalt), 0), + }; +} + +/** + * Vad ett tillstånd tillåter. + * + * Läsning och export är sanna i ALLA tillstånd. Se filhuvudet — det är + * verkstadens kunds underlag, inte verkstadens, och inte vårt. + */ +export function behorighet(tillstand) { + const last = tillstand === "last"; + return { + lasa: true, + exportera: true, + nyttArende: !last, + dokumentera: !last, + avsluta: !last, + }; +} + +/** + * Beskedet till kunden. Rak text, ingen förhandling, inget hot. + * + * Statusspråket gäller även här: ett konstaterande och vad som händer + * härnäst. + */ +export function besked(status) { + if (status.tillstand === "aktiv") return null; + if (status.tillstand === "varning") { + return ( + `Invoice ${status.aldsta} is past due. New cases can be started for ${status.dagarKvar} more ` + + `day${status.dagarKvar === 1 ? "" : "s"}. Reading and export are never affected.` + ); + } + return ( + `Invoice ${status.aldsta} is past due and the grace period has ended. New cases cannot be started. ` + + "Existing cases remain readable and exportable — that record belongs to your customer, not to us." + ); +} + +/** + * Nästa fakturaperiod. + * + * Månadsvis från registreringsdagen, inte från den första i månaden: en + * organisation som registrerar sig den 20:e ska inte få en faktura på + * elva dagar som sin första upplevelse av produkten. + */ +export function nastaPeriod(registrerad, senastFakturerad = null) { + const start = new Date(senastFakturerad ?? registrerad); + if (Number.isNaN(start.getTime())) return null; + const fran = new Date(start); + if (senastFakturerad) fran.setMonth(fran.getMonth() + 1); + const till = new Date(fran); + till.setMonth(till.getMonth() + 1); + till.setDate(till.getDate() - 1); + return { fran: fran.toISOString().slice(0, 10), till: till.toISOString().slice(0, 10) }; +} + +/** Är det dags att fakturera perioden? */ +export function forfallenFakturering(registrerad, senastFakturerad, nu = new Date()) { + const period = nastaPeriod(registrerad, senastFakturerad); + if (!period) return null; + return new Date(period.fran) <= nu ? period : null; +} diff --git a/felsokning/app/services/gemensam/fakturapdf.mjs b/felsokning/app/services/gemensam/fakturapdf.mjs new file mode 100644 index 0000000..a5ea39b --- /dev/null +++ b/felsokning/app/services/gemensam/fakturapdf.mjs @@ -0,0 +1,200 @@ +// ALVA-PROC-0001 · Fakturan som PDF. +// +// ---- Varför den är skriven för hand ------------------------------------- +// +// Kravet var inga beroenden. En PDF-generator drar in ett bibliotek som +// drar in fem till, och varje sådant är en försörjningskedja vi inte +// granskar — för att rita text i två spalter. +// +// PDF är ett textformat. En ensidig faktura med inbyggda typsnitt är +// omkring tvåhundra rader, och de raderna går att läsa och förstå av den +// som ska lita på dem. Det är samma resonemang som gjorde +// plattformstjänsten ramverkslös. +// +// ---- Vad som INTE görs --------------------------------------------------- +// +// Inga bilder, ingen egen typografi, ingen radbrytning av godtycklig +// bredd. De 14 basfonterna finns i varje PDF-läsare och behöver inte +// bäddas in; Helvetica räcker för en faktura, och en faktura som ser +// tråkig ut är fortfarande en faktura. +// +// WinAnsi täcker svenska tecken. Det som ligger utanför ersätts hellre än +// att ge en trasig glyf — en faktura med kråkfötter ser förfalskad ut. + +/** Sidmått i punkter. A4. */ +const BREDD = 595.28; +const HOJD = 841.89; +const MARGINAL = 56; + +/** Tecken PDF:ens strängsyntax måste få undantagna. */ +const flykta = (s) => String(s).replace(/([\\()])/g, "\\$1"); + +/** + * WinAnsi-kodning av det vi faktiskt använder. + * + * Latin-1 räcker till 0xFF och täcker åäöÅÄÖ. Utanför det ersätts + * tecknet — hellre en punkt än en trasig glyf. + */ +function winansi(text) { + let ut = ""; + for (const tecken of String(text)) { + const k = tecken.codePointAt(0); + if (k === 0x2013 || k === 0x2014) ut += "-"; // tankstreck + else if (k === 0x00a0) ut += " "; // hårt mellanslag + else if (k === 0x2212) ut += "-"; // minustecken + else if (k <= 0xff) ut += tecken; + else ut += "."; + } + return ut; +} + +/** En textinstruktion i innehållsströmmen. */ +function text(x, y, storlek, innehall, font = "F1") { + return `BT /${font} ${storlek} Tf 1 0 0 1 ${x.toFixed(2)} ${y.toFixed(2)} Tm (${flykta(winansi(innehall))}) Tj ET\n`; +} + +function linje(x1, y1, x2, y2, tjocklek = 0.5) { + return `${tjocklek} w ${x1.toFixed(2)} ${y1.toFixed(2)} m ${x2.toFixed(2)} ${y2.toFixed(2)} l S\n`; +} + +/** + * Bygger fakturans PDF. + * + * @param faktura dokumentet ur fakturering.mjs + * @param avsandare valfria uppgifter om utfärdaren (ALVA-SPEC-050) + * @returns Buffer + */ +export function fakturaPdf(faktura, avsandare = {}) { + const rader = []; + let y = HOJD - MARGINAL; + + const skriv = (storlek, innehall, font = "F1", hopp = null) => { + rader.push(text(MARGINAL, y, storlek, innehall, font)); + y -= hopp ?? storlek + 6; + }; + + // ---- Huvud ------------------------------------------------------------ + skriv(20, "ALVA", "F2", 28); + skriv(9, "GUIDED DIAGNOSTIC PLATFORM", "F1", 24); + + skriv(15, `Invoice ${faktura.beteckning}`, "F2", 22); + + // ---- Parter och datum ------------------------------------------------- + const uppgifter = [ + ["Organization", faktura.organisation], + ["Period", `${faktura.period.fran} - ${faktura.period.till}`], + ["Issued", faktura.utfardad], + ["Due", faktura.forfaller], + ]; + for (const [etikett, varde] of uppgifter) { + rader.push(text(MARGINAL, y, 9, etikett, "F1")); + rader.push(text(MARGINAL + 120, y, 9, String(varde), "F2")); + y -= 14; + } + y -= 10; + + // ---- Rader ------------------------------------------------------------ + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y, 1)); + y -= 16; + for (const [x, etikett] of [ + [MARGINAL, "ITEM"], + [MARGINAL + 250, "QTY"], + [MARGINAL + 330, "UNIT PRICE"], + [BREDD - MARGINAL - 80, "AMOUNT"], + ]) { + rader.push(text(x, y, 8, etikett, "F2")); + } + y -= 6; + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y)); + y -= 18; + + const belopp = (ore) => `${(Math.abs(ore) / 100).toFixed(2)}${ore < 0 ? "-" : ""} ${faktura.valuta}`; + + for (const r of faktura.rader) { + rader.push(text(MARGINAL, y, 10, r.benamning, "F2")); + rader.push(text(MARGINAL + 250, y, 10, `${r.antal}`)); + rader.push(text(MARGINAL + 330, y, 10, belopp(r.apris))); + rader.push(text(BREDD - MARGINAL - 80, y, 10, belopp(r.belopp))); + y -= 13; + // Underlaget under raden: varje rad ska bära var antalet kommer ifrån, + // och det gäller på papper lika mycket som på skärm. + rader.push(text(MARGINAL + 8, y, 8, r.underlag)); + y -= 18; + } + + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y)); + y -= 18; + + // ---- Summering -------------------------------------------------------- + for (const [etikett, varde, fet] of [ + ["Net", belopp(faktura.netto), false], + [`VAT ${Math.round(faktura.momssats * 100)} %`, belopp(faktura.moms), false], + ["Total", belopp(faktura.totalt), true], + ]) { + rader.push(text(MARGINAL + 330, y, fet ? 11 : 10, etikett, fet ? "F2" : "F1")); + rader.push(text(BREDD - MARGINAL - 80, y, fet ? 11 : 10, varde, fet ? "F2" : "F1")); + y -= fet ? 20 : 15; + } + + // ---- Betalning -------------------------------------------------------- + y -= 10; + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y)); + y -= 18; + skriv(9, faktura.betalningssatt, "F2", 14); + skriv(8, "Payment is registered by an administrator when the funds arrive.", "F1", 20); + + if (faktura.krediterar) { + skriv(9, `Credit note for ${faktura.krediterar}`, "F2", 14); + if (faktura.orsak) skriv(8, faktura.orsak, "F1", 20); + } + + // ---- Utfärdarens uppgifter ------------------------------------------- + // + // Tomma fält utelämnas. En faktura med "— ej satt" i adressfältet är + // sämre än en utan adressfält: den påstår att någon fyllt i det. + const fot = [avsandare.foretagsnamn, avsandare.adress, avsandare.momsnummer, avsandare.epost].filter(Boolean); + let fy = MARGINAL + 40; + for (const rad of fot.reverse()) { + rader.push(text(MARGINAL, fy, 8, rad)); + fy += 11; + } + rader.push(text(BREDD - MARGINAL - 120, MARGINAL, 8, faktura.version ?? "ALVA-PROC-0001")); + + return montera(rader.join("")); +} + +/** + * Sätter ihop PDF-filen med korrekt korsreferenstabell. + * + * Byteoffsetarna i xref måste stämma på byten. Det är den enda delen av + * formatet som inte förlåter slarv — en läsare som inte hittar objektet + * visar ingenting alls, inte en trasig sida. + */ +function montera(innehall) { + const objekt = [ + "<< /Type /Catalog /Pages 2 0 R >>", + "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", + `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${BREDD.toFixed(2)} ${HOJD.toFixed(2)}] ` + + "/Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> /Contents 4 0 R >>", + null, // 4: innehållsströmmen, sätts nedan + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>", + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>", + ]; + + const strom = Buffer.from(innehall, "latin1"); + objekt[3] = `<< /Length ${strom.length} >>\nstream\n${innehall}endstream`; + + let pdf = "%PDF-1.4\n"; + const offsetar = []; + objekt.forEach((kropp, i) => { + offsetar.push(Buffer.byteLength(pdf, "latin1")); + pdf += `${i + 1} 0 obj\n${kropp}\nendobj\n`; + }); + + const xrefOffset = Buffer.byteLength(pdf, "latin1"); + pdf += `xref\n0 ${objekt.length + 1}\n0000000000 65535 f \n`; + for (const o of offsetar) pdf += `${String(o).padStart(10, "0")} 00000 n \n`; + pdf += `trailer\n<< /Size ${objekt.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`; + + return Buffer.from(pdf, "latin1"); +} diff --git a/felsokning/app/services/gemensam/fakturering.mjs b/felsokning/app/services/gemensam/fakturering.mjs new file mode 100644 index 0000000..ba1c5ef --- /dev/null +++ b/felsokning/app/services/gemensam/fakturering.mjs @@ -0,0 +1,257 @@ +// ALVA-PROC-0001 · Fakturering. +// +// ---- Varför fakturan skapas här och inte hos en betalleverantör ------- +// +// ALVA säljs inte över disk. Aktiveringssekvensen på webbplatsen säger +// det redan: ansökan granskas, faktura utfärdas, betalning registreras, +// organisationen aktiveras. Det finns ingen kassa att gå igenom, och +// därför ingen betalleverantör att fråga vad kunden ska betala. +// +// Det betyder att beloppet måste komma någonstans ifrån, och det enda +// hållbara stället är organisationens faktiska tillstånd: hur många +// aktiva användare den har, vilken licensperiod som gäller, vilka +// moduler som är påslagna. En summa någon skriver in för hand kan säga +// emot verkligheten. En summa som härleds kan inte det. +// +// Samma resonemang som sammanfattningen (ALVA-PROC-0030): härledd, inte +// inmatad. Det är också det som gör fakturan granskbar i efterhand — +// underlaget står i raderna, inte i någons minne. +// +// ---- Vad som INTE finns här ------------------------------------------- +// +// Ingen betalningshantering, inga kortuppgifter, ingen extern tjänst. +// Betalning registreras av en administratör när pengarna kommit in. +// Systemet påstår aldrig att något är betalt utan att en människa sagt +// det, av samma skäl som en kontroll aldrig är verifierad utan evidens. +// +// ---- Oföränderlighet --------------------------------------------------- +// +// En utfärdad faktura ändras aldrig. Blev den fel utfärdas en +// kreditfaktura som pekar på den, och en ny. Det är samma regel som för +// händelseloggen: en felaktig post rättas inte, den bemöts. + +/** + * Prislistan. + * + * Beloppen är i minsta valutaenhet (öre) för att undvika flyttalsfel — + * en faktura får aldrig avrunda fel åt något håll. + * + * VÄRDENA NEDAN ÄR PLATSHÅLLARE och ska sättas av produktägaren per + * marknad. Mekanismen är oberoende av dem: byt siffror, och varje + * kommande faktura följer med. + */ +export const PRISLISTA = { + valuta: "SEK", + /** Plattformslicens, per organisation och år. */ + plattform_ar: 2_400_000, + /** Användarlicens, per aktiv användare och månad. */ + anvandare_manad: 39_000, + /** Tillval, per modul och år. */ + moduler: { + forsakringsrapportering: { namn: "Insurance reporting", ar: 600_000 }, + flottanalys: { namn: "Fleet analytics", ar: 960_000 }, + kunskapskallor_oem: { namn: "OEM knowledge sources", ar: 1_440_000 }, + }, + /** Mervärdesskatt. 25 % är svensk normalskattesats. */ + momssats: 0.25, + /** Betalningsvillkor i dagar. */ + betalningsvillkor: 30, +}; + +/** Fakturans tillstånd. Ordningen är enkelriktad utom kredit. */ +export const FAKTURASTATUS = ["utfardad", "betald", "krediterad"]; + +/** + * Statusen är en projektion, inte en kolumn. + * + * Följden av att en utfärdad faktura aldrig ändras: "betald" kan inte + * skrivas ovanpå "utfärdad". Betalningen är en händelse som inträffar + * efteråt, och tillståndet härleds ur händelserna — samma förhållande + * som mellan ärendets tillstånd och dess logg. + * + * Kreditering väger tyngst. En krediterad faktura som också hunnit bli + * betald är fortfarande krediterad; det är återbetalningen som är kvar + * att göra, och den frågan besvaras inte av ett statusfält. + * + * @param poster [{ typ: "betald" | "krediterad", ... }] + */ +export function fakturastatus(poster = []) { + if (poster.some((p) => p?.typ === "krediterad")) return "krediterad"; + if (poster.some((p) => p?.typ === "betald")) return "betald"; + return "utfardad"; +} + +/** + * Granskar en fakturaperiod innan något härleds ur den. + * + * En bakvänd period ger noll månader i manaderMellan(), vilket tyst + * skulle bli en faktura utan användarrad i stället för ett fel. Den + * sortens tystnad är värre än ett avslag: fakturan ser rimlig ut. + * + * Returnerar en felsträng, eller null när perioden duger. + */ +export function granskaPeriod(period) { + const datum = (v) => typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v) && !Number.isNaN(Date.parse(v)); + if (!period || !datum(period.fran) || !datum(period.till)) { + return "Perioden kräver fran och till som datum (ÅÅÅÅ-MM-DD)."; + } + if (new Date(period.till) < new Date(period.fran)) { + return "Perioden slutar före den börjar."; + } + // Ett tak finns för att ett skrivfel i årtalet inte ska bli en faktura + // på nittio år. Fem år rymmer varje rimlig licensperiod. + if (manaderMellan(period.fran, period.till) > 60) { + return "Perioden är längre än fem år — kontrollera datumen."; + } + return null; +} + +/** + * Beräknar fakturarader ur organisationens tillstånd. + * + * Varje rad bär sitt underlag — antal, à-pris och vad antalet kommer + * ifrån. En granskare ska kunna se varför summan blev den den blev utan + * att fråga någon. + * + * @param org { namn, licensstart, moduler: string[] } + * @param aktiva antal aktiva användare i organisationen + * @param period { fran: "2026-01-01", till: "2026-12-31" } + * @param prislista valfri; standard är PRISLISTA + */ +export function fakturarader(org, aktiva, period, prislista = PRISLISTA) { + const rader = []; + const månader = manaderMellan(period.fran, period.till); + + rader.push({ + benamning: "Platform license", + underlag: `${org?.namn ?? "Organization"} · ${period.fran} – ${period.till}`, + antal: 1, + enhet: "organization/year", + apris: prislista.plattform_ar, + belopp: prislista.plattform_ar, + }); + + if (aktiva > 0 && månader > 0) { + rader.push({ + benamning: "User licenses", + // Antalet är inte en uppskattning: det är de konton som faktiskt + // kan logga in när fakturan utfärdas. + underlag: `${aktiva} active users × ${månader} months`, + antal: aktiva * månader, + enhet: "user/month", + apris: prislista.anvandare_manad, + belopp: aktiva * månader * prislista.anvandare_manad, + }); + } + + for (const nyckel of org?.moduler ?? []) { + const modul = prislista.moduler[nyckel]; + if (!modul) continue; + rader.push({ + benamning: modul.namn, + underlag: `Enterprise module · ${period.fran} – ${period.till}`, + antal: 1, + enhet: "module/year", + apris: modul.ar, + belopp: modul.ar, + }); + } + + return rader; +} + +/** + * Sätter ihop en faktura. + * + * Returnerar ett objekt som är fullständigt i sig själv: raderna, summan, + * momsen, förfallodagen och vad allt härleddes ur. Den som läser den om + * två år ska inte behöva systemet för att förstå den. + */ +export function fakturera({ nummer, org, aktiva, period, utfardad, prislista = PRISLISTA }) { + const rader = fakturarader(org, aktiva, period, prislista); + const netto = rader.reduce((summa, r) => summa + r.belopp, 0); + // Momsen räknas på nettosumman, inte per rad: radvis avrundning ger + // ören som inte stämmer mot totalen. + const moms = Math.round(netto * prislista.momssats); + + return { + version: "ALVA-PROC-0001", + beteckning: fakturabeteckning(nummer), + organisation: org?.namn ?? "Organization", + period, + utfardad, + forfaller: adderaDagar(utfardad, prislista.betalningsvillkor), + valuta: prislista.valuta, + rader, + netto, + momssats: prislista.momssats, + moms, + totalt: netto + moms, + status: "utfardad", + betalningssatt: "Invoice. No online payment is accepted.", + }; +} + +/** + * Kreditering. + * + * En utfärdad faktura ändras aldrig. Krediteringen är en egen post som + * pekar tillbaka, med samma rader och omvänt tecken, och den kräver ett + * skäl — av samma anledning som ett avslut kräver ett varför. + */ +export function kreditera(faktura, { nummer, utfardad, orsak }) { + if (!orsak || orsak.trim().length < 10) { + return { fel: "En kreditering kräver ett skäl som går att granska i efterhand." }; + } + return { + kredit: { + ...faktura, + beteckning: fakturabeteckning(nummer), + utfardad, + krediterar: faktura.beteckning, + orsak: orsak.trim(), + rader: faktura.rader.map((r) => ({ ...r, antal: -r.antal, belopp: -r.belopp })), + netto: -faktura.netto, + moms: -faktura.moms, + totalt: -faktura.totalt, + status: "krediterad", + }, + }; +} + +/** ALVA-INV-0001 */ +export const fakturabeteckning = (nummer) => `ALVA-INV-${String(nummer).padStart(4, "0")}`; + +/** Hela månader mellan två datum, inklusive startmånaden. */ +export function manaderMellan(fran, till) { + const a = new Date(fran); + const b = new Date(till); + if (Number.isNaN(a.getTime()) || Number.isNaN(b.getTime()) || b < a) return 0; + return (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth()) + 1; +} + +function adderaDagar(datum, dagar) { + const d = new Date(datum); + if (Number.isNaN(d.getTime())) return datum; + d.setDate(d.getDate() + dagar); + return d.toISOString().slice(0, 10); +} + +/** + * Belopp för läsning. Öre in, sträng ut. + * + * Fast antal decimaler och hårt mellanslag som tusentalsavskiljare — + * belopp jämförs oftare än de läses, precis som beteckningar. + */ +/** Hårt mellanslag: ett belopp får aldrig brytas över en radbrytning. */ +const TUSENTAL = "\u00A0"; + +export function formateraBelopp(ore, valuta = PRISLISTA.valuta) { + const negativt = ore < 0; + const kronor = Math.abs(ore) / 100; + const text = kronor + .toFixed(2) + .replace(".", ",") + .replace(/\B(?=(\d{3})+(?!\d))/g, TUSENTAL); + return `${negativt ? "−" : ""}${text} ${valuta}`; +} diff --git a/felsokning/app/services/gemensam/faser.mjs b/felsokning/app/services/gemensam/faser.mjs new file mode 100644 index 0000000..d140330 --- /dev/null +++ b/felsokning/app/services/gemensam/faser.mjs @@ -0,0 +1,107 @@ +// ALVA-modellen tillämpad på metodikbiblioteket. +// +// ALVA är en metod, inte ett gränssnitt. Om metoden bara fanns i +// marknadsföringen vore den en etikett på en wizard. Här är den den +// struktur som varje procedur faktiskt följer: +// +// A Analysis Samla fakta. Inte hypoteser. +// L Localization Avgränsa. Identifiera exakt område. +// V Verification Verifiera orsaken. Inte symptomet. +// A Action Utför korrigering. Verifiera resultat. Dokumentera. +// +// Varje steg i varje metodik hör till exakt en fas. Klassificeringen är +// data och inte en gissning i vyn, av två skäl: den ska gå att granska, +// och en procedur vars faser inte går ihop ska falla i test i stället +// för att se rätt ut på skärmen. +// +// ---- Varför just den här indelningen --------------------------------- +// +// Gränsen mellan L och V är den enda som är svår, och den är också den +// som bär hela metoden. Att mäta spänning vid en komponent är +// lokalisering — den avgränsar var felet finns. Att mäta spänningsfall +// över samma komponent under last är verifiering — den fastställer +// varför. Samma instrument, samma komponent, olika fas. +// +// Regeln vi tillämpar: ett steg är Verification när det kan avfärda en +// kandidatorsak. Kan det bara flytta uppmärksamheten är det Localization. + +/** Steg-id → fas. Uttömmande; ett okänt steg-id faller i test. */ +export const STEGFAS = { + // ---- Analysis: vad hände, när, var. Inga slutsatser. ---------------- + sakerhet: "analysis", // Förutsättningen för att få samla fakta alls. + symptom: "analysis", + forutsattningar: "analysis", + grund: "analysis", + + // ---- Localization: avgränsa området --------------------------------- + visuell: "localization", + felkoder: "localization", + avlasning: "localization", + koder: "localization", + buss: "localization", + inspelning: "localization", + lokalisering: "localization", + batteri: "localization", + glapp: "localization", + metod: "localization", + reproduktion: "localization", // vägprovet som pekar ut hjulet — avgränsning, inte åtgärd + + // ---- Verification: fastställ orsaken -------------------------------- + matningar: "verification", + kontroller: "verification", + grundkontroller: "verification", + rela: "verification", + start: "verification", + laddning: "verification", + krypstrom: "verification", + mekanik: "verification", + tandning_bransle: "verification", + packning: "verification", + system: "verification", + styrning: "verification", + orsak: "verification", + installning: "verification", + + // ---- Action: korrigera och verifiera resultatet --------------------- + provkorning: "action", + funktionstest: "action", + kalibrering: "action", +}; + +export const FASORDNING = ["analysis", "localization", "verification", "action"]; + +/** Fasen för ett steg. Okänt steg → analysis, och testet faller. */ +export const fasFor = (stegId) => STEGFAS[stegId] ?? "analysis"; + +/** + * Faserna en metodik faktiskt innehåller, i ALVA-ordning. + * + * Alla metodiker innehåller inte alla fyra. En läckagemetodik som slutar + * med lokalisering är inte ofullständig — läckan är hittad, åtgärden är + * ett annat arbete. Att tvinga in ett Action-steg där hade varit att + * fylla ut modellen i stället för att tillämpa den. + */ +export function faserI(metodik) { + const funna = new Set((metodik?.steg ?? []).map((s) => fasFor(s.id))); + return FASORDNING.filter((f) => funna.has(f)); +} + +/** + * Klara faser givet loggen: en fas är klar när metodikens samtliga steg i + * den fasen är dokumenterade. + */ +export function klaraFaser(metodik, handelser) { + const besvarade = new Set(); + const utforda = new Set(); + for (const h of handelser) { + if (h?.typ === "fraga_besvarad") besvarade.add(`${h.stegId}/${h.frageId}`); + if (h?.typ === "kontroll_utford") utforda.add(`${h.stegId}/${h.kontrollId}`); + } + const klar = (steg) => + (steg.fragor ?? []).every((f) => besvarade.has(`${steg.id}/${f.id}`)) && + (steg.kontroller ?? []).every((k) => utforda.has(`${steg.id}/${k.id}`)); + + return faserI(metodik).filter((fas) => + (metodik.steg ?? []).filter((s) => fasFor(s.id) === fas).every(klar), + ); +} diff --git a/felsokning/app/services/gemensam/foretagsuppgifter.mjs b/felsokning/app/services/gemensam/foretagsuppgifter.mjs new file mode 100644 index 0000000..3cc2204 --- /dev/null +++ b/felsokning/app/services/gemensam/foretagsuppgifter.mjs @@ -0,0 +1,198 @@ +// ALVA-SPEC-050 · Företagsuppgifter och lagstadgad information. +// +// ---- Varför tyskt krav som golv ---------------------------------------- +// +// Tyskland har den strängaste kravbilden på vad en näringsidkare måste +// hålla synligt: Impressumspflicht enligt § 5 DDG är namngiven, +// utkrävbar och avmahnbar på ett sätt som saknar motsvarighet i övriga +// EU. Bygger man för den listan uppfyller man i praktiken de andra +// marknadernas krav på köpet, och man slipper en andra genomgång per +// land. Det är därför listan nedan är tysk och inte svensk. +// +// ---- Vad modulen INTE gör ----------------------------------------------- +// +// Den hittar inte på uppgifter. Ett Impressum med påhittad adress eller +// påhittat organisationsnummer är inte ett halvfärdigt Impressum utan ett +// vilseledande, och skadan är större än den tomma rutans. +// +// Osatta fält märks därför som osatta, och `saknade()` returnerar dem så +// att driften kan spärra en driftsättning på listan i stället för att +// upptäcka den när brevet kommer. +// +// ---- Två fällor som är aktuella just nu --------------------------------- +// +// ODR-LÄNKEN SKA BORT. Förordning (EU) 524/2013 upphävdes genom +// förordning (EU) 2024/3228 och plattformen stängdes 20 juli 2025. +// Länken till ec.europa.eu/odr har legat i tiotusentals fotnoter sedan +// 2016 och pekar numera ingenstans. Den som låtit den ligga kvar visar +// upp en död hänvisning som en lagstadgad upplysning. +// +// § 5 TMG HETER § 5 DDG. Telemediengesetz ersattes av +// Digitale-Dienste-Gesetz den 14 maj 2024. Innehållet i plikten är +// oförändrat; hänvisningen är det inte. + +/** Fälten, med sin rättsliga grund. `kravs` styr vad `saknade()` larmar om. */ +export const FALT = [ + { + id: "foretagsnamn", + rubrik: "Firma", + grund: "§ 5 Abs. 1 Nr. 1 DDG", + kravs: true, + hjalp: "Fullständigt firmanamn inklusive bolagsform (GmbH, AB, Ltd).", + }, + { + id: "adress", + rubrik: "Anschrift", + grund: "§ 5 Abs. 1 Nr. 1 DDG", + kravs: true, + hjalp: "Gatuadress. En postbox räcker inte — adressen ska gå att delge på.", + }, + { + id: "foretradare", + rubrik: "Vertretungsberechtigte", + grund: "§ 5 Abs. 1 Nr. 1 DDG", + kravs: true, + hjalp: "Behörig företrädare, t.ex. verkställande direktör eller Geschäftsführer.", + }, + { + id: "epost", + rubrik: "E-Mail", + grund: "§ 5 Abs. 1 Nr. 2 DDG", + kravs: true, + hjalp: "En adress som läses. Ett kontaktformulär ersätter den inte.", + }, + { + id: "telefon", + rubrik: "Telefon", + grund: "§ 5 Abs. 1 Nr. 2 DDG", + kravs: true, + hjalp: "Snabb elektronisk kontakt ska vara möjlig; telefon är det som normalt godtas.", + }, + { + id: "register", + rubrik: "Registergericht und -nummer", + grund: "§ 5 Abs. 1 Nr. 4 DDG", + kravs: true, + hjalp: "Handelsregister och HRB-nummer, eller motsvarande register i hemlandet.", + }, + { + id: "momsnummer", + rubrik: "Umsatzsteuer-Identifikationsnummer", + grund: "§ 27a UStG", + kravs: true, + hjalp: "VAT-nummer, om ett sådant finns.", + }, + { + id: "tillsyn", + rubrik: "Aufsichtsbehörde", + grund: "§ 5 Abs. 1 Nr. 3 DDG", + kravs: false, + hjalp: "Endast om verksamheten kräver tillstånd. Diagnostikverktyg gör normalt inte det.", + }, + { + id: "ansvarig_innehall", + rubrik: "Inhaltlich Verantwortlicher", + grund: "§ 18 Abs. 2 MStV", + kravs: false, + hjalp: "Krävs för journalistiskt-redaktionellt innehåll. En produktwebbplats behöver det sällan.", + }, + { + id: "dataskyddsombud", + rubrik: "Datenschutzbeauftragter", + grund: "Art. 37 DSGVO", + kravs: false, + hjalp: "Krävs vid regelbunden och systematisk övervakning i stor omfattning, eller känsliga uppgifter i stor omfattning.", + }, +]; + +/** + * Handlingar som ska finnas och nås från varje sida. + * + * `nar` säger när kravet inträder — flera av dem är nya, och en fot som + * byggdes för två år sedan saknar dem utan att någon märkt det. + */ +export const HANDLINGAR = [ + { + id: "impressum", + rubrik: "Impressum", + grund: "§ 5 DDG (ersatte § 5 TMG den 14 maj 2024)", + kravs: true, + nar: "Alltid, för kommersiella digitala tjänster.", + }, + { + id: "dataskydd", + rubrik: "Datenschutzerklärung", + grund: "Art. 13–14 DSGVO", + kravs: true, + nar: "Alltid när personuppgifter behandlas.", + }, + { + id: "villkor", + rubrik: "Allgemeine Geschäftsbedingungen", + grund: "§§ 305 ff. BGB", + kravs: true, + nar: "När standardvillkor används.", + }, + { + id: "tillganglighet", + rubrik: "Erklärung zur Barrierefreiheit", + grund: "BFSG (Barrierefreiheitsstärkungsgesetz), i kraft 28 juni 2025", + kravs: true, + nar: "Tjänster till konsument. Mikroföretag som endast tillhandahåller tjänster är undantagna.", + }, + { + id: "tvistlosning", + rubrik: "Verbraucherstreitbeilegung", + grund: "§ 36 VSBG", + kravs: true, + nar: "Besked om man deltar i tvistlösning inför konsumentskiljenämnd — även ett nej ska stå.", + }, + { + id: "visselblasare", + rubrik: "Hinweisgeberkanal", + grund: "HinSchG", + kravs: false, + nar: "Från 50 anställda.", + }, +]; + +/** + * Vad som INTE ska stå i foten längre. + * + * En upphävd hänvisning är inte harmlös: den ser ut som en upplysning och + * pekar ingenstans. + */ +export const AVSKAFFAT = [ + { + id: "odr", + rubrik: "Länk till EU:s ODR-plattform", + grund: "Förordning (EU) 524/2013 upphävd genom förordning (EU) 2024/3228", + text: + "Plattformen togs ur drift den 20 juli 2025 och tog inte emot nya ärenden efter 20 mars 2025. Länken ska tas bort helt, inte bytas ut.", + }, + { + id: "tmg", + rubrik: 'Hänvisning till "§ 5 TMG"', + grund: "Telemediengesetz ersatt av Digitale-Dienste-Gesetz 14 maj 2024", + text: "Plikten är oförändrad, men paragrafen heter § 5 DDG. Enklast är att inte hänvisa till paragrafen alls.", + }, +]; + +/** + * Vilka obligatoriska fält som saknas. + * + * Driften kan spärra en driftsättning på den här listan i stället för att + * upptäcka bristen när ett avmahnungsbrev kommer. + */ +export function saknade(uppgifter = {}) { + return FALT.filter((f) => f.kravs && !String(uppgifter[f.id] ?? "").trim()).map((f) => ({ + id: f.id, + rubrik: f.rubrik, + grund: f.grund, + })); +} + +/** Är uppgifterna kompletta nog för att visas som ett riktigt Impressum? */ +export function komplett(uppgifter = {}) { + return saknade(uppgifter).length === 0; +} diff --git a/felsokning/app/services/gemensam/forsakring.mjs b/felsokning/app/services/gemensam/forsakring.mjs new file mode 100644 index 0000000..f615863 --- /dev/null +++ b/felsokning/app/services/gemensam/forsakring.mjs @@ -0,0 +1,227 @@ +// ALVA-SPEC-041 · Försäkringsvillkor som underlag. +// +// ---- Varför den här modulen aldrig svarar ja eller nej ------------------ +// +// Den avgör ingenting. Den kan inte avgöra något, och en version som såg +// ut att göra det vore skadlig på ett sätt som inte märks förrän någon +// förlitat sig på den. +// +// Skälet är inte försiktighet utan sakförhållandet: vad som gäller +// bestäms av KUNDENS EGET försäkringsbrev och de villkor som gällde när +// avtalet tecknades — inte av vad ett bolag skriver på sin webbplats i +// dag, och inte av vad vi noterat. Samma bolag har flera produkter med +// olika gränser, gränserna ändras, och ett tecknat avtal följer sina egna +// villkor även efter att bolaget ändrat dem. +// +// Modulen ger därför tre saker och inte ett fjärde: +// +// 1. VAD SOM AVGÖR frågan — de villkorstyper som styr maskinskada, +// oberoende av bolag. Den delen är stabil. +// 2. VAD REGISTRET SÄGER — noterade gränser per bolag och produkt, med +// källa och datum. Underlag för ett samtal, inte ett besked. +// 3. VAD SOM MÅSTE KONTROLLERAS i det enskilda fallet. +// +// Det fjärde — ett utfall — finns inte, och `bedom()` returnerar med +// avsikt inget fält som liknar ett. +// +// ---- Registrets hållbarhet ---------------------------------------------- +// +// Uppgifterna nedan är avlästa från bolagens egna sidor vid det datum som +// står på varje post. De åldras. En post äldre än GILTIGHETSTID_DAGAR +// märks som föråldrad i gränssnittet i stället för att tigas ihjäl — +// en gammal siffra som ser aktuell ut är värre än ingen siffra. +// +// Organisationen kan lägga till bolag och produkter. Ett register som +// bara vi fyller kommer aldrig att täcka de mindre bolagen. + +/** En post äldre än så här märks som föråldrad. */ +export const GILTIGHETSTID_DAGAR = 180; + +/** + * Vad som avgör en maskinskadefråga, oavsett bolag. + * + * Den här listan är modulens stabila del. Gränserna nedanför ändras; + * frågorna gör det inte. + */ +export const AVGORANDE_VILLKOR = [ + { + id: "alder", + rubrik: "Vehicle age", + fraga: "Is the vehicle within the age limit of the policy in force?", + text: "Machine damage cover ends at an age limit that differs by insurer and by product tier within the same insurer.", + }, + { + id: "stracka", + rubrik: "Odometer", + fraga: "Is the recorded distance within the limit?", + text: "Age and distance are alternative ceilings — whichever is reached first ends the cover. The odometer reading therefore has to be documented, not estimated.", + }, + { + id: "plotsligt", + rubrik: "Sudden and unforeseen", + fraga: "Is the failure sudden and unforeseen, rather than gradual?", + text: "Machine damage cover is written for sudden internal failure. Gradual deterioration, wear and ageing are normally excluded — and this is the exclusion most claims turn on.", + }, + { + id: "komponent", + rubrik: "Component in scope", + fraga: "Is the failed component named in the policy's list?", + text: "Cover is usually limited to an enumerated list — powertrain, steering, brake and electrical systems are common; wear parts, exhaust and bodywork usually are not.", + }, + { + id: "underhall", + rubrik: "Maintenance history", + fraga: "Has the vehicle been serviced according to the manufacturer's schedule?", + text: "Neglected or undocumented servicing is a common ground for reduction or refusal. The service record is evidence, and its absence is also evidence.", + }, + { + id: "orsak", + rubrik: "Excluded cause", + fraga: "Was the failure caused by something the policy excludes?", + text: "Wrong fuel, lack of oil or coolant, frost damage, tuning or modification, competition use and consequential damage from an earlier unrepaired fault are typical exclusions.", + }, + { + id: "sjalvrisk", + rubrik: "Deductible", + fraga: "What is the deductible, and does it exceed the repair cost?", + text: "Deductibles are often graded by vehicle age. A claim below the deductible is not worth filing, and saying so early is part of the service.", + }, +]; + +/** + * Noterade gränser per bolag och produkt. + * + * `kontrollerad` är avläsningsdatum, inte giltighetsdatum. Ingen post här + * uttalar sig om vad som gäller för en enskild kund. + */ +export const BOLAG = [ + { + id: "if", + namn: "If", + marknad: "SE", + produkter: [ + { namn: "Maskinskadeförsäkring", maxAlderAr: 10, maxMil: 12_000 }, + { namn: "Stor bilförsäkring", maxAlderAr: 10, maxMil: 15_000 }, + ], + kalla: "https://www.if.se/privat/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "folksam", + namn: "Folksam", + marknad: "SE", + produkter: [ + { namn: "Maskinskadeförsäkring", maxAlderAr: 8, maxMil: 15_000 }, + { namn: "Bilförsäkring Stor", maxAlderAr: 10, maxMil: 15_000 }, + ], + kalla: "https://www.folksam.se/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "trygghansa", + namn: "Trygg-Hansa", + marknad: "SE", + produkter: [ + { namn: "Maskinskadeförsäkring", maxAlderAr: 8, maxMil: 12_000 }, + { namn: "Bilförsäkring stor / märkesförsäkring", maxAlderAr: 10, maxMil: 15_000 }, + ], + kalla: "https://www.trygghansa.se/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "svedea", + namn: "Svedea", + marknad: "SE", + produkter: [{ namn: "Maskinskadeförsäkring", maxAlderAr: 10, maxMil: 15_000 }], + kalla: "https://www.svedea.se/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "watercircles", + namn: "WaterCircles", + marknad: "SE", + produkter: [{ namn: "Maskinskadeförsäkring", maxAlderAr: 12, maxMil: 17_000 }], + kalla: "https://www.watercircles.se/tips-och-rad/maskinskadeforsakring/10925/", + kontrollerad: "2026-08-06", + }, + { + id: "hedvig", + namn: "Hedvig", + marknad: "SE", + produkter: [{ namn: "Maskinskadeskydd", maxAlderAr: 8, maxMil: 12_000 }], + kalla: "https://www.hedvig.com/se/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, +]; + +/** Är posten äldre än giltighetstiden? */ +export function foraldrad(post, nu = new Date()) { + const d = new Date(post.kontrollerad); + if (Number.isNaN(d.getTime())) return true; + return (nu - d) / 86_400_000 > GILTIGHETSTID_DAGAR; +} + +/** Bredaste och smalaste noterade gräns — spannet, inte ett svar. */ +export function spann(bolag = BOLAG) { + const produkter = bolag.flatMap((b) => b.produkter); + if (produkter.length === 0) return null; + return { + alderAr: { minsta: Math.min(...produkter.map((p) => p.maxAlderAr)), storsta: Math.max(...produkter.map((p) => p.maxAlderAr)) }, + mil: { minsta: Math.min(...produkter.map((p) => p.maxMil)), storsta: Math.max(...produkter.map((p) => p.maxMil)) }, + }; +} + +/** + * Underlag för ett samtal med kunden. INTE ett utfall. + * + * Returnerar avsiktligt inget fält som säger täckt eller ej. Det som + * returneras är: vilka noterade produkter fordonet ligger inom respektive + * utanför, vilka frågor som återstår, och det obligatoriska nästa steget + * — att läsa kundens eget försäkringsbrev. + * + * Ett fordon som ligger utanför SAMTLIGA noterade gränser får ändå inget + * "nej". Registret kan vara ofullständigt, produkten kan vara en annan, + * och villkoren vid tecknandet kan ha varit andra. + */ +export function bedom({ alderAr, mil, bolagId } = {}, register = BOLAG, nu = new Date()) { + const urval = bolagId ? register.filter((b) => b.id === bolagId) : register; + + const inom = []; + const utanfor = []; + const oklart = []; + + for (const b of urval) { + for (const p of b.produkter) { + const post = { bolag: b.namn, produkt: p.namn, gransAr: p.maxAlderAr, gransMil: p.maxMil, kalla: b.kalla, foraldrad: foraldrad(b, nu) }; + const alderKant = typeof alderAr === "number"; + const milKant = typeof mil === "number"; + if (!alderKant && !milKant) { + oklart.push(post); + continue; + } + const overAlder = alderKant && alderAr > p.maxAlderAr; + const overMil = milKant && mil > p.maxMil; + if (overAlder || overMil) utanfor.push(post); + else if (alderKant && milKant) inom.push(post); + else oklart.push(post); + } + } + + return { + inom, + utanfor, + oklart, + // Frågorna som återstår oavsett vad gränserna säger. Ålder och + // sträcka är bara två av sju, och de fem andra avgör oftare. + kvarstaende: AVGORANDE_VILLKOR.filter((v) => !["alder", "stracka"].includes(v.id)), + nastaSteg: + "Read the customer's own policy document and the terms in force when it was taken out. The register is a starting point for the conversation, never the answer.", + }; +} + +/** Förbehållet. Ska stå i varje vy som visar registret. */ +export const FORBEHALL = + "ALVA does not decide whether a claim is covered and cannot do so. Cover is determined by the customer's own " + + "policy and the terms in force when it was taken out — not by this register, which records what insurers " + + "published on the dates shown. Figures age, insurers differ between their own product tiers, and an existing " + + "policy keeps its original terms. Use this to frame the conversation and the questions; the insurer decides."; diff --git a/felsokning/app/services/gemensam/garantier.mjs b/felsokning/app/services/gemensam/garantier.mjs new file mode 100644 index 0000000..18fde23 --- /dev/null +++ b/felsokning/app/services/gemensam/garantier.mjs @@ -0,0 +1,396 @@ +// ALVA-SPEC-040 · Garantiregimer, EU och USA. +// +// ---- Vad den här modulen är, och inte är ------------------------------- +// +// Den är beslutsstöd för verkstaden, inte juridisk rådgivning. Varje regel +// bär sin källa och det datum uppgiften kontrollerades, så att den som +// lutar sig mot den kan gå till originaltexten i stället för att tro på +// oss. En regel utan källa hör inte hemma här. +// +// ---- Den skiljelinje allt annat hänger på ------------------------------ +// +// LAGSTADGAT och AVTALAT är inte samma sak, och blandas ihop dagligen i +// verkstadsledet. +// +// Lagstadgat Följer av lag eller förordning. Gäller oavsett vad någon +// skrivit i ett garantihäfte. Går inte att avtala bort mot +// en konsument. +// +// Avtalat Tillverkarens frivilliga löfte. Villkoren är dess egna +// och varierar mellan märken, marknader och årsmodeller. +// +// Det som förvånar är hur lite av det verkstaden dagligen kallar "garanti" +// som faktiskt är lagstadgat: +// +// NYBILSGARANTI avtalad. Ingen lag i EU eller USA kräver den. +// ROSTSKYDDSGARANTI avtalad. Ingen lag kräver den, någonstans. +// LACKGARANTI avtalad. +// DRIVLINEGARANTI avtalad. +// AVGASGARANTI LAGSTADGAD i bägge jurisdiktionerna, med olika +// längd, och den är den enda av dem som är det. +// +// Att påstå att en rostskyddsgaranti är lagstadgad är alltså fel, och att +// tro att en avgasgaranti bara är en tillverkarutfästelse är fel åt andra +// hållet — det andra felet kostar kunden pengar. +// +// ---- Vad som INTE finns här -------------------------------------------- +// +// Inga märkesvillkor. De är tusentals, ändras per årsmodell och marknad, +// och en föråldrad siffra här vore värre än ingen siffra alls. Modulen +// säger vad LAGEN kräver som golv och vilka kategorier av avtalade +// garantier som brukar finnas — inte vad ett visst märke lovar. + +/** Datum då uppgifterna nedan senast kontrollerades mot källorna. */ +export const KONTROLLERAD = "2026-08-06"; + +/** + * Regimerna. + * + * `grund` skiljer lagstadgat från avtalat. `bärare` säger VEM som svarar — + * en fråga verkstaden behöver besvara innan den ringer någon: säljaren + * (EU:s konsumentköp) är sällan tillverkaren. + */ +export const REGIMER = [ + // ---- EU, lagstadgat --------------------------------------------------- + { + id: "EU-KONFORMITET", + jurisdiktion: "EU", + grund: "lag", + bärare: "säljaren", + rubrik: "Legal guarantee of conformity", + kortform: "2 years minimum, seller liable", + källa: "Directive (EU) 2019/771, Art. 10–11", + källänk: "https://eur-lex.europa.eu/eli/dir/2019/771/oj/eng", + period: { år: 2 }, + text: + "The seller is liable for any lack of conformity existing at delivery that becomes apparent within two years. A lack of conformity appearing within one year is presumed to have existed at delivery — the seller must prove otherwise. Member States may set longer periods and may extend the reversed burden of proof to two years.", + // Det här är det fel verkstaden gör oftast: skickar kunden till + // tillverkaren för något som är säljarens ansvar. + anmärkning: + "This is a claim against the SELLER, not the manufacturer, and it exists independently of any manufacturer warranty.", + avvikelser: [ + { marknad: "Sverige", text: "Three years (konsumentköplagen 2022:260), reversed burden of proof two years." }, + { marknad: "Frankrike, Portugal", text: "Reversed burden of proof extended to the full two years." }, + ], + }, + { + id: "EU-MVBER", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Servicing outside the authorised network does not void the warranty", + kortform: "Independent servicing is protected", + källa: + "Regulation (EU) 461/2010, prolonged to 31 May 2028 by Regulation (EU) 2023/822; Supplementary Guidelines 2010/C 138/05", + källänk: "https://eur-lex.europa.eu/eli/reg/2023/822/oj/eng", + period: null, + text: + "A manufacturer may not make its warranty conditional on the vehicle being serviced within the authorised network for work not covered by the warranty, nor on the exclusive use of its own branded parts for such work. Warranty cover therefore survives servicing by an independent workshop, provided the work follows the manufacturer's specification and uses parts of matching quality.", + anmärkning: + "The rule that matters most to an independent workshop. Document the specification followed and the part quality — that documentation is what makes the protection usable if the claim is later contested.", + avvikelser: [], + }, + { + id: "EU-AVGAS-EURO6", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Emissions durability — Euro 5/6", + kortform: "5 years / 100 000 km in-service conformity", + källa: "Regulation (EC) No 715/2007, Art. 4(2) and Annex VII", + källänk: "https://eur-lex.europa.eu/eli/reg/2007/715/oj/eng", + period: { år: 5, km: 100_000 }, + text: + "In-service conformity is checked for up to 5 years or 100 000 km, whichever comes first. Durability testing of pollution control devices for type approval covers 160 000 km.", + anmärkning: + "A durability requirement on the manufacturer's type approval — not a consumer warranty in the US sense. It gives no direct claim to the owner; the corresponding owner-facing cover is contractual.", + avvikelser: [], + }, + { + id: "EU-AVGAS-EURO7", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Emissions durability — Euro 7", + kortform: "8 years / 160 000 km, plus 25 % additional lifetime", + källa: "Regulation (EU) 2024/1257, Art. 5 and Annex IV", + källänk: "https://eur-lex.europa.eu/eli/reg/2024/1257/oj/eng", + period: { år: 8, km: 160_000 }, + text: + "The main lifetime for M1 and N1 rises from 5 years/100 000 km to 8 years/160 000 km, with an additional lifetime of a further 25 % (to 10 years/200 000 km) governed by durability multipliers.", + anmärkning: + "Applies to new types from 29 November 2026 and to all new vehicles from 29 November 2027. A vehicle registered before those dates keeps the Euro 6 regime.", + avvikelser: [], + }, + { + id: "EU-BATTERI-EURO7", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Battery durability — Euro 7", + kortform: "≥80 % at 5 y/100 000 km, ≥72 % at 8 y/160 000 km", + källa: "Regulation (EU) 2024/1257, Art. 8 and Annex VII", + källänk: "https://eur-lex.europa.eu/eli/reg/2024/1257/oj/eng", + period: { år: 8, km: 160_000 }, + text: + "For passenger cars (M1) the battery must retain at least 80 % of its energy storage capability after 5 years or 100 000 km, and not fall below 72 % after 8 years or 160 000 km.", + anmärkning: + "A type-approval durability requirement. A measured State of Health below the threshold is evidence in a claim, not a claim in itself — and it must be measured with a traceable instrument to carry that weight.", + avvikelser: [], + }, + + // ---- USA, lagstadgat -------------------------------------------------- + { + id: "US-AVGAS-PRESTANDA", + jurisdiktion: "US", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Federal emissions performance warranty", + kortform: "2 years / 24 000 miles", + källa: "Clean Air Act § 207, 42 U.S.C. § 7541; 40 CFR Part 85 Subpart V", + källänk: "https://www.ecfr.gov/current/title-40/chapter-I/subchapter-C/part-85/subpart-V", + period: { år: 2, miles: 24_000 }, + text: + "The manufacturer must repair, at no cost, a vehicle that fails an approved emissions test during the first 2 years or 24 000 miles because of a defect in an emission control component.", + anmärkning: + "Runs from the date of sale to the first owner and follows the vehicle — a later owner inherits the remaining period.", + avvikelser: [], + }, + { + id: "US-AVGAS-HUVUDDELAR", + jurisdiktion: "US", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Federal warranty — specified major emission control components", + kortform: "8 years / 80 000 miles", + källa: "Clean Air Act § 207(i); 40 CFR § 85.2103", + källänk: "https://www.ecfr.gov/current/title-40/chapter-I/subchapter-C/part-85/subpart-V/section-85.2103", + period: { år: 8, miles: 80_000 }, + text: + "Specified major emission control components carry 8 years or 80 000 miles. For light-duty vehicles, light-duty trucks and medium-duty passenger vehicles these include catalytic converters, SCR catalysts and related components, and particulate filters and traps, on both spark-ignition and compression-ignition engines.", + anmärkning: + "Routinely missed. A catalytic converter on a six-year-old vehicle is frequently quoted to the customer as a paid repair when it is not one.", + avvikelser: [], + }, + { + id: "US-MMWA", + jurisdiktion: "US", + grund: "lag", + bärare: "tillverkaren", + rubrik: "No tie-in: aftermarket parts and independent service", + kortform: "Warranty cannot require branded parts or dealer service", + källa: "Magnuson–Moss Warranty Act, 15 U.S.C. § 2302(c)", + källänk: "https://www.law.cornell.edu/uscode/text/15/2302", + period: null, + text: + "A warrantor may not condition a written or implied warranty on the consumer using an article or service identified by brand or corporate name, unless it is provided free of charge or the FTC has granted a waiver.", + anmärkning: + "The US counterpart to the MVBER rule. Denying a claim requires the manufacturer to show that a specific part actually caused the failure — which is a question of evidence, and therefore a question of what the workshop documented.", + avvikelser: [], + }, + { + id: "US-CA-AVGAS", + jurisdiktion: "US-CA", + grund: "lag", + bärare: "tillverkaren", + rubrik: "California emissions warranty", + kortform: "3 y/50 000 mi, high-priced parts 7 y/70 000 mi", + källa: "13 CCR §§ 2035–2039 (CARB)", + källänk: "https://www.law.cornell.edu/regulations/california/13-CCR-2039", + period: { år: 3, miles: 50_000 }, + text: + "Emission-related parts are warranted 3 years or 50 000 miles. A single warranted part above an annually indexed cost threshold is covered 7 years or 70 000 miles. The threshold was USD 770 for model year 2025.", + anmärkning: + "Applies in California and in the states that have adopted CARB standards — always check which regime the individual vehicle was certified to before quoting the federal figures.", + avvikelser: [], + }, + { + id: "US-CA-BATTERI", + jurisdiktion: "US-CA", + grund: "lag", + bärare: "tillverkaren", + rubrik: "California ZEV battery warranty — Advanced Clean Cars II", + kortform: "≥70 % SoH at 8 y/100 000 mi (MY2026–2030)", + källa: "Advanced Clean Cars II, 13 CCR § 1962.4 (CARB)", + källänk: "https://ww2.arb.ca.gov/resources/fact-sheets/california-vehicle-and-emissions-warranty-periods", + period: { år: 8, miles: 100_000 }, + text: + "For model years 2026–2030 the battery must be warranted to retain at least 70 % of certified State of Health for 8 years or 100 000 miles; from model year 2031 the floor rises to 75 %. Propulsion-related parts follow the 3 y/50 000 mi and 7 y/70 000 mi structure.", + anmärkning: "Separate from the durability requirement that range be retained over 10 years/150 000 miles.", + avvikelser: [], + }, + + // ---- Avtalade kategorier ---------------------------------------------- + // + // Ingen av dem har lagstöd. De tas med därför att de är vad kunden MENAR + // när den säger garanti, och verkstaden måste kunna säga vilket slag av + // löfte den talar om. Spannen är vad som förekommer på marknaden, inte + // vad något märke utfäster. + { + id: "AVTAL-NYBIL", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "New-vehicle warranty", + kortform: "Typically 2–7 years — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "The manufacturer's general warranty against defects. Length, mileage cap and exclusions are set by the manufacturer and vary by brand, market and model year.", + anmärkning: + "No law in the EU or the US requires it. In the EU it sits on top of the statutory conformity right against the seller and cannot reduce it.", + avvikelser: [], + }, + { + id: "AVTAL-ROST", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Corrosion / anti-perforation warranty", + kortform: "Typically 6–12 years — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "Cover against rust perforation from the inside out. Usually excludes surface corrosion, stone-chip damage and rust caused by unrepaired accident damage, and is commonly conditioned on documented periodic inspection.", + anmärkning: + "There is no statutory corrosion warranty anywhere in the EU or the US. The inspection condition is the usual reason a claim is refused — which makes the inspection record the decisive evidence.", + avvikelser: [], + }, + { + id: "AVTAL-LACK", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Paint warranty", + kortform: "Typically 2–3 years — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: "Cover against paint defects. Normally separate from, and shorter than, the corrosion warranty.", + anmärkning: "", + avvikelser: [], + }, + { + id: "AVTAL-DRIVLINA", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Powertrain warranty", + kortform: "Varies — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "Engine, transmission and driveline cover, often longer than the general new-vehicle warranty and frequently conditioned on servicing at the prescribed intervals.", + anmärkning: + "The service-interval condition is legitimate — what is not legitimate is requiring that the service be performed by the authorised network (MVBER; Magnuson–Moss § 2302(c)).", + avvikelser: [], + }, + { + id: "AVTAL-BATTERI", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Traction battery warranty", + kortform: "Commonly 8 years / 160 000 km with a State of Health floor", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "Commercial cover for the traction battery, usually expressed as a period plus a retained State of Health, often around 70 %.", + anmärkning: + "Sits alongside the statutory durability requirements (Euro 7 in the EU, ACC II in California) and is frequently more generous than them.", + avvikelser: [], + }, +]; + +/** Jurisdiktioner som går att välja. `US-CA` ärver de federala reglerna. */ +export const JURISDIKTIONER = [ + { id: "EU", namn: "European Union" }, + { id: "US", namn: "United States (federal)" }, + { id: "US-CA", namn: "United States — California / CARB states" }, +]; + +/** + * Regimer som gäller för en jurisdiktion. + * + * Kalifornien ÄRVER de federala reglerna och lägger sina egna ovanpå. + * Att visa enbart de kaliforniska hade utelämnat Magnuson–Moss, som är + * den regel verkstaden behöver mest av alla. + */ +export function regimerFor(jurisdiktion) { + const gäller = (r) => { + if (r.jurisdiktion === "EU/US") return true; + if (jurisdiktion === "US-CA") return r.jurisdiktion === "US" || r.jurisdiktion === "US-CA"; + return r.jurisdiktion === jurisdiktion; + }; + return REGIMER.filter(gäller); +} + +/** + * Vilka LAGSTADGADE regimer ett visst fordon fortfarande ligger inom. + * + * Returnerar tre listor i stället för ett filtrerat urval, därför att + * "utanför" och "går inte att avgöra" är olika besked och bara det ena + * betyder att kunden ska betala. Saknas ålder eller mätarställning + * hamnar regimen under `oklart` — aldrig under `utanfor`. Att påstå att + * en garanti har gått ut när underlaget inte räcker är precis den sortens + * obefogade säkerhet produkten finns för att undvika. + * + * @param jurisdiktion "EU" | "US" | "US-CA" + * @param fordon { alderAr?, km?, miles? } + */ +export function mojligenGallande(jurisdiktion, fordon = {}) { + const inom = []; + const utanfor = []; + const oklart = []; + + for (const r of regimerFor(jurisdiktion)) { + if (r.grund !== "lag") continue; + if (!r.period) { + // Regler utan tidsgräns — MVBER, Magnuson–Moss — gäller alltid. + inom.push(r); + continue; + } + + const bedömningar = []; + if (typeof r.period.år === "number") { + bedömningar.push(typeof fordon.alderAr === "number" ? fordon.alderAr <= r.period.år : null); + } + if (typeof r.period.km === "number") { + const km = fordon.km ?? (typeof fordon.miles === "number" ? fordon.miles * 1.609344 : null); + bedömningar.push(typeof km === "number" ? km <= r.period.km : null); + } + if (typeof r.period.miles === "number") { + const miles = fordon.miles ?? (typeof fordon.km === "number" ? fordon.km / 1.609344 : null); + bedömningar.push(typeof miles === "number" ? miles <= r.period.miles : null); + } + + // Perioderna är "whichever comes first": ett överskridet mått räcker + // för att regimen ska vara passerad, oavsett vad det andra säger. + if (bedömningar.some((b) => b === false)) utanfor.push(r); + else if (bedömningar.some((b) => b === null)) oklart.push(r); + else inom.push(r); + } + + return { inom, utanfor, oklart }; +} + +/** + * Det verkstaden behöver få veta oavsett ärende. + * + * Bägge jurisdiktionerna har en regel som säger samma sak: att arbetet + * utförs här upphäver inte garantin. Den är värd att lyfta separat, + * eftersom motsatsen ofta påstås — och ibland av kunden själv. + */ +export function skyddadReparation(jurisdiktion) { + return regimerFor(jurisdiktion).find((r) => r.id === (jurisdiktion === "EU" ? "EU-MVBER" : "US-MMWA")) ?? null; +} + +/** Förbehållet. Står i varje vy som visar regimerna. */ +export const FORBEHALL = + "Decision support, not legal advice. Figures are statutory minimums verified against the cited sources on " + + `${KONTROLLERAD}; contractual warranties are set by the manufacturer and are not reproduced here. ` + + "Verify against the current text of the cited instrument before relying on it in a dispute."; diff --git a/felsokning/app/services/gemensam/grind.mjs b/felsokning/app/services/gemensam/grind.mjs new file mode 100644 index 0000000..490b95f --- /dev/null +++ b/felsokning/app/services/gemensam/grind.mjs @@ -0,0 +1,313 @@ +// Kvalitetsgrinden på serversidan. +// +// Revisionen (docs/QUALITY-AUDIT.md, C-2) fann att grinden bara fanns i +// webbläsaren. Ett ärende kunde därmed avslutas utan evidens genom ett +// anrop till API:t, och rapporten presenterade det som komplett — trots +// att dokumentationen påstod att systemet aldrig kan skriva en slutsats +// ECM inte godkänt. +// +// Modulen utvärderar de obligatoriska raderna direkt mot händelseloggen. +// Klientens ecm.ts har fler rader (råd, rekommendationer, härledd +// evidensnivå per påstående) och fortsätter vara vägledningen i +// gränssnittet. Det som blockerar ett avslut bor här. +// +// Avsiktligt: reglerna är rena funktioner av (händelser, metodik). Ingen +// databasåtkomst, ingen tid, inget slumpmässigt — samma logg ger alltid +// samma utfall, vilket är vad som gör en spärr granskbar. + +import { granskaSlutsats } from "./motivering.mjs"; +import { inomTak, sakerhetstak } from "./sakerhet.mjs"; +import { STANDARD, arJakande, arendetypNu, t } from "./sprak/index.mjs"; + +const UNDANTAG_MOTIVERAT = (h) => typeof h.undantag === "string" && h.undantag.trim().length > 0; + +// Frågor som måste besvaras jakande för att arbetet ska få avslutas. +// Speglar SPARRFRAGOR i app/src/felsokning/metodik.ts; ett test jämför +// listorna så de inte kan glida isär. +export const SPARRFRAGOR = { + "hogvolt/sakerhet/behorighet": "grind.hogvolt.behorighet", + "hogvolt/sakerhet/avstangt": "grind.hogvolt.spanningslos", +}; + +/** Händelser av en viss typ. */ +const av = (handelser, typ) => handelser.filter((h) => h.typ === typ); + +/** + * Utvärderar avslutsgrinden. + * + * @returns lista med hinder; tom lista betyder att ärendet får avslutas. + */ +export function grinda(handelser, metodik, sprak = STANDARD) { + const hinder = []; + + // Hindret bär både nyckeln och den färdiga texten. Nyckeln därför att + // en klient som byter språk ska kunna rendera om utan att fråga + // servern; texten därför att ett hinder som passerar genom en logg + // eller en PDF måste vara läsbart utan katalogen. + const krav = (id, nyckel, ok, detaljNyckel, variabler) => { + if (ok) return; + const post = { id, nyckel, rubrik: t(sprak, nyckel, variabler) }; + if (detaljNyckel) { + post.detaljNyckel = detaljNyckel; + post.detalj = t(sprak, detaljNyckel, variabler); + } + hinder.push(post); + }; + + // Fritext som kommer ur data, inte ur katalogen — namnen på metodikens + // kontroller. De står på metodikens språk och kan inte översättas här. + const kravMedData = (id, nyckel, ok, detalj) => { + if (ok) return; + const post = { id, nyckel, rubrik: t(sprak, nyckel) }; + if (detalj) post.detalj = detalj; + hinder.push(post); + }; + + krav("objekt", "grind.objekt", handelser.some((h) => h.typ === "objekt_identifierat")); + + // Historiken får besvaras nekande, men då krävs en motivering — annars + // vore "Nej" ett sätt att hoppa över kontrollen utan att det syns. + const historik = av(handelser, "historik_kontrollerad").at(-1); + krav( + "historik", + "grind.historik", + Boolean(historik) && (historik.kontrollerad || (historik.kommentar ?? "").trim().length > 0), + historik ? "grind.historik.nekad" : "grind.historik.saknas", + ); + + // Mätarställningen ska vara FOTOGRAFERAD, inte bara inskriven. + // + // Skillnaden är hela dess bevisvärde. En inskriven siffra är teknikerns + // påstående om vad som stod på mätaren; ett foto visar vad som stod + // där. Mätarställningen är samtidigt det som avgör garanti- och + // försäkringsfrågor i efterhand, och den enda uppgift i ärendet som + // någon kan ha ett intresse av att skriva fel. + // + // Klienten krävde redan fotot — men bara klienten, och en regel som + // bara finns i gränssnittet är en vana, inte en spärr. Det är samma + // förhållande som QUALITET C-2 gällde, fast åt andra hållet: här var + // det servern som var svagare, vilket är värre, eftersom servern är + // den som är auktoritativ. + // + // Undantag finns och måste finnas — en trasig display, en timräknare + // som sitter oåtkomligt, en spegling som inte går att fota bort. Men + // undantaget kräver en motivering, av samma skäl som ett nekat + // historiksvar gör det. + for (const [lage, nyckel] of [ + ["ingaende", "grind.matarstallning.ingaende"], + ["utgaende", "grind.matarstallning.utgaende"], + ]) { + const poster = av(handelser, "matarstallning").filter((h) => h.lage === lage); + const dokumenterad = poster.length > 0; + const fotograferad = poster.some((h) => h.bilagaId || h.dataUrl); + const motiverat = poster.some((h) => (h.undantag ?? "").trim().length > 0); + krav( + `matarstallning_${lage}`, + nyckel, + dokumenterad && (fotograferad || motiverat), + dokumenterad ? "grind.matarstallning.ej_foto" : "grind.matarstallning.saknas", + ); + } + + krav("reproducering", "grind.reproducering", handelser.some((h) => h.typ === "reproducering")); + + krav("felorsak", "grind.felorsak", handelser.some((h) => h.typ === "felorsak")); + + // Säkerhetsnivån är ett tak, inte ett val (ALVA-SPEC-071). En felorsak + // som påstår högre säkerhet än underlaget bär spärrar avslutet — inte + // för att slutsatsen är fel, utan för att PÅSTÅENDET om den är det. + // Sänkning prövas aldrig: ärlig osäkerhet är information. + const felorsaken = av(handelser, "felorsak").at(-1); + if (felorsaken?.sakerhet) { + const tak = sakerhetstak(handelser); + krav( + "sakerhetstak", + "grind.sakerhet", + inomTak(felorsaken.sakerhet, tak), + "grind.sakerhet.detalj", + { niva: felorsaken.sakerhet, tak }, + ); + } + + // Åtgärdskedjan. En utebliven åtgärd är ett giltigt utfall — men bara + // när skälet står i loggen. + const atgarder = av(handelser, "atgard_utford"); + const utfordArbete = atgarder.some((h) => h.utford === true); + krav("atgard", "grind.atgard", atgarder.length > 0, "grind.atgard.saknas"); + + if (utfordArbete) { + const beslut = av(handelser, "kundbeslut"); + krav("kundbeslut", "grind.kundbeslut", beslut.length > 0); + + // Hårt fel, inte en varning: arbete utfört trots att kunden avböjt. + krav( + "avbojt_men_utfort", + "grind.kundbeslut.avbojt", + !beslut.some((h) => h.beslut === "avbojt"), + "grind.kundbeslut.avbojt.detalj", + ); + + krav("kvalitetskontroll", "grind.kvalitetskontroll", handelser.some((h) => h.typ === "kvalitetskontroll")); + } + + // Metodikens kontroller: varje kontroll ska bära evidens eller ett + // dokumenterat undantag. Det är den rad som gör metodiken bindande. + const utforda = new Map(); + for (const h of av(handelser, "kontroll_utford")) utforda.set(`${h.stegId}/${h.kontrollId}`, h); + + const saknade = []; + const fotokravande = []; + const foton = av(handelser, "foto").length; + for (const steg of metodik?.steg ?? []) { + for (const kontroll of steg.kontroller ?? []) { + const h = utforda.get(`${steg.id}/${kontroll.id}`); + if (!h) { + saknade.push(`${steg.rubrik} · ${kontroll.text}`); + continue; + } + if (UNDANTAG_MOTIVERAT(h)) continue; + + // En fotokontroll verifieras av fotot. Regeln krävde tidigare ett + // textresultat även här — trots att gränssnittet märker fältet + // "Observation (valfritt)". Grinden nekade därmed avslut på nästan + // varje riktigt ärende, vilket inte syntes så länge klienten hade + // ett eget och mildare villkor (QUALITY-AUDIT-2 · M-7). + if (kontroll.krav === "foto") { + fotokravande.push(kontroll.text); + continue; + } + if (!(h.resultat ?? "").trim()) saknade.push(`${steg.rubrik} · ${kontroll.text} (utan resultat)`); + } + } + kravMedData("metodik_kontroller", "grind.kontroller", saknade.length === 0, saknade.slice(0, 5).join(" · ")); + krav("foton", "grind.foton", foton >= fotokravande.length, fotokravande.length ? "grind.foton.detalj" : undefined, { + kontroller: fotokravande.length, + foton, + }); + + // ALVA-RULE-200 · Slutsatsen. Ett ärende stängs aldrig utan att + // teknikern lämnat ett varför — motiveringen som knyter slutsatsen + // till underlaget, vad som uteslöts, och vad som kvarstår osäkert. + // + // Detta är den rad som saknas i varje verkstadsprotokoll: underlaget + // säger vad som mättes, slutsatsen vad som är fel, men ingenting + // varför det ena medför det andra. Det är precis det steget en + // garantihandläggare eller försäkringsbedömare behöver granska. + const slutsats = av(handelser, "slutsats").at(-1); + for (const brist of granskaSlutsats(slutsats, handelser, sprak)) { + hinder.push({ id: `slutsats_${brist.falt}`, nyckel: brist.nyckel, rubrik: brist.text }); + } + + // Säkerhetsspärrar. Ett nekande svar på en spärrfråga får aldrig + // passera som "besvarad" — se SPARRFRAGOR i klientens metodik.ts. + // Klientens spärr är vägledning; den här är hindret (QUALITET M-2). + for (const [fraga, nyckel] of Object.entries(SPARRFRAGOR)) { + const [metodikId, stegId, frageId] = fraga.split("/"); + if (metodik?.id !== metodikId) continue; + const svaret = av(handelser, "fraga_besvarad").findLast( + (h) => h.stegId === stegId && h.frageId === frageId, + ); + krav(`sparr_${frageId}`, nyckel, arJakande(svaret?.svar), "grind.sparr.ej_uppfyllt"); + } + + // Teknisk eskalering (FGS-1.0 §8, DISS-mönstret): en öppnad förfrågan + // till tillverkare eller garantigivare utan dokumenterat svar spärrar + // avslutet. Bulletinerna kräver att svaret inväntas innan ytterligare + // åtgärder görs — ett ärende som stängs med öppen eskalering har inte + // hela orsakskedjan dokumenterad, och garantianspråket kan ogiltigas. + { + const oppna = new Map(); + for (const h of av(handelser, "eskalering")) { + const nyckel = String(h.referens ?? "").trim(); + if (h.status === "oppnad") oppna.set(nyckel, h); + else if (oppna.has(nyckel)) oppna.delete(nyckel); + else if (nyckel === "" && oppna.size === 1) oppna.clear(); + } + krav("eskalering", "grind.eskalering", oppna.size === 0, "grind.eskalering.detalj", { + referens: [...oppna.keys()].map((r) => r || "—").join(", "), + }); + } + + // Evidensnivån måste överstiga E0 — annars avslutas ett ärende utan + // att någonting alls har dokumenterats. + krav( + "evidens", + "grind.evidens", + handelser.some((h) => ["foto", "video", "matvarde", "observation", "kontroll_utford"].includes(h.typ)), + "grind.evidens.saknas", + ); + + return hinder; +} + +/** + * Extra krav som följer av ärendetypen. Reglerna kommer från regelpaketet + * (ECM Knowledge Library) och är alltså data, inte kod. + */ +export function grindaArendetyp(handelser, regelpaket, sprak = STANDARD) { + // Ärendetypen slås upp i sin nuvarande form. Ett ärende som öppnades + // när typerna hette "Garanti" ska få garantins extra krav även efter + // att de bytt språk — se ARENDETYP_ARV. + const typ = arendetypNu(av(handelser, "arendetyp_satt").at(-1)?.arendetyp); + // Paketet finns i två former: den distribuerade filens rika form + // (arendetypRegler med id/rubrik/detaljVidBrist) och den slimmade + // (arendetyper[typ].krav). Servern matade tidigare in den rika formen + // i en funktion som bara läste den slimmade — varje ärendetypskrav + // blev en tyst no-op på servern, och spärren fanns bara som råd i + // klienten. Det är exakt C-2-mönstret, återuppstått genom ett format. + const regler = [ + ...(regelpaket?.arendetyper?.[typ]?.krav ?? []), + ...(regelpaket?.arendetypRegler?.[typ] ?? []).map((r) => ({ typ: r.krav, rubrik: r.rubrik, detalj: r.detaljVidBrist })), + ]; + const hinder = []; + + const betalareReferens = () => av(handelser, "betalare").some((h) => String(h.referens ?? "").trim()); + const uppfyllt = { + miltal: () => av(handelser, "matarstallning").length > 0, + historik: () => av(handelser, "historik_kontrollerad").some((h) => h.kontrollerad), + foto: () => av(handelser, "foto").length > 0, + // Referensen kan komma ur den skannade arbetsordern ELLER ur en + // registrerad betalare — alla verkstäder skannar inte (FGS-1.0 §4). + claim: () => harReferens(handelser, ["ao_claim", "claim"]) || betalareReferens(), + skadenummer: () => harReferens(handelser, ["ao_skadenummer", "skadenummer"]) || betalareReferens(), + // FGS-1.0 §1/§18: betalaren fastställd — spår och namn — innan + // ärendet kan stängas i de spår där någon annan än kunden betalar. + betalare: () => av(handelser, "betalare").some((h) => String(h.namn ?? "").trim() && String(h.spar ?? "").trim()), + // Goodwill och externa garantigivare kräver godkännande/claim + // authorization INNAN reparationen ersätts (FGS-1.0 §12). + godkannande: () => av(handelser, "betalare").some((h) => String(h.godkannande ?? "").trim()), + }; + + for (const regel of regler) { + const kontroll = uppfyllt[regel.typ ?? regel]; + // En okänd kravtyp får aldrig tolkas som uppfylld. Ett regelpaket som + // inte går att utvärdera ska stoppa avslutet, inte släppa igenom det. + const krav = regel.typ ?? regel; + if (!kontroll) { + hinder.push({ + id: `arendetyp_${krav}`, + nyckel: "grind.arendetyp.okant", + rubrik: t(sprak, "grind.arendetyp.okant", { krav }), + }); + } else if (!kontroll()) { + // Regelpaketets egen rubrik står på regelpaketets språk och går + // inte att översätta här — den är data från kunden, inte vår text. + hinder.push({ + id: `arendetyp_${krav}`, + nyckel: regel.rubrik ? undefined : "grind.arendetyp.krav", + rubrik: regel.rubrik ?? t(sprak, "grind.arendetyp.krav", { krav }), + ...(regel.detalj ? { detalj: regel.detalj } : {}), + }); + } + } + return hinder; +} + +function harReferens(handelser, nycklar) { + for (const h of av(handelser, "arbetsorder_skannad")) { + for (const f of h.falt ?? []) { + if (nycklar.includes(f.id) && String(f.varde ?? "").trim()) return true; + } + } + return false; +} diff --git a/felsokning/app/services/gemensam/handelser.mjs b/felsokning/app/services/gemensam/handelser.mjs new file mode 100644 index 0000000..87738f5 --- /dev/null +++ b/felsokning/app/services/gemensam/handelser.mjs @@ -0,0 +1,226 @@ +// Händelsevalidering vid API-gränsen. +// +// Bakgrund: revisionen (docs/QUALITY-AUDIT.md, C-1 och M-3) fann att servern +// bara kontrollerade att `anvandare` var *en sträng* och `tidpunkt` *sanningsvärd* +// — inte att de var sanna — och att `handelse` togs emot som godtycklig JSON. +// +// Två skilda problem med samma botemedel: +// +// Härkomst Vem som utförde arbetet och när är det som gör loggen till en +// bevis­kedja. Sätts de av anroparen är loggen en signerad behållare +// för overifierade påståenden. Båda härleds nu ur den verifierade +// token respektive serverns klocka. +// +// Form Loggen är append-only. En felaktig post kan aldrig rättas, bara +// kommenteras. Därför måste formen kontrolleras *innan* skrivning — +// efteråt är det för sent för alltid. +// +// Klientens klocka kastas inte bort. Vid offline-arbete är den det enda som +// finns, så den bevaras som `registrerad_tidpunkt` bredvid serverns +// mottagningstid. Glappet blir synligt i stället för osynligt. + +/** Fält som aldrig får komma från klienten. */ +export const SERVERÄGDA_FÄLT = ["anvandare", "tidpunkt"]; + +// Varje händelsetyp med sina obligatoriska fält. Listan är avsiktligt +// fullständig: en typ som inte står här avvisas, eftersom en okänd typ i en +// append-only-logg är permanent skräp. +export const HÄNDELSESCHEMA = { + objekt_identifierat: { objekt: "objekt" }, + arbetsorder_skannad: { falt: "lista" }, + felbeskrivning: { text: "text" }, + arendetyp_satt: { arendetyp: "text" }, + fraga_besvarad: { stegId: "text", frageId: "text", fraga: "text", svar: "text" }, + kontroll_utford: { stegId: "text", kontrollId: "text", text: "text" }, + observation: { text: "text" }, + matvarde: { beskrivning: "text", varde: "text" }, // matdonId valfritt — se M-1 + hypotes: { text: "text", niva: ["medel", "lag"] }, + foto: { beskrivning: "text" }, + video: { beskrivning: "text" }, + matarstallning: { lage: ["ingaende", "utgaende"], varde: "text" }, + historik_kontrollerad: { kontrollerad: "boolean" }, + reproducering: { status: ["ja", "delvis", "nej"], beskrivning: "text" }, + felorsak: { + avvikelse: "text", + orsaker: "lista", + underlag: "lista", + sakerhet: ["hog", "medel", "lag"], + atgard: "text", + }, + atgardsforslag: { beskrivning: "text" }, + kundbeslut: { beslut: ["godkant", "avbojt", "delvis"], kanal: "text" }, + atgard_utford: { beskrivning: "text", utford: "boolean" }, + kvalitetskontroll: { + resultat: ["symptomet_borta", "kvarstar", "delvis", "ej_verifierbar"], + beskrivning: "text", + }, + // FGS-1.0: betalarspåret, eskaleringen och reservdelen är händelser i + // loggen — inte fritext i en kommentar — så att grinden kan räkna på dem. + betalare: { + spar: ["fabriksgaranti", "vagnskadegaranti", "forsakring", "extern_garanti", "leasing", "goodwill", "kund"], + namn: "text", + }, + eskalering: { status: ["oppnad", "besvarad"], beskrivning: "text" }, + reservdel: { artikelnummer: "text", beskrivning: "text" }, + kommentar: { text: "text" }, + kategori_byte: { kategori: "text" }, + inaktivitet_forklarad: { text: "text", minuter: "tal" }, + overlamning: { fran: "text" }, + ansvarig_satt: { ansvarig: "text" }, + ai_svar: { rader: "lista", nastaSteg: "text", modell: "text" }, + export_skapad: { format: "text", version: "tal" }, + // ALVA-RULE-200: teknikerns varför. Fälten valideras utöver formen i + // services/gemensam/motivering.mjs — här kontrolleras bara att de finns. + slutsats: { motivering: "text", uteslutet: "text", kvarstaende: "text" }, + arende_avslutat: {}, +}; + +// ---- Valfria fält (QUALITY-AUDIT-2 · C-5) ------------------------------ +// +// Schemat kontrollerade tidigare bara att de OBLIGATORISKA fälten fanns. +// Det itererade schemats nycklar, aldrig händelsens, så vilket okänt fält +// som helst passerade och sparades ordagrant. +// +// Två garantier vilade på motsatsen. Krypto-shreddingen skyddar en fast +// lista av fältNAMN: ett registreringsnummer på en vanlig observation +// kom aldrig in på listan, krypterades aldrig, och överlevde därför att +// nyckeln förstördes — medan raderingskvittot ändå sa att subjektet var +// raderat. Och delningsfiltret arbetar på typnivå, inte fältnivå, så +// samma fält gick ut i kundens delningslänk. +// +// Därför är listan nedan uttömmande och avvisningen hård. Ett fält som +// inte står här eller bland de obligatoriska finns inte, och en anropare +// som tror sig ha sparat något får aldrig veta att det gick bra. +export const VALFRIA_FÄLT = { + // Bilagefälten ärvs via intersektion med Bilaga i domänmodellen. + arbetsorder_skannad: ["bilagaId", "bilagaHash", "dataUrl"], + foto: ["bilagaId", "bilagaHash", "dataUrl"], + video: ["bilagaId", "bilagaHash", "dataUrl"], + matarstallning: ["undantag", "bilagaId", "bilagaHash", "dataUrl"], + + kontroll_utford: ["resultat", "undantag"], + betalare: ["referens", "godkannande"], + eskalering: ["referens", "kanal"], + reservdel: ["serienummer", "batch", "sparad"], + // `kalla` bär härkomsten för värden som kommit in via ett + // diagnosprotokoll (ALVA-SPEC-020). Fältet måste vara deklarerat: + // integrationen fungerade tidigare bara därför att schemat var öppet. + observation: ["kalla"], + // `kalibreradVidMatning` är serverns stämpel av ett FAKTUM vid + // mottagandet: gällde mätdonets kalibrering då? Grinden får inte + // fråga klockan (samma logg ska alltid ge samma utfall), så faktumet + // måste frysas när det fortfarande är ett faktum. Klientens värde + // skrivs alltid över av mätdonsFakta (TÜV-2 T-13). + matvarde: ["enhet", "matdonId", "matdonBeteckning", "matdonKalibreradTill", "kalibreradVidMatning", "kalla"], + overlamning: ["till"], + historik_kontrollerad: ["kommentar"], + felorsak: ["motivering", "ytterligareKontroller"], + atgardsforslag: ["uppskattadKostnad", "uppskattadTid"], + kundbeslut: ["kommentar", "kontaktperson"], + atgard_utford: ["delar", "motivering"], + slutsats: ["atgardsval", "orsakFastställd"], + // Versionen som gällde VID AVSLUTET. Ritningsstämpeln läste den ur + // den aktuella konstanten, så ett ärende stängt under 1.0 visade 3.1 — + // precis det stämpelns egen beskrivning säger att den inte ska göra. + arende_avslutat: ["signatur", "plattformsversion"], +}; + +/** + * Fält som systemet självt sätter efter valideringen. + * + * `anvandarId` skrivs av tillPost ur den verifierade token, och + * `registrerad_tidpunkt` bevarar klientens klocka vid offline-arbete. + * Bägge måste passera när en redan skriven händelse valideras om. + */ +export const SYSTEMFÄLT = ["typ", "anvandarId", "registrerad_tidpunkt"]; + +// Hypotesen får aldrig anta hög tillförlitlighet — samma regel som +// typsystemet upprätthåller i klienten, upprepad här därför att servern inte +// kan lita på att klienten är vår. +const GILTIG = { + text: (v) => typeof v === "string" && v.length > 0 && v.length <= 20000, + tal: (v) => typeof v === "number" && Number.isFinite(v), + boolean: (v) => typeof v === "boolean", + lista: (v) => Array.isArray(v) && v.length <= 500, + objekt: (v) => v !== null && typeof v === "object" && !Array.isArray(v), +}; + +function fältFel(typ, nyckel, regel, värde) { + if (Array.isArray(regel)) { + return regel.includes(värde) ? null : `${typ}.${nyckel} måste vara en av: ${regel.join(", ")}`; + } + return GILTIG[regel](värde) ? null : `${typ}.${nyckel} har fel form (väntade ${regel})`; +} + +/** + * Validerar en händelses form. Returnerar null när den duger, annars en + * förklaring avsedd att läsas av en människa som felsöker en integration. + */ +export function granskaHändelse(handelse) { + if (handelse === null || typeof handelse !== "object" || Array.isArray(handelse)) { + return "handelse måste vara ett objekt"; + } + const schema = HÄNDELSESCHEMA[handelse.typ]; + if (!schema) { + return `okänd händelsetyp: ${JSON.stringify(handelse.typ)}`; + } + for (const [nyckel, regel] of Object.entries(schema)) { + const fel = fältFel(handelse.typ, nyckel, regel, handelse[nyckel]); + if (fel) return fel; + } + + // C-5: schemat är stängt. Allt som inte är deklarerat avvisas — annars + // kan personuppgifter hängas på en vanlig händelse, undgå krypteringen + // och följa med ut i delningslänken. + const tillåtna = new Set([ + ...SYSTEMFÄLT, + ...Object.keys(schema), + ...(VALFRIA_FÄLT[handelse.typ] ?? []), + ]); + const okända = Object.keys(handelse).filter((n) => !tillåtna.has(n)); + if (okända.length > 0) { + return `${handelse.typ}: okända fält avvisas (${okända.join(", ")})`; + } + return null; +} + +/** + * Bygger den post som faktiskt skrivs. + * + * Härkomsten kommer härifrån och ingen annanstans: `anvandare` ur den + * verifierade token, `tidpunkt` ur serverns klocka. Klientens tidsstämpel + * bevaras separat när den finns, eftersom den är det enda som finns vid + * offline-arbete — men den avgör aldrig när något anses ha skett. + */ +export function tillPost(post, anspr, nu = new Date()) { + const fel = granskaHändelse(post?.handelse); + if (fel) return { fel }; + if (typeof post.id !== "string" || !/^[A-Za-z0-9_:.-]{1,128}$/.test(post.id)) { + return { fel: "id saknas eller har otillåtna tecken" }; + } + + const klient = typeof post.tidpunkt === "string" ? post.tidpunkt : null; + const klientTid = klient && !Number.isNaN(Date.parse(klient)) ? klient : null; + + return { + post: { + id: post.id, + tidpunkt: nu.toISOString(), + anvandare: anspr.namn, + handelse: { + ...post.handelse, + // Fälten nedan går inte att sätta utifrån; skrivs de över här är det + // just poängen. + anvandarId: anspr.sub, + // Signaturen vid avslut var teknikerns egen text — ett fält som + // HETER signatur men inte var en (panelgranskningen, Volvo-sätet). + // Nu skrivs den ur verifierad token, precis som `anvandare`. + // Vad den intygar är därmed exakt vad den kan intyga: vem som var + // inloggad när avslutet togs emot. Kedjans försegling intygar + // resten — att loggen är den som förelåg då. + ...(post.handelse.typ === "arende_avslutat" ? { signatur: anspr.namn } : {}), + ...(klientTid && klientTid !== nu.toISOString() ? { registrerad_tidpunkt: klientTid } : {}), + }, + }, + }; +} diff --git a/felsokning/app/services/gemensam/integration.mjs b/felsokning/app/services/gemensam/integration.mjs new file mode 100644 index 0000000..aa50486 --- /dev/null +++ b/felsokning/app/services/gemensam/integration.mjs @@ -0,0 +1,188 @@ +// ALVA-SPEC-020 · Integration interface. +// +// Verkstaden har redan system: ett DMS, ett diagnosinstrument som +// producerar protokoll, ett videooffertsystem. ALVA ska koppla in sig i +// dem — inte ersätta dem, och inte kräva att de anpassar sig. +// +// ---- Vad jag INTE bygger --------------------------------------------- +// +// Färdiga klienter mot namngivna leverantörer. Jag känner inte Beonodes +// eller ServiceCams faktiska API:er, och en uppfunnen endpoint som ser +// färdig ut är sämre än en tom: den ser ut att fungera tills någon +// försöker, och då är felet dyrare att hitta. +// +// I stället: ett stabilt gränssnitt med PROFILER. En profil beskriver +// vad en kategori av system förväntar sig — fältnamn, format, riktning — +// och valideras mot leverantören innan den märks som `validated`. Tills +// dess står den som `draft`, och det syns i gränssnittet. +// +// Det är samma princip som Knowledge Sources: leverantören är data. +// +// ---- Riktningar -------------------------------------------------------- +// +// IN Diagnosprotokoll, arbetsorder, fordonsdata. Blir händelser i +// loggen med källa och tidsstämpel bevarade. +// UT Ärendet, rapporten, slutsatsen, mediet. Levereras som webhook +// eller hämtas via API. +// +// Riktningen är inte symmetrisk. Inkommande data blir evidens och måste +// därför bära sin härkomst hela vägen; utgående data är en projektion +// och kan formas fritt. + +/** Kategorier av system. Namnen är generiska med avsikt. */ +export const KATEGORIER = { + diagnosprotokoll: { + namn: "Diagnostic protocol", + riktning: "in", + beskrivning: "Fault codes, live data and readouts from a diagnostic instrument.", + // Vad ALVA gör av det: varje avläsning blir evidens, inte en bilaga. + blir: ["matvarde", "observation", "foto"], + }, + dms: { + namn: "Dealer management system", + riktning: "bada", + beskrivning: "Work orders in, completed cases and time records out.", + blir: ["arbetsorder_skannad", "objekt_identifierat"], + }, + videooffert: { + namn: "Video quotation", + riktning: "bada", + beskrivning: "Video evidence in, the case record and closing statement out.", + blir: ["video", "foto"], + }, + fordonsdata: { + namn: "Vehicle data", + riktning: "in", + beskrivning: "Identification, equipment level, service campaigns.", + blir: ["objekt_identifierat"], + }, + garanti: { + namn: "Warranty administration", + riktning: "ut", + beskrivning: "Claim substantiation: evidence chain, closing statement, traceability package.", + blir: [], + }, + forsakring: { + namn: "Insurance assessment", + riktning: "ut", + beskrivning: "Assessor-facing record: what was checked, what was ruled out, and why.", + blir: [], + }, + skadekalkyl: { + namn: "Damage calculation", + riktning: "bada", + beskrivning: + "Damage estimation systems used between workshops and insurers — CABAS is the Nordic standard. " + + "In: damage assessment and vehicle identification. Out: the diagnostic record substantiating that " + + "the damage was investigated rather than assumed.", + blir: ["objekt_identifierat", "observation", "foto"], + // Det ALVA tillför en skadekalkyl är inte fler poster utan + // beviskedjan bakom dem: vad som kontrollerades, vad som uteslöts + // och varför. Det är den enda del av kalkylen som i dag inte går + // att granska i efterhand. + tillfor: ["slutsats", "felorsak", "reproducering"], + }, +}; + +/** + * Profilens mognad. Samma resonemang som konnektorernas livscykel: + * "det finns kod" är inte samma sak som "det fungerar mot leverantören". + */ +export const MOGNAD = ["draft", "tested", "validated"]; + +/** + * Utgående händelser. En integration prenumererar på det den behöver — + * ett videooffertsystem bryr sig inte om varje mätvärde. + */ +export const UTGAENDE = { + "arende.skapat": "A case was opened.", + "arende.fas": "The case entered a new ALVA phase.", + "arende.slutsats": "A closing statement was recorded.", + "arende.avslutat": "The case was closed and passed the quality gate.", + "media.tillagt": "Photo or video evidence was added.", + "atgardsforslag.lamnat": "A repair proposal was issued to the customer.", + "kundbeslut.registrerat": "The customer's decision was recorded.", +}; + +/** + * Signerar en utgående leverans. + * + * HMAC över tidsstämpel och kropp, i det format nästan alla + * webhook-mottagare redan förstår. Tidsstämpeln ingår i signaturen så + * att en avlyssnad leverans inte går att spela upp igen. + */ +export function signeraLeverans(kropp, hemlighet, tidsstampel, hmac) { + const underlag = `${tidsstampel}.${kropp}`; + return `t=${tidsstampel},v1=${hmac("sha256", hemlighet).update(underlag).digest("hex")}`; +} + +/** + * Verifierar en inkommande leverans från oss. + * + * Exponerad så att mottagaren kan använda exakt samma kod som avsändaren + * — de flesta integrationsfel uppstår i glappet mellan två + * implementationer av samma signatur. + */ +export function verifieraLeverans(kropp, huvud, hemlighet, hmac, timingSafeEqual, nu = Date.now()) { + const delar = Object.fromEntries( + String(huvud ?? "") + .split(",") + .map((d) => d.split("=")), + ); + if (!delar.t || !delar.v1) return { ok: false, orsak: "Malformed signature header." }; + // Fem minuter. Nog för klockglapp, kort nog att en fångad leverans + // inte går att spela upp i morgon. + if (Math.abs(nu - Number(delar.t) * 1000) > 300_000) return { ok: false, orsak: "Timestamp outside tolerance." }; + + const väntad = hmac("sha256", hemlighet).update(`${delar.t}.${kropp}`).digest("hex"); + const a = Buffer.from(delar.v1); + const b = Buffer.from(väntad); + if (a.length !== b.length || !timingSafeEqual(a, b)) return { ok: false, orsak: "Signature mismatch." }; + return { ok: true }; +} + +/** + * Normaliserar ett inkommande diagnosprotokoll till händelser. + * + * Profilen beskriver var värdena ligger i leverantörens format; den här + * funktionen gör dem till evidens. Härkomsten bevaras i varje händelse — + * ett värde som kommit utifrån får aldrig se ut som något teknikern + * själv mätt. + */ +export function protokollTillHandelser(protokoll, profil, kalla) { + const ut = []; + // Vägen kommer från leverantörsprofilen, alltså utifrån. Uppslaget + // begränsas till egna, uppräkningsbara egenskaper: annars når en + // profil med `__proto__` eller `constructor.prototype` fram till + // prototypkedjan. Läsningen är visserligen ofarlig i sig, men en + // extern indata ska inte ha den friheten (QUALITY-AUDIT-2 · m-8). + const plocka = (objekt, vag) => + String(vag ?? "") + .split(".") + .reduce((o, n) => (o !== null && typeof o === "object" && Object.hasOwn(o, n) ? o[n] : undefined), objekt); + + for (const kod of plocka(protokoll, profil?.felkoder?.vag) ?? []) { + ut.push({ + typ: "observation", + text: `${plocka(kod, profil.felkoder.kod) ?? "?"} — ${plocka(kod, profil.felkoder.text) ?? "utan beskrivning"}`, + kalla, + }); + } + + for (const värde of plocka(protokoll, profil?.matvarden?.vag) ?? []) { + ut.push({ + typ: "matvarde", + beskrivning: String(plocka(värde, profil.matvarden.beskrivning) ?? "Avläst värde"), + varde: String(plocka(värde, profil.matvarden.varde) ?? ""), + enhet: plocka(värde, profil.matvarden.enhet) ?? undefined, + // Instrumentets identitet följer med när leverantören lämnar den. + // Utan den nedgraderas värdet till E1 — ett avläst tal utan känt + // instrument är inte en spårbar mätning (QUALITY-AUDIT M-1). + matdonId: plocka(värde, profil.matvarden.instrumentId) ?? undefined, + matdonBeteckning: plocka(värde, profil.matvarden.instrument) ?? undefined, + kalla, + }); + } + + return ut.filter((h) => h.typ !== "matvarde" || h.varde !== ""); +} diff --git a/felsokning/app/services/gemensam/kedja.mjs b/felsokning/app/services/gemensam/kedja.mjs new file mode 100644 index 0000000..c742b9a --- /dev/null +++ b/felsokning/app/services/gemensam/kedja.mjs @@ -0,0 +1,108 @@ +// ALVA-SPEC-070 · Hashkedjan. +// +// ---- Varför triggrar inte räcker ---------------------------------------- +// +// Loggen är append-only, genomdrivet av databastriggrar. Panelgranskningen +// (docs/PANELGRANSKNING-2026-08.md, datasäkerhetssätet) satte fingret på +// gränsen för det skyddet: en trigger skyddar mot APPLIKATIONEN, inte mot +// den som äger databasen. Den som har superuser släpper triggern, ändrar +// raden och återskapar triggern, och ingenting syns. +// +// För ett system vars hela värde är bevisvärde är det fel viloläge. En +// hashkedja flyttar skyddet från behörighet till matematik: varje händelse +// bär en hash av sitt eget innehåll OCH föregående händelses hash. Den som +// ändrar en rad i efterhand bryter varje efterföljande länk, och brottet +// går att peka ut — inte bara konstatera. +// +// ---- Vad kedjan bevisar, och inte --------------------------------------- +// +// Kedjan bevisar att loggen inte ändrats SEDAN DEN SKREVS, i den ordning +// den skrevs. Den bevisar inte att det som skrevs var sant — det är +// grindens och evidensmodellens uppgift — och den bevisar ingenting om +// tiden FÖRE serverns mottagande. Rapporten ska säga just det och inte +// mer: "innehållet är oförändrat sedan mottagandet". +// +// Förseglingen vid avslut (HMAC över roten med en nyckel utanför +// databasen) skyddar mot nästa angrepp i ordningen: den som äger databasen +// och räknar OM hela kedjan efter sin ändring. Utan nyckeln kan en +// omräknad kedja inte förseglas om, och en försegling som inte stämmer +// mot roten är beviset. +// +// ---- Konstruktion -------------------------------------------------------- +// +// länk_n = SHA256(länk_{n-1} · id · tidpunkt · anvandare · digest) +// +// `digest` är innehållshashen av den kanoniska händelsen — samma +// `klientdigest` som kollisionsprövningen redan använder, så kedjan och +// dubblettskyddet kan aldrig ha olika uppfattning om vad en händelse +// "är". Fälten skiljs med radbrytning: utan avskiljare vore +// ("ab","c") och ("a","bc") samma länk. +// +// Första länken utgår från ärende-id:t i stället för en tom sträng, så +// att två ärenden med identisk första händelse ändå får olika kedjor — +// en kedja ska inte gå att lyfta från ett ärende till ett annat. + +import { createHash, createHmac, timingSafeEqual } from "node:crypto"; + +/** Kedjans startvärde för ett ärende. */ +export const grund = (arendeId) => + createHash("sha256").update(`alva-kedja\n${arendeId}`).digest("hex"); + +/** Nästa länk. Alla fält är strängar; digest är händelsens innehållshash. */ +export function lank(foregaende, { id, tidpunkt, anvandare, digest }) { + return createHash("sha256") + .update(`${foregaende}\n${id}\n${tidpunkt}\n${anvandare}\n${digest ?? ""}`) + .digest("hex"); +} + +/** + * Verifierar en hel kedja mot lagrade länkar. + * + * @param arendeId ärendet kedjan tillhör + * @param rader [{id, tidpunkt, anvandare, digest, kedjehash}] i kedjeordning + * @returns {ok, rot, brott} — brott pekar ut FÖRSTA raden som inte + * stämmer. Allt efter den är följdfel och räknas inte upp: + * den som läser ska laga ett brott i taget, inte skrämmas av + * hundra. + * + * Rader utan lagrad kedjehash (skrivna innan kolumnen fanns) bryter inte + * kedjan men märks som `okedjade` — tystnad hade låtit gammal data se + * starkare ut än den är, och det är precis den sortens övertro kedjan + * finns för att förhindra. + */ +export function verifiera(arendeId, rader) { + let h = grund(arendeId); + let okedjade = 0; + for (let i = 0; i < rader.length; i++) { + const rad = rader[i]; + h = lank(h, rad); + if (rad.kedjehash == null) { + okedjade++; + continue; + } + if (rad.kedjehash !== h) { + return { ok: false, rot: null, okedjade, brott: { index: i, id: rad.id } }; + } + } + return { ok: true, rot: rader.length > 0 ? h : grund(arendeId), okedjade }; +} + +/** + * Förseglar en kedjerot med en nyckel som hålls utanför databasen. + * + * HMAC, inte hash: poängen är att den som äger databasen inte kan räkna + * om förseglingen efter en ändring. Nyckeln är serverns, inte + * teknikerns — förseglingen intygar att PLATTFORMEN såg denna rot vid + * denna tid, ingenting om vem som tryckte på knappen. Det intyget står + * händelsens serverägda `anvandare`-fält för. + */ +export function forsegla(nyckel, arendeId, rot, tidpunkt) { + return createHmac("sha256", nyckel).update(`${arendeId}\n${rot}\n${tidpunkt}`).digest("hex"); +} + +/** Prövar en försegling. Konstanttid — en försegling är en hemlighet. */ +export function provaForsegling(nyckel, arendeId, rot, tidpunkt, forsegling) { + const ratt = Buffer.from(forsegla(nyckel, arendeId, rot, tidpunkt), "hex"); + const given = Buffer.from(String(forsegling ?? ""), "hex"); + return ratt.length === given.length && given.length > 0 && timingSafeEqual(ratt, given); +} diff --git a/felsokning/app/services/gemensam/metodiker.mjs b/felsokning/app/services/gemensam/metodiker.mjs new file mode 100644 index 0000000..cfc5b11 --- /dev/null +++ b/felsokning/app/services/gemensam/metodiker.mjs @@ -0,0 +1,967 @@ +// Metodikbiblioteket — delad kod. +// +// Låg tidigare bara i klienten, vilket gjorde att servern inte kunde +// utvärdera kvalitetsgrindens metodikrader (QUALITET C-2). Datan bor nu +// här; app/src/felsokning/metodiker.ts återexporterar den med typer, så +// det finns fortfarande exakt en sanning. +// +// En metodik är inte en checklista över vad som brukar vara fel — det +// vore att gissa åt teknikern. Den är en ordning för att utesluta: +// symptomet verifieras först, sedan det billiga och synliga, sedan det +// som kräver mätning, sist det som kräver demontering. +// +// Tre regler gäller alla metodiker här: +// +// 1. Varje kontroll har ett minimikrav (mätvärde, foto eller +// observation). En kryssruta är inte evidens. +// 2. Symptomsteget frågar aldrig "vad är fel" utan "vad händer, när, +// var". Kundens ord blir ett verifierat symptom först när det +// reproducerats. +// 3. Där arbetet kan skada någon kommer säkerhetssteget först och kan +// inte hoppas över — se HÖGVOLT. +// +// Att "täcka allt" går inte att lova. Det som går är att täcka fordonets +// system systematiskt och låta GENERISK vara ett strukturellt komplett +// skyddsnät: den fungerar även för det ingen förutsett. +// +// ---- Språk och granskningsstatus (ALVA-SPEC-060) ------------------------ +// +// Texten är skriven på svenska och översatt till engelska när engelska +// blev källspråk. Det är alltså en ÖVERSÄTTNING som ännu inte lästs av en +// fackman på målspråket, och den ska behandlas som en sådan tills den är +// det — samma krav som ställs på de nio andra språken. +// +// Det spelar roll här och ingen annanstans i produkten: det här är den +// enda text i systemet som säger åt någon vad den ska göra med ett +// fordon. "Mät och dokumentera spänningsfrihet före ingrepp" är inte en +// etikett. +// +// NYCKELORDEN är ett undantag och är avsiktligt tvåspråkiga. De matchas +// mot teknikerns egen fritext; att byta ut de svenska mot engelska hade +// gjort metodikvalet blint för allt en svensk verkstad skriver. Fler +// nyckelord kan bara ge fler träffar, aldrig färre. + +export const VIBRATION = { + id: "vibration", + namn: "Vibration while driving", + omrade: "Wheels and balance", + nyckelord: ["vibration", "vibrer", "skak", "obalans", "ojämn gång", "rattvibration", "vibrat", "shak", "shimm", "imbalance", "unbalance", "wheel wobble", "rough running"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + beskrivning: + "The questions are answered from a road test — drive before the vehicle is lifted. Vibration under braking is brake judder — use the braking system methodology for it.", + fragor: [ + { id: "hastighetsberoende", text: "Is the vibration speed-dependent?", svarstyp: "janej" }, + { id: "var", text: "Where is the vibration felt?", svarstyp: "val", val: ["In the steering wheel", "In the seat", "Throughout the vehicle"] }, + { + id: "nar", + text: "When does the vibration occur?", + svarstyp: "val", + val: ["Under acceleration", "At steady speed", "Under braking", "Always while driving"], + }, + { id: "intervall", text: "Does it disappear above or below a certain speed range?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + beskrivning: "Photograph every wheel. Keep observation separate from conclusion.", + kontroller: [ + { id: "foto_vf", text: "Photograph the left front wheel", krav: "foto" }, + { id: "foto_hf", text: "Photograph the right front wheel", krav: "foto" }, + { id: "foto_vb", text: "Photograph the left rear wheel", krav: "foto" }, + { id: "foto_hb", text: "Photograph the right rear wheel", krav: "foto" }, + { id: "dot", text: "Document the DOT / manufacturing date", krav: "kommentar" }, + { id: "slitage", text: "Check tyre wear and the wear pattern", krav: "kommentar" }, + { id: "vikter", text: "Check for missing balance weights and deposits (mud, snow) inside the rim", krav: "kommentar" }, + ], + }, + { + id: "kontroller", + rubrik: "Measurements", + kontroller: [ + { id: "lufttryck", text: "Check tyre pressure", krav: "matvarde" }, + { id: "hjulmoment", text: "Check wheel torque", krav: "matvarde" }, + { id: "kast", text: "Measure radial and lateral runout at the rim with a dial gauge; measure at the tyre if the rim is within limits", krav: "matvarde" }, + { id: "balansering", text: "Measure wheel balance on the balancer — residual imbalance in grams per plane (road force where available)", krav: "matvarde" }, + { id: "bussningar", text: "Check bushings and joints", krav: "kommentar" }, + { id: "drivaxlar", text: "Check drive shafts and inner CV joints when the vibration is load-dependent", krav: "kommentar" }, + ], + }, + { + id: "provkorning", + rubrik: "Road test", + beskrivning: + "Check that the wheels are visibly secure before the test; provoke the vibration only within legal speed and where traffic allows.", + kontroller: [ + { id: "pk_hastighet", text: "Speed at which the vibration occurs", krav: "matvarde" }, + { id: "pk_acc", text: "Change under acceleration", krav: "kommentar" }, + { id: "pk_motorbroms", text: "Change under engine braking", krav: "kommentar" }, + { id: "pk_kurva", text: "Change when cornering", krav: "kommentar" }, + { id: "pk_var", text: "Whether the vibration is felt in the steering wheel or the body", krav: "kommentar" }, + ], + }, + ], +}; + +export const ELSYSTEM = { + id: "elsystem", + namn: "Electrical system and power supply", + omrade: "Electrical", + nyckelord: ["relä", "rela", "säkring", "elektr", "spänning", "volt", "lampa", "belysning", "ström", "kortslut", "kabel", "kabla", "kontaktdon", "jordfel", "relay", "fuse", "electric", "voltage", "lamp", "light", "current", "short circuit", "wiring", "harness", "connector", "earth fault", "ground fault"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { id: "reproducerbart", text: "Can the fault be reproduced?", svarstyp: "janej" }, + { + id: "omfattning", + text: "Is the function completely dead or partly working?", + svarstyp: "val", + val: ["Completely dead", "Partly working", "Intermittent"], + }, + { id: "funktion", text: "Which function is missing or faulty?", svarstyp: "text" }, + { id: "forandring", text: "Has anything been repaired or changed recently?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "foto_komponent", text: "Photograph the component and its connections", krav: "foto" }, + { id: "skador", text: "Check for visible damage, corrosion and loose connectors", krav: "kommentar" }, + { id: "vicktest", text: "Wiggle test — flex the harness and connectors while monitoring the function; document where the fault can be provoked", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + beskrivning: + "Measure under load. A voltage without load says almost nothing about a poor connection. Never probe airbag or other pyrotechnic (SRS) circuits with a meter — deployment risk; use the diagnostic tool and the manufacturer's procedure for those.", + kontroller: [ + { id: "batterispanning", text: "Measure battery voltage", krav: "matvarde" }, + { id: "sakringar", text: "Measure voltage on both sides of the fuses concerned, circuit under load", krav: "matvarde" }, + { id: "matningsspanning", text: "Measure supply voltage at the component under load and compare with battery voltage — more than about 0.5 V lower indicates a supply-side voltage drop", krav: "matvarde" }, + { id: "jord", text: "Measure voltage drop to earth under load — more than about 0.2 V indicates a poor earth point or connection", krav: "matvarde" }, + ], + }, + { + id: "rela", + rubrik: "Relay and control signal", + kontroller: [ + { id: "rela_klick", text: "Check whether the relay clicks when energized — a click only proves the coil circuit; verify voltage on the load side, or substitute a known-good identical relay", krav: "kommentar" }, + { id: "styrsignal", text: "Measure the control signal to the relay", krav: "matvarde" }, + ], + }, + { + id: "funktionstest", + rubrik: "Function test", + kontroller: [{ id: "test", text: "Perform a function test and document the result", krav: "kommentar" }], + }, + ], +}; + +export const START_LADDNING = { + id: "start_laddning", + namn: "Starting and charging system", + omrade: "Electrical", + nyckelord: ["startar inte", "startproblem", "startmotor", "generator", "laddning", "batteriet dör", "urladdat", "batterilampa", "drar inte runt", "klick vid start", "will not start", "wont start", "no start", "starter", "alternator", "charging", "battery dies", "flat battery", "battery light", "does not crank", "click when starting"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "beteende", + text: "What happens when starting is attempted?", + svarstyp: "val", + val: ["Nothing at all", "A single click", "Cranks slowly", "Cranks normally but does not fire", "Starts intermittently"], + }, + { id: "kallt_varmt", text: "Does the fault appear cold, warm or always?", svarstyp: "val", val: ["Cold", "Warm", "Always"] }, + { id: "stillestand", text: "How long has the vehicle stood between attempts?", svarstyp: "text" }, + { id: "laddat", text: "Has the battery been charged or replaced recently?", svarstyp: "text" }, + ], + }, + { + id: "batteri", + rubrik: "Battery condition", + beskrivning: + "A weak battery causes consequential faults across the vehicle. Rule it out first. Battery safety: remove rings and metal watch straps, ventilate — a charging battery gives off hydrogen; never break a high-current connection at the post.", + kontroller: [ + { id: "vilospanning", text: "Measure resting voltage after at least one hour at rest, surface charge removed — about 12.6 V or more is fully charged, 12.4 V about 75 %, below 12.0 V discharged", krav: "matvarde" }, + { id: "batteritest", text: "Load or conductance test with the battery charged to at least 75 % state of charge — photograph the result, measured versus rated CCA", krav: "foto" }, + { id: "poler", text: "Check battery terminals and earth straps", krav: "kommentar" }, + ], + }, + { + id: "start", + rubrik: "Starting system", + beskrivning: + "Before any cranking test: gearbox in park or neutral, parking brake applied. Disable starting (fuel or ignition) where the test requires cranking without the engine firing, and keep hands and test leads clear of belt drives and hot surfaces. Cranks normally but does not fire — use the engine running methodology.", + kontroller: [ + { id: "startspanning", text: "Measure voltage at the starter motor during cranking — should stay above about 9.6 V at normal temperature; lower points to the battery, the cables or a dragging starter", krav: "matvarde" }, + { id: "spanningsfall_plus", text: "Measure voltage drop in the positive lead during cranking — no more than about 0.5 V", krav: "matvarde" }, + { id: "spanningsfall_jord", text: "Measure voltage drop in the earth lead during cranking — no more than about 0.5 V", krav: "matvarde" }, + { id: "startsignal", text: "Check the control signal to the starter relay", krav: "matvarde" }, + ], + }, + { + id: "laddning", + rubrik: "Charging system", + kontroller: [ + { id: "laddspanning", text: "Measure charging voltage at idle and at raised engine speed with consumers on — conventional systems 13.8–14.8 V; with smart charging, compare against the control unit's requested voltage instead of a fixed limit", krav: "matvarde" }, + { id: "rippel", text: "Measure AC ripple at the battery with the engine running and loads on — more than about 0.5 V indicates failed diodes", krav: "matvarde" }, + { id: "rem", text: "Check the drive belt and its tension", krav: "kommentar" }, + ], + }, + { + id: "krypstrom", + rubrik: "Parasitic drain", + beskrivning: "Only relevant when the battery discharges while the vehicle stands.", + kontroller: [ + { id: "vilostrom", text: "Measure quiescent current with a clamp, or in series without breaking continuity, once the vehicle has gone to sleep — typically below 30–50 mA; note the vehicle's specified value", krav: "matvarde" }, + { id: "sakringsdrag", text: "Localize the circuit by measuring the millivolt drop across each fuse with the vehicle still asleep; pull fuses only as a last resort — removing one wakes modules", krav: "kommentar" }, + ], + }, + ], +}; + +export const MOTOR_DRIFT = { + id: "motor_drift", + namn: "Engine running and power", + omrade: "Engine", + nyckelord: ["ryck", "tappar kraft", "effektbrist", "misständ", "ojämn tomgång", "tomgång", "motorlampa", "kraftlös", "hack", "går på tre", "jerk", "hesitat", "loses power", "power loss", "misfire", "rough idle", "idle", "engine light", "check engine", "gutless", "running on three"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "nar", + text: "When does the fault appear?", + svarstyp: "val", + val: ["On a cold start", "At idle", "Under load", "At a particular engine speed", "Always"], + }, + { id: "varvtal", text: "At what engine speed or load?", svarstyp: "text" }, + { id: "varningslampa", text: "Is the engine warning lamp on or flashing?", svarstyp: "val", val: ["On steady", "Flashing", "Off"] }, + { id: "brankslebyte", text: "Was fuel added or service performed shortly before?", svarstyp: "text" }, + ], + }, + { + id: "felkoder", + rubrik: "Fault codes and measured data", + beskrivning: "Read before anything is dismantled. A fault code points to a circuit, not to a broken component.", + kontroller: [ + { id: "dtc", text: "Read fault codes — photograph the diagnostic screen", krav: "foto" }, + { id: "frysdata", text: "Document the freeze frame data for the code", krav: "foto" }, + { id: "misstandning", text: "Read the misfire counter per cylinder", krav: "matvarde" }, + { id: "branslekorrigering", text: "Read short- and long-term fuel trim", krav: "matvarde" }, + ], + }, + { + id: "mekanik", + rubrik: "Basic engine condition", + beskrivning: "Without sound basic condition, every sensor value is hard to interpret.", + kontroller: [ + { id: "kompression", text: "Measure compression or relative compression — deactivate ignition and injection first; document per cylinder, and locate a low cylinder with a leak-down test", krav: "matvarde" }, + { id: "vevhustryck", text: "Measure crankcase pressure at idle", krav: "matvarde" }, + { id: "insugslackage", text: "Check the intake system for unmetered air", krav: "kommentar" }, + ], + }, + { + id: "tandning_bransle", + rubrik: "Ignition and fuel", + kontroller: [ + { id: "tandstift", text: "Check the spark plugs — photograph all of them", krav: "foto" }, + { id: "spolar", text: "Swap ignition coils between cylinders and re-read the misfire counter — document whether the fault moves with the coil", krav: "matvarde" }, + { id: "bransletryck", text: "Measure fuel pressure under load — depressurize before opening the system, never open the high-pressure side (read it via the control unit), and keep ignition sources away", krav: "matvarde" }, + { id: "insprutare", text: "Check injector back-leakage (leak-off) quantity or injector control", krav: "matvarde" }, + ], + }, + { + id: "provkorning", + rubrik: "Road test with logging", + kontroller: [ + { id: "logg", text: "Log measured data while driving where the fault appears — with a flashing warning lamp, do not drive under load; log at idle or light load", krav: "foto" }, + { id: "reproducerat", text: "Document the conditions under which the fault appeared", krav: "kommentar" }, + ], + }, + ], +}; + +export const BROMSAR = { + id: "bromsar", + namn: "Braking system", + omrade: "Chassis", + nyckelord: ["broms", "bromspedal", "skevhet", "gnisslar vid inbromsning", "abs", "bromsvätska", "handbroms", "drar snett vid inbromsning", "brake", "brake pedal", "judder", "squeal when braking", "brake fluid", "handbrake", "parking brake", "pulls when braking"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Judder or pulsation", "Abnormal noise", "Long pedal travel", "Soft pedal", "Pulls to one side", "The brake is binding"], + }, + { id: "nar", text: "At what speed and braking force?", svarstyp: "text" }, + { id: "varningslampa", text: "Is the brake or ABS warning lamp on?", svarstyp: "janej" }, + { id: "senaste", text: "When were brake components last replaced?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "vatskeniva", text: "Check the brake fluid level and condition in the reservoir before the vehicle is moved", krav: "foto" }, + { id: "foto_belagg", text: "Photograph pads and discs on every wheel", krav: "foto" }, + { id: "belaggtjocklek", text: "Measure pad thickness", krav: "matvarde" }, + { id: "skivtjocklek", text: "Measure disc thickness and compare with the minimum", krav: "matvarde" }, + { id: "lackage", text: "Check hoses, pipes and calipers for leaks", krav: "kommentar" }, + { id: "dtc", text: "Read fault codes from the ABS control unit", krav: "foto" }, + ], + }, + { + id: "reproduktion", + rubrik: "Road test", + beskrivning: "Only after the fluid level and pedal feel are checked. Provoke braking only where traffic allows.", + kontroller: [ + { id: "pk_temp", text: "Compare disc temperature side to side with an IR thermometer after the road test", krav: "matvarde" }, + { id: "pk_beteende", text: "Document judder, pull or binding during the test", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + kontroller: [ + { id: "skivkast", text: "Measure disc runout with a dial gauge — if out of limits, clean the hub face and measure hub runout before condemning the disc", krav: "matvarde" }, + { id: "tjockleksvariation", text: "Measure disc thickness variation", krav: "matvarde" }, + { id: "vatska", text: "Measure the brake fluid's boiling point or water content", krav: "matvarde" }, + { id: "okens_rorlighet", text: "Check that calipers and slide pins move freely", krav: "kommentar" }, + ], + }, + { + id: "system", + rubrik: "System and ABS", + kontroller: [ + { id: "hjulgivare", text: "Check the wheel speed sensor signals", krav: "matvarde" }, + { id: "parkeringsbroms", text: "Check the parking brake's function and travel", krav: "kommentar" }, + { id: "bromsprov", text: "Perform a brake test on a test bench — observe the restrictions for four-wheel drive and use service mode for an electronic parking brake; photograph the report", krav: "foto" }, + ], + }, + ], +}; + +export const STYRNING_FJADRING = { + id: "styrning_fjadring", + namn: "Steering and suspension", + omrade: "Chassis", + nyckelord: ["drar åt", "styrning", "ratt", "glapp", "stötdämpar", "fjäder", "fjädr", "hjulinställning", "spårar", "gungar", "knäpper i kurva", "servostyrning", "pulls", "steering", "steering wheel", "play", "damper", "shock absorber", "spring", "suspension", "wheel alignment", "tracking", "wallow", "clunk in corners", "power steering"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Pulls to one side", "Play in the steering wheel", "Abnormal noise", "Poor road feel", "Uneven tyre wear", "Wallows or bounces"], + }, + { id: "underlag", text: "Does it appear on a particular surface or in a particular corner?", svarstyp: "text" }, + { id: "handelse", text: "Has the vehicle struck something or hit a kerb?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "foto_slitage", text: "Photograph the tyres' wear pattern", krav: "foto" }, + { id: "dampare", text: "Check dampers for leakage", krav: "kommentar" }, + { id: "damask", text: "Check boots on track rod ends and drive joints", krav: "kommentar" }, + { id: "fjadrar", text: "Check springs for breakage and compare ride height side to side", krav: "matvarde" }, + { id: "servo", text: "Check power steering — fluid level and leaks (hydraulic) or fault codes (EPS)", krav: "kommentar" }, + ], + }, + { + id: "glapp", + rubrik: "Play check", + beskrivning: + "With the vehicle securely supported, unload the joint being checked according to the suspension design — support the lower arm where the spring acts on it. Rock the wheel at 12 and 6 o'clock for wheel bearings, at 3 and 9 o'clock for steering joints. Document where the play is, not only that it exists.", + kontroller: [ + { id: "hjullager", text: "Check wheel bearing play — state location and estimated magnitude", krav: "kommentar" }, + { id: "styrleder", text: "Check the outer tie rod ends and ball joints", krav: "kommentar" }, + { id: "bussningar", text: "Check bushings", krav: "kommentar" }, + { id: "kuggstang", text: "Check the steering rack mountings and the inner tie rods (axial joints)", krav: "kommentar" }, + ], + }, + { + id: "installning", + rubrik: "Wheel alignment", + kontroller: [ + { id: "dacktryck", text: "Check tyre pressures before measuring", krav: "matvarde" }, + { id: "dackskifte", text: "When the vehicle pulls: swap the front tyres side to side to rule out tyre pull before adjusting", krav: "kommentar" }, + { id: "matning", text: "Measure wheel alignment — photograph the report before adjustment", krav: "foto" }, + { id: "avvikelse", text: "Document deviations from the manufacturer's values", krav: "matvarde" }, + ], + }, + ], +}; + +export const KYLSYSTEM = { + id: "kylsystem", + namn: "Cooling system and overheating", + omrade: "Engine", + nyckelord: ["överhett", "koka", "kylvätska", "temperatur", "termostat", "kylare", "vattenpump", "värme i kupén", "expansionskärl", "overheat", "boiling", "coolant", "temperature", "thermostat", "radiator", "water pump", "cabin heat", "expansion tank"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "nar", + text: "When does the temperature rise?", + svarstyp: "val", + val: ["At idle", "At low speed", "At high speed", "Under load or on a hill", "Always"], + }, + { id: "kylvatskeforlust", text: "Is coolant being lost? How much and how often?", svarstyp: "text" }, + { id: "varme", text: "Does the cabin heater work?", svarstyp: "janej" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "niva", text: "Check the coolant level cold — photograph the expansion tank", krav: "foto" }, + { id: "lackage", text: "Check for visible leaks at hoses, radiator and pump", krav: "kommentar" }, + { id: "kylarpaket", text: "Check the radiator core and the A/C condenser for external blockage", krav: "foto" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + beskrivning: "Never open a hot, pressurized system — scalding risk. Work on a cooled-down engine.", + kontroller: [ + { id: "trycktest", text: "Pressure test the cooling system cold — pressurize to the cap's rated pressure, never above", krav: "matvarde" }, + { id: "lock", text: "Pressure test the expansion tank cap against its rated pressure", krav: "matvarde" }, + { id: "temperatur", text: "Measure inlet and outlet temperature at the radiator with an IR thermometer", krav: "matvarde" }, + { id: "flakt", text: "Measure the temperature at which the cooling fan cuts in — keep clear of the fan, it can start without warning", krav: "matvarde" }, + { id: "givare", text: "Compare the control unit's temperature value with the measured one", krav: "matvarde" }, + { id: "termostat", text: "Measure the upper radiator hose temperature at the thermostat's opening point — document the opening temperature", krav: "matvarde" }, + ], + }, + { + id: "packning", + rubrik: "Cylinder head gasket", + beskrivning: "Performed when coolant is lost without an external leak.", + kontroller: [ + { id: "avgastest", text: "Combustion leak test (block tester) in the coolant", krav: "foto" }, + { id: "kompression_kyl", text: "Connect a pressure tester to the cold system and document the pressure build-up over time with the engine running — never open hot", krav: "matvarde" }, + ], + }, + ], +}; + +export const DRIVLINA = { + id: "drivlina", + namn: "Transmission and driveline", + omrade: "Driveline", + nyckelord: ["växellåda", "växlar", "koppling", "automatlåda", "rycker vid växling", "slir", "drivknut", "kardan", "differential", "kärvar i växeln", "gearbox", "transmission", "shift", "clutch", "automatic", "jerks when shifting", "slip", "drive joint", "cv joint", "cv boot", "driveshaft", "axle", "diff whine", "propshaft", "sticks in gear"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Slips", "Jerks when shifting", "Will not engage a gear", "Abnormal noise", "Vibration under acceleration", "Leakage"], + }, + { id: "vaxel", text: "Which gear or engine speed?", svarstyp: "text" }, + { id: "kall_varm", text: "Cold or warm transmission?", svarstyp: "val", val: ["Cold", "Warm", "No difference"] }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "olja", text: "Check the oil level and the oil's condition — photograph", krav: "foto" }, + { id: "lackage", text: "Check for leaks at seals and the sump", krav: "kommentar" }, + { id: "fasten", text: "Check the transmission mounts", krav: "kommentar" }, + { id: "drivaxlar", text: "Check drive shaft boots and CV joints — clicking on full lock points to an outer joint", krav: "kommentar" }, + { id: "kardan", text: "Check the propshaft, centre bearing and universal joints; document driveline lash", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements and readout", + kontroller: [ + { id: "dtc", text: "Read fault codes from the transmission control unit", krav: "foto" }, + { id: "adaptioner", text: "Document adaptation values and clutch wear", krav: "matvarde" }, + { id: "oljetemp", text: "Measure oil temperature during the road test", krav: "matvarde" }, + ], + }, + { + id: "provkorning", + rubrik: "Road test", + kontroller: [ + { id: "vaxlingspunkter", text: "Document shift points (engine speed at up- and downshift) and behaviour", krav: "matvarde" }, + { id: "belastning", text: "Road test under the load where the fault appears", krav: "kommentar" }, + { id: "koppling_slir", text: "Clutch slip test on a manual gearbox: high gear, low speed, full throttle — document whether engine speed rises without road speed; check pedal free play", krav: "kommentar" }, + ], + }, + ], +}; + +export const AVGAS_EMISSION = { + id: "avgas_emission", + namn: "Exhaust system and emissions", + omrade: "Engine", + nyckelord: ["partikelfilt", "dpf", "adblue", "katalysator", "lambda", "avgas", "emission", "rökutveckling", "svart rök", "blå rök", "os", "egr", "regenerer", "utsläpp", "particulate filt", "catalyst", "exhaust", "smoke", "black smoke", "blue smoke", "regenerat"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Warning lamp", "Smoke", "Power limitation", "Odour", "Failed inspection"], + }, + { id: "rokfarg", text: "If smoke — what colour and when?", svarstyp: "text" }, + { id: "korprofil", text: "Describe the vehicle's usage profile (mostly short or long journeys)?", svarstyp: "text" }, + ], + }, + { + id: "avlasning", + rubrik: "Readout", + kontroller: [ + { id: "dtc", text: "Read fault codes — photograph the diagnostic screen", krav: "foto" }, + { id: "sotmangd", text: "Read the calculated and measured soot load in the particulate filter", krav: "matvarde" }, + { id: "regenerering", text: "Document the number and outcome of the most recent regenerations", krav: "kommentar" }, + { id: "lambda", text: "Read the lambda sensor signals before and after the catalyst", krav: "matvarde" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + beskrivning: "Run the engine only with exhaust extraction connected, or outdoors.", + kontroller: [ + { id: "mottryck", text: "Measure exhaust back pressure", krav: "matvarde" }, + { id: "differenstryck", text: "Measure differential pressure across the particulate filter at idle and at raised engine speed — state the engine speed with each value", krav: "matvarde" }, + { id: "adblue_niva", text: "Check AdBlue level and quality where fitted", krav: "matvarde" }, + { id: "egr", text: "Check the EGR valve's function and position", krav: "matvarde" }, + { id: "emissioner", text: "Measure emissions at idle and raised idle (CO/HC/lambda, or smoke opacity for diesel) — document against the limit", krav: "matvarde" }, + ], + }, + { + id: "orsak", + rubrik: "Underlying cause", + beskrivning: "A blocked filter is usually a consequence, not the cause. Document what produced the soot.", + kontroller: [ + { id: "grundorsak", text: "Check injection, turbocharger and air flow", krav: "kommentar" }, + { id: "korstrackor", text: "Assess whether the usage profile allows regeneration", krav: "kommentar" }, + ], + }, + ], +}; + +export const KLIMAT = { + id: "klimat", + namn: "Climate system", + omrade: "Comfort", + nyckelord: ["ac", "luftkonditionering", "klimatanläggning", "kyler inte", "kompressor", "köldmedi", "imma", "immig", "fläkt", "lukt i kupén", "värmepaket", "air conditioning", "climate", "no cooling", "compressor", "refrigerant", "misting", "fan", "smell in the cabin", "heater matrix"], + steg: [ + { + id: "sakerhet", + rubrik: "Safety — performed first", + beskrivning: + "Work on the refrigerant circuit requires certification for mobile air-conditioning (EU F-gas regulation). Refrigerant under pressure causes cold burns — protective glasses and gloves.", + fragor: [ + { id: "certifikat", text: "Are you certified for mobile air-conditioning work?", svarstyp: "janej" }, + ], + kontroller: [ + { id: "skydd", text: "Document the protective equipment for refrigerant handling", krav: "kommentar" }, + ], + }, + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["No cooling at all", "Cools poorly", "Cools intermittently", "Abnormal noise", "Odour", "Misting on the windows"], + }, + { id: "sidor", text: "Does it differ between the right and left sides?", svarstyp: "janej" }, + { id: "service", text: "When was the AC last serviced?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "kondensor", text: "Photograph the condenser — check for blockage and damage", krav: "foto" }, + { id: "kupefilter", text: "Check the cabin filter", krav: "foto" }, + { id: "kompressor_rem", text: "Check the compressor drive and the magnetic clutch", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + kontroller: [ + { id: "tryck", text: "Measure high and low pressure at the prescribed engine speed (about 1500–2000 rpm), AC at maximum, after stabilization — document the ambient temperature", krav: "matvarde" }, + { id: "utblastemp", text: "Measure the outlet temperature at the centre vent — AC at maximum, recirculation on, after a few minutes' stabilization", krav: "matvarde" }, + { id: "mangd", text: "Weigh the refrigerant recovered when evacuating and compare with the nominal charge on the vehicle's label", krav: "matvarde" }, + { id: "lackagesok", text: "Trace the leak with tracer gas or UV — photograph the leak point", krav: "foto" }, + ], + }, + { + id: "styrning", + rubrik: "Climate control", + kontroller: [ + { id: "dtc", text: "Read fault codes from the climate control unit", krav: "foto" }, + { id: "spjall", text: "Check the movement of the flap actuators", krav: "kommentar" }, + ], + }, + ], +}; + +export const HOGVOLT = { + id: "hogvolt", + namn: "High-voltage system — electric and hybrid", + omrade: "High voltage", + nyckelord: ["högvolt", "elbil", "hybrid", "traktionsbatteri", "hv", "laddning av bil", "räckvidd", "laddbox", "orange kabel", "sköldpadda", "high voltage", "high-voltage", "ev", "electric vehicle", "traction battery", "charging the car", "range", "wallbox", "orange cable", "turtle"], + steg: [ + { + id: "sakerhet", + rubrik: "Safety — performed first", + beskrivning: + "High-voltage systems can be lethal. No work on the system begins until the steps below are documented. Requires authorization for work on high-voltage systems — without it, stop after non-intrusive readout: no removal of covers or connectors, no insulation testing with applied test voltage, and never any work on an energized system. The readout and charging steps are non-intrusive; de-energization is completed and documented before any physical intervention.", + fragor: [ + { id: "behorighet", text: "Are you authorized to work on high-voltage systems?", svarstyp: "janej" }, + { id: "avstangt", text: "Is the vehicle de-energized per the manufacturer's procedure, with absence of voltage verified by measurement?", svarstyp: "janej" }, + ], + kontroller: [ + { id: "skyddsutrustning", text: "Document before starting: the work area cordoned off and signed, and the protective equipment in use — insulated gloves (EN 60903, within retest date), face shield, insulated tools (EN 60900)", krav: "foto" }, + { id: "service_disconnect", text: "Document that the ignition is off, the key out of range, and the service disconnect removed and kept under the sole control of the person working — locked out against re-energization", krav: "foto" }, + { id: "vantetid", text: "Document the manufacturer's prescribed capacitor discharge waiting time and the actual time waited (minutes)", krav: "matvarde" }, + { id: "spanningsfrihet", text: "Verify absence of voltage at the manufacturer's designated measuring points (HV+ to HV−, and each to chassis) with a CAT III rated tester — prove the tester against a known source immediately before and after; document the readings", krav: "matvarde" }, + ], + }, + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Will not charge", "Reduced range", "Power limitation", "Warning message", "The vehicle will not start"], + }, + { id: "laddtyp", text: "At which charge type does the fault appear (AC, DC, which charge point)?", svarstyp: "text" }, + { id: "temperatur", text: "At what ambient and battery temperature?", svarstyp: "text" }, + ], + }, + { + id: "avlasning", + rubrik: "Readout", + beskrivning: "Performed with a diagnostic tool — no intervention in the system is required.", + kontroller: [ + { id: "dtc", text: "Read fault codes from every high-voltage control unit", krav: "foto" }, + { id: "soh", text: "Read the battery's state of health (SOH) and state of charge (SOC)", krav: "matvarde" }, + { id: "cellspanning", text: "Read cell voltages and document the largest deviation", krav: "matvarde" }, + { id: "isolationsvarde", text: "Read the system's insulation value", krav: "matvarde" }, + { id: "temperaturer", text: "Read the battery temperature sensors", krav: "matvarde" }, + { id: "hvil", text: "Read the status of the high-voltage interlock loop (HVIL)", krav: "kommentar" }, + ], + }, + { + id: "laddning", + rubrik: "Charging chain", + kontroller: [ + { id: "laddkabel", text: "Check the charging cable and connector — photograph the pins", krav: "foto" }, + { id: "laddlogg", text: "Read the charging history and the reasons for interruptions", krav: "kommentar" }, + { id: "extern", text: "Test against another charge point to rule out an external cause", krav: "kommentar" }, + ], + }, + ], +}; + +export const DIAGNOS_NATVERK = { + id: "diagnos_natverk", + namn: "Fault codes and communication", + omrade: "Diagnostics", + nyckelord: ["felkod", "dtc", "can", "canbuss", "kommunikationsfel", "ingen kontakt", "styrenhet", "obd", "diagnos", "flera lampor", "fault code", "can bus", "communication fault", "no communication", "control unit", "ecu", "several lamps"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "omfattning", + text: "How many systems are affected?", + svarstyp: "val", + val: ["One control unit", "Several control units", "The whole vehicle", "The diagnostic tool only"], + }, + { id: "nar", text: "Did the fault appear after work, a battery change or an update?", svarstyp: "text" }, + ], + }, + { + id: "grund", + rubrik: "Basic preconditions", + beskrivning: "Communication faults are more often due to supply and earth than to the control unit.", + kontroller: [ + { id: "batteri", text: "Measure battery voltage during the readout", krav: "matvarde" }, + { id: "obd_matning", text: "Measure supply (pin 16) and earth (pins 4 and 5) at the diagnostic socket — supply near battery voltage", krav: "matvarde" }, + { id: "sakringar", text: "Check the fuses for the control units concerned", krav: "kommentar" }, + ], + }, + { + id: "koder", + rubrik: "Fault codes in context", + beskrivning: "A fault code points to a circuit, not to a broken component. Read every system before any code is cleared.", + kontroller: [ + { id: "helavlasning", text: "Read every control unit — photograph the list", krav: "foto" }, + { id: "frysdata", text: "Document the freeze frame data before clearing", krav: "kommentar" }, + { id: "efter_radering", text: "Clear, road test, and document which codes returned", krav: "kommentar" }, + ], + }, + { + id: "buss", + rubrik: "Bus measurement", + kontroller: [ + { id: "terminering", text: "Measure the bus termination resistance between CAN-H and CAN-L with the battery disconnected — expect about 60 ohm; on gateway vehicles measure on the affected segment, not only at the diagnostic socket", krav: "matvarde" }, + { id: "bussignal", text: "Measure the CAN signal with an oscilloscope — photograph the trace. High-speed CAN: about 2.5 to 3.5 V (CAN-H) and 2.5 to 1.5 V (CAN-L); note error frames or bus off", krav: "foto" }, + { id: "avdelning", text: "Split the bus to localize the disturbing node", krav: "kommentar" }, + ], + }, + ], +}; + +export const LACKAGE = { + id: "lackage", + namn: "Leakage", + omrade: "General", + nyckelord: ["läck", "läckage", "oljefläck", "dropp", "vatten i bilen", "fukt", "blöt matta", "oljeförbrukning", "leak", "leakage", "oil stain", "drip", "water in the car", "damp", "wet carpet", "oil consumption"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { id: "vatska", text: "What does the fluid look like — colour, consistency, smell — and where does it appear?", svarstyp: "text" }, + { id: "mangd", text: "How much and how often?", svarstyp: "text" }, + { id: "plats", text: "Where on the ground is the stain relative to the vehicle?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Localization", + beskrivning: "Clean first. An old leak hides where the new one comes from.", + kontroller: [ + { id: "foto_fore", text: "Photograph the area before cleaning", krav: "foto" }, + { id: "rengoring", text: "Clean the area and document that it is dry", krav: "foto" }, + { id: "identifiering", text: "Identify the fluid on white paper — photograph", krav: "foto" }, + { id: "provkorning", text: "Road test, or let the engine run, and photograph where the fluid appears — no road test on a suspected fuel or brake fluid leak: fuel means no ignition sources and engine off; brakes are verified and repaired before any driving", krav: "foto" }, + ], + }, + { + id: "metod", + rubrik: "Leak tracing", + kontroller: [ + { id: "uv", text: "Leak test with UV dye where applicable — photograph the fluorescence", krav: "foto" }, + { id: "trycktest", text: "Pressure test the system", krav: "matvarde" }, + { id: "niva", text: "Measure the change in level over time", krav: "matvarde" }, + { id: "vattenprov", text: "For water ingress: hose test section by section and check the drains — photograph the entry point", krav: "foto" }, + ], + }, + ], +}; + +export const MISSLJUD = { + id: "missljud", + namn: "Abnormal noise", + omrade: "General", + nyckelord: ["missljud", "ljud", "knack", "gnissel", "gnissl", "vinande", "brumm", "skram", "väsande", "tjut", "noise", "sound", "knock", "squeak", "squeal", "whine", "drone", "rattle", "hiss", "screech"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + beskrivning: "An abnormal noise that has not been reproduced cannot be repaired. Record it.", + fragor: [ + { + id: "karaktar", + text: "What is the character of the noise?", + svarstyp: "val", + val: ["Knock", "Squeal", "Whine", "Drone", "Rattle", "Hissing", "Screech"], + }, + { + id: "nar", + text: "When is it heard?", + svarstyp: "val", + val: ["On starting", "At idle", "While driving", "Under braking", "When cornering", "Over bumps"], + }, + { id: "beroende", text: "Does the noise follow engine speed or road speed?", svarstyp: "val", val: ["Engine speed", "Road speed", "Neither"] }, + { id: "temperatur", text: "Any difference cold versus warm?", svarstyp: "text" }, + ], + }, + { + id: "inspelning", + rubrik: "Document the noise", + beskrivning: + "Video with sound is evidence. A description in text is not. On a road test one person drives, one operates the equipment.", + kontroller: [ + { id: "video", text: "Record the noise on video under the conditions in which it occurs", krav: "foto" }, + { id: "forhallanden", text: "Document road speed, engine speed and surface during the recording", krav: "matvarde" }, + ], + }, + { + id: "lokalisering", + rubrik: "Localization", + kontroller: [ + { id: "stetoskop", text: "Localize with a stethoscope or sound probe when stationary — keep the probe clear of rotating parts; use chassis ears on a road test for speed-dependent noise", krav: "kommentar" }, + { id: "avlastning", text: "Check whether the noise changes when unloaded or loaded", krav: "kommentar" }, + { id: "frikoppling", text: "Document whether the noise changes in neutral or when coasting, and when cornering left versus right", krav: "kommentar" }, + { id: "uteslutning", text: "Document which sources were ruled out, and how", krav: "kommentar" }, + ], + }, + ], +}; + +export const ADAS = { + id: "adas", + namn: "Driver assistance and calibration", + omrade: "Diagnostics", + nyckelord: ["adas", "kamera", "radar", "filhållning", "adaptiv farthållare", "kalibrer", "vindruta", "parkeringssensor", "döda vinkeln", "nödbroms", "camera", "lane keep", "adaptive cruise", "calibrat", "windscreen", "windshield", "parking sensor", "blind spot", "emergency brak"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "funktion", + text: "Which function is faulty?", + svarstyp: "val", + val: ["Lane keeping", "Adaptive cruise control", "Automatic emergency braking", "Blind spot", "Parking aid", "Reversing camera"], + }, + { id: "ingrepp", text: "Have the windscreen, body or suspension been worked on recently?", svarstyp: "text" }, + { id: "forhallanden", text: "Does the fault appear in particular weather or light?", svarstyp: "text" }, + ], + }, + { + id: "forutsattningar", + rubrik: "Preconditions for calibration", + beskrivning: + "A calibration performed under the wrong preconditions produces a fault that looks repaired. Document that the requirements are met before it is carried out. Dynamic calibration has its own requirements — speed, clear lane markings, weather and light — per the manufacturer.", + kontroller: [ + { id: "dtc", text: "Read fault codes from the control units concerned", krav: "foto" }, + { id: "hjulinstallning", text: "Check wheel alignment — rear axle tracking affects the camera", krav: "matvarde" }, + { id: "dacktryck", text: "Check tyre pressure and ride height", krav: "matvarde" }, + { id: "fordonskondition", text: "Document that vehicle condition meets the manufacturer's requirements — load, fuel level, battery support, steering wheel centred", krav: "kommentar" }, + { id: "sikt", text: "Check the camera's and radar's field of view for dirt or damage — after a windscreen replacement, verify correct glass and camera mount", krav: "foto" }, + { id: "yta", text: "Document that the calibration area meets the requirements for flatness and lighting", krav: "foto" }, + ], + }, + { + id: "kalibrering", + rubrik: "Calibration", + kontroller: [ + { id: "utford", text: "Perform the calibration by the manufacturer's method — photograph the result", krav: "foto" }, + { id: "provkorning", text: "Verify the function by the manufacturer's test method — never provoke emergency braking or steering interventions on public roads; confirm the system is active and free of warnings", krav: "kommentar" }, + ], + }, + ], +}; + +export const GENERISK = { + id: "generisk", + namn: "General structured diagnosis", + omrade: "General", + nyckelord: [], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { id: "energikallor", text: "Does the work involve high voltage, fuel, gas or stored energy? If yes — switch to the appropriate methodology, or document the safety measures first", svarstyp: "janej" }, + { id: "reproducerbart", text: "Can the fault be reproduced?", svarstyp: "janej" }, + { + id: "nar", + text: "When does the fault appear?", + svarstyp: "val", + val: ["Always", "Intermittently", "Only while running", "Only when starting"], + }, + { + id: "var", + text: "Where is the fault experienced?", + svarstyp: "val", + val: ["Front", "Rear", "Right", "Left", "Engine and power source", "Cabin and driver's position", "Unknown"], + }, + { + id: "hur", + text: "How is the fault experienced?", + svarstyp: "val", + val: ["Shakes or vibrates", "Abnormal noise (knock, squeal, whine)", "Jerking", "Loses power", "Stalls completely", "Other"], + }, + { id: "forlopp", text: "Describe the sequence when the fault occurs", svarstyp: "text" }, + { id: "forandring", text: "Has anything changed recently (service, repair, environment)?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "foto_objekt", text: "Photograph the object and the area concerned", krav: "foto" }, + { id: "skador", text: "Check for visible damage, leaks or loose connections", krav: "kommentar" }, + { id: "typskylt", text: "Document the type plate or marking", krav: "foto" }, + ], + }, + { + id: "grundkontroller", + rubrik: "Basic checks", + kontroller: [ + { + id: "matning", + text: "Take basic measurements (voltage, pressure, temperature — whatever is relevant)", + krav: "matvarde", + }, + { id: "dtc", text: "Read fault codes where the object has diagnostics — photograph", krav: "foto" }, + { id: "sakringar", text: "Check fuses and protective devices", krav: "kommentar" }, + { id: "anslutningar", text: "Check connectors and terminations", krav: "kommentar" }, + ], + }, + { + id: "funktionstest", + rubrik: "Function test", + kontroller: [{ id: "test", text: "Perform a function test and document the result", krav: "kommentar" }], + }, + ], +}; + +// Ordningen styr bara hur de visas. Valet görs på nyckelord, inte på +// position — GENERISK ligger sist eftersom den är fallback. +export const ALLA_METODIKER = [ + VIBRATION, + BROMSAR, + STYRNING_FJADRING, + ELSYSTEM, + START_LADDNING, + MOTOR_DRIFT, + KYLSYSTEM, + DRIVLINA, + AVGAS_EMISSION, + KLIMAT, + HOGVOLT, + DIAGNOS_NATVERK, + LACKAGE, + MISSLJUD, + ADAS, + GENERISK, +]; diff --git a/felsokning/app/services/gemensam/motivering.mjs b/felsokning/app/services/gemensam/motivering.mjs new file mode 100644 index 0000000..0bba7a0 --- /dev/null +++ b/felsokning/app/services/gemensam/motivering.mjs @@ -0,0 +1,178 @@ +// ALVA-RULE-200 · Closing statement. +// +// Ett ärende stängs aldrig utan att teknikern har lämnat ett varför. +// +// Det låter enkelt och är det inte. En obligatorisk fritextruta blir +// "klart" på tredje ärendet, och då har vi bara gjort dokumentationen +// långsammare utan att göra den bättre. Ett varför som ska hålla i en +// garantitvist måste vara strukturerat, kopplat till evidensen, och +// kvalitetsgranskat mot det som faktiskt skrivs när någon har bråttom. +// +// ---- Vad ett varför faktiskt består av ------------------------------- +// +// Fyra frågor med olika adressat. De blandas i dag ihop till en enda +// rad i en arbetsorder, och det är därför verkstadsprotokoll är svåra +// att använda i efterhand: +// +// MOTIVERING Varför följer slutsatsen av evidensen? +// Läses av: nästa tekniker, försäkringsbedömaren. +// Detta är den rad som saknas i varje system jag sett. +// Underlaget säger VAD som mättes. Slutsatsen säger VAD +// som är fel. Ingenting säger varför det ena medför det +// andra — och det är precis det steget en bedömare +// behöver granska. +// +// UTESLUTET Vad övervägdes och varför föll det bort? +// Läses av: försäkringsbolaget, garantihandläggaren. +// En felsökning utan uteslutna alternativ är en gissning +// som råkade stämma. Härleds delvis ur loggen: en +// hypotes som dokumenterats och inte blev slutsatsen +// MÅSTE bemötas. +// +// ÅTGÄRDSVAL Varför denna åtgärd och inte en annan? +// Läses av: kunden, kostnadsgranskaren. +// Byte eller justering? Varför hela enheten? +// +// KVARSTÅENDE Vad är fortfarande osäkert? +// Läses av: nästa tekniker, flottansvarig. +// Får vara "inget" — men det måste sägas aktivt. +// +// ---- Den ärliga vägen ------------------------------------------------- +// +// Ibland vet teknikern inte varför. Systemet måste ha en väg för det som +// inte är en lögn: orsaken kunde inte fastställas, och varför den inte +// kunde det. Det är också ett varför, och ofta ett mer användbart än en +// påhittad orsak. + +import { EVIDENSORD, ICKESVAR, MINSTA_ORSAKSORD, ORSAKSORD } from "./sprak/ord.mjs"; +import { STANDARD, t } from "./sprak/index.mjs"; + +export const MINSTA_MOTIVERING = 40; +export const MINSTA_KORT = 15; + +const normalisera = (text) => String(text ?? "").trim().toLowerCase().replace(/\s+/g, " "); + +/** Är texten ett icke-svar? Jämför mot hela fältet, inte delsträngar. */ +function ärIckesvar(text) { + const n = normalisera(text).replace(/[.!]+$/, ""); + return ICKESVAR.includes(n); +} + +// Ett resonemang innehåller ett därför. Utan orsakssamband är texten en +// omformulering av slutsatsen, inte ett skäl för den. +// +// Orden jämförs som delsträngar och kommer från alla språk samtidigt — +// se sprak/ord.mjs för varför unionen är rätt riktning att fela åt. +function harOrsakssamband(text) { + const n = normalisera(text); + return ORSAKSORD.some((ord) => ord.length >= MINSTA_ORSAKSORD && n.includes(ord)); +} + +/** + * Refererar texten till dokumenterad evidens? Godtar både uttryckliga + * ord för underlag och konkreta mätvärden med enhet — den som skriver + * "12,4 V vid stift 14" hänvisar till en mätning även utan att säga + * ordet "mätning". + */ +function refererarEvidens(text) { + const n = normalisera(text); + const värde = /\d+([.,]\d+)?\s*(v|volt|a|ampere|ω|ohm|bar|kpa|mpa|nm|°c|c|mm|km|mil|%|g|hz|rpm|varv)\b/; + return EVIDENSORD.some((o) => n.includes(o)) || värde.test(n); +} + +/** + * Granskar ett fält i slutsatsen. + * @returns null när fältet duger, annars en förklaring till teknikern. + */ +export function granskaFalt(faltNyckel, text, { minsta = MINSTA_KORT, kravOrsak = false, sprak = STANDARD } = {}) { + const rå = String(text ?? "").trim(); + const falt = t(sprak, faltNyckel); + if (!rå) return t(sprak, "slutsats.saknas", { falt }); + if (ärIckesvar(rå)) return t(sprak, "slutsats.ickesvar.falt", { falt, text: rå }); + if (rå.length < minsta) { + return t(sprak, "slutsats.for_kort", { falt, langd: rå.length, minsta }); + } + if (kravOrsak && !harOrsakssamband(rå) && !refererarEvidens(rå)) { + return t(sprak, "slutsats.utan_varfor", { falt }); + } + return null; +} + +/** + * Hypoteser som dokumenterats men inte blivit slutsatsen. + * + * Härleds ur loggen: den som skrivit ned en misstanke och sedan landat i + * något annat måste säga varför den föll bort. Det är den enskilda regel + * som gör underlaget användbart för en försäkringsbedömare — utan den är + * en felsökning en gissning som råkade stämma. + */ +export function obemottaHypoteser(handelser, slutsats) { + const hypoteser = handelser.filter((h) => h?.typ === "hypotes").map((h) => h.text); + if (hypoteser.length === 0) return []; + + const uteslutet = normalisera(slutsats?.uteslutet); + const orsak = normalisera( + [slutsats?.motivering, ...(handelser.filter((h) => h?.typ === "felorsak").map((h) => h.avvikelse) ?? [])].join(" "), + ); + + // En hypotes är bemött om något meningsbärande ord ur den återkommer i + // uteslutandet eller i den fastställda orsaken. Trubbigt, men det + // fångar den som skriver "uteslutet: inget" med tre öppna hypoteser i + // loggen. + return hypoteser.filter((text) => { + const ord = normalisera(text) + .split(/[^a-zåäöéü0-9]+/) + .filter((o) => o.length >= 5); + if (ord.length === 0) return false; + return !ord.some((o) => uteslutet.includes(o) || orsak.includes(o)); + }); +} + +/** + * Granskar hela slutsatsen inför avslut. + * + * @param slutsats händelsen av typ "slutsats" + * @param handelser hela loggen, för de regler som härleds ur den + * @returns lista med brister; tom lista betyder godkänd + */ +export function granskaSlutsats(slutsats, handelser = [], sprak = STANDARD) { + const brister = []; + const lägg = (fält, text) => text && brister.push({ falt: fält, text }); + const g = (nyckel, text, val) => granskaFalt(nyckel, text, { ...val, sprak }); + + if (!slutsats) { + return [{ falt: "slutsats", nyckel: "slutsats.utan_slutsats", text: t(sprak, "slutsats.utan_slutsats") }]; + } + + const fastställd = slutsats.orsakFastställd !== false; + + if (fastställd) { + lägg("motivering", g("slutsats.falt.motivering", slutsats.motivering, { minsta: MINSTA_MOTIVERING, kravOrsak: true })); + } else { + // Den ärliga vägen. Att orsaken inte kunnat fastställas är ett + // giltigt utfall — men varför den inte kunnat det är fortfarande ett + // varför, och det måste stå där. + lägg( + "motivering", + g("slutsats.falt.motivering_ej", slutsats.motivering, { minsta: MINSTA_MOTIVERING, kravOrsak: true }), + ); + } + + lägg("uteslutet", g("slutsats.falt.uteslutet", slutsats.uteslutet, { minsta: MINSTA_KORT })); + lägg("kvarstaende", g("slutsats.falt.kvarstaende", slutsats.kvarstaende, { minsta: 5 })); + + const utfördArbete = handelser.some((h) => h?.typ === "atgard_utford" && h.utford === true); + if (utfördArbete) { + lägg("atgardsval", g("slutsats.falt.atgardsval", slutsats.atgardsval, { minsta: MINSTA_KORT, kravOrsak: true })); + } + + for (const text of obemottaHypoteser(handelser, slutsats)) { + brister.push({ + falt: "uteslutet", + nyckel: "slutsats.hypotes_obemott", + text: t(sprak, "slutsats.hypotes_obemott", { text }), + }); + } + + return brister; +} diff --git a/felsokning/app/services/gemensam/observation.mjs b/felsokning/app/services/gemensam/observation.mjs new file mode 100644 index 0000000..83be12e --- /dev/null +++ b/felsokning/app/services/gemensam/observation.mjs @@ -0,0 +1,166 @@ +// Observation — spårning och mätvärden utan nya beroenden. +// +// Tjänsterna har medvetet nästan inga beroenden: plattformen har +// pg-drivrutinen, orkestern har Claude-klienten. Att dra in ett +// OpenTelemetry-SDK med trettio paket för att mäta fyra saker vore fel +// avvägning. +// +// I stället två standarder som båda bara är text på stdout: +// +// W3C Trace Context traceparent-huvudet följer med genom hela +// kedjan, så en teknikers anrop går att följa +// från klienten via plattformen till orkestern. +// +// CloudWatch EMF strukturerad JSON som CloudWatch själv plockar +// mätvärden ur. Ingen agent, ingen SDK, inget +// som kan sluta fungera tyst. +// +// Det som mäts är valt efter en fråga: vad vill man veta klockan tre på +// natten när något är långsamt? Svaret är var tiden gick — inte hur +// många anrop som skett. + +import { randomBytes } from "node:crypto"; + +export const NAMNRYMD = "GuidadFelsokning"; + +const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/; + +/** + * Läser inkommande traceparent eller startar ett nytt spår. + * Ett anrop utan huvud är inte ett fel — de flesta kommer utifrån. + */ +export function spårFrån(huvud) { + const träff = typeof huvud === "string" ? huvud.trim().match(TRACEPARENT) : null; + return { + spårId: träff ? träff[1] : randomBytes(16).toString("hex"), + förälder: träff ? träff[2] : null, + spanId: randomBytes(8).toString("hex"), + }; +} + +// Skickas vidare till nästa tjänst i kedjan. +export function traceparent(spår) { + return `00-${spår.spårId}-${spår.spanId}-01`; +} + +/** + * Ett spann. Mäter tid och samlar barn, så att en logg-rad kan svara på + * "var tog tiden vägen" utan att någon behöver korrelera flera rader. + */ +export function starta(namn, spår) { + const början = process.hrtime.bigint(); + const barn = []; + + return { + namn, + spår, + + // Tidtagning på det som faktiskt kan vara långsamt: databasen, + // modellanropet, objektlagringen, kundens leverantör. + async mät(delnamn, arbete) { + const start = process.hrtime.bigint(); + try { + return await arbete(); + } finally { + barn.push({ namn: delnamn, ms: Number(process.hrtime.bigint() - start) / 1e6 }); + } + }, + + ms() { + return Number(process.hrtime.bigint() - början) / 1e6; + }, + + delar() { + // Samma delnamn flera gånger (t.ex. tre databasfrågor) slås ihop: + // antalet och summan säger mer än en lista. + const summa = {}; + for (const d of barn) { + summa[d.namn] ??= { antal: 0, ms: 0 }; + summa[d.namn].antal += 1; + summa[d.namn].ms += d.ms; + } + for (const d of Object.values(summa)) d.ms = Math.round(d.ms * 100) / 100; + return summa; + }, + }; +} + +// En rad JSON per händelse. CloudWatch Logs Insights kan fråga på +// vilket fält som helst utan att någon behöver skriva ett regex. +export function logga(nivå, meddelande, fält = {}) { + process.stdout.write( + `${JSON.stringify({ + nivå, + meddelande, + tid: new Date().toISOString(), + ...fält, + })}\n`, + ); +} + +/** + * Mätvärden i CloudWatch Embedded Metric Format. + * + * Formatet är en logg-rad som CloudWatch känner igen och extraherar + * mätvärden ur. Poängen är att mätvärdet och sammanhanget står i samma + * rad: när latensen är hög går det att gå direkt till anropen som + * orsakade den, i stället för att gissa utifrån en graf. + * + * Dimensioner hålls medvetet få. Varje unik kombination är en egen + * tidsserie som kostar, så organisation eller ärende-id får aldrig bli + * en dimension — de ligger som vanliga fält. + */ +export function mätvärde(namn, värde, enhet, dimensioner = {}, extra = {}) { + const nycklar = Object.keys(dimensioner); + + process.stdout.write( + `${JSON.stringify({ + _aws: { + Timestamp: Date.now(), + CloudWatchMetrics: [ + { + Namespace: NAMNRYMD, + Dimensions: nycklar.length > 0 ? [nycklar] : [[]], + Metrics: [{ Name: namn, Unit: enhet }], + }, + ], + }, + ...dimensioner, + [namn]: värde, + ...extra, + })}\n`, + ); +} + +/** + * Avslutar ett spann: en strukturerad logg-rad med hela nedbrytningen, + * och ett mätvärde för latensen. + * + * Fel loggas som fel men mäts på samma ställe — annars syns inte att + * felen är snabba och de lyckade anropen långsamma, vilket är precis + * den sortens sak som förvirrar en felsökning. + */ +export function avsluta(spann, { status, väg, extra = {} }) { + const ms = spann.ms(); + const delar = spann.delar(); + + logga(status >= 500 ? "fel" : "info", spann.namn, { + spårId: spann.spår.spårId, + spanId: spann.spår.spanId, + väg, + status, + ms: Math.round(ms * 100) / 100, + delar, + ...extra, + }); + + mätvärde("Svarstid", ms, "Milliseconds", { Tjänst: spann.namn, Väg: väg }, { status }); + + // En egen serie för fel gör larmet enkelt: larma på summan, inte på + // ett förhållande som måste räknas ut. + if (status >= 500) { + mätvärde("Fel", 1, "Count", { Tjänst: spann.namn, Väg: väg }, { spårId: spann.spår.spårId }); + } + + return ms; +} diff --git a/felsokning/app/services/gemensam/personuppgifter.mjs b/felsokning/app/services/gemensam/personuppgifter.mjs new file mode 100644 index 0000000..997aad2 --- /dev/null +++ b/felsokning/app/services/gemensam/personuppgifter.mjs @@ -0,0 +1,162 @@ +// Personuppgifter i en append-only-logg. +// +// Revisionen (docs/QUALITY-AUDIT.md, C-3) fann att systemet inte kunde +// uppfylla artikel 17: databastriggern hindrar aktivt radering, och +// kundens namn, telefonnummer, e-post, registreringsnummer och VIN +// ligger i klartext i loggen. +// +// "Append-only" och "radering på begäran" är inte oförenliga, men de +// måste förenas medvetet. Lösningen här är krypto-shredding: +// +// Identifierande fält lagras krypterade med en nyckel som är unik per +// registrerad person. Radering sker genom att nyckeln förstörs. Loggen +// förblir intakt och hashverifierbar — det som blir oåtkomligt är +// identifieringen, inte protokollet över vad som kontrollerades. +// +// Det är den enda konstruktion jag känner till där bevisvärdet överlever +// en raderingsbegäran. Ett ärende där kunden begärt radering kan +// fortfarande visa att lufttrycket mättes till 2,4 bar av en behörig +// tekniker klockan 08:42 — bara inte längre vem bilen tillhörde. +// +// Vad som INTE krypteras: mätvärden, observationer, kontrollresultat, +// tidsstämplar, teknikerns roll. De är verksamhetsdata och bär inget +// personvärde i sig. Att kryptera allt vore enklare att beskriva och +// sämre i praktiken — då raderas beviset tillsammans med personuppgiften. + +import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto"; +import { arendetypNu } from "./sprak/index.mjs"; + +/** + * Fält som räknas som identifierande och därför krypteras. + * + * Registreringsnummer och VIN står med avsikt med. De pekar ut ett + * fordon, men ett fordon pekar i praktiken ut en ägare — EDPB behandlar + * VIN som personuppgift när kopplingen finns, och här finns den. + */ +export const IDENTIFIERANDE = { + objekt_identifierat: ["identifierare", "vin", "agare", "kund"], + arbetsorder_skannad: [ + "kund_namn", + "kund_foretag", + "kund_telefon", + "kund_epost", + "fordon_regnr", + "fordon_vin", + ], + kundbeslut: ["kontaktperson"], +}; + +export const MASKERAT = "[raderat på begäran]"; + +/** + * Krypterar ett värde med den registrerades nyckel. + * Formatet bär nyckelns id, så att en post går att koppla till rätt + * nyckel utan att nyckeln behöver finnas. + */ +export function skydda(värde, nyckelId, nyckel) { + const iv = randomBytes(12); + const chiffer = createCipheriv("aes-256-gcm", nyckel, iv); + const ut = Buffer.concat([chiffer.update(String(värde), "utf8"), chiffer.final()]); + return `pd1:${nyckelId}:${iv.toString("base64url")}:${chiffer.getAuthTag().toString("base64url")}:${ut.toString("base64url")}`; +} + +/** + * Öppnar ett skyddat värde. + * + * Saknas nyckeln returneras MASKERAT — det är det normala utfallet efter + * en radering, inte ett fel. Ett kastat undantag här skulle göra hela + * ärendet oläsbart, vilket vore precis fel: resten av bevisningen ska + * överleva. + */ +export function öppna(skyddat, nycklar) { + if (typeof skyddat !== "string" || !skyddat.startsWith("pd1:")) return skyddat; + const [, nyckelId, iv, tagg, data] = skyddat.split(":"); + const nyckel = nycklar.get(nyckelId); + if (!nyckel) return MASKERAT; + try { + const chiffer = createDecipheriv("aes-256-gcm", nyckel, Buffer.from(iv, "base64url")); + chiffer.setAuthTag(Buffer.from(tagg, "base64url")); + return chiffer.update(Buffer.from(data, "base64url"), undefined, "utf8") + chiffer.final("utf8"); + } catch { + // Fel nyckel eller manipulerad chiffertext. Att skilja de två fallen + // åt i ett felmeddelande vore ett orakel; behandla dem lika. + return MASKERAT; + } +} + +/** Krypterar de identifierande fälten i en händelse. */ +export function skyddaHändelse(handelse, nyckelId, nyckel) { + const fält = IDENTIFIERANDE[handelse.typ]; + if (!fält) return handelse; + + if (handelse.typ === "arbetsorder_skannad") { + return { + ...handelse, + falt: (handelse.falt ?? []).map((f) => + fält.includes(f.id) && f.varde ? { ...f, varde: skydda(f.varde, nyckelId, nyckel) } : f, + ), + }; + } + if (handelse.typ === "objekt_identifierat") { + const objekt = { ...handelse.objekt }; + for (const n of fält) if (objekt[n]) objekt[n] = skydda(objekt[n], nyckelId, nyckel); + return { ...handelse, objekt }; + } + const ut = { ...handelse }; + for (const n of fält) if (ut[n]) ut[n] = skydda(ut[n], nyckelId, nyckel); + return ut; +} + +/** Öppnar de identifierande fälten igen för visning. */ +export function öppnaHändelse(handelse, nycklar) { + const fält = IDENTIFIERANDE[handelse?.typ]; + if (!fält) return handelse; + + if (handelse.typ === "arbetsorder_skannad") { + return { ...handelse, falt: (handelse.falt ?? []).map((f) => ({ ...f, varde: öppna(f.varde, nycklar) })) }; + } + if (handelse.typ === "objekt_identifierat") { + const objekt = { ...handelse.objekt }; + for (const n of fält) if (objekt[n]) objekt[n] = öppna(objekt[n], nycklar); + return { ...handelse, objekt }; + } + const ut = { ...handelse }; + for (const n of fält) if (ut[n]) ut[n] = öppna(ut[n], nycklar); + return ut; +} + +/** + * Gallringsbeslut. + * + * Retention är inte en teknisk detalj utan ett juridiskt ställningstagande, + * och det skiljer sig mellan ärendetyper: ett garantiärende måste kunna + * visas upp under garantitiden, ett kontantärende inte. Standardvärdena + * nedan är utgångspunkter — varje organisation ska sätta sina egna, och + * den som inte gör ett aktivt val får det försiktigaste. + */ +export const GALLRING_MANADER = { + Warranty: 120, // Följer den längsta rimliga garantitiden på en drivlina. + Goodwill: 120, + Insurance: 120, // Preskription för försäkringskrav. + Complaint: 36, // Konsumentköplagens reklamationsrätt. + "Used-vehicle warranty": 60, + standard: 36, +}; + +/** + * Gallringsdatum för ett avslutat ärende. + * + * Ärendetypen normaliseras genom ARENDETYP_ARV innan den slås upp. Det + * är inte en artighet mot gamla data utan en spärr: när typerna bytte + * språk hade ett garantiärende vars logg säger "Garanti" fallit ur + * tabellen och tagit standardvärdet 36 månader i stället för 120 — + * alltså raderat garantiunderlaget sju år för tidigt, tyst, och + * oåterkalleligt. Ett uppslag som missar ska ge det FÖRSIKTIGASTE + * utfallet; här gav det det farligaste. + */ +export function gallringsdatum(arendetyp, avslutat, manader = GALLRING_MANADER) { + const antal = manader[arendetypNu(arendetyp)] ?? manader.standard; + const d = new Date(avslutat); + d.setMonth(d.getMonth() + antal); + return d; +} diff --git a/felsokning/app/services/gemensam/sakerhet.mjs b/felsokning/app/services/gemensam/sakerhet.mjs new file mode 100644 index 0000000..774d175 --- /dev/null +++ b/felsokning/app/services/gemensam/sakerhet.mjs @@ -0,0 +1,74 @@ +// ALVA-SPEC-071 · Säkerhetsnivåns tak. +// +// ---- Bakgrund (panelgranskningen, garantisätet) -------------------------- +// +// Säkerhetsnivån i felorsaksanalysen — hög, medel, låg — valdes fritt av +// teknikern. Ett självskattat värde som ser ut som en mätning är värre än +// inget värde: en garantihandläggare kan inte prissätta det, och den som +// har bråttom väljer "hög" av samma skäl som den skriver "klart". +// +// Panelens lösning, som blev regeln här: nivån HÄRLEDS ur underlaget och +// sätter ett TAK. Teknikern kan sänka — den som vet något om fordonets +// historia som underlaget inte visar ska kunna vara ärligt osäker — men +// aldrig höja. Asymmetrin är poängen: ingen kan påstå högre säkerhet än +// evidensen bär, och en sänkning är information i stället för misstanke. +// +// ---- Vad taket räknar på ------------------------------------------------- +// +// HÖG kräver att orsakskedjan är sluten i båda ändar: symptomet +// reproducerat (inte "delvis" — delvis är ett annat ord för att felet +// inte är förstått) OCH minst ett spårbart mätvärde (mätdon ur registret, +// alltså E4). En slutsats utan mätning kan vara rätt, men den kan inte +// vara HÖG — det är precis skillnaden mellan att veta och att känna igen. +// +// MEDEL kräver evidens över E1: foto, video eller mätvärde. Enbart +// observationer — teknikerns egna påståenden — bär inte medel. +// +// LÅG är alltid tillåten. Ett golv vore att tvinga fram falsk säkerhet. +// +// Taket är avsiktligt ETT MÅTT PÅ UNDERLAGET, inte på resonemanget. +// Professorssätet varnade för att en formel som räknar foton utger sig +// för att mäta diagnostisk säkerhet — det gör den inte, och det står +// därför i både namn och dokumentation att det är ett TAK, inte ett +// betyg. Formeln säger vad underlaget som mest kan bära; om slutsatsen +// förtjänar det avgörs fortfarande av en människa. + +const ORDNING = ["lag", "medel", "hog"]; + +/** Rangordnar nivåer; okänd nivå räknas som lägst. */ +const rang = (niva) => Math.max(0, ORDNING.indexOf(niva)); + +/** + * Högsta säkerhetsnivå underlaget bär. + * + * @param handelser ärendets händelselogg (klartextform) + * @returns "hog" | "medel" | "lag" + */ +export function sakerhetstak(handelser) { + const av = (typ) => handelser.filter((h) => h?.typ === typ); + + const reproducerat = av("reproducering").some((h) => h.status === "ja"); + // Spårbart = mätdonet kommer ur registret OCH kalibreringen gällde + // när mätningen togs emot. Stämpeln `kalibreradVidMatning` sätts av + // servern vid uppslaget (TÜV T-2, TÜV-2 T-13) — utan den räknade + // taket ett instrument med utgången kalibrering som spårbart, och + // "hög" gick att nå med ett mätdon som evidensmodellen själv graderar + // E1. Fältet går inte att dikta från klienten: mätdonsFakta skriver + // alltid över det. + // + // Strikt även bakåt: ett mätvärde utan stämpel (skrivet före + // regeln) räknas INTE som spårbart. Samma princip som för påstådda + // beteckningar — det som inte kan styrkas bär inte "hög". + const sparbartMatvarde = av("matvarde").some((h) => h.matdonId && h.kalibreradVidMatning === true); + const evidensOverE1 = + sparbartMatvarde || av("matvarde").length > 0 || av("foto").length > 0 || av("video").length > 0; + + if (reproducerat && sparbartMatvarde) return "hog"; + if (evidensOverE1) return "medel"; + return "lag"; +} + +/** Är den påstådda nivån inom taket? Sänkning är alltid tillåten. */ +export function inomTak(pastadd, tak) { + return rang(pastadd) <= rang(tak); +} diff --git a/felsokning/app/services/gemensam/sammanfattning.mjs b/felsokning/app/services/gemensam/sammanfattning.mjs new file mode 100644 index 0000000..eeeb58e --- /dev/null +++ b/felsokning/app/services/gemensam/sammanfattning.mjs @@ -0,0 +1,150 @@ +// ALVA-PROC-0030 · Case summary. +// +// Några få meningar som ger vem som helst en bild av ärendet: kunden i +// telefon, försäkringshandläggaren som öppnar en akt, teknikern som tar +// över efter helgen. +// +// ---- Varför den är härledd och inte genererad ------------------------- +// +// Frestelsen är att låta modellen skriva den. Det vore fel här, och +// skälet är inte försiktighet utan funktion: +// +// Den ska gå att lita på. En sammanfattning som en bedömare läser +// blir en del av beslutsunderlaget. Är den +// genererad måste den granskas mot loggen +// varje gång — och då är den inte längre en +// genväg. +// +// Den ska vara stabil. Samma ärende måste ge samma text i dag och +// om två år. En sammanfattning som ändrar sig +// mellan visningar är oanvändbar i en tvist. +// +// Den ska fungera offline. Verkstadsgolvet har dålig täckning. +// +// Sammanfattningen är därför en projektion, precis som briefen och +// rapporten. 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. +// +// Modellen har fortfarande en roll — men i andra änden: den kan föreslå +// en språkligt jämnare formulering som teknikern GODKÄNNER, och då blir +// godkännandet en händelse i loggen. Det som visas här är alltid det +// härledda. + +const handelserI = (arende) => (arende.handelser ?? []).map((p) => p.handelse ?? p); +const av = (handelser, typ) => handelser.filter((h) => h?.typ === typ); +const sista = (handelser, typ) => av(handelser, typ).at(-1); + +const REPRODUKTION = { + ja: "Symptomet reproducerades.", + delvis: "Symptomet kunde delvis reproduceras.", + nej: "Symptomet kunde inte reproduceras under de förhållanden som rådde vid undersökningen.", +}; + +const KVALITET = { + symptomet_borta: "Vid kvalitetskontrollen var symptomet borta.", + kvarstar: "Vid kvalitetskontrollen kvarstod symptomet.", + delvis: "Vid kvalitetskontrollen kvarstod symptomet delvis.", + ej_verifierbar: "Kvalitetskontrollen kunde inte verifiera resultatet.", +}; + +/** Klipper en mening vid en rimlig gräns utan att kapa mitt i ett ord. */ +function korta(text, max = 220) { + const rå = String(text ?? "").trim().replace(/\s+/g, " "); + if (rå.length <= max) return rå; + const brytpunkt = rå.lastIndexOf(" ", max); + return `${rå.slice(0, brytpunkt > max * 0.6 ? brytpunkt : max)}…`; +} + +/** + * Bygger sammanfattningen. + * + * Ordningen är den en läsare faktiskt behöver: vad gällde det, vad sade + * kunden, kunde vi se det, vad blev orsaken, vad gjorde vi, höll det, + * vad är kvar. Varje mening utelämnas när underlaget saknas — utom de + * som handlar om att något saknas, för de är själva poängen. + * + * @returns { meningar[], text, fullstandig, saknas[] } + */ +export function sammanfatta(arende) { + const h = handelserI(arende); + const meningar = []; + const saknas = []; + + // 1 · Objektet. + const objekt = sista(h, "objekt_identifierat")?.objekt; + if (objekt) { + const delar = [objekt.beskrivning, objekt.identifierare].filter(Boolean).join(", "); + meningar.push(`Ärendet gäller ${delar || "ett identifierat objekt"}.`); + } else { + saknas.push("objektidentifiering"); + meningar.push("Objektet är inte identifierat i loggen."); + } + + // 2 · Kundens ord, ordagrant men kortat. + const fel = sista(h, "felbeskrivning")?.text; + if (fel) meningar.push(`Kunden beskrev: ”${korta(fel, 160)}”`); + else saknas.push("felbeskrivning"); + + // 3 · Kunde vi se det? Detta är den mening en bedömare läser först. + const repro = sista(h, "reproducering"); + if (repro) meningar.push(REPRODUKTION[repro.status] ?? "Reproduktionen är dokumenterad."); + else saknas.push("symptomverifiering"); + + // 4 · Orsaken, med teknikerns eget varför när det finns. + const orsak = sista(h, "felorsak"); + const slutsats = sista(h, "slutsats"); + if (slutsats && slutsats.orsakFastställd === false) { + meningar.push(`Orsaken kunde inte fastställas: ${korta(slutsats.motivering, 200)}`); + } else if (orsak) { + const kategori = (orsak.orsaker ?? []).join(", "); + meningar.push(`Konstaterad avvikelse: ${korta(orsak.avvikelse, 180)}${kategori ? ` Bedömd orsak: ${kategori}.` : ""}`); + if (slutsats?.motivering) meningar.push(`Motivering: ${korta(slutsats.motivering, 220)}`); + } else { + saknas.push("felorsaksanalys"); + } + + // 5 · Vad som gjordes. + const atgard = sista(h, "atgard_utford"); + if (atgard) { + meningar.push( + atgard.utford + ? `Utförd åtgärd: ${korta(atgard.beskrivning, 160)}` + : `Ingen åtgärd utfördes: ${korta(atgard.motivering || atgard.beskrivning, 160)}`, + ); + } else saknas.push("åtgärd"); + + // 6 · Höll det? + const kvalitet = sista(h, "kvalitetskontroll"); + if (kvalitet) meningar.push(KVALITET[kvalitet.resultat] ?? "Kvalitetskontrollen är dokumenterad."); + + // 7 · Vad som är kvar. Utelämnas aldrig när den finns — det är den + // enda meningen som talar om vad läsaren själv behöver göra. + if (slutsats?.kvarstaende) meningar.push(`Kvarstående: ${korta(slutsats.kvarstaende, 160)}`); + + return { + version: "ALVA-PROC-0030", + meningar, + text: meningar.join(" "), + /** Sant när inget obligatoriskt underlag saknas. */ + fullstandig: saknas.length === 0, + saknas, + }; +} + +/** + * Kortformen: en enda mening för en lista eller ett API-svar där + * utrymmet är en rad. + */ +export function enrading(arende) { + const h = handelserI(arende); + const objekt = sista(h, "objekt_identifierat")?.objekt; + const slutsats = sista(h, "slutsats"); + const orsak = sista(h, "felorsak"); + const avslutat = h.some((x) => x?.typ === "arende_avslutat"); + + const vad = objekt?.identifierare ?? "Oidentifierat objekt"; + if (!avslutat) return `${vad} — pågående.`; + if (slutsats?.orsakFastställd === false) return `${vad} — avslutat utan fastställd orsak.`; + if (orsak) return `${vad} — ${korta(orsak.avvikelse, 90)}`; + return `${vad} — avslutat.`; +} diff --git a/felsokning/app/services/gemensam/sprak/de.mjs b/felsokning/app/services/gemensam/sprak/de.mjs new file mode 100644 index 0000000..46a5344 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/de.mjs @@ -0,0 +1,329 @@ +// ALVA-SPEC-060 · Deutsch. +// +// Anweisungen stehen im Infinitiv, nicht in der Anrede: "Kilometerzähler +// fotografieren", nicht "Fotografieren Sie den Kilometerzähler". Das ist +// der Registerton technischer Anweisungen im deutschen Sprachraum und +// entspricht ALVA-SPEC-001 §7 — das System meldet einen Zustand, es +// wendet sich nicht an den Benutzer. +// +// `granskat: false`: die Oberfläche ist übersetzt, der Verfahrenstext +// ist nicht von einer Fachkraft geprüft. Siehe index.mjs. + +export const DE = { + // ---- Evidenzgrade ------------------------------------------------------- + "evidens.E1": "Beobachtung", + "evidens.E2": "Fotografie", + "evidens.E3": "Video mit Ton", + "evidens.E4": "Messung, kalibriertes Messmittel", + "evidens.E5": "Dokument", + "evidens.ej_kalibrerad": "Kalibrierung fehlt oder ist abgelaufen", + "evidens.ej_angivet": "Messmittel nicht angegeben", + "evidens.ej_fotograferad": "eingetragen, nicht fotografiert", + + // ---- Qualitätsschranke -------------------------------------------------- + "grind.objekt": "Identität des Fahrzeugs oder Objekts verifiziert", + "grind.historik": "Fahrzeughistorie geprüft, oder das Unterlassen begründet", + "grind.historik.nekad": "Eine abgelehnte Historienprüfung erfordert eine Begründung.", + "grind.historik.saknas": "Keine Historienprüfung dokumentiert.", + "grind.matarstallning.ingaende": "Kilometerstand bei Annahme fotografiert", + "grind.matarstallning.utgaende": "Kilometerstand bei Rückgabe fotografiert", + "grind.matarstallning.saknas": "Kein Kilometerstand dokumentiert.", + "grind.matarstallning.ej_foto": + "Der Kilometerstand ist eingetragen, aber nicht fotografiert. Kilometerzähler fotografieren, oder angeben, warum dies nicht möglich ist.", + "grind.reproducering": "Symptomverifizierung: reproduziert oder als nicht reproduzierbar dokumentiert", + "grind.felorsak": "Grundursachenanalyse dokumentiert", + "grind.atgard": "Korrekturmaßnahme dokumentiert, oder ihr Ausbleiben begründet", + "grind.atgard.saknas": "Weder eine durchgeführte Maßnahme noch ein Grund für deren Ausbleiben ist dokumentiert.", + "grind.kundbeslut": "Kundenentscheidung zum Vorschlag erfasst", + "grind.kundbeslut.avbojt": "Arbeit trotz abgelehntem Vorschlag durchgeführt", + "grind.kundbeslut.avbojt.detalj": + "Der Kunde hat den Vorschlag abgelehnt, es ist jedoch durchgeführte Arbeit dokumentiert.", + "grind.kvalitetskontroll": "Qualitätsprüfung durchgeführt — Symptom nachweislich behoben", + "grind.kontroller": "Prüfpunkte der Methodik: Evidenz oder dokumentierte Ausnahme", + "grind.foton": "Fotos für die Prüfpunkte vorhanden, die sie erfordern", + "grind.slutsats": "Abschlussfeststellung (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Hochvolt-Qualifikation bestätigt", + "grind.hogvolt.spanningslos": "Fahrzeug nach Herstellerverfahren spannungsfrei geschaltet, Spannungsfreiheit durch Messung festgestellt", + "grind.regelpaket": "Signatur des Regelpakets stimmt nicht — Abschluss gesperrt.", + "grind.regelpaket.osignerat": "Ein externes Regelpaket wird ohne Signatur verwendet — Abschluss gesperrt.", + "grind.evidens": "Evidenzgrad über E0", + "grind.evidens.saknas": "Es liegt keinerlei Evidenz im Protokoll vor.", + "grind.foton.detalj": "{kontroller} Prüfpunkte erfordern ein Foto, {foton} Fotos im Protokoll.", + "grind.sparr.ej_uppfyllt": "Die Sicherheitsanforderung ist nicht erfüllt.", + "grind.arendetyp.okant": "Unbekannte Anforderung im Regelpaket: {krav}", + "grind.arendetyp.krav": "Anforderung für diese Vorgangsart: {krav}", + "grind.sakerhet": "Konfidenzniveau innerhalb dessen, was die Evidenz trägt", + "grind.sakerhet.detalj": "Die angegebene Konfidenz ({niva}) übersteigt, was die Evidenz trägt ({tak}). Evidenz ergänzen oder das Niveau senken — ehrliche Unsicherheit ist Information.", + "grind.eskalering": "Technische Eskalationen beantwortet", + "grind.eskalering.detalj": "Eine Eskalation ist offen ({referens}). Die Antwort abwarten und dokumentieren, bevor der Vorgang abgeschlossen wird.", + + + // ---- Abschlussfeststellung (ALVA-RULE-200) ------------------------------ + "slutsats.rubrik": "Abschlussfeststellung", + "slutsats.konstaterat": "Was festgestellt wurde", + "slutsats.evidens": "Welche Evidenz dies trägt", + "slutsats.avfardat": "Welche Hypothesen verworfen wurden, und warum", + "slutsats.osakert": "Was unsicher bleibt", + "slutsats.ickesvar": + "Das ist keine Antwort. Angeben, was festgestellt wurde und welche Evidenz dies trägt.", + "slutsats.falt.motivering": "Begründung", + "slutsats.falt.motivering_ej": "Grund, warum die Ursache nicht festgestellt werden konnte", + "slutsats.falt.uteslutet": "Verworfene Alternativen", + "slutsats.falt.kvarstaende": "Verbleibende Unsicherheit", + "slutsats.falt.atgardsval": "Wahl der Maßnahme", + "slutsats.saknas": "{falt} fehlt.", + "slutsats.ickesvar.falt": "{falt}: „{text}“ ist keine Begründung. Angeben, was tatsächlich gilt, und warum.", + "slutsats.for_kort": "{falt} ist zu kurz ({langd} von mindestens {minsta} Zeichen), um im Nachhinein prüfbar zu sein.", + "slutsats.utan_varfor": "{falt} nennt das Was, nicht das Warum. Die Feststellung an die Evidenz binden — was darin begründet diese Folgerung?", + "slutsats.utan_slutsats": "Der Vorgang kann ohne Abschlussfeststellung nicht abgeschlossen werden. Angeben, warum die Feststellung aus der Evidenz folgt.", + "slutsats.hypotes_obemott": "Die Hypothese „{text}“ steht im Protokoll, wird aber nicht behandelt. Angeben, warum sie verworfen wurde oder warum sie offen bleibt.", + + + // ---- Vorgangsablauf ----------------------------------------------------- + "arende.nytt": "Neuer Vorgang", + "arende.oppna": "Offene Vorgänge", + "arende.avslutade": "Abgeschlossene Vorgänge", + "arende.avsluta": "Vorgang abschließen", + "arende.avslutat": "Vorgang abgeschlossen", + "arende.kan_ej_avslutas": "Der Vorgang kann noch nicht abgeschlossen werden", + "arende.hinder": "Vor dem Abschluss offen", + "arende.overlamna": "Übergeben", + "arende.ansvarig": "Verantwortlich", + + // ---- Vorprüfung --------------------------------------------------------- + "pre.rubrik": "Vorprüfung — vor Arbeitsbeginn", + "pre.historik.fraga": + "Ist die Fahrzeughistorie geprüft? (frühere Arbeiten, wiederkehrende Fehler, TSB, Rückruf- und Serviceaktionen)", + "pre.historik.ja": "Ja — geprüft", + "pre.historik.nej": "Nein", + "pre.historik.skal": "Grund, warum die Historie nicht geprüft wurde (erforderlich)", + "pre.historik.relevant": "Relevante frühere Arbeiten (optional — Ursachenkette)", + "pre.matarstallning": "Kilometerstand", + "pre.fotografera": "Kombiinstrument fotografieren", + "pre.felbeskrivning": "Fehlerbeschreibung des Kunden verifiziert", + "pre.observationer": "Sonstiges bei der Annahme?", + + // ---- Handlungen --------------------------------------------------------- + "handling.spara": "Speichern", + "handling.avbryt": "Abbrechen", + "handling.fortsatt": "Weiter", + "handling.tillbaka": "Zurück", + "handling.dokumentera": "Dokumentieren", + "handling.fotografera": "Fotografieren", + "handling.spela_in": "Video aufnehmen", + "handling.undantag": "Ausnahme dokumentieren", + "handling.undantag.skal": "Begründung der Ausnahme (erforderlich)", + "handling.exportera": "Exportieren", + "handling.skriv_ut": "Drucken", + + // ---- Messung ------------------------------------------------------------ + "matning.varde": "Wert", + "matning.enhet": "Einheit", + "matning.matdon": "Messmittel", + "matning.matdon.valj": "Messmittel wählen", + "matning.matdon.okant": "Unbekanntes Messmittel. Das Messmittel vor dem Speichern der Messung registrieren.", + "matning.kalibrerad_till": "Kalibriert bis", + + // ---- Bericht ------------------------------------------------------------ + "rapport.rubrik": "Vorgangsbericht", + "rapport.sammanfattning": "Zusammenfassung", + "rapport.evidens": "Evidenz", + "rapport.atgarder": "Maßnahmen", + "rapport.harledd": + "Aus dem Vorgangsprotokoll abgeleitet. Beobachtungen und Messungen werden ohne Schlussfolgerungen wiedergegeben, denen die Grundlage fehlt.", + + // ---- Sprache ------------------------------------------------------------ + "sprak.valj": "Sprache", + "sprak.granskat": "Geprüft", + "sprak.ogranskat": "Nicht geprüft", + "sprak.tackning": "{procent} % der Oberfläche", + + "metodik.ogranskad": + "Der Verfahrenstext wurde nicht von einer Fachkraft auf {sprak} geprüft. Schritte und Prüfpunkte werden auf Englisch angezeigt, wo keine geprüfte Übersetzung vorliegt — eine ungeprüfte Übersetzung einer Sicherheitsanweisung ist schlechter als eine fremdsprachige, weil sie nicht fremd aussieht.", + "metodik.pa_engelska": "Auf Englisch angezeigt — keine geprüfte Übersetzung auf {sprak}", + + // ---- Öffentliche Website (webb.) --------------------------------------- + "webb.nav.oversikt": "Überblick", + "webb.ansok": "Konto beantragen", + "webb.loggain": "Anmeldung", + "webb.fot.impressum": "Impressum", + "webb.fot.dataskydd": "Datenschutz", + "webb.fot.villkor": "Nutzungsbedingungen", + "webb.fot.tillganglighet": "Barrierefreiheit", + "webb.fot.sprak": "Sprachen", + "webb.fot.utgavor": "Versionshinweise", + + "webb.hero.position": "Geführte Diagnoseplattform", + "webb.hero.definition": "Standardisierte Diagnoseprozeduren für wiederholbare und verifizierbare Fehlersuche.", + + "webb.metod.etikett": "Methodik", + "webb.metod.rubrik": "Das ALVA-Modell", + "webb.metod.ingress": + "ALVA ist eine standardisierte Methode für systematische Analyse, Lokalisierung, Verifikation und Maßnahme. Jede Entscheidung ist nachvollziehbar, jede Schlussfolgerung verifizierbar, jede Maßnahme reproduzierbar.", + "webb.fas.analysis.syfte": "Evidenz sammeln.", + "webb.fas.analysis.avgransning": "Nur Fakten. Hypothesen werden in dieser Phase nicht erfasst.", + "webb.fas.localization.syfte": "Den Fehler isolieren.", + "webb.fas.localization.avgransning": "Den Bereich eingrenzen. Die Ursache ist noch nicht festgestellt.", + "webb.fas.verification.syfte": "Grundursache bestätigen.", + "webb.fas.verification.avgransning": "Verifiziert wird die Ursache — nicht das Symptom.", + "webb.fas.action.syfte": "Korrekturmaßnahme durchführen.", + "webb.fas.action.avgransning": "Ergebnis verifiziert und dokumentiert. Andernfalls ist die Phase unvollständig.", + + "webb.drift.etikett": "Betrieb", + "webb.drift.rubrik": "So funktioniert es", + "webb.drift.s1.rubrik": "Organisation anlegen", + "webb.drift.s1.text": "Die Registrierung wird eingereicht und geprüft.", + "webb.drift.s2.rubrik": "Benutzer erhalten Konten", + "webb.drift.s2.text": "Rollen werden vom Administrator der Organisation vergeben.", + "webb.drift.s3.rubrik": "Geführte Diagnoseprozeduren", + "webb.drift.s3.text": "Jeder Vorgang folgt einer definierten Prozedur.", + "webb.drift.s4.rubrik": "Verifikation", + "webb.drift.s4.text": "Die Grundursache wird vor der Korrekturmaßnahme bestätigt.", + "webb.drift.s5.rubrik": "Dokumentation", + "webb.drift.s5.text": "Der Bericht wird aus dem Vorgangsprotokoll erzeugt.", + "webb.drift.s6.rubrik": "Kontinuierliche Verbesserung", + "webb.drift.s6.text": "Verifizierte Ergebnisse verfeinern nachfolgende Prozeduren.", + + "webb.larande.etikett": "Fähigkeit", + "webb.larande.rubrik": "Lernende Organisation", + "webb.larande.p1": + "Die Plattform verfeinert Diagnoseprozeduren fortlaufend mit verifizierter Betriebserfahrung aus der eigenen Organisation.", + "webb.larande.p2": + "Jede abgeschlossene Diagnose fließt in die nachfolgende Anleitung ein. Nur verifizierte Ergebnisse werden verwendet — ein Vorgang, der ohne bestätigte Grundursache geschlossen wurde, trägt bewusst nichts bei.", + "webb.larande.p3": + "Die Wissensbasis gehört der Organisation. Sie wird aus Ihren Vorgängen, Ihren Prozeduren und Ihrer Dokumentation abgeleitet und nicht über Kunden hinweg zusammengeführt.", + "webb.larande.block": "Abgeleitet aus", + "webb.larande.k1": "Verifizierte Grundursachen", + "webb.larande.k2": "Bestätigte Korrekturmaßnahmen", + "webb.larande.k3": "Wiederkehrende Fehlerkategorien", + "webb.larande.k4": "Nachweise abgeschlossener Prozeduren", + "webb.larande.k5": "Dokumentation der Organisation", + + "webb.rapport.etikett": "Berichtswesen", + "webb.rapport.rubrik": "Vierteljährlicher Verbesserungsbericht", + "webb.rapport.ingress": + "Einmal im Quartal beantwortet die Plattform sechs Fragen zur eigenen Arbeit des Betriebs. Jede Antwort wird aus dem Vorgangsprotokoll abgeleitet — nichts wird geschätzt, und niemand wird befragt.", + "webb.rapport.f1": "Welche Fehler diagnostizieren wir jetzt beim ersten Mal richtig?", + "webb.rapport.f2": "Wie oft erweist sich eine vermutete Ursache als die tatsächliche?", + "webb.rapport.f3": "Welche Fehler kehren in der Flotte immer wieder?", + "webb.rapport.f4": "Welche Prozedurschritte werden übersprungen, und warum?", + "webb.rapport.f5": "Was weiß der Betrieb jetzt, was er im letzten Quartal nicht wusste?", + "webb.rapport.f6": "Werden Vorgänge abgeschlossen, verifiziert und dokumentiert — oder nur abgeschlossen?", + + "webb.pris.etikett": "Kommerzielles", + "webb.pris.rubrik": "Lizenzierung", + "webb.pris.komponent": "Komponente", + "webb.pris.grund": "Grundlage", + "webb.pris.plattform": "Plattformlizenz", + "webb.pris.plattform.grund": "Je Organisation, jährlich", + "webb.pris.anvandare": "Benutzerlizenzen", + "webb.pris.anvandare.grund": "Pro aktivem Benutzer, monatlich", + "webb.pris.moduler": "Unternehmensmodule", + "webb.pris.moduler.grund": "Optional, je Modul", + "webb.pris.betalning": "Keine Online-Zahlung. Keine Abo-Anmeldung. Die Rechnungsstellung folgt auf die Prüfung des Antrags.", + "webb.pris.aktivering": "Aktivierungsablauf", + "webb.pris.a1": "Die Organisation reicht die Registrierung ein.", + "webb.pris.a2": "Der Antrag wird geprüft.", + "webb.pris.a3": "Die Rechnung wird gestellt.", + "webb.pris.a4": "Die Zahlung wird verbucht.", + "webb.pris.a5": "Die Organisation wird aktiviert.", + "webb.pris.a6": "Weitere Benutzer werden monatlich abgerechnet.", + + "webb.kallor.etikett": "Infrastruktur", + "webb.kallor.rubrik": "Operative Wissensinfrastruktur", + "webb.kallor.p1": + "ALVA nutzt die eigenen autorisierten Wissensquellen der Organisation. Die Architektur ist anbieterneutral: Jede Quelle implementiert dieselbe Schnittstelle, und kein Anbieter wird vorausgesetzt.", + "webb.kallor.p2": + "Eine Beta-Installation arbeitet allein mit interner Dokumentation. Externe Anbieter werden später über separate Konnektoren angebunden, ohne Änderung am Plattformkern.", + "webb.kallor.block": "Auflösungsreihenfolge der Quellen", + "webb.kallor.k1": "Interne Unternehmensprozeduren", + "webb.kallor.k2": "OEM-Dokumentation", + "webb.kallor.k3": "Technische Bulletins", + "webb.kallor.k4": "Garantieinformationen", + "webb.kallor.k5": "Werkstatthandbücher", + "webb.kallor.k6": "Historische verifizierte Diagnosen", + "webb.kallor.k7": "Bewährte Praxis der Organisation", + + "webb.login.etikett": "Zugang", + "webb.login.demo": + "Diese Anmeldung authentifiziert nichts. Geben Sie Beliebiges in E-Mail und Passwort ein und drücken Sie Anmelden — kein Konto ist nötig, und das Portal dahinter zeigt feste Beispieldaten. Mit einer Plattforminstanz verbunden authentifiziert sich diese Seite gegen sie, und ohne gültige Sitzung ist das Portal geschlossen.", + "webb.login.losenord": "Passwort", + "webb.login.ofullstandigt": "Authentifizierung unvollständig. E-Mail und Passwort erforderlich.", + "webb.login.misslyckades": "Authentifizierung fehlgeschlagen.", + "webb.login.logga_in": "Anmelden", + "webb.login.loggar_in": "Anmeldung läuft", + "webb.login.glomt": "Passwort vergessen", + + "webb.ansokan.etikett": "Registrierung", + "webb.ansokan.demo": + "Nichts wird gesendet. Das Absenden dieses Formulars speichert nichts, benachrichtigt niemanden und erzeugt keinen Antrag — dahinter steht noch keine Antragsentgegennahme. Die Beschreibung unten sagt, wie die Registrierung funktionieren soll, nicht was heute geschieht.", + "webb.ansokan.avsikt": "Vorgesehener Betrieb: Anträge werden manuell geprüft, und in diesem Schritt wird keine Zahlung erhoben.", + "webb.falt.foretag": "Unternehmen", + "webb.falt.orgnummer": "Handelsregisternummer", + "webb.falt.kontakt": "Ansprechpartner", + "webb.falt.epost": "E-Mail", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Anzahl Techniker", + "webb.falt.bransch": "Branche", + "webb.falt.land": "Land", + "webb.falt.anvandare": "Erwartete Benutzerzahl", + "webb.falt.noteringar": "Anmerkungen", + "webb.falt.kravs": "(erforderlich)", + "webb.ansokan.skicka": "Antrag absenden", + "webb.ansokan.mottagen.etikett": "Antrag", + "webb.ansokan.mottagen": "Eingereicht", + "webb.ansokan.mottagen.demo": + "Nichts wurde übermittelt, und niemand wurde benachrichtigt. Die Referenz unten wurde in Ihrem Browser aus Ihren Eingaben berechnet und existiert nirgendwo sonst — sie ist weg, wenn Sie diese Seite schließen.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Referenz", + "webb.ansokan.granskning": + "Anträge werden manuell geprüft. Bei Genehmigung wird eine Rechnung gestellt. Die Organisation wird aktiviert, sobald die Zahlung verbucht ist.", + + "webb.sprak.etikett": "Lokalisierung", + "webb.sprak.rubrik": "Sprachen", + "webb.sprak.ingress": + "Englisch ist Standard- und Quellsprache. Neun Übersetzungen folgen. Was die Plattform nicht tut: behaupten, dass eine übersetzte Oberfläche eine übersetzte Methode bedeutet.", + "webb.sprak.princip.etikett": "Prinzip", + "webb.sprak.princip.rubrik": "Zwei Arten von Text", + "webb.sprak.granssnitt.rubrik": "Oberflächentext", + "webb.sprak.granssnitt.beteckning": "Fällt still zurück", + "webb.sprak.granssnitt.text": + "Beschriftungen, Schaltflächen, Status. Begrenzter Bestand, selten geändert. Eine englische Zeichenkette bei einem deutschen Benutzer ist ein Ärgernis, keine Gefahr — eine fehlende Übersetzung fällt daher kommentarlos auf Englisch zurück.", + "webb.sprak.metodik.rubrik": "Verfahrenstext", + "webb.sprak.metodik.beteckning": "Fällt nie still zurück", + "webb.sprak.metodik.text": + "Anweisungen für Arbeiten an einem Fahrzeug. Hier ist eine ungeprüfte Übersetzung schlimmer als eine fremdsprachige — denn Englisch sieht fremd aus, während eine schlechte Übersetzung wie eine Anweisung aussieht. Sie wird auf Englisch gezeigt und gekennzeichnet, mit benannter Sprache.", + "webb.sprak.invariant": + "Die Methode selbst wird nie übersetzt. Phasennamen und Statuswörter sind ALVAs Struktur und lesen sich in jedem Land gleich, sodass ein Prüfer eine rumänische und eine deutsche Vorgangsakte lesen kann, ohne zu wissen, in welcher Sprache der Betrieb arbeitet.", + "webb.sprak.tackning.etikett": "Abdeckung", + "webb.sprak.tackning.rubrik": "Was übersetzt ist, und was geprüft ist", + "webb.sprak.kolumn.sprak": "Sprache", + "webb.sprak.kolumn.granssnitt": "Oberfläche", + "webb.sprak.kolumn.granskat": "Methodik von einer Fachkraft geprüft", + "webb.sprak.granskat.ja": "Ja", + "webb.sprak.granskat.nej": "Nein — Verfahrenstext auf Englisch", + "webb.sprak.matt": + "Die Abdeckung der Oberfläche wird gemessen, nicht geschätzt: Ein Test lässt den Build scheitern, wenn irgendein Schlüssel in irgendeiner Sprache fehlt. Der Prüfstatus ist eine Aussage über Menschen, nicht über Dateien, und wird von Hand gesetzt.", + "webb.sprak.kallsprak": + "Der englische Verfahrenstext ist eine Übersetzung der schwedischen Quelle und wurde noch nicht von einer im Handwerk tätigen Fachkraft gelesen. Er wird am selben Maßstab gemessen wie die anderen neun Sprachen, und dieselbe Aussage steht hier über ihn, statt ihn still auszunehmen, weil er die Quelle ist.", + "webb.sprak.bevis.etikett": "Verifikation", + "webb.sprak.bevis.rubrik": "Die Sätze, die einen Vorgang stoppen", + "webb.sprak.bevis.ingress": + "Dies sind die Sätze, die einem Techniker den Abschluss eines Vorgangs verweigern. Eine Verweigerung, die niemand versteht, ist eine Verweigerung ohne Ausweg — also sind es die richtigen Sätze, um eine Übersetzung zu beurteilen.", + "webb.sprak.ej_granskad": "Nicht geprüft", + "webb.sprak.granskad": "Geprüft", + "webb.sprak.granskad.text": + "Der Verfahrenstext auf {sprak} wurde von einer im Handwerk tätigen Fachkraft gelesen. Schritte und Prüfpunkte werden durchgehend auf {sprak} angezeigt.", + "webb.sprak.val.etikett": "Betrieb", + "webb.sprak.val.rubrik": "Wie die Sprache gewählt wird", + "webb.sprak.val.s1.rubrik": "Wahl des Benutzers", + "webb.sprak.val.s1.text": "Was die einzelne Person gewählt hat.", + "webb.sprak.val.s2.rubrik": "Einstellung der Organisation", + "webb.sprak.val.s2.text": "Die Dokumentationssprache des Betriebs.", + "webb.sprak.val.s3.rubrik": "Browsersprache", + "webb.sprak.val.s3.text": "Die erste Sprache, die die Plattform erkennt.", + "webb.sprak.val.s4.rubrik": "Englisch", + "webb.sprak.val.s4.text": "Der Standard, und die Quelle.", + "webb.sprak.val.notering": + "Die Organisation steht bewusst über dem Browser. Ein Betrieb in Deutschland mit einem polnischen Techniker braucht eine gemeinsame Dokumentationssprache — die Sprache der Vorgangsakte darf nicht davon abhängen, wer die Zeile gerade geschrieben hat.", +}; diff --git a/felsokning/app/services/gemensam/sprak/en.mjs b/felsokning/app/services/gemensam/sprak/en.mjs new file mode 100644 index 0000000..472e92d --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/en.mjs @@ -0,0 +1,362 @@ +// ALVA-SPEC-060 · English — the source language. +// +// Every key that exists lives here first. A key added to another +// catalogue but not to this one is not a translation; it is a string +// with no original, and the test refuses it. +// +// ---- Naming -------------------------------------------------------------- +// +// `område.sak` — the prefix says where the string is used, so a +// translator can read the file in the order the user meets it rather +// than alphabetically. +// +// ---- Tone --------------------------------------------------------------- +// +// The status language of ALVA-SPEC-001 §7 applies to every string here: +// the system reports state, it does not address the user. "Verification +// passed", not "Well done!". A translator who makes it friendlier has +// made it wrong. + +export const EN = { + // ---- Phases (ALVA) ---------------------------------------------------- + "fas.analys": "Analysis", + "fas.lokalisering": "Localization", + "fas.verifiering": "Verification", + "fas.atgard": "Action", + + // ---- Status ------------------------------------------------------------ + "status.pending": "Pending", + "status.in_progress": "In progress", + "status.complete": "Complete", + "status.passed": "Passed", + "status.failed": "Failed", + "status.blocked": "Blocked", + "status.incomplete": "Incomplete", + "status.not_applicable": "Not applicable", + + // ---- Evidence grades ---------------------------------------------------- + "evidens.E1": "Observation", + "evidens.E2": "Photograph", + "evidens.E3": "Video with sound", + "evidens.E4": "Measurement, calibrated instrument", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "calibration missing or expired", + "evidens.ej_angivet": "instrument not stated", + "evidens.ej_fotograferad": "entered, not photographed", + + // ---- The quality gate --------------------------------------------------- + // + // These are the highest-stakes strings in the product. Each one is the + // reason a technician cannot close a case, so it has to say what is + // missing — not that something is missing. + "grind.objekt": "Vehicle or object identification verified", + "grind.historik": "Vehicle history checked, or the omission justified", + "grind.historik.nekad": "A declined history check requires a stated reason.", + "grind.historik.saknas": "No history check documented.", + "grind.matarstallning.ingaende": "Incoming odometer reading photographed", + "grind.matarstallning.utgaende": "Outgoing odometer reading photographed", + "grind.matarstallning.saknas": "No odometer reading documented.", + "grind.matarstallning.ej_foto": + "The reading is entered but not photographed. Photograph the odometer, or state why that is not possible.", + "grind.reproducering": "Symptom verification: reproduced, or documented as not reproducible", + "grind.felorsak": "Root cause analysis documented", + "grind.atgard": "Corrective action documented, or its absence justified", + "grind.atgard.saknas": "Neither a performed action nor a reason for its absence is documented.", + "grind.kundbeslut": "Customer decision on the proposal recorded", + "grind.kundbeslut.avbojt": "Work performed despite a declined proposal", + "grind.kundbeslut.avbojt.detalj": "The customer declined the proposal, but work has been documented as performed.", + "grind.kvalitetskontroll": "Quality check performed — symptom verified as resolved", + "grind.kontroller": "Methodology checks: evidence, or a documented exemption", + "grind.foton": "Photographs present for checks that require them", + "grind.slutsats": "Closing statement (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "High-voltage authorization confirmed", + "grind.hogvolt.spanningslos": "Vehicle de-energized per the manufacturer’s procedure, absence of voltage verified by measurement", + "grind.regelpaket": "Rule package signature does not match — closing blocked.", + "grind.regelpaket.osignerat": "An external rule package is in use without a signature — closing blocked.", + "grind.evidens": "Evidence above E0", + "grind.evidens.saknas": "No evidence of any kind is present in the log.", + "grind.foton.detalj": "{kontroller} checks require a photograph; {foton} photographs are in the log.", + "grind.sparr.ej_uppfyllt": "The safety requirement is not met.", + "grind.arendetyp.okant": "Unknown requirement in the rule package: {krav}", + "grind.arendetyp.krav": "Requirement for this case type: {krav}", + "grind.sakerhet": "Confidence level within what the evidence supports", + "grind.sakerhet.detalj": "The stated confidence ({niva}) exceeds what the evidence supports ({tak}). Add supporting evidence, or lower the confidence — honest uncertainty is information.", + "grind.eskalering": "Technical escalations answered", + "grind.eskalering.detalj": "An escalation is open ({referens}). Await the answer and document it before the case is closed.", + + + // ---- The closing statement (ALVA-RULE-200) ------------------------------ + "slutsats.rubrik": "Closing statement", + "slutsats.konstaterat": "What was established", + "slutsats.evidens": "Which evidence carries it", + "slutsats.avfardat": "Which hypotheses were dismissed, and why", + "slutsats.osakert": "What remains uncertain", + "slutsats.ickesvar": "That is not an answer. State what was established and which evidence carries it.", + "slutsats.falt.motivering": "Rationale", + "slutsats.falt.motivering_ej": "Reason the cause could not be established", + "slutsats.falt.uteslutet": "Dismissed alternatives", + "slutsats.falt.kvarstaende": "Remaining uncertainty", + "slutsats.falt.atgardsval": "Choice of action", + "slutsats.saknas": "{falt} is missing.", + "slutsats.ickesvar.falt": "{falt}: “{text}” is not a reason. State what actually applies, and why.", + "slutsats.for_kort": "{falt} is too short ({langd} of at least {minsta} characters) to be reviewable afterwards.", + "slutsats.utan_varfor": "{falt} states what, but not why. Tie the conclusion to the evidence — what in it makes this follow?", + "slutsats.utan_slutsats": "The case cannot be closed without a closing statement. State why the conclusion follows from the evidence.", + "slutsats.hypotes_obemott": "The hypothesis “{text}” is in the log but is not addressed. State why it was dismissed, or why it remains open.", + + + // ---- Case flow ----------------------------------------------------------- + "arende.nytt": "New case", + "arende.oppna": "Open cases", + "arende.avslutade": "Closed cases", + "arende.avsluta": "Close case", + "arende.avslutat": "Case closed", + "arende.kan_ej_avslutas": "The case cannot be closed yet", + "arende.hinder": "Outstanding before closing", + "arende.overlamna": "Hand over", + "arende.ansvarig": "Assigned to", + + // ---- Pre-diagnostics ------------------------------------------------------ + "pre.rubrik": "Pre-diagnostics — before the work begins", + "pre.historik.fraga": "Has the vehicle’s history been checked? (previous work, recurring faults, TSBs, campaigns)", + "pre.historik.ja": "Yes — checked", + "pre.historik.nej": "No", + "pre.historik.skal": "Reason the history was not checked (required)", + "pre.historik.relevant": "Relevant previous work (optional — causal chain)", + "pre.matarstallning": "Odometer reading", + "pre.fotografera": "Photograph the instrument cluster", + "pre.felbeskrivning": "Customer’s fault description verified", + "pre.observationer": "Anything else noted at vehicle reception?", + + // ---- Common actions -------------------------------------------------------- + "handling.spara": "Save", + "handling.avbryt": "Cancel", + "handling.fortsatt": "Continue", + "handling.tillbaka": "Back", + "handling.dokumentera": "Document", + "handling.fotografera": "Photograph", + "handling.spela_in": "Record video", + "handling.undantag": "Document an exemption", + "handling.undantag.skal": "Reason for the exemption (required)", + "handling.exportera": "Export", + "handling.skriv_ut": "Print", + + // ---- Measurement ------------------------------------------------------------- + "matning.varde": "Value", + "matning.enhet": "Unit", + "matning.matdon": "Instrument", + "matning.matdon.valj": "Select instrument", + "matning.matdon.okant": "Unknown instrument. Register it before saving the measurement.", + "matning.kalibrerad_till": "Calibration valid until", + + // ---- Report -------------------------------------------------------------------- + "rapport.rubrik": "Case report", + "rapport.sammanfattning": "Summary", + "rapport.evidens": "Evidence", + "rapport.atgarder": "Actions", + "rapport.harledd": "Generated from the case log. Observations and measurements are reported without conclusions that lack support.", + + // ---- Language ---------------------------------------------------------------------- + "sprak.valj": "Language", + "sprak.granskat": "Reviewed", + "sprak.ogranskat": "Not reviewed", + "sprak.tackning": "{procent}% of the interface", + + // Shown when the methodology content has not been reviewed by a + // specialist in the user’s language. Deliberately specific: a general + // disclaimer is read by nobody. + "metodik.ogranskad": + "Procedure text has not been reviewed by a technical specialist in {sprak}. Steps and checks are shown in English where no reviewed translation exists — an unreviewed translation of a safety instruction is worse than a foreign-language one, because it does not look foreign.", + "metodik.pa_engelska": "Shown in English — no reviewed translation in {sprak}", + + // ---- Public website (webb.) --------------------------------------------- + // + // The marketing site and the platform are the same system, so the site + // speaks through the same catalogue. Keys follow the page order. The + // portal chrome is deliberately absent: the portal is the product, and + // its language is governed by account and organization, not by the + // visitor’s footer choice. + "webb.nav.oversikt": "Overview", + "webb.ansok": "Request account", + "webb.loggain": "Sign in", + "webb.fot.impressum": "Legal notice", + "webb.fot.dataskydd": "Privacy", + "webb.fot.villkor": "Terms", + "webb.fot.tillganglighet": "Accessibility", + "webb.fot.sprak": "Languages", + "webb.fot.utgavor": "Release notes", + + "webb.hero.position": "Guided Diagnostic Platform", + "webb.hero.definition": "Standardized diagnostic procedures for repeatable and verifiable troubleshooting.", + + "webb.metod.etikett": "Methodology", + "webb.metod.rubrik": "The ALVA model", + "webb.metod.ingress": + "ALVA is a standardized method for systematic analysis, localization, verification and action. Every decision is traceable, every conclusion verifiable, every action reproducible.", + "webb.fas.analysis.syfte": "Collect evidence.", + "webb.fas.analysis.avgransning": "Facts only. Hypotheses are not collected in this phase.", + "webb.fas.localization.syfte": "Isolate the fault.", + "webb.fas.localization.avgransning": "Narrow the area. The cause is not yet established.", + "webb.fas.verification.syfte": "Confirm root cause.", + "webb.fas.verification.avgransning": "The cause is verified — not the symptom.", + "webb.fas.action.syfte": "Execute corrective action.", + "webb.fas.action.avgransning": "Result verified and documented. Otherwise the phase is incomplete.", + + "webb.drift.etikett": "Operation", + "webb.drift.rubrik": "How it works", + "webb.drift.s1.rubrik": "Create organization", + "webb.drift.s1.text": "An application is submitted and reviewed.", + "webb.drift.s2.rubrik": "Users receive accounts", + "webb.drift.s2.text": "Roles are assigned by the organization administrator.", + "webb.drift.s3.rubrik": "Guided diagnostic procedures", + "webb.drift.s3.text": "Each case follows a defined procedure.", + "webb.drift.s4.rubrik": "Verification", + "webb.drift.s4.text": "Root cause is confirmed before corrective action.", + "webb.drift.s5.rubrik": "Documentation", + "webb.drift.s5.text": "The record is generated from the case log.", + "webb.drift.s6.rubrik": "Continuous improvement", + "webb.drift.s6.text": "Verified outcomes refine subsequent procedures.", + + "webb.larande.etikett": "Capability", + "webb.larande.rubrik": "Enterprise learning", + "webb.larande.p1": + "The platform continuously refines diagnostic procedures using verified operational experience from your own organization.", + "webb.larande.p2": + "Every completed diagnostic contributes to subsequent guidance. Only verified outcomes are used — a case closed without a confirmed root cause contributes nothing, by design.", + "webb.larande.p3": + "The knowledge base belongs to the organization. It is derived from your cases, your procedures and your documentation, and it is not aggregated across customers.", + "webb.larande.block": "Derived from", + "webb.larande.k1": "Verified root causes", + "webb.larande.k2": "Confirmed corrective actions", + "webb.larande.k3": "Recurring fault categories", + "webb.larande.k4": "Procedure completion records", + "webb.larande.k5": "Organization documentation", + + "webb.rapport.etikett": "Reporting", + "webb.rapport.rubrik": "Quarterly improvement report", + "webb.rapport.ingress": + "Once a quarter, the platform answers six questions about the workshop’s own work. Every answer is derived from the case log — nothing is estimated, and nothing is asked of anyone.", + "webb.rapport.f1": "Which faults are we now diagnosing correctly the first time?", + "webb.rapport.f2": "How often does a suspected cause turn out to be the actual one?", + "webb.rapport.f3": "Which faults keep coming back across the fleet?", + "webb.rapport.f4": "Which procedure steps are being skipped, and why?", + "webb.rapport.f5": "What does the workshop know now that it did not know last quarter?", + "webb.rapport.f6": "Are cases being completed, verified and documented — or only completed?", + + "webb.pris.etikett": "Commercial", + "webb.pris.rubrik": "Licensing", + "webb.pris.komponent": "Component", + "webb.pris.grund": "Basis", + "webb.pris.plattform": "Platform license", + "webb.pris.plattform.grund": "Per organization, annually", + "webb.pris.anvandare": "User licenses", + "webb.pris.anvandare.grund": "Per active user, monthly", + "webb.pris.moduler": "Enterprise modules", + "webb.pris.moduler.grund": "Optional, per module", + "webb.pris.betalning": "No online payment. No subscription sign-up. Invoicing follows review of the application.", + "webb.pris.aktivering": "Activation sequence", + "webb.pris.a1": "Organization submits an application.", + "webb.pris.a2": "Application is reviewed.", + "webb.pris.a3": "Invoice is issued.", + "webb.pris.a4": "Payment is registered.", + "webb.pris.a5": "Organization is activated.", + "webb.pris.a6": "Additional users are billed monthly.", + + "webb.kallor.etikett": "Infrastructure", + "webb.kallor.rubrik": "Operational Knowledge Infrastructure", + "webb.kallor.p1": + "ALVA uses the organization’s own authorized knowledge sources. The architecture is provider-agnostic: every source implements the same interface, and no provider is assumed.", + "webb.kallor.p2": + "A beta installation operates on internal documentation alone. External providers are enabled later through separate connectors, without change to the platform core.", + "webb.kallor.block": "Source resolution order", + "webb.kallor.k1": "Internal company procedures", + "webb.kallor.k2": "OEM documentation", + "webb.kallor.k3": "Technical bulletins", + "webb.kallor.k4": "Warranty information", + "webb.kallor.k5": "Service manuals", + "webb.kallor.k6": "Historical verified diagnostics", + "webb.kallor.k7": "Organization best practices", + + "webb.login.etikett": "Access", + "webb.login.demo": + "This login authenticates nothing. Type anything into Email and Password and press Sign in — no account is needed, and the portal behind it presents fixed example data. When connected to a platform instance, this page authenticates against it, and the portal is closed without a valid session.", + "webb.login.losenord": "Password", + "webb.login.ofullstandigt": "Authentication incomplete. Email and password required.", + "webb.login.misslyckades": "Authentication failed.", + "webb.login.logga_in": "Sign in", + "webb.login.loggar_in": "Signing in", + "webb.login.glomt": "Forgot password", + + "webb.ansokan.etikett": "Registration", + "webb.ansokan.demo": + "Nothing is sent. Submitting this form stores nothing, notifies no one, and creates no application — there is no intake behind it yet. The description below states how registration is intended to work, not what happens today.", + "webb.ansokan.avsikt": "Intended operation: applications are reviewed manually and no payment is taken at this stage.", + "webb.falt.foretag": "Company", + "webb.falt.orgnummer": "Company registration number", + "webb.falt.kontakt": "Contact person", + "webb.falt.epost": "Email", + "webb.falt.telefon": "Phone", + "webb.falt.tekniker": "Number of technicians", + "webb.falt.bransch": "Industry", + "webb.falt.land": "Country", + "webb.falt.anvandare": "Expected number of users", + "webb.falt.noteringar": "Notes", + "webb.falt.kravs": "(required)", + "webb.ansokan.skicka": "Submit application", + "webb.ansokan.mottagen.etikett": "Application", + "webb.ansokan.mottagen": "Submitted", + "webb.ansokan.mottagen.demo": + "Nothing was submitted and no one was notified. The reference below was computed in your browser from what you typed, and exists nowhere else — it will be gone when you close this page.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Reference", + "webb.ansokan.granskning": + "Applications are reviewed manually. An invoice is issued on approval. The organization is activated once payment is registered.", + + "webb.sprak.etikett": "Translation", + "webb.sprak.rubrik": "Languages", + "webb.sprak.ingress": + "English is the default and the source language. Nine translations follow. What the platform does not do is claim that a translated interface means a translated method.", + "webb.sprak.princip.etikett": "Principle", + "webb.sprak.princip.rubrik": "Two kinds of text", + "webb.sprak.granssnitt.rubrik": "Interface text", + "webb.sprak.granssnitt.beteckning": "Falls back silently", + "webb.sprak.granssnitt.text": + "Labels, buttons, statuses. Finite, rarely changed. An English string reaching a German user is an irritation, not a hazard — so a missing translation falls back to English without comment.", + "webb.sprak.metodik.rubrik": "Procedure text", + "webb.sprak.metodik.beteckning": "Never falls back silently", + "webb.sprak.metodik.text": + "Instructions for work on a vehicle. Here an unreviewed translation is worse than a foreign-language one — because English looks foreign, while a bad translation looks like an instruction. It is shown in English and marked, with the language named.", + "webb.sprak.invariant": + "The method itself is never translated. Phase names and status words are ALVA’s structure and read identically in every country, so an auditor can read a Romanian and a German case record without knowing which language the workshop works in.", + "webb.sprak.tackning.etikett": "Coverage", + "webb.sprak.tackning.rubrik": "What is translated, and what is reviewed", + "webb.sprak.kolumn.sprak": "Language", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Method reviewed by a specialist", + "webb.sprak.granskat.ja": "Yes", + "webb.sprak.granskat.nej": "No — procedure text shown in English", + "webb.sprak.matt": + "Interface coverage is measured, not estimated: a test fails the build if any key is missing from any language. Review status is a statement about people, not about files, and is set by hand.", + "webb.sprak.kallsprak": + "The English procedure text is a translation of the Swedish source and has not yet been read by a specialist working in the trade. It is held to the same standard as the other nine languages, and the same statement is made about it here rather than quietly excepted because it is the source.", + "webb.sprak.bevis.etikett": "Verification", + "webb.sprak.bevis.rubrik": "The strings that stop a case", + "webb.sprak.bevis.ingress": + "These are the sentences that refuse to let a technician close a case. A refusal nobody understands is a refusal with no way through — so they are the right strings to judge a translation by.", + "webb.sprak.ej_granskad": "Not reviewed", + "webb.sprak.granskad": "Reviewed", + "webb.sprak.granskad.text": + "Procedure text in {sprak} has been read by a specialist working in the trade. Steps and checks are shown in {sprak} throughout.", + "webb.sprak.val.etikett": "Operation", + "webb.sprak.val.rubrik": "How the language is chosen", + "webb.sprak.val.s1.rubrik": "User preference", + "webb.sprak.val.s1.text": "What the individual has selected.", + "webb.sprak.val.s2.rubrik": "Organization setting", + "webb.sprak.val.s2.text": "The workshop’s documentation language.", + "webb.sprak.val.s3.rubrik": "Browser language", + "webb.sprak.val.s3.text": "The first language the platform recognizes.", + "webb.sprak.val.s4.rubrik": "English", + "webb.sprak.val.s4.text": "The default, and the source.", + "webb.sprak.val.notering": + "The organization ranks above the browser deliberately. A workshop in Germany with a Polish technician needs one documentation language — the case record must not change language depending on who happened to write the line.", +}; diff --git a/felsokning/app/services/gemensam/sprak/es.mjs b/felsokning/app/services/gemensam/sprak/es.mjs new file mode 100644 index 0000000..cebc9c2 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/es.mjs @@ -0,0 +1,327 @@ +// ALVA-SPEC-060 · Español. +// +// Las instrucciones van en infinitivo, sin dirigirse al usuario: +// «Fotografiar el cuentakilómetros», no «Fotografíe el cuentakilómetros». +// Es el registro de las instrucciones técnicas y respeta ALVA-SPEC-001 §7 +// — el sistema informa de un estado, no se dirige al operario. +// +// `granskat: false`: la interfaz está traducida; el texto de +// procedimiento no está revisado por un especialista. Véase index.mjs. + +export const ES = { + // ---- Grados de evidencia ------------------------------------------------ + "evidens.E1": "Observación", + "evidens.E2": "Fotografía", + "evidens.E3": "Vídeo con sonido", + "evidens.E4": "Medición, instrumento calibrado", + "evidens.E5": "Documento", + "evidens.ej_kalibrerad": "calibración ausente o caducada", + "evidens.ej_angivet": "instrumento no indicado", + "evidens.ej_fotograferad": "introducido, no fotografiado", + + // ---- El control de cierre ----------------------------------------------- + "grind.objekt": "Identificación del vehículo u objeto verificada", + "grind.historik": "Historial del vehículo comprobado, o su omisión justificada", + "grind.historik.nekad": "Una comprobación de historial rechazada exige que se indique el motivo.", + "grind.historik.saknas": "No hay ninguna comprobación de historial documentada.", + "grind.matarstallning.ingaende": "Kilometraje de entrada fotografiado", + "grind.matarstallning.utgaende": "Kilometraje de salida fotografiado", + "grind.matarstallning.saknas": "No hay ningún kilometraje documentado.", + "grind.matarstallning.ej_foto": + "El kilometraje está introducido pero no fotografiado. Fotografiar el cuentakilómetros, o indicar por qué no es posible.", + "grind.reproducering": "Verificación del síntoma: reproducido, o documentado como no reproducible", + "grind.felorsak": "Análisis de la causa raíz documentado", + "grind.atgard": "Acción correctiva documentada, o su ausencia justificada", + "grind.atgard.saknas": "No se ha documentado ni una acción realizada ni un motivo de su ausencia.", + "grind.kundbeslut": "Decisión del cliente sobre la propuesta: registrada", + "grind.kundbeslut.avbojt": "Trabajo realizado pese a una propuesta rechazada", + "grind.kundbeslut.avbojt.detalj": + "El cliente rechazó la propuesta, pero hay trabajo documentado como realizado.", + "grind.kvalitetskontroll": "Control de calidad realizado — síntoma verificado como resuelto", + "grind.kontroller": "Puntos de control de la metodología: evidencia, o exención documentada", + "grind.foton": "Hay fotografías para los puntos de control que las exigen", + "grind.slutsats": "Conclusión (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Habilitación de alta tensión confirmada", + "grind.hogvolt.spanningslos": "Vehículo sin tensión según el procedimiento del fabricante, ausencia de tensión verificada mediante medición", + "grind.regelpaket": "La firma del paquete de reglas no coincide — cierre bloqueado.", + "grind.regelpaket.osignerat": "Se utiliza un paquete de reglas externo sin firma — cierre bloqueado.", + "grind.evidens": "Nivel de evidencia superior a E0", + "grind.evidens.saknas": "No hay ninguna evidencia de ningún tipo en el registro.", + "grind.foton.detalj": "{kontroller} puntos de control exigen fotografía, {foton} fotografías en el registro.", + "grind.sparr.ej_uppfyllt": "El requisito de seguridad no se cumple.", + "grind.arendetyp.okant": "Requisito desconocido en el paquete de reglas: {krav}", + "grind.arendetyp.krav": "Requisito para este tipo de expediente: {krav}", + "grind.sakerhet": "Nivel de confianza dentro de lo que sustenta la evidencia", + "grind.sakerhet.detalj": "La confianza indicada ({niva}) supera lo que sustenta la evidencia ({tak}). Añadir evidencia que la sustente, o bajar el nivel — la incertidumbre honesta es información.", + "grind.eskalering": "Escalaciones técnicas respondidas", + "grind.eskalering.detalj": "Hay una escalación abierta ({referens}). Esperar la respuesta y documentarla antes de cerrar el expediente.", + + + // ---- La conclusión (ALVA-RULE-200) -------------------------------------- + "slutsats.rubrik": "Conclusión", + "slutsats.konstaterat": "Qué se ha establecido", + "slutsats.evidens": "Qué evidencia lo sustenta", + "slutsats.avfardat": "Qué hipótesis se han descartado, y por qué", + "slutsats.osakert": "Qué sigue siendo incierto", + "slutsats.ickesvar": "Eso no es una respuesta. Indicar qué se ha establecido y qué evidencia lo sustenta.", + "slutsats.falt.motivering": "Justificación", + "slutsats.falt.motivering_ej": "Motivo por el que no se pudo establecer la causa", + "slutsats.falt.uteslutet": "Alternativas descartadas", + "slutsats.falt.kvarstaende": "Incertidumbre restante", + "slutsats.falt.atgardsval": "Elección de la acción", + "slutsats.saknas": "Falta el campo «{falt}».", + "slutsats.ickesvar.falt": "{falt}: «{text}» no es un motivo. Indicar qué se aplica realmente, y por qué.", + "slutsats.for_kort": "El campo «{falt}» es demasiado corto ({langd} de al menos {minsta} caracteres) para poder revisarse después.", + "slutsats.utan_varfor": "{falt} indica el qué, pero no el porqué. Vincular la conclusión con la evidencia — ¿qué hay en ella que la sustente?", + "slutsats.utan_slutsats": "El expediente no se puede cerrar sin una conclusión. Indicar por qué la conclusión se desprende de la evidencia.", + "slutsats.hypotes_obemott": "La hipótesis «{text}» figura en el registro pero no se aborda. Indicar por qué se descartó, o por qué sigue abierta.", + + + // ---- Flujo del expediente ----------------------------------------------- + "arende.nytt": "Nuevo expediente", + "arende.oppna": "Expedientes abiertos", + "arende.avslutade": "Expedientes cerrados", + "arende.avsluta": "Cerrar el expediente", + "arende.avslutat": "Expediente cerrado", + "arende.kan_ej_avslutas": "El expediente todavía no se puede cerrar", + "arende.hinder": "Pendiente antes del cierre", + "arende.overlamna": "Transferir", + "arende.ansvarig": "Responsable", + + // ---- Comprobación previa ------------------------------------------------ + "pre.rubrik": "Comprobación previa — antes de iniciar el trabajo", + "pre.historik.fraga": + "¿Se ha comprobado el historial del vehículo? (trabajos anteriores, averías recurrentes, TSB, campañas)", + "pre.historik.ja": "Sí — comprobado", + "pre.historik.nej": "No", + "pre.historik.skal": "Motivo por el que no se ha comprobado el historial (obligatorio)", + "pre.historik.relevant": "Trabajos anteriores relevantes (opcional — cadena causal)", + "pre.matarstallning": "Kilometraje", + "pre.fotografera": "Fotografiar el cuadro de instrumentos", + "pre.felbeskrivning": "Descripción de la avería según el cliente verificada", + "pre.observationer": "¿Algo más en la recepción?", + + // ---- Acciones ----------------------------------------------------------- + "handling.spara": "Guardar", + "handling.avbryt": "Cancelar", + "handling.fortsatt": "Continuar", + "handling.tillbaka": "Atrás", + "handling.dokumentera": "Documentar", + "handling.fotografera": "Fotografiar", + "handling.spela_in": "Grabar vídeo", + "handling.undantag": "Documentar una exención", + "handling.undantag.skal": "Motivo de la exención (obligatorio)", + "handling.exportera": "Exportar", + "handling.skriv_ut": "Imprimir", + + // ---- Medición ----------------------------------------------------------- + "matning.varde": "Valor", + "matning.enhet": "Unidad", + "matning.matdon": "Instrumento", + "matning.matdon.valj": "Seleccionar instrumento", + "matning.matdon.okant": "Instrumento desconocido. Registrarlo antes de guardar la medición.", + "matning.kalibrerad_till": "Calibrado hasta", + + // ---- Informe ------------------------------------------------------------ + "rapport.rubrik": "Informe del expediente", + "rapport.sammanfattning": "Resumen", + "rapport.evidens": "Evidencia", + "rapport.atgarder": "Acciones", + "rapport.harledd": + "Derivado del registro del expediente. Las observaciones y las mediciones se presentan sin conclusiones que carezcan de sustento.", + + // ---- Idioma ------------------------------------------------------------- + "sprak.valj": "Idioma", + "sprak.granskat": "Revisado", + "sprak.ogranskat": "Sin revisar", + "sprak.tackning": "{procent} % de la interfaz", + + "metodik.ogranskad": + "El texto de procedimiento no ha sido revisado por un especialista técnico en {sprak}. Los pasos y los puntos de control se muestran en inglés cuando no existe una traducción revisada — una traducción sin revisar de una instrucción de seguridad es peor que una en lengua extranjera, porque no parece extranjera.", + "metodik.pa_engelska": "Mostrado en inglés — no hay traducción revisada en {sprak}", + + // ---- Sitio público (webb.) --------------------------------------------- + "webb.nav.oversikt": "Visión general", + "webb.ansok": "Solicitar cuenta", + "webb.loggain": "Iniciar sesión", + "webb.fot.impressum": "Aviso legal", + "webb.fot.dataskydd": "Privacidad", + "webb.fot.villkor": "Condiciones", + "webb.fot.tillganglighet": "Accesibilidad", + "webb.fot.sprak": "Idiomas", + "webb.fot.utgavor": "Notas de versión", + + "webb.hero.position": "Plataforma de diagnóstico guiado", + "webb.hero.definition": "Procedimientos de diagnóstico estandarizados para una localización de averías repetible y verificable.", + + "webb.metod.etikett": "Metodología", + "webb.metod.rubrik": "El modelo ALVA", + "webb.metod.ingress": + "ALVA es un método estandarizado de análisis, localización, verificación y acción sistemáticos. Cada decisión es trazable, cada conclusión verificable, cada acción reproducible.", + "webb.fas.analysis.syfte": "Reunir evidencia.", + "webb.fas.analysis.avgransning": "Solo hechos. En esta fase no se recogen hipótesis.", + "webb.fas.localization.syfte": "Aislar la avería.", + "webb.fas.localization.avgransning": "Delimitar la zona. La causa aún no está establecida.", + "webb.fas.verification.syfte": "Confirmar la causa raíz.", + "webb.fas.verification.avgransning": "Se verifica la causa — no el síntoma.", + "webb.fas.action.syfte": "Ejecutar la acción correctiva.", + "webb.fas.action.avgransning": "Resultado verificado y documentado. De lo contrario, la fase está incompleta.", + + "webb.drift.etikett": "Operativa", + "webb.drift.rubrik": "Cómo funciona", + "webb.drift.s1.rubrik": "Crear la organización", + "webb.drift.s1.text": "El registro se envía y se revisa.", + "webb.drift.s2.rubrik": "Los usuarios reciben cuentas", + "webb.drift.s2.text": "El administrador de la organización asigna los roles.", + "webb.drift.s3.rubrik": "Procedimientos de diagnóstico guiados", + "webb.drift.s3.text": "Cada expediente sigue un procedimiento definido.", + "webb.drift.s4.rubrik": "Verificación", + "webb.drift.s4.text": "La causa raíz se confirma antes de la acción correctiva.", + "webb.drift.s5.rubrik": "Documentación", + "webb.drift.s5.text": "El informe se genera a partir del registro del expediente.", + "webb.drift.s6.rubrik": "Mejora continua", + "webb.drift.s6.text": "Los resultados verificados refinan los procedimientos posteriores.", + + "webb.larande.etikett": "Capacidad", + "webb.larande.rubrik": "Aprendizaje de la organización", + "webb.larande.p1": + "La plataforma refina continuamente los procedimientos de diagnóstico con la experiencia operativa verificada de su propia organización.", + "webb.larande.p2": + "Cada diagnóstico completado alimenta la guía posterior. Solo se usan resultados verificados — un expediente cerrado sin causa raíz confirmada no aporta nada, por diseño.", + "webb.larande.p3": + "La base de conocimiento pertenece a la organización. Se deriva de sus casos, sus procedimientos y su documentación, y no se agrega entre clientes.", + "webb.larande.block": "Derivada de", + "webb.larande.k1": "Causas raíz verificadas", + "webb.larande.k2": "Acciones correctivas confirmadas", + "webb.larande.k3": "Categorías de averías recurrentes", + "webb.larande.k4": "Registros de procedimientos completados", + "webb.larande.k5": "Documentación de la organización", + + "webb.rapport.etikett": "Informes", + "webb.rapport.rubrik": "Informe trimestral de mejora", + "webb.rapport.ingress": + "Una vez al trimestre, la plataforma responde seis preguntas sobre el propio trabajo del taller. Cada respuesta se deriva del registro de expedientes — nada se estima y no se pregunta a nadie.", + "webb.rapport.f1": "¿Qué averías diagnosticamos ahora bien a la primera?", + "webb.rapport.f2": "¿Con qué frecuencia la causa sospechada resulta ser la real?", + "webb.rapport.f3": "¿Qué averías vuelven una y otra vez en la flota?", + "webb.rapport.f4": "¿Qué pasos del procedimiento se omiten, y por qué?", + "webb.rapport.f5": "¿Qué sabe el taller ahora que no sabía el trimestre pasado?", + "webb.rapport.f6": "¿Se cierran, verifican y documentan los expedientes — o solo se cierran?", + + "webb.pris.etikett": "Comercial", + "webb.pris.rubrik": "Licencias", + "webb.pris.komponent": "Componente", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licencia de plataforma", + "webb.pris.plattform.grund": "Por organización, anual", + "webb.pris.anvandare": "Licencias de usuario", + "webb.pris.anvandare.grund": "Por usuario activo, mensual", + "webb.pris.moduler": "Módulos de empresa", + "webb.pris.moduler.grund": "Opcionales, por módulo", + "webb.pris.betalning": "Sin pago en línea. Sin alta de suscripción. La facturación sigue a la revisión de la solicitud.", + "webb.pris.aktivering": "Secuencia de activación", + "webb.pris.a1": "La organización envía el registro.", + "webb.pris.a2": "La solicitud se revisa.", + "webb.pris.a3": "Se emite la factura.", + "webb.pris.a4": "Se registra el pago.", + "webb.pris.a5": "La organización se activa.", + "webb.pris.a6": "Los usuarios adicionales se facturan mensualmente.", + + "webb.kallor.etikett": "Infraestructura", + "webb.kallor.rubrik": "Infraestructura operativa de conocimiento", + "webb.kallor.p1": + "ALVA usa las fuentes de conocimiento autorizadas propias de la organización. La arquitectura es neutral respecto al proveedor: cada fuente implementa la misma interfaz y no se presupone ningún proveedor.", + "webb.kallor.p2": + "Una instalación beta trabaja solo con documentación interna. Los proveedores externos se habilitan más adelante mediante conectores separados, sin cambios en el núcleo de la plataforma.", + "webb.kallor.block": "Orden de resolución de fuentes", + "webb.kallor.k1": "Procedimientos internos de la empresa", + "webb.kallor.k2": "Documentación OEM", + "webb.kallor.k3": "Boletines técnicos", + "webb.kallor.k4": "Información de garantía", + "webb.kallor.k5": "Manuales de taller", + "webb.kallor.k6": "Histórico de diagnósticos verificados", + "webb.kallor.k7": "Buenas prácticas de la organización", + + "webb.login.etikett": "Acceso", + "webb.login.demo": + "Este acceso no autentica nada. Escriba cualquier cosa en Correo y Contraseña y pulse Iniciar sesión — no hace falta cuenta, y el portal detrás presenta datos de ejemplo fijos. Conectada a una instancia de la plataforma, esta página se autentica contra ella, y el portal queda cerrado sin una sesión válida.", + "webb.login.losenord": "Contraseña", + "webb.login.ofullstandigt": "Autenticación incompleta. Se requieren correo y contraseña.", + "webb.login.misslyckades": "Autenticación fallida.", + "webb.login.logga_in": "Iniciar sesión", + "webb.login.loggar_in": "Iniciando sesión", + "webb.login.glomt": "Contraseña olvidada", + + "webb.ansokan.etikett": "Registro", + "webb.ansokan.demo": + "No se envía nada. Enviar este formulario no guarda nada, no avisa a nadie y no crea ninguna solicitud — todavía no hay ningún proceso de recepción detrás. La descripción de abajo indica cómo está previsto que funcione el registro, no lo que ocurre hoy.", + "webb.ansokan.avsikt": "Funcionamiento previsto: las solicitudes se revisan manualmente y en esta fase no se cobra ningún pago.", + "webb.falt.foretag": "Empresa", + "webb.falt.orgnummer": "Número de registro mercantil", + "webb.falt.kontakt": "Persona de contacto", + "webb.falt.epost": "Correo electrónico", + "webb.falt.telefon": "Teléfono", + "webb.falt.tekniker": "Número de técnicos", + "webb.falt.bransch": "Sector", + "webb.falt.land": "País", + "webb.falt.anvandare": "Número previsto de usuarios", + "webb.falt.noteringar": "Observaciones", + "webb.falt.kravs": "(obligatorio)", + "webb.ansokan.skicka": "Enviar la solicitud", + "webb.ansokan.mottagen.etikett": "Solicitud", + "webb.ansokan.mottagen": "Enviada", + "webb.ansokan.mottagen.demo": + "No se transmitió nada y no se avisó a nadie. La referencia de abajo se calculó en su navegador a partir de lo que escribió y no existe en ningún otro lugar — desaparecerá al cerrar esta página.", + "webb.ansokan.status": "Estado", + "webb.ansokan.referens": "Referencia", + "webb.ansokan.granskning": + "Las solicitudes se revisan manualmente. La factura se emite al aprobarse. La organización se activa cuando se registra el pago.", + + "webb.sprak.etikett": "Localización", + "webb.sprak.rubrik": "Idiomas", + "webb.sprak.ingress": + "El inglés es el idioma predeterminado y el idioma fuente. Le siguen nueve traducciones. Lo que la plataforma no hace es afirmar que una interfaz traducida signifique un método traducido.", + "webb.sprak.princip.etikett": "Principio", + "webb.sprak.princip.rubrik": "Dos clases de texto", + "webb.sprak.granssnitt.rubrik": "Texto de interfaz", + "webb.sprak.granssnitt.beteckning": "Recurre al inglés en silencio", + "webb.sprak.granssnitt.text": + "Etiquetas, botones, estados. Finito, rara vez cambia. Una cadena en inglés que llega a un usuario alemán es una molestia, no un peligro — así que una traducción ausente recurre al inglés sin comentario.", + "webb.sprak.metodik.rubrik": "Texto de procedimiento", + "webb.sprak.metodik.beteckning": "Nunca recurre al inglés en silencio", + "webb.sprak.metodik.text": + "Instrucciones para trabajar en un vehículo. Aquí una traducción sin revisar es peor que una en lengua extranjera — porque el inglés parece extranjero, mientras que una mala traducción parece una instrucción. Se muestra en inglés y se marca, con el idioma nombrado.", + "webb.sprak.invariant": + "El método en sí nunca se traduce. Los nombres de fase y las palabras de estado son la estructura de ALVA y se leen igual en todos los países, de modo que un auditor puede leer un expediente rumano y uno alemán sin saber en qué idioma trabaja el taller.", + "webb.sprak.tackning.etikett": "Cobertura", + "webb.sprak.tackning.rubrik": "Qué está traducido, y qué está revisado", + "webb.sprak.kolumn.sprak": "Idioma", + "webb.sprak.kolumn.granssnitt": "Interfaz", + "webb.sprak.kolumn.granskat": "Método revisado por un especialista", + "webb.sprak.granskat.ja": "Sí", + "webb.sprak.granskat.nej": "No — texto de procedimiento en inglés", + "webb.sprak.matt": + "La cobertura de la interfaz se mide, no se estima: una prueba hace fallar la compilación si falta alguna clave en algún idioma. El estado de revisión es una afirmación sobre personas, no sobre archivos, y se fija a mano.", + "webb.sprak.kallsprak": + "El texto de procedimiento en inglés es una traducción de la fuente sueca y aún no ha sido leído por un especialista que ejerza el oficio. Se le exige lo mismo que a los otros nueve idiomas, y aquí se hace sobre él la misma declaración en lugar de exceptuarlo en silencio por ser la fuente.", + "webb.sprak.bevis.etikett": "Verificación", + "webb.sprak.bevis.rubrik": "Las frases que detienen un expediente", + "webb.sprak.bevis.ingress": + "Estas son las frases que impiden a un técnico cerrar un expediente. Una negativa que nadie entiende es una negativa sin salida — por eso son las frases correctas para juzgar una traducción.", + "webb.sprak.ej_granskad": "Sin revisar", + "webb.sprak.granskad": "Revisado", + "webb.sprak.granskad.text": + "El texto de procedimiento en {sprak} ha sido leído por un especialista que ejerce el oficio. Los pasos y los puntos de control se muestran en {sprak} en todo momento.", + "webb.sprak.val.etikett": "Operativa", + "webb.sprak.val.rubrik": "Cómo se elige el idioma", + "webb.sprak.val.s1.rubrik": "Preferencia del usuario", + "webb.sprak.val.s1.text": "Lo que la persona ha seleccionado.", + "webb.sprak.val.s2.rubrik": "Configuración de la organización", + "webb.sprak.val.s2.text": "El idioma de documentación del taller.", + "webb.sprak.val.s3.rubrik": "Idioma del navegador", + "webb.sprak.val.s3.text": "El primer idioma que la plataforma reconoce.", + "webb.sprak.val.s4.rubrik": "Inglés", + "webb.sprak.val.s4.text": "El predeterminado, y la fuente.", + "webb.sprak.val.notering": + "La organización va deliberadamente por delante del navegador. Un taller en Alemania con un técnico polaco necesita un solo idioma de documentación — el expediente no debe cambiar de idioma según quién escribiera la línea.", +}; diff --git a/felsokning/app/services/gemensam/sprak/fr.mjs b/felsokning/app/services/gemensam/sprak/fr.mjs new file mode 100644 index 0000000..273b585 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/fr.mjs @@ -0,0 +1,328 @@ +// ALVA-SPEC-060 · Français. +// +// Les instructions sont à l’infinitif, sans s’adresser à l’utilisateur : +// « Photographier le compteur », et non « Photographiez le compteur ». +// C’est le registre des consignes techniques, et cela respecte +// ALVA-SPEC-001 §7 — le système signale un état, il ne s’adresse pas à +// l’opérateur. +// +// `granskat: false` : l’interface est traduite, le texte de procédure +// n’est pas relu par un spécialiste. Voir index.mjs. + +export const FR = { + // ---- Degrés de preuve --------------------------------------------------- + "evidens.E1": "Observation", + "evidens.E2": "Photographie", + "evidens.E3": "Vidéo avec son", + "evidens.E4": "Mesure, instrument étalonné", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "étalonnage manquant ou expiré", + "evidens.ej_angivet": "instrument non indiqué", + "evidens.ej_fotograferad": "saisi, non photographié", + + // ---- Le contrôle de clôture --------------------------------------------- + "grind.objekt": "Identification du véhicule ou de l’objet vérifiée", + "grind.historik": "Historique du véhicule vérifié, ou son omission justifiée", + "grind.historik.nekad": "Un contrôle d’historique refusé exige que le motif soit indiqué.", + "grind.historik.saknas": "Aucun contrôle d’historique documenté.", + "grind.matarstallning.ingaende": "Kilométrage à la réception photographié", + "grind.matarstallning.utgaende": "Kilométrage à la restitution photographié", + "grind.matarstallning.saknas": "Aucun kilométrage documenté.", + "grind.matarstallning.ej_foto": + "Le kilométrage est saisi mais non photographié. Photographier le compteur, ou indiquer pourquoi cela n’est pas possible.", + "grind.reproducering": "Vérification du symptôme : reproduit, ou documenté comme non reproductible", + "grind.felorsak": "Analyse de la cause racine documentée", + "grind.atgard": "Action corrective documentée, ou son absence justifiée", + "grind.atgard.saknas": "Ni action réalisée ni motif de son absence n’est documenté.", + "grind.kundbeslut": "Décision du client sur la proposition enregistrée", + "grind.kundbeslut.avbojt": "Travaux réalisés malgré une proposition refusée", + "grind.kundbeslut.avbojt.detalj": + "Le client a refusé la proposition, mais des travaux sont documentés comme réalisés.", + "grind.kvalitetskontroll": "Contrôle qualité effectué — symptôme vérifié comme résolu", + "grind.kontroller": "Points de contrôle de la méthode : preuve, ou dérogation documentée", + "grind.foton": "Photographies présentes pour les points de contrôle qui les exigent", + "grind.slutsats": "Conclusion (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Habilitation haute tension confirmée", + "grind.hogvolt.spanningslos": "Véhicule consigné hors tension selon la procédure du constructeur, absence de tension vérifiée par mesure", + "grind.regelpaket": "La signature du paquet de règles ne correspond pas — clôture bloquée.", + "grind.regelpaket.osignerat": "Un paquet de règles externe est utilisé sans signature — clôture bloquée.", + "grind.evidens": "Niveau de preuve supérieur à E0", + "grind.evidens.saknas": "Aucune preuve d’aucune sorte ne figure dans le journal.", + "grind.foton.detalj": "{kontroller} points de contrôle exigent une photographie, {foton} photographies dans le journal.", + "grind.sparr.ej_uppfyllt": "L’exigence de sécurité n’est pas satisfaite.", + "grind.arendetyp.okant": "Exigence inconnue dans le paquet de règles : {krav}", + "grind.arendetyp.krav": "Exigence pour ce type de dossier : {krav}", + "grind.sakerhet": "Niveau de confiance dans les limites de ce que les preuves soutiennent", + "grind.sakerhet.detalj": "La confiance indiquée ({niva}) dépasse ce que les preuves soutiennent ({tak}). Compléter les preuves, ou abaisser le niveau — une incertitude honnête est une information.", + "grind.eskalering": "Escalades techniques traitées", + "grind.eskalering.detalj": "Une escalade est ouverte ({referens}). Attendre la réponse et la documenter avant de clôturer le dossier.", + + + // ---- La conclusion (ALVA-RULE-200) -------------------------------------- + "slutsats.rubrik": "Conclusion", + "slutsats.konstaterat": "Ce qui est établi", + "slutsats.evidens": "Quelles preuves l’étayent", + "slutsats.avfardat": "Quelles hypothèses ont été écartées, et pourquoi", + "slutsats.osakert": "Ce qui reste incertain", + "slutsats.ickesvar": "Ce n’est pas une réponse. Indiquer ce qui est établi et quelles preuves l’étayent.", + "slutsats.falt.motivering": "Justification", + "slutsats.falt.motivering_ej": "Motif pour lequel la cause n’a pas pu être établie", + "slutsats.falt.uteslutet": "Alternatives écartées", + "slutsats.falt.kvarstaende": "Incertitude restante", + "slutsats.falt.atgardsval": "Choix de l’action", + "slutsats.saknas": "Le champ « {falt} » est absent.", + "slutsats.ickesvar.falt": "{falt} : « {text} » n’est pas un motif. Indiquer ce qui s’applique réellement, et pourquoi.", + "slutsats.for_kort": "Le champ « {falt} » est trop court ({langd} caractères sur un minimum de {minsta}) pour être vérifiable après coup.", + "slutsats.utan_varfor": "{falt} indique le quoi, mais pas le pourquoi. Relier la conclusion aux preuves — qu’est-ce qui, dans celles-ci, la rend fondée ?", + "slutsats.utan_slutsats": "Le dossier ne peut pas être clôturé sans conclusion. Indiquer pourquoi la conclusion découle des preuves.", + "slutsats.hypotes_obemott": "L’hypothèse « {text} » figure dans le journal mais n’est pas traitée. Indiquer pourquoi elle a été écartée, ou pourquoi elle reste ouverte.", + + + // ---- Flux du dossier ---------------------------------------------------- + "arende.nytt": "Nouveau dossier", + "arende.oppna": "Dossiers ouverts", + "arende.avslutade": "Dossiers clôturés", + "arende.avsluta": "Clôturer le dossier", + "arende.avslutat": "Dossier clôturé", + "arende.kan_ej_avslutas": "Le dossier ne peut pas encore être clôturé", + "arende.hinder": "En suspens avant clôture", + "arende.overlamna": "Transmettre", + "arende.ansvarig": "Responsable", + + // ---- Contrôle préalable ------------------------------------------------- + "pre.rubrik": "Contrôle préalable — avant le début des travaux", + "pre.historik.fraga": + "L’historique du véhicule a-t-il été vérifié ? (travaux antérieurs, défauts récurrents, TSB, campagnes)", + "pre.historik.ja": "Oui — vérifié", + "pre.historik.nej": "Non", + "pre.historik.skal": "Motif pour lequel l’historique n’a pas été vérifié (obligatoire)", + "pre.historik.relevant": "Travaux antérieurs pertinents (facultatif — chaîne causale)", + "pre.matarstallning": "Kilométrage", + "pre.fotografera": "Photographier le tableau de bord", + "pre.felbeskrivning": "Description du défaut par le client vérifiée", + "pre.observationer": "Autre chose à la réception ?", + + // ---- Actions ------------------------------------------------------------ + "handling.spara": "Enregistrer", + "handling.avbryt": "Annuler", + "handling.fortsatt": "Continuer", + "handling.tillbaka": "Retour", + "handling.dokumentera": "Documenter", + "handling.fotografera": "Photographier", + "handling.spela_in": "Enregistrer une vidéo", + "handling.undantag": "Documenter une dérogation", + "handling.undantag.skal": "Motif de la dérogation (obligatoire)", + "handling.exportera": "Exporter", + "handling.skriv_ut": "Imprimer", + + // ---- Mesure ------------------------------------------------------------- + "matning.varde": "Valeur", + "matning.enhet": "Unité", + "matning.matdon": "Instrument", + "matning.matdon.valj": "Sélectionner un instrument", + "matning.matdon.okant": "Instrument inconnu. Le déclarer avant d’enregistrer la mesure.", + "matning.kalibrerad_till": "Étalonné jusqu’au", + + // ---- Rapport ------------------------------------------------------------ + "rapport.rubrik": "Rapport de dossier", + "rapport.sammanfattning": "Synthèse", + "rapport.evidens": "Preuves", + "rapport.atgarder": "Actions", + "rapport.harledd": + "Généré à partir du journal du dossier. Les observations et les mesures sont rapportées sans conclusions dépourvues de fondement.", + + // ---- Langue ------------------------------------------------------------- + "sprak.valj": "Langue", + "sprak.granskat": "Relu", + "sprak.ogranskat": "Non relu", + "sprak.tackning": "{procent} % de l’interface", + + "metodik.ogranskad": + "Le texte de procédure n’a pas été relu par un spécialiste technique en {sprak}. Les étapes et les points de contrôle sont affichés en anglais lorsqu’aucune traduction relue n’existe — une traduction non relue d’une consigne de sécurité est pire qu’une consigne en langue étrangère, parce qu’elle n’a pas l’air étrangère.", + "metodik.pa_engelska": "Affiché en anglais — aucune traduction relue en {sprak}", + + // ---- Site public (webb.) ----------------------------------------------- + "webb.nav.oversikt": "Aperçu", + "webb.ansok": "Demander un compte", + "webb.loggain": "Connexion", + "webb.fot.impressum": "Mentions légales", + "webb.fot.dataskydd": "Confidentialité", + "webb.fot.villkor": "Conditions", + "webb.fot.tillganglighet": "Accessibilité", + "webb.fot.sprak": "Langues", + "webb.fot.utgavor": "Notes de version", + + "webb.hero.position": "Plateforme de diagnostic guidé", + "webb.hero.definition": "Des procédures de diagnostic normalisées pour une recherche de pannes répétable et vérifiable.", + + "webb.metod.etikett": "Méthodologie", + "webb.metod.rubrik": "Le modèle ALVA", + "webb.metod.ingress": + "ALVA est une méthode normalisée d’analyse, de localisation, de vérification et d’action systématiques. Chaque décision est traçable, chaque conclusion vérifiable, chaque action reproductible.", + "webb.fas.analysis.syfte": "Recueillir les preuves.", + "webb.fas.analysis.avgransning": "Des faits uniquement. Les hypothèses ne sont pas recueillies dans cette phase.", + "webb.fas.localization.syfte": "Circonscrire le défaut.", + "webb.fas.localization.avgransning": "Restreindre la zone. La cause n’est pas encore établie.", + "webb.fas.verification.syfte": "Confirmer la cause racine.", + "webb.fas.verification.avgransning": "C’est la cause qui est vérifiée — pas le symptôme.", + "webb.fas.action.syfte": "Exécuter l’action corrective.", + "webb.fas.action.avgransning": "Résultat vérifié et documenté. Sinon la phase est incomplète.", + + "webb.drift.etikett": "Fonctionnement", + "webb.drift.rubrik": "Comment ça fonctionne", + "webb.drift.s1.rubrik": "Créer l’organisation", + "webb.drift.s1.text": "L’inscription est soumise puis examinée.", + "webb.drift.s2.rubrik": "Les utilisateurs reçoivent leurs comptes", + "webb.drift.s2.text": "Les rôles sont attribués par l’administrateur de l’organisation.", + "webb.drift.s3.rubrik": "Procédures de diagnostic guidées", + "webb.drift.s3.text": "Chaque dossier suit une procédure définie.", + "webb.drift.s4.rubrik": "Vérification", + "webb.drift.s4.text": "La cause racine est confirmée avant l’action corrective.", + "webb.drift.s5.rubrik": "Documentation", + "webb.drift.s5.text": "Le rapport est généré à partir du journal du dossier.", + "webb.drift.s6.rubrik": "Amélioration continue", + "webb.drift.s6.text": "Les résultats vérifiés affinent les procédures suivantes.", + + "webb.larande.etikett": "Capacité", + "webb.larande.rubrik": "Apprentissage de l’organisation", + "webb.larande.p1": + "La plateforme affine en continu les procédures de diagnostic à partir de l’expérience opérationnelle vérifiée de votre propre organisation.", + "webb.larande.p2": + "Chaque diagnostic achevé nourrit le guidage suivant. Seuls les résultats vérifiés sont utilisés — un dossier clôturé sans cause racine confirmée n’apporte rien, à dessein.", + "webb.larande.p3": + "La base de connaissances appartient à l’organisation. Elle est dérivée de vos dossiers, de vos procédures et de votre documentation, et elle n’est pas agrégée entre clients.", + "webb.larande.block": "Dérivée de", + "webb.larande.k1": "Causes racines vérifiées", + "webb.larande.k2": "Actions correctives confirmées", + "webb.larande.k3": "Catégories de défauts récurrents", + "webb.larande.k4": "Procédures menées à terme", + "webb.larande.k5": "Documentation de l’organisation", + + "webb.rapport.etikett": "Rapports", + "webb.rapport.rubrik": "Rapport trimestriel d’amélioration", + "webb.rapport.ingress": + "Une fois par trimestre, la plateforme répond à six questions sur le travail de l’atelier lui-même. Chaque réponse est dérivée du journal des dossiers — rien n’est estimé, et rien n’est demandé à personne.", + "webb.rapport.f1": "Quels défauts diagnostiquons-nous désormais correctement du premier coup ?", + "webb.rapport.f2": "À quelle fréquence une cause soupçonnée se révèle-t-elle être la vraie ?", + "webb.rapport.f3": "Quels défauts reviennent sans cesse dans le parc ?", + "webb.rapport.f4": "Quelles étapes de procédure sont sautées, et pourquoi ?", + "webb.rapport.f5": "Que sait l’atelier aujourd’hui qu’il ne savait pas au trimestre dernier ?", + "webb.rapport.f6": "Les dossiers sont-ils clôturés, vérifiés et documentés — ou seulement clôturés ?", + + "webb.pris.etikett": "Commercial", + "webb.pris.rubrik": "Licences", + "webb.pris.komponent": "Composant", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licence de plateforme", + "webb.pris.plattform.grund": "Par organisation, annuelle", + "webb.pris.anvandare": "Licences utilisateur", + "webb.pris.anvandare.grund": "Par utilisateur actif, mensuelle", + "webb.pris.moduler": "Modules d’entreprise", + "webb.pris.moduler.grund": "Optionnels, par module", + "webb.pris.betalning": "Aucun paiement en ligne. Aucune souscription d’abonnement. La facturation suit l’examen de la demande.", + "webb.pris.aktivering": "Séquence d’activation", + "webb.pris.a1": "L’organisation soumet son inscription.", + "webb.pris.a2": "La demande est examinée.", + "webb.pris.a3": "La facture est émise.", + "webb.pris.a4": "Le paiement est enregistré.", + "webb.pris.a5": "L’organisation est activée.", + "webb.pris.a6": "Les utilisateurs supplémentaires sont facturés mensuellement.", + + "webb.kallor.etikett": "Infrastructure", + "webb.kallor.rubrik": "Infrastructure de connaissances opérationnelles", + "webb.kallor.p1": + "ALVA utilise les sources de connaissances autorisées propres à l’organisation. L’architecture est neutre vis-à-vis des fournisseurs : chaque source implémente la même interface, et aucun fournisseur n’est présupposé.", + "webb.kallor.p2": + "Une installation bêta fonctionne sur la seule documentation interne. Les fournisseurs externes sont activés plus tard par des connecteurs séparés, sans modification du cœur de la plateforme.", + "webb.kallor.block": "Ordre de résolution des sources", + "webb.kallor.k1": "Procédures internes de l’entreprise", + "webb.kallor.k2": "Documentation OEM", + "webb.kallor.k3": "Bulletins techniques", + "webb.kallor.k4": "Informations de garantie", + "webb.kallor.k5": "Manuels d’atelier", + "webb.kallor.k6": "Diagnostics vérifiés historiques", + "webb.kallor.k7": "Bonnes pratiques de l’organisation", + + "webb.login.etikett": "Accès", + "webb.login.demo": + "Cette connexion n’authentifie rien. Saisissez n’importe quoi dans E-mail et Mot de passe et appuyez sur Se connecter — aucun compte n’est nécessaire, et le portail derrière présente des données d’exemple figées. Reliée à une instance de la plateforme, cette page s’authentifie auprès d’elle, et le portail est fermé sans session valide.", + "webb.login.losenord": "Mot de passe", + "webb.login.ofullstandigt": "Authentification incomplète. E-mail et mot de passe requis.", + "webb.login.misslyckades": "Échec de l’authentification.", + "webb.login.logga_in": "Se connecter", + "webb.login.loggar_in": "Connexion en cours", + "webb.login.glomt": "Mot de passe oublié", + + "webb.ansokan.etikett": "Inscription", + "webb.ansokan.demo": + "Rien n’est envoyé. Soumettre ce formulaire n’enregistre rien, ne notifie personne et ne crée aucune demande — aucun traitement n’existe encore derrière. La description ci-dessous indique comment l’inscription est censée fonctionner, pas ce qui se passe aujourd’hui.", + "webb.ansokan.avsikt": "Fonctionnement prévu : les demandes sont examinées manuellement et aucun paiement n’est perçu à ce stade.", + "webb.falt.foretag": "Entreprise", + "webb.falt.orgnummer": "Numéro d’immatriculation de l’entreprise", + "webb.falt.kontakt": "Personne à contacter", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Téléphone", + "webb.falt.tekniker": "Nombre de techniciens", + "webb.falt.bransch": "Secteur", + "webb.falt.land": "Pays", + "webb.falt.anvandare": "Nombre d’utilisateurs attendu", + "webb.falt.noteringar": "Remarques", + "webb.falt.kravs": "(obligatoire)", + "webb.ansokan.skicka": "Soumettre la demande", + "webb.ansokan.mottagen.etikett": "Demande", + "webb.ansokan.mottagen": "Soumise", + "webb.ansokan.mottagen.demo": + "Rien n’a été transmis et personne n’a été notifié. La référence ci-dessous a été calculée dans votre navigateur à partir de votre saisie et n’existe nulle part ailleurs — elle disparaîtra à la fermeture de cette page.", + "webb.ansokan.status": "Statut", + "webb.ansokan.referens": "Référence", + "webb.ansokan.granskning": + "Les demandes sont examinées manuellement. Une facture est émise à l’approbation. L’organisation est activée une fois le paiement enregistré.", + + "webb.sprak.etikett": "Localisation", + "webb.sprak.rubrik": "Langues", + "webb.sprak.ingress": + "L’anglais est la langue par défaut et la langue source. Neuf traductions suivent. Ce que la plateforme ne fait pas : prétendre qu’une interface traduite signifie une méthode traduite.", + "webb.sprak.princip.etikett": "Principe", + "webb.sprak.princip.rubrik": "Deux sortes de texte", + "webb.sprak.granssnitt.rubrik": "Texte d’interface", + "webb.sprak.granssnitt.beteckning": "Retombe silencieusement", + "webb.sprak.granssnitt.text": + "Étiquettes, boutons, statuts. Fini, rarement modifié. Une chaîne anglaise qui atteint un utilisateur allemand est un agacement, pas un danger — une traduction manquante retombe donc sur l’anglais sans commentaire.", + "webb.sprak.metodik.rubrik": "Texte de procédure", + "webb.sprak.metodik.beteckning": "Ne retombe jamais silencieusement", + "webb.sprak.metodik.text": + "Des instructions pour travailler sur un véhicule. Ici, une traduction non relue est pire qu’une version en langue étrangère — parce que l’anglais a l’air étranger, tandis qu’une mauvaise traduction a l’air d’une instruction. Elle est affichée en anglais et signalée, avec la langue nommée.", + "webb.sprak.invariant": + "La méthode elle-même n’est jamais traduite. Les noms de phase et les mots de statut sont la structure d’ALVA et se lisent à l’identique dans chaque pays, si bien qu’un auditeur peut lire un dossier roumain et un dossier allemand sans savoir dans quelle langue l’atelier travaille.", + "webb.sprak.tackning.etikett": "Couverture", + "webb.sprak.tackning.rubrik": "Ce qui est traduit, et ce qui est relu", + "webb.sprak.kolumn.sprak": "Langue", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Méthode relue par un spécialiste", + "webb.sprak.granskat.ja": "Oui", + "webb.sprak.granskat.nej": "Non — texte de procédure affiché en anglais", + "webb.sprak.matt": + "La couverture de l’interface est mesurée, pas estimée : un test fait échouer la compilation si une clé manque dans une langue. Le statut de relecture est une affirmation sur des personnes, pas sur des fichiers, et il est fixé à la main.", + "webb.sprak.kallsprak": + "Le texte de procédure anglais est une traduction de la source suédoise et n’a pas encore été lu par un spécialiste exerçant le métier. Il est tenu au même niveau d’exigence que les neuf autres langues, et la même déclaration est faite ici à son sujet au lieu de l’excepter discrètement parce qu’il est la source.", + "webb.sprak.bevis.etikett": "Vérification", + "webb.sprak.bevis.rubrik": "Les phrases qui arrêtent un dossier", + "webb.sprak.bevis.ingress": + "Ce sont les phrases qui empêchent un technicien de clôturer un dossier. Un refus que personne ne comprend est un refus sans issue — ce sont donc les bonnes phrases pour juger une traduction.", + "webb.sprak.ej_granskad": "Non relu", + "webb.sprak.granskad": "Relu", + "webb.sprak.granskad.text": + "Le texte de procédure en {sprak} a été lu par un spécialiste exerçant le métier. Les étapes et les points de contrôle sont affichés en {sprak} partout.", + "webb.sprak.val.etikett": "Fonctionnement", + "webb.sprak.val.rubrik": "Comment la langue est choisie", + "webb.sprak.val.s1.rubrik": "Préférence de l’utilisateur", + "webb.sprak.val.s1.text": "Ce que la personne a choisi.", + "webb.sprak.val.s2.rubrik": "Réglage de l’organisation", + "webb.sprak.val.s2.text": "La langue de documentation de l’atelier.", + "webb.sprak.val.s3.rubrik": "Langue du navigateur", + "webb.sprak.val.s3.text": "La première langue que la plateforme reconnaît.", + "webb.sprak.val.s4.rubrik": "Anglais", + "webb.sprak.val.s4.text": "La langue par défaut, et la source.", + "webb.sprak.val.notering": + "L’organisation passe délibérément avant le navigateur. Un atelier en Allemagne avec un technicien polonais a besoin d’une seule langue de documentation — le dossier ne doit pas changer de langue selon qui a écrit la ligne.", +}; diff --git a/felsokning/app/services/gemensam/sprak/index.mjs b/felsokning/app/services/gemensam/sprak/index.mjs new file mode 100644 index 0000000..38ba86a --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/index.mjs @@ -0,0 +1,237 @@ +// ALVA-SPEC-060 · Språk. +// +// ---- Två sorters text, och de behandlas olika --------------------------- +// +// GRÄNSSNITTSTEXT är etiketter, knappar, statusar och grindens hinder. +// Den är ändlig, den ändras sällan, och en engelsk sträng som slinker +// igenom till en tysk användare är irriterande men ofarlig. +// +// METODIKINNEHÅLL är instruktioner för arbete på ett fordon: "kontrollera +// bromsvätskans kokpunkt", "säkerställ att fordonet är spänningslöst". +// Där är en oöversatt eller halvöversatt sträng inte irriterande utan +// farlig, och en maskinöversättning som ingen fackman läst är värre än +// engelska — därför att engelska SYNS som ett annat språk, medan en +// felaktig översättning ser ut som en instruktion. +// +// Modulen behandlar dem därför olika. Gränssnittstext faller tillbaka på +// engelska tyst. Metodikinnehåll gör det ALDRIG tyst: det märks som +// oöversatt, med språk och granskningsstatus, så att teknikern vet att +// den läser något som inte är skrivet för dess marknad. +// +// ---- Källspråket är engelska -------------------------------------------- +// +// Inte därför att engelska är bäst, utan därför att ALVA:s struktur redan +// är engelsk och oföränderlig (ALVA-SPEC-001 §2). Ett system där +// strukturen är engelsk och källtexten svensk har två källor att hålla +// synkade, och den ena vinner alltid tyst. +// +// ---- Katalogen är stängd ------------------------------------------------- +// +// Varje nyckel måste finnas i varje språk. Ett test låser det, av samma +// skäl som händelseschemat är stängt: en saknad nyckel som tyst faller +// tillbaka blir aldrig upptäckt, och blir aldrig översatt. +// +// ---- Och en del av den får INTE översättas ------------------------------ +// +// ALVA-SPEC-001 §2 slår fast att fasnamn och statusord är strukturen och +// skrivs likadant i varje land — precis som ett ISO-krav inte blir +// "internationell standard 9001" på svenska. En tysk rapport och en +// rumänsk ska visa samma fyra faser och samma statusord, så att en +// revisor kan läsa båda utan att veta vilket språk verkstaden arbetar på. +// +// Det hade gått att skriva in dem oöversatta i varje katalog och låsa +// dem med ett test. Det är svagare: en översättare som ser "Passed" i +// den polska filen översätter den, och testet blir en tvist i stället +// för en regel. I stället saknas nycklarna helt i översättningarna, och +// `t()` hämtar dem alltid ur engelskan. Då finns det ingenting att +// översätta fel. + +import { EN } from "./en.mjs"; +import { SV } from "./sv.mjs"; +import { DE } from "./de.mjs"; +import { FR } from "./fr.mjs"; +import { ES } from "./es.mjs"; +import { IT } from "./it.mjs"; +import { PL } from "./pl.mjs"; +import { NL } from "./nl.mjs"; +import { PT } from "./pt.mjs"; +import { RO } from "./ro.mjs"; + +/** + * Språken. + * + * Urvalet är EU:s största modersmål plus svenska, som är hemmamarknaden. + * `granskat` säger om en fackman har läst igenom METODIKINNEHÅLLET på + * språket — inte gränssnittet. Se filhuvudet: det är den skillnaden som + * betyder något. + */ +export const SPRAK = [ + { kod: "en", namn: "English", egetNamn: "English", granskat: true }, + { kod: "de", namn: "German", egetNamn: "Deutsch", granskat: false }, + { kod: "fr", namn: "French", egetNamn: "Français", granskat: false }, + { kod: "es", namn: "Spanish", egetNamn: "Español", granskat: false }, + { kod: "it", namn: "Italian", egetNamn: "Italiano", granskat: false }, + { kod: "pl", namn: "Polish", egetNamn: "Polski", granskat: false }, + { kod: "nl", namn: "Dutch", egetNamn: "Nederlands", granskat: false }, + { kod: "pt", namn: "Portuguese", egetNamn: "Português", granskat: false }, + { kod: "ro", namn: "Romanian", egetNamn: "Română", granskat: false }, + { kod: "sv", namn: "Swedish", egetNamn: "Svenska", granskat: true }, +]; + +/** Standardspråket. Även källspråket. */ +export const STANDARD = "en"; + +export const KATALOGER = { en: EN, sv: SV, de: DE, fr: FR, es: ES, it: IT, pl: PL, nl: NL, pt: PT, ro: RO }; + +/** + * Nyckelområden som är ALVA:s struktur och därför aldrig översätts. + * + * `fas.` — de fyra faserna. `status.` — statusorden. Se filhuvudet. + */ +export const INVARIANTA = ["fas.", "status."]; + +/** Om nyckeln tillhör strukturen och alltid ska hämtas ur engelskan. */ +export const arInvariant = (nyckel) => INVARIANTA.some((p) => String(nyckel).startsWith(p)); + +/** + * Slår upp en gränssnittssträng. + * + * Invarianta nycklar hämtas alltid ur engelskan, oavsett språk och + * oavsett vad en katalog råkar innehålla. + * + * Faller i övrigt tillbaka på engelska när nyckeln saknas i språket, och + * på nyckeln själv när den saknas överallt — det sista ska aldrig hända + * och fångas av testet, men en tom sträng i gränssnittet vore värre än + * en synlig nyckel. + * + * `variabler` ersätter {namn} i strängen. Ingen ordning antas: språk + * sätter satsdelar i olika ordning, och en översättning som tvingas följa + * engelskans ordföljd blir fel svenska, tyska och polska på en gång. + */ +export function t(sprak, nyckel, variabler = {}) { + const katalog = arInvariant(nyckel) ? EN : (KATALOGER[sprak] ?? EN); + const rå = katalog[nyckel] ?? EN[nyckel] ?? nyckel; + return String(rå).replace(/\{(\w+)\}/g, (_, n) => (n in variabler ? String(variabler[n]) : `{${n}}`)); +} + +/** Bunden variant, så en vy slipper skicka språket i varje anrop. */ +export const oversattare = (sprak) => (nyckel, variabler) => t(sprak, nyckel, variabler); + +/** + * Vilka nycklar som saknas per språk. + * + * Används av testet — men är exporterad även för driften, som kan visa + * täckningsgraden i stället för att påstå att ett språk är klart. + */ +export function saknade(katalog) { + const nycklar = Object.keys(EN).filter((n) => !arInvariant(n)); + return nycklar.filter((n) => !(n in katalog) || !String(katalog[n]).trim()); +} + +/** + * Invarianta nycklar som en katalog felaktigt definierar. + * + * Tom lista är det normala. En träff betyder att någon har översatt + * strukturen, och det ska stoppa bygget — inte tyst ignoreras av `t()`, + * även om `t()` ignorerar det. + */ +export function otillatna(katalog) { + return Object.keys(katalog).filter(arInvariant); +} + +/** + * Nycklar som finns i katalogen men inte i engelskan. + * + * En sträng utan original. Den kan aldrig visas — `t()` slår upp mot + * EN:s nycklar — så den är antingen ett stavfel eller en kvarleva. + */ +export function overflodiga(katalog) { + return Object.keys(katalog).filter((n) => !arInvariant(n) && !(n in EN)); +} + +/** Andel av gränssnittet som finns på språket. */ +export function tackning(kod) { + const katalog = KATALOGER[kod]; + if (!katalog) return 0; + const totalt = Object.keys(EN).filter((n) => !arInvariant(n)).length; + return totalt === 0 ? 1 : (totalt - saknade(katalog).length) / totalt; +} + +/** + * Väljer språk ur det som finns att gå på. + * + * Ordningen är avsiktlig: användarens eget val väger tyngst, därefter + * organisationens, därefter webbläsarens, sist engelska. Organisationen + * går före webbläsaren därför att en verkstad i Tyskland med en polsk + * tekniker ska kunna ha ett gemensamt dokumentationsspråk — rapporten ska + * inte byta språk beroende på vem som råkade skriva raden. + */ +export function valjSprak({ anvandare, organisation, webblasare } = {}) { + const kanda = new Set(SPRAK.map((s) => s.kod)); + const forsta = [anvandare, organisation, ...(webblasare ?? [])] + .filter(Boolean) + .map((k) => String(k).slice(0, 2).toLowerCase()) + .find((k) => kanda.has(k)); + return forsta ?? STANDARD; +} + +/** + * Besked om metodikinnehåll som inte är granskat på användarens språk. + * + * Returnerar null när allt är i sin ordning. Annars en text som säger + * VAD som inte är granskat och VILKET språk texten faktiskt är på — inte + * en allmän brasklapp, som ingen läser. + */ +export function metodikvarning(sprak) { + const s = SPRAK.find((x) => x.kod === sprak); + if (!s || s.granskat) return null; + return t(sprak, "metodik.ogranskad", { sprak: s.egetNamn }); +} + +/** + * Ärendetyper som skrevs på svenska innan engelska blev källspråket. + * + * Ärendetypen är DATA: den ligger i händelseloggen och slår upp + * regelpaketets extra krav. Ett byte av språk får därför inte göras tyst + * — ett gammalt garantiärende vars typ heter "Garanti" hade annars inte + * matchat "Warranty", fått noll extra krav, och släppts igenom grinden + * med FÄRRE krav än när det öppnades. Ingenting i gränssnittet hade + * visat det. + * + * Tabellen är därför en del av regeln, inte en bekvämlighet, och den tas + * bort först när ingen logg innehåller de gamla värdena. + */ +export const ARENDETYP_ARV = { + "Privat kund": "Private customer", + Företagskund: "Business customer", + Garanti: "Warranty", + Försäkring: "Insurance", + Reklamation: "Complaint", + Begagnatgaranti: "Used-vehicle warranty", + "Intern kvalitetskontroll": "Internal quality check", + "Teknisk utredning": "Technical investigation", +}; + +/** Ärendetypen i sin nuvarande form, oavsett vilket språk den skrevs i. */ +export const arendetypNu = (typ) => ARENDETYP_ARV[typ] ?? typ; + +/** + * Jakande svar på en säkerhetsspärr. + * + * Svaret på en ja/nej-fråga ligger i loggen som text, och spärrfrågorna + * släpper bara igenom ett jakande svar. När knappen bytte från "Ja" till + * "Yes" gick de två isär: klienten skrev "Yes", servern jämförde mot + * "Ja". Utfallet blev fail-closed — högvoltsärenden gick inte att + * avsluta alls — vilket är rätt riktning för ett fel att falla åt, men + * det var en slump och inte en konstruktion. + * + * Det verkliga felet är att en säkerhetsspärr avgörs av en textsträng. + * Rätt lösning är ett booleskt fält i händelsen; tills det finns står + * båda formerna här, och de gäller alla språk samtidigt av samma skäl + * som ordlistorna i ord.mjs gör det: den som svarar ska inte kunna + * missas för att organisationens språkinställning var en annan. + */ +export const JAKANDE = new Set(["Yes", "Ja", "Oui", "Sí", "Si", "Sì", "Tak", "Da", "Sim", "Ja."]); + +/** Är svaret jakande, oavsett vilket språk knappen stod på? */ +export const arJakande = (svar) => JAKANDE.has(String(svar ?? "").trim()); diff --git a/felsokning/app/services/gemensam/sprak/it.mjs b/felsokning/app/services/gemensam/sprak/it.mjs new file mode 100644 index 0000000..1427205 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/it.mjs @@ -0,0 +1,328 @@ +// ALVA-SPEC-060 · Italiano. +// +// I pulsanti usano l’imperativo breve («Salva», «Annulla»), come vuole +// la convenzione delle interfacce italiane; le istruzioni all’interno +// delle frasi restano all’infinito («Fotografare il contachilometri»), +// senza rivolgersi all’operatore — ALVA-SPEC-001 §7. +// +// `granskat: false`: l’interfaccia è tradotta, il testo delle procedure +// non è revisionato da uno specialista. Vedi index.mjs. + +export const IT = { + // ---- Gradi di evidenza -------------------------------------------------- + "evidens.E1": "Osservazione", + "evidens.E2": "Fotografia", + "evidens.E3": "Video con audio", + "evidens.E4": "Misurazione, strumento tarato", + "evidens.E5": "Documento", + "evidens.ej_kalibrerad": "taratura mancante o scaduta", + "evidens.ej_angivet": "strumento non indicato", + "evidens.ej_fotograferad": "inserito, non fotografato", + + // ---- Il controllo di chiusura ------------------------------------------- + "grind.objekt": "Identificazione del veicolo o dell’oggetto verificata", + "grind.historik": "Storico del veicolo verificato, o l’omissione motivata", + "grind.historik.nekad": "Un controllo dello storico rifiutato richiede l’indicazione di un motivo.", + "grind.historik.saknas": "Nessun controllo dello storico documentato.", + "grind.matarstallning.ingaende": "Chilometraggio in accettazione fotografato", + "grind.matarstallning.utgaende": "Chilometraggio alla riconsegna fotografato", + "grind.matarstallning.saknas": "Nessun chilometraggio documentato.", + "grind.matarstallning.ej_foto": + "Il chilometraggio è inserito ma non fotografato. Fotografare il contachilometri, oppure indicare perché non è possibile.", + "grind.reproducering": "Verifica del sintomo: riprodotto, oppure documentato come non riproducibile", + "grind.felorsak": "Analisi della causa radice documentata", + "grind.atgard": "Intervento correttivo documentato, o la sua assenza motivata", + "grind.atgard.saknas": "Non è documentato né un intervento eseguito né un motivo della sua assenza.", + "grind.kundbeslut": "Decisione del cliente sulla proposta registrata", + "grind.kundbeslut.avbojt": "Lavoro eseguito nonostante una proposta rifiutata", + "grind.kundbeslut.avbojt.detalj": + "Il cliente ha rifiutato la proposta, ma risulta documentato lavoro eseguito.", + "grind.kvalitetskontroll": "Controllo qualità eseguito — sintomo verificato come risolto", + "grind.kontroller": "Punti di controllo della metodologia: evidenza, oppure deroga documentata", + "grind.foton": "Fotografie presenti per i punti di controllo che le richiedono", + "grind.slutsats": "Conclusione (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Abilitazione alta tensione confermata", + "grind.hogvolt.spanningslos": "Veicolo messo fuori tensione secondo la procedura del costruttore, assenza di tensione verificata mediante misurazione", + "grind.regelpaket": "La firma del pacchetto di regole non corrisponde — chiusura bloccata.", + "grind.regelpaket.osignerat": "È in uso un pacchetto di regole esterno privo di firma — chiusura bloccata.", + "grind.evidens": "Livello di evidenza superiore a E0", + "grind.evidens.saknas": "Nel registro non è presente alcuna evidenza.", + "grind.foton.detalj": "{kontroller} punti di controllo richiedono una fotografia, {foton} fotografie nel registro.", + "grind.sparr.ej_uppfyllt": "Il requisito di sicurezza non è soddisfatto.", + "grind.arendetyp.okant": "Requisito sconosciuto nel pacchetto di regole: {krav}", + "grind.arendetyp.krav": "Requisito per questo tipo di pratica: {krav}", + "grind.sakerhet": "Livello di confidenza entro quanto sostiene l’evidenza", + "grind.sakerhet.detalj": "La confidenza indicata ({niva}) supera quanto sostiene l’evidenza ({tak}). Integrare l’evidenza, oppure abbassare il livello — l’incertezza onesta è informazione.", + "grind.eskalering": "Escalation tecniche risolte", + "grind.eskalering.detalj": "Un’escalation è aperta ({referens}). Attendere la risposta e documentarla prima di chiudere la pratica.", + + + // ---- La conclusione (ALVA-RULE-200) ------------------------------------- + "slutsats.rubrik": "Conclusione", + "slutsats.konstaterat": "Che cosa è stato accertato", + "slutsats.evidens": "Quale evidenza lo sostiene", + "slutsats.avfardat": "Quali ipotesi sono state escluse, e perché", + "slutsats.osakert": "Che cosa resta incerto", + "slutsats.ickesvar": + "Questa non è una risposta. Indicare che cosa è stato accertato e quale evidenza lo sostiene.", + "slutsats.falt.motivering": "Motivazione", + "slutsats.falt.motivering_ej": "Motivo per cui la causa non è stata accertata", + "slutsats.falt.uteslutet": "Alternative escluse", + "slutsats.falt.kvarstaende": "Incertezza residua", + "slutsats.falt.atgardsval": "Scelta dell’intervento", + "slutsats.saknas": "{falt} manca.", + "slutsats.ickesvar.falt": "{falt}: «{text}» non è una motivazione. Indicare che cosa vale realmente, e perché.", + "slutsats.for_kort": "{falt} è troppo breve ({langd} su almeno {minsta} caratteri) per consentirne la verifica in seguito.", + "slutsats.utan_varfor": "{falt} indica che cosa, non perché. Collegare la conclusione all’evidenza — che cosa in essa la sostiene?", + "slutsats.utan_slutsats": "La pratica non può essere chiusa senza una conclusione. Indicare perché la conclusione discende dall’evidenza.", + "slutsats.hypotes_obemott": "L’ipotesi «{text}» è presente nel registro ma non viene trattata. Indicare perché è stata esclusa, o perché resta aperta.", + + + // ---- Flusso della pratica ----------------------------------------------- + "arende.nytt": "Nuova pratica", + "arende.oppna": "Pratiche aperte", + "arende.avslutade": "Pratiche chiuse", + "arende.avsluta": "Chiudi la pratica", + "arende.avslutat": "Pratica chiusa", + "arende.kan_ej_avslutas": "La pratica non può ancora essere chiusa", + "arende.hinder": "Da completare prima della chiusura", + "arende.overlamna": "Trasferisci", + "arende.ansvarig": "Responsabile", + + // ---- Controllo preliminare ---------------------------------------------- + "pre.rubrik": "Controllo preliminare — prima dell’inizio del lavoro", + "pre.historik.fraga": + "Lo storico del veicolo è stato verificato? (lavori precedenti, guasti ricorrenti, TSB, campagne)", + "pre.historik.ja": "Sì — verificato", + "pre.historik.nej": "No", + "pre.historik.skal": "Motivo per cui lo storico non è stato verificato (obbligatorio)", + "pre.historik.relevant": "Lavori precedenti rilevanti (facoltativo — catena causale)", + "pre.matarstallning": "Chilometraggio", + "pre.fotografera": "Fotografare il quadro strumenti", + "pre.felbeskrivning": "Descrizione del guasto fornita dal cliente verificata", + "pre.observationer": "Altro in fase di accettazione?", + + // ---- Azioni ------------------------------------------------------------- + "handling.spara": "Salva", + "handling.avbryt": "Annulla", + "handling.fortsatt": "Continua", + "handling.tillbaka": "Indietro", + "handling.dokumentera": "Documenta", + "handling.fotografera": "Fotografa", + "handling.spela_in": "Registra video", + "handling.undantag": "Documenta una deroga", + "handling.undantag.skal": "Motivo della deroga (obbligatorio)", + "handling.exportera": "Esporta", + "handling.skriv_ut": "Stampa", + + // ---- Misurazione -------------------------------------------------------- + "matning.varde": "Valore", + "matning.enhet": "Unità", + "matning.matdon": "Strumento", + "matning.matdon.valj": "Seleziona strumento", + "matning.matdon.okant": "Strumento sconosciuto. Registrarlo prima di salvare la misurazione.", + "matning.kalibrerad_till": "Tarato fino al", + + // ---- Rapporto ----------------------------------------------------------- + "rapport.rubrik": "Rapporto della pratica", + "rapport.sammanfattning": "Sintesi", + "rapport.evidens": "Evidenze", + "rapport.atgarder": "Interventi", + "rapport.harledd": + "Derivato dal registro della pratica. Le osservazioni e le misurazioni sono riportate senza conclusioni prive di fondamento.", + + // ---- Lingua ------------------------------------------------------------- + "sprak.valj": "Lingua", + "sprak.granskat": "Revisionato", + "sprak.ogranskat": "Non revisionato", + "sprak.tackning": "{procent} % dell’interfaccia", + + "metodik.ogranskad": + "Il testo delle procedure non è stato revisionato da uno specialista tecnico in {sprak}. I passaggi e i punti di controllo sono mostrati in inglese dove non esiste una traduzione revisionata — una traduzione non revisionata di un’istruzione di sicurezza è peggiore di una in lingua straniera, perché non sembra straniera.", + "metodik.pa_engelska": "Mostrato in inglese — nessuna traduzione revisionata in {sprak}", + + // ---- Sito pubblico (webb.) --------------------------------------------- + "webb.nav.oversikt": "Panoramica", + "webb.ansok": "Richiedi un account", + "webb.loggain": "Accedi", + "webb.fot.impressum": "Note legali", + "webb.fot.dataskydd": "Privacy", + "webb.fot.villkor": "Condizioni", + "webb.fot.tillganglighet": "Accessibilità", + "webb.fot.sprak": "Lingue", + "webb.fot.utgavor": "Note di rilascio", + + "webb.hero.position": "Piattaforma di diagnosi guidata", + "webb.hero.definition": "Procedure diagnostiche standardizzate per una ricerca guasti ripetibile e verificabile.", + + "webb.metod.etikett": "Metodologia", + "webb.metod.rubrik": "Il modello ALVA", + "webb.metod.ingress": + "ALVA è un metodo standardizzato di analisi, localizzazione, verifica e azione sistematiche. Ogni decisione è tracciabile, ogni conclusione verificabile, ogni azione riproducibile.", + "webb.fas.analysis.syfte": "Raccogliere le evidenze.", + "webb.fas.analysis.avgransning": "Solo fatti. In questa fase non si raccolgono ipotesi.", + "webb.fas.localization.syfte": "Circoscrivere il guasto.", + "webb.fas.localization.avgransning": "Restringere l’area. La causa non è ancora stabilita.", + "webb.fas.verification.syfte": "Confermare la causa radice.", + "webb.fas.verification.avgransning": "Si verifica la causa — non il sintomo.", + "webb.fas.action.syfte": "Eseguire l’azione correttiva.", + "webb.fas.action.avgransning": "Risultato verificato e documentato. Altrimenti la fase è incompleta.", + + "webb.drift.etikett": "Funzionamento", + "webb.drift.rubrik": "Come funziona", + "webb.drift.s1.rubrik": "Creare l’organizzazione", + "webb.drift.s1.text": "La registrazione viene inviata ed esaminata.", + "webb.drift.s2.rubrik": "Gli utenti ricevono gli account", + "webb.drift.s2.text": "I ruoli sono assegnati dall’amministratore dell’organizzazione.", + "webb.drift.s3.rubrik": "Procedure diagnostiche guidate", + "webb.drift.s3.text": "Ogni pratica segue una procedura definita.", + "webb.drift.s4.rubrik": "Verifica", + "webb.drift.s4.text": "La causa radice viene confermata prima dell’azione correttiva.", + "webb.drift.s5.rubrik": "Documentazione", + "webb.drift.s5.text": "Il rapporto è generato dal registro della pratica.", + "webb.drift.s6.rubrik": "Miglioramento continuo", + "webb.drift.s6.text": "Gli esiti verificati affinano le procedure successive.", + + "webb.larande.etikett": "Capacità", + "webb.larande.rubrik": "Apprendimento dell’organizzazione", + "webb.larande.p1": + "La piattaforma affina di continuo le procedure diagnostiche con l’esperienza operativa verificata della vostra stessa organizzazione.", + "webb.larande.p2": + "Ogni diagnosi completata alimenta la guida successiva. Si usano solo esiti verificati — una pratica chiusa senza causa radice confermata non apporta nulla, per costruzione.", + "webb.larande.p3": + "La base di conoscenza appartiene all’organizzazione. È derivata dalle vostre pratiche, dalle vostre procedure e dalla vostra documentazione, e non viene aggregata tra clienti.", + "webb.larande.block": "Derivata da", + "webb.larande.k1": "Cause radice verificate", + "webb.larande.k2": "Azioni correttive confermate", + "webb.larande.k3": "Categorie di guasti ricorrenti", + "webb.larande.k4": "Procedure portate a termine", + "webb.larande.k5": "Documentazione dell’organizzazione", + + "webb.rapport.etikett": "Rendicontazione", + "webb.rapport.rubrik": "Rapporto trimestrale di miglioramento", + "webb.rapport.ingress": + "Una volta a trimestre la piattaforma risponde a sei domande sul lavoro dell’officina stessa. Ogni risposta è derivata dal registro delle pratiche — nulla è stimato, e a nessuno viene chiesto nulla.", + "webb.rapport.f1": "Quali guasti diagnostichiamo ora correttamente al primo tentativo?", + "webb.rapport.f2": "Quanto spesso una causa sospetta si rivela quella reale?", + "webb.rapport.f3": "Quali guasti continuano a ripresentarsi nella flotta?", + "webb.rapport.f4": "Quali passaggi della procedura vengono saltati, e perché?", + "webb.rapport.f5": "Cosa sa oggi l’officina che non sapeva il trimestre scorso?", + "webb.rapport.f6": "Le pratiche vengono chiuse, verificate e documentate — o soltanto chiuse?", + + "webb.pris.etikett": "Commerciale", + "webb.pris.rubrik": "Licenze", + "webb.pris.komponent": "Componente", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licenza di piattaforma", + "webb.pris.plattform.grund": "Per organizzazione, annuale", + "webb.pris.anvandare": "Licenze utente", + "webb.pris.anvandare.grund": "Per utente attivo, mensile", + "webb.pris.moduler": "Moduli enterprise", + "webb.pris.moduler.grund": "Facoltativi, per modulo", + "webb.pris.betalning": "Nessun pagamento online. Nessuna sottoscrizione di abbonamento. La fatturazione segue l’esame della richiesta.", + "webb.pris.aktivering": "Sequenza di attivazione", + "webb.pris.a1": "L’organizzazione invia la registrazione.", + "webb.pris.a2": "La richiesta viene esaminata.", + "webb.pris.a3": "Viene emessa la fattura.", + "webb.pris.a4": "Il pagamento viene registrato.", + "webb.pris.a5": "L’organizzazione viene attivata.", + "webb.pris.a6": "Gli utenti aggiuntivi sono fatturati mensilmente.", + + "webb.kallor.etikett": "Infrastruttura", + "webb.kallor.rubrik": "Infrastruttura operativa della conoscenza", + "webb.kallor.p1": + "ALVA usa le fonti di conoscenza autorizzate proprie dell’organizzazione. L’architettura è neutrale rispetto al fornitore: ogni fonte implementa la stessa interfaccia, e nessun fornitore è presupposto.", + "webb.kallor.p2": + "Un’installazione beta lavora sulla sola documentazione interna. I fornitori esterni si abilitano in seguito tramite connettori separati, senza modifiche al nucleo della piattaforma.", + "webb.kallor.block": "Ordine di risoluzione delle fonti", + "webb.kallor.k1": "Procedure interne dell’azienda", + "webb.kallor.k2": "Documentazione OEM", + "webb.kallor.k3": "Bollettini tecnici", + "webb.kallor.k4": "Informazioni di garanzia", + "webb.kallor.k5": "Manuali di officina", + "webb.kallor.k6": "Storico delle diagnosi verificate", + "webb.kallor.k7": "Buone pratiche dell’organizzazione", + + "webb.login.etikett": "Accesso", + "webb.login.demo": + "Questo accesso non autentica nulla. Scrivete qualsiasi cosa in E-mail e Password e premete Accedi — non serve alcun account, e il portale dietro presenta dati di esempio fissi. Collegata a un’istanza della piattaforma, questa pagina si autentica presso di essa, e senza una sessione valida il portale è chiuso.", + "webb.login.losenord": "Password", + "webb.login.ofullstandigt": "Autenticazione incompleta. E-mail e password sono richieste.", + "webb.login.misslyckades": "Autenticazione non riuscita.", + "webb.login.logga_in": "Accedi", + "webb.login.loggar_in": "Accesso in corso", + "webb.login.glomt": "Password dimenticata", + + "webb.ansokan.etikett": "Registrazione", + "webb.ansokan.demo": + "Non viene inviato nulla. Inviare questo modulo non salva nulla, non avvisa nessuno e non crea alcuna richiesta — dietro non c’è ancora alcun sistema che la riceva. La descrizione qui sotto dice come la registrazione è pensata per funzionare, non cosa accade oggi.", + "webb.ansokan.avsikt": "Funzionamento previsto: le richieste sono esaminate manualmente e in questa fase non viene riscosso alcun pagamento.", + "webb.falt.foretag": "Azienda", + "webb.falt.orgnummer": "Numero di iscrizione al Registro delle Imprese", + "webb.falt.kontakt": "Referente", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefono", + "webb.falt.tekniker": "Numero di tecnici", + "webb.falt.bransch": "Settore", + "webb.falt.land": "Paese", + "webb.falt.anvandare": "Numero previsto di utenti", + "webb.falt.noteringar": "Note", + "webb.falt.kravs": "(obbligatorio)", + "webb.ansokan.skicka": "Invia la richiesta", + "webb.ansokan.mottagen.etikett": "Richiesta", + "webb.ansokan.mottagen": "Inviata", + "webb.ansokan.mottagen.demo": + "Nulla è stato trasmesso e nessuno è stato avvisato. Il riferimento qui sotto è stato calcolato nel vostro browser da ciò che avete digitato e non esiste altrove — sparirà alla chiusura di questa pagina.", + "webb.ansokan.status": "Stato", + "webb.ansokan.referens": "Riferimento", + "webb.ansokan.granskning": + "Le richieste sono esaminate manualmente. All’approvazione viene emessa una fattura. L’organizzazione viene attivata alla registrazione del pagamento.", + + "webb.sprak.etikett": "Localizzazione", + "webb.sprak.rubrik": "Lingue", + "webb.sprak.ingress": + "L’inglese è la lingua predefinita e la lingua di origine. Seguono nove traduzioni. Ciò che la piattaforma non fa è affermare che un’interfaccia tradotta significhi un metodo tradotto.", + "webb.sprak.princip.etikett": "Principio", + "webb.sprak.princip.rubrik": "Due tipi di testo", + "webb.sprak.granssnitt.rubrik": "Testo d’interfaccia", + "webb.sprak.granssnitt.beteckning": "Ripiega in silenzio", + "webb.sprak.granssnitt.text": + "Etichette, pulsanti, stati. Un insieme finito, che cambia di rado. Una stringa inglese che raggiunge un utente tedesco è un fastidio, non un pericolo — una traduzione mancante ripiega quindi sull’inglese senza commento.", + "webb.sprak.metodik.rubrik": "Testo di procedura", + "webb.sprak.metodik.beteckning": "Non ripiega mai in silenzio", + "webb.sprak.metodik.text": + "Istruzioni per lavorare su un veicolo. Qui una traduzione non revisionata è peggiore di una in lingua straniera — perché l’inglese sembra straniero, mentre una cattiva traduzione sembra un’istruzione. Viene mostrata in inglese e contrassegnata, con la lingua nominata.", + "webb.sprak.invariant": + "Il metodo in sé non viene mai tradotto. I nomi delle fasi e le parole di stato sono la struttura di ALVA e si leggono identici in ogni paese, così un revisore può leggere un fascicolo rumeno e uno tedesco senza sapere in quale lingua lavora l’officina.", + "webb.sprak.tackning.etikett": "Copertura", + "webb.sprak.tackning.rubrik": "Cosa è tradotto, e cosa è revisionato", + "webb.sprak.kolumn.sprak": "Lingua", + "webb.sprak.kolumn.granssnitt": "Interfaccia", + "webb.sprak.kolumn.granskat": "Metodo revisionato da uno specialista", + "webb.sprak.granskat.ja": "Sì", + "webb.sprak.granskat.nej": "No — testo di procedura in inglese", + "webb.sprak.matt": + "La copertura dell’interfaccia è misurata, non stimata: un test fa fallire la build se una chiave manca in una lingua. Lo stato di revisione è un’affermazione su persone, non su file, e si imposta a mano.", + "webb.sprak.kallsprak": + "Il testo di procedura inglese è una traduzione della fonte svedese e non è ancora stato letto da uno specialista che esercita il mestiere. È tenuto allo stesso standard delle altre nove lingue, e qui se ne fa la stessa dichiarazione invece di esentarlo in silenzio perché è la fonte.", + "webb.sprak.bevis.etikett": "Verifica", + "webb.sprak.bevis.rubrik": "Le frasi che fermano una pratica", + "webb.sprak.bevis.ingress": + "Queste sono le frasi che negano a un tecnico la chiusura di una pratica. Un rifiuto che nessuno capisce è un rifiuto senza via d’uscita — sono quindi le frasi giuste per giudicare una traduzione.", + "webb.sprak.ej_granskad": "Non revisionato", + "webb.sprak.granskad": "Revisionato", + "webb.sprak.granskad.text": + "Il testo di procedura in {sprak} è stato letto da uno specialista che esercita il mestiere. Passaggi e punti di controllo sono mostrati in {sprak} ovunque.", + "webb.sprak.val.etikett": "Funzionamento", + "webb.sprak.val.rubrik": "Come viene scelta la lingua", + "webb.sprak.val.s1.rubrik": "Preferenza dell’utente", + "webb.sprak.val.s1.text": "Ciò che la singola persona ha selezionato.", + "webb.sprak.val.s2.rubrik": "Impostazione dell’organizzazione", + "webb.sprak.val.s2.text": "La lingua di documentazione dell’officina.", + "webb.sprak.val.s3.rubrik": "Lingua del browser", + "webb.sprak.val.s3.text": "La prima lingua che la piattaforma riconosce.", + "webb.sprak.val.s4.rubrik": "Inglese", + "webb.sprak.val.s4.text": "La predefinita, e la fonte.", + "webb.sprak.val.notering": + "L’organizzazione precede deliberatamente il browser. Un’officina in Germania con un tecnico polacco ha bisogno di un’unica lingua di documentazione — il fascicolo non deve cambiare lingua a seconda di chi ha scritto la riga.", +}; diff --git a/felsokning/app/services/gemensam/sprak/nl.mjs b/felsokning/app/services/gemensam/sprak/nl.mjs new file mode 100644 index 0000000..ce9b89c --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/nl.mjs @@ -0,0 +1,327 @@ +// ALVA-SPEC-060 · Nederlands. +// +// Knoppen staan in de infinitief («Opslaan», «Annuleren»), zoals de +// Nederlandse interfaceconventie voorschrijft. Instructies binnen een +// zin richten zich niet tot de gebruiker — ALVA-SPEC-001 §7: het systeem +// meldt een toestand. +// +// `granskat: false`: de interface is vertaald, de proceduretekst is niet +// door een vakspecialist gecontroleerd. Zie index.mjs. + +export const NL = { + // ---- Bewijsgraden ------------------------------------------------------- + "evidens.E1": "Waarneming", + "evidens.E2": "Foto", + "evidens.E3": "Video met geluid", + "evidens.E4": "Meting, gekalibreerd meetmiddel", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "kalibratie ontbreekt of is verlopen", + "evidens.ej_angivet": "meetmiddel niet opgegeven", + "evidens.ej_fotograferad": "ingevoerd, niet gefotografeerd", + + // ---- De afsluitcontrole ------------------------------------------------- + "grind.objekt": "Identificatie van voertuig of object geverifieerd", + "grind.historik": "Voertuighistorie gecontroleerd, of het achterwege blijven ervan gemotiveerd", + "grind.historik.nekad": "Een geweigerde historiecontrole vereist een opgegeven reden.", + "grind.historik.saknas": "Geen historiecontrole gedocumenteerd.", + "grind.matarstallning.ingaende": "Kilometerstand bij inname gefotografeerd", + "grind.matarstallning.utgaende": "Kilometerstand bij aflevering gefotografeerd", + "grind.matarstallning.saknas": "Geen kilometerstand gedocumenteerd.", + "grind.matarstallning.ej_foto": + "De kilometerstand is ingevoerd maar niet gefotografeerd. Kilometerteller fotograferen, of aangeven waarom dat niet mogelijk is.", + "grind.reproducering": "Symptoomverificatie: gereproduceerd, of gedocumenteerd als niet reproduceerbaar", + "grind.felorsak": "Analyse van de grondoorzaak gedocumenteerd", + "grind.atgard": "Corrigerende maatregel gedocumenteerd, of het uitblijven ervan gemotiveerd", + "grind.atgard.saknas": "Noch een uitgevoerde maatregel noch een reden voor het uitblijven ervan is gedocumenteerd.", + "grind.kundbeslut": "Klantbeslissing over het voorstel vastgelegd", + "grind.kundbeslut.avbojt": "Werk uitgevoerd ondanks een afgewezen voorstel", + "grind.kundbeslut.avbojt.detalj": + "De klant heeft het voorstel afgewezen, maar er is werk gedocumenteerd als uitgevoerd.", + "grind.kvalitetskontroll": "Kwaliteitscontrole uitgevoerd — symptoom aantoonbaar verholpen", + "grind.kontroller": "Controlepunten van de methodiek: bewijs, of een gedocumenteerde uitzondering", + "grind.foton": "Foto's aanwezig bij de controles die dat vereisen", + "grind.slutsats": "Eindconclusie (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Hoogvoltbevoegdheid bevestigd", + "grind.hogvolt.spanningslos": "Voertuig spanningsloos gemaakt volgens de procedure van de fabrikant, spanningsloosheid door meting vastgesteld", + "grind.regelpaket": "Handtekening van het regelpakket komt niet overeen — afsluiten geblokkeerd.", + "grind.regelpaket.osignerat": "Er wordt een extern regelpakket zonder handtekening gebruikt — afsluiten geblokkeerd.", + "grind.evidens": "Bewijsgraad boven E0", + "grind.evidens.saknas": "Er is geen enkel bewijs in het logboek aanwezig.", + "grind.foton.detalj": "{kontroller} controles vereisen een foto, {foton} foto's in het logboek.", + "grind.sparr.ej_uppfyllt": "Aan de veiligheidseis is niet voldaan.", + "grind.arendetyp.okant": "Onbekende eis in het regelpakket: {krav}", + "grind.arendetyp.krav": "Eis voor dit zaaktype: {krav}", + "grind.sakerhet": "Betrouwbaarheidsniveau binnen wat het bewijs draagt", + "grind.sakerhet.detalj": "De opgegeven betrouwbaarheid ({niva}) overstijgt wat het bewijs draagt ({tak}). Het bewijs aanvullen, of het niveau verlagen — eerlijke onzekerheid is informatie.", + "grind.eskalering": "Technische escalaties beantwoord", + "grind.eskalering.detalj": "Er staat een escalatie open ({referens}). Wacht het antwoord af en documenteer het voordat de zaak wordt afgesloten.", + + + // ---- De eindconclusie (ALVA-RULE-200) ----------------------------------- + "slutsats.rubrik": "Eindconclusie", + "slutsats.konstaterat": "Wat is vastgesteld", + "slutsats.evidens": "Welk bewijs dat draagt", + "slutsats.avfardat": "Welke hypothesen zijn verworpen, en waarom", + "slutsats.osakert": "Wat onzeker blijft", + "slutsats.ickesvar": "Dat is geen antwoord. Aangeven wat is vastgesteld en welk bewijs dat draagt.", + "slutsats.falt.motivering": "Onderbouwing", + "slutsats.falt.motivering_ej": "Reden waarom de oorzaak niet kon worden vastgesteld", + "slutsats.falt.uteslutet": "Verworpen alternatieven", + "slutsats.falt.kvarstaende": "Resterende onzekerheid", + "slutsats.falt.atgardsval": "Keuze van de maatregel", + "slutsats.saknas": "{falt} ontbreekt.", + "slutsats.ickesvar.falt": "{falt}: „{text}” is geen onderbouwing. Aangeven wat er werkelijk geldt, en waarom.", + "slutsats.for_kort": "{falt} is te kort ({langd} van minstens {minsta} tekens) om achteraf toetsbaar te zijn.", + "slutsats.utan_varfor": "{falt} geeft het wat aan, niet het waarom. De conclusie met het bewijs verbinden — wat daarin maakt dat dit volgt?", + "slutsats.utan_slutsats": "De zaak kan niet worden afgesloten zonder eindconclusie. Aangeven waarom de conclusie uit het bewijs volgt.", + "slutsats.hypotes_obemott": "De hypothese „{text}” staat in het logboek maar wordt niet behandeld. Aangeven waarom zij is verworpen, of waarom zij open blijft.", + + + // ---- Zaakverloop -------------------------------------------------------- + "arende.nytt": "Nieuwe zaak", + "arende.oppna": "Openstaande zaken", + "arende.avslutade": "Afgesloten zaken", + "arende.avsluta": "Zaak afsluiten", + "arende.avslutat": "Zaak afgesloten", + "arende.kan_ej_avslutas": "De zaak kan nog niet worden afgesloten", + "arende.hinder": "Openstaand vóór afsluiting", + "arende.overlamna": "Overdragen", + "arende.ansvarig": "Verantwoordelijke", + + // ---- Vooronderzoek ------------------------------------------------------ + "pre.rubrik": "Vooronderzoek — voordat het werk begint", + "pre.historik.fraga": + "Is de voertuighistorie gecontroleerd? (eerdere werkzaamheden, terugkerende storingen, TSB's, campagnes)", + "pre.historik.ja": "Ja — gecontroleerd", + "pre.historik.nej": "Nee", + "pre.historik.skal": "Reden waarom de historie niet is gecontroleerd (verplicht)", + "pre.historik.relevant": "Relevante eerdere werkzaamheden (optioneel — oorzakelijke keten)", + "pre.matarstallning": "Kilometerstand", + "pre.fotografera": "Instrumentenpaneel fotograferen", + "pre.felbeskrivning": "Storingsbeschrijving van de klant geverifieerd", + "pre.observationer": "Nog iets bij de inname?", + + // ---- Handelingen -------------------------------------------------------- + "handling.spara": "Opslaan", + "handling.avbryt": "Annuleren", + "handling.fortsatt": "Doorgaan", + "handling.tillbaka": "Terug", + "handling.dokumentera": "Documenteren", + "handling.fotografera": "Fotograferen", + "handling.spela_in": "Video opnemen", + "handling.undantag": "Een uitzondering documenteren", + "handling.undantag.skal": "Reden voor de uitzondering (verplicht)", + "handling.exportera": "Exporteren", + "handling.skriv_ut": "Afdrukken", + + // ---- Meting ------------------------------------------------------------- + "matning.varde": "Waarde", + "matning.enhet": "Eenheid", + "matning.matdon": "Meetmiddel", + "matning.matdon.valj": "Meetmiddel kiezen", + "matning.matdon.okant": "Onbekend meetmiddel. Registreren voordat de meting wordt opgeslagen.", + "matning.kalibrerad_till": "Gekalibreerd tot", + + // ---- Rapport ------------------------------------------------------------ + "rapport.rubrik": "Zaakrapport", + "rapport.sammanfattning": "Samenvatting", + "rapport.evidens": "Bewijs", + "rapport.atgarder": "Maatregelen", + "rapport.harledd": + "Afgeleid uit het zaaklogboek. Waarnemingen en metingen worden weergegeven zonder conclusies waarvoor de onderbouwing ontbreekt.", + + // ---- Taal --------------------------------------------------------------- + "sprak.valj": "Taal", + "sprak.granskat": "Gecontroleerd", + "sprak.ogranskat": "Niet gecontroleerd", + "sprak.tackning": "{procent} % van de interface", + + "metodik.ogranskad": + "De proceduretekst is niet gecontroleerd door een technisch specialist voor {sprak}. Stappen en controlepunten worden in het Engels weergegeven waar geen gecontroleerde vertaling bestaat — een ongecontroleerde vertaling van een veiligheidsinstructie is slechter dan een anderstalige, omdat die er niet anderstalig uitziet.", + "metodik.pa_engelska": "Weergegeven in het Engels — geen gecontroleerde vertaling in {sprak}", + + // ---- Publieke website (webb.) ------------------------------------------ + "webb.nav.oversikt": "Overzicht", + "webb.ansok": "Account aanvragen", + "webb.loggain": "Inloggen", + "webb.fot.impressum": "Colofon", + "webb.fot.dataskydd": "Privacybeleid", + "webb.fot.villkor": "Voorwaarden", + "webb.fot.tillganglighet": "Toegankelijkheid", + "webb.fot.sprak": "Talen", + "webb.fot.utgavor": "Release-informatie", + + "webb.hero.position": "Geleid diagnoseplatform", + "webb.hero.definition": "Gestandaardiseerde diagnoseprocedures voor herhaalbaar en verifieerbaar storingzoeken.", + + "webb.metod.etikett": "Methodiek", + "webb.metod.rubrik": "Het ALVA-model", + "webb.metod.ingress": + "ALVA is een gestandaardiseerde methode voor systematische analyse, lokalisatie, verificatie en actie. Elke beslissing is traceerbaar, elke conclusie verifieerbaar, elke actie reproduceerbaar.", + "webb.fas.analysis.syfte": "Bewijs verzamelen.", + "webb.fas.analysis.avgransning": "Alleen feiten. Hypothesen worden in deze fase niet verzameld.", + "webb.fas.localization.syfte": "De storing insluiten.", + "webb.fas.localization.avgransning": "Het gebied verkleinen. De oorzaak staat nog niet vast.", + "webb.fas.verification.syfte": "De grondoorzaak bevestigen.", + "webb.fas.verification.avgransning": "De oorzaak wordt geverifieerd — niet het symptoom.", + "webb.fas.action.syfte": "De corrigerende maatregel uitvoeren.", + "webb.fas.action.avgransning": "Resultaat geverifieerd en gedocumenteerd. Anders is de fase onvolledig.", + + "webb.drift.etikett": "Werking", + "webb.drift.rubrik": "Zo werkt het", + "webb.drift.s1.rubrik": "Organisatie aanmaken", + "webb.drift.s1.text": "De registratie wordt ingediend en beoordeeld.", + "webb.drift.s2.rubrik": "Gebruikers krijgen accounts", + "webb.drift.s2.text": "Rollen worden toegekend door de beheerder van de organisatie.", + "webb.drift.s3.rubrik": "Geleide diagnoseprocedures", + "webb.drift.s3.text": "Elke zaak volgt een gedefinieerde procedure.", + "webb.drift.s4.rubrik": "Verificatie", + "webb.drift.s4.text": "De grondoorzaak wordt bevestigd vóór de corrigerende maatregel.", + "webb.drift.s5.rubrik": "Documentatie", + "webb.drift.s5.text": "Het dossier wordt gegenereerd uit het zaaklogboek.", + "webb.drift.s6.rubrik": "Continue verbetering", + "webb.drift.s6.text": "Geverifieerde uitkomsten verfijnen volgende procedures.", + + "webb.larande.etikett": "Functionaliteit", + "webb.larande.rubrik": "Lerende organisatie", + "webb.larande.p1": + "Het platform verfijnt diagnoseprocedures voortdurend met geverifieerde praktijkervaring uit de eigen organisatie.", + "webb.larande.p2": + "Elke afgeronde diagnose voedt de volgende begeleiding. Alleen geverifieerde uitkomsten worden gebruikt — een zaak die zonder bevestigde grondoorzaak is afgesloten draagt bewust niets bij.", + "webb.larande.p3": + "De kennisbank is van de organisatie. Ze wordt afgeleid uit uw zaken, uw procedures en uw documentatie, en wordt niet over klanten heen samengevoegd.", + "webb.larande.block": "Afgeleid uit", + "webb.larande.k1": "Geverifieerde grondoorzaken", + "webb.larande.k2": "Bevestigde corrigerende maatregelen", + "webb.larande.k3": "Terugkerende storingscategorieën", + "webb.larande.k4": "Registraties van afgeronde procedures", + "webb.larande.k5": "Documentatie van de organisatie", + + "webb.rapport.etikett": "Rapportage", + "webb.rapport.rubrik": "Verbeterrapport per kwartaal", + "webb.rapport.ingress": + "Eén keer per kwartaal beantwoordt het platform zes vragen over het eigen werk van de werkplaats. Elk antwoord wordt afgeleid uit het zaaklogboek — niets wordt geschat, en niemand wordt iets gevraagd.", + "webb.rapport.f1": "Welke storingen stellen we nu in één keer goed vast?", + "webb.rapport.f2": "Hoe vaak blijkt een vermoede oorzaak de werkelijke te zijn?", + "webb.rapport.f3": "Welke storingen blijven in het wagenpark terugkomen?", + "webb.rapport.f4": "Welke procedurestappen worden overgeslagen, en waarom?", + "webb.rapport.f5": "Wat weet de werkplaats nu dat ze vorig kwartaal niet wist?", + "webb.rapport.f6": "Worden zaken afgerond, geverifieerd en gedocumenteerd — of alleen afgerond?", + + "webb.pris.etikett": "Commercieel", + "webb.pris.rubrik": "Licenties", + "webb.pris.komponent": "Onderdeel", + "webb.pris.grund": "Grondslag", + "webb.pris.plattform": "Platformlicentie", + "webb.pris.plattform.grund": "Per organisatie, jaarlijks", + "webb.pris.anvandare": "Gebruikerslicenties", + "webb.pris.anvandare.grund": "Per actieve gebruiker, maandelijks", + "webb.pris.moduler": "Bedrijfsmodules", + "webb.pris.moduler.grund": "Optioneel, per module", + "webb.pris.betalning": "Geen onlinebetaling. Geen abonnementsregistratie. Facturering volgt op beoordeling van de aanvraag.", + "webb.pris.aktivering": "Activeringsvolgorde", + "webb.pris.a1": "De organisatie dient de registratie in.", + "webb.pris.a2": "De aanvraag wordt beoordeeld.", + "webb.pris.a3": "De factuur wordt verstuurd.", + "webb.pris.a4": "De betaling wordt geregistreerd.", + "webb.pris.a5": "De organisatie wordt geactiveerd.", + "webb.pris.a6": "Extra gebruikers worden maandelijks gefactureerd.", + + "webb.kallor.etikett": "Infrastructuur", + "webb.kallor.rubrik": "Operationele kennisinfrastructuur", + "webb.kallor.p1": + "ALVA gebruikt de eigen geautoriseerde kennisbronnen van de organisatie. De architectuur is leveranciersneutraal: elke bron implementeert dezelfde interface, en geen leverancier wordt verondersteld.", + "webb.kallor.p2": + "Een bèta-installatie werkt uitsluitend met interne documentatie. Externe leveranciers worden later via aparte koppelingen ingeschakeld, zonder wijziging aan de platformkern.", + "webb.kallor.block": "Volgorde van bronraadpleging", + "webb.kallor.k1": "Interne bedrijfsprocedures", + "webb.kallor.k2": "OEM-documentatie", + "webb.kallor.k3": "Technische bulletins", + "webb.kallor.k4": "Garantie-informatie", + "webb.kallor.k5": "Werkplaatshandboeken", + "webb.kallor.k6": "Historische geverifieerde diagnoses", + "webb.kallor.k7": "Best practices van de organisatie", + + "webb.login.etikett": "Toegang", + "webb.login.demo": + "Deze login authenticeert niets. Typ wat dan ook in E-mail en Wachtwoord en druk op Inloggen — er is geen account nodig, en het portaal erachter toont vaste voorbeeldgegevens. Verbonden met een platforminstantie authenticeert deze pagina zich daartegen, en zonder geldige sessie is het portaal gesloten.", + "webb.login.losenord": "Wachtwoord", + "webb.login.ofullstandigt": "Authenticatie onvolledig. E-mail en wachtwoord vereist.", + "webb.login.misslyckades": "Authenticatie mislukt.", + "webb.login.logga_in": "Inloggen", + "webb.login.loggar_in": "Bezig met inloggen", + "webb.login.glomt": "Wachtwoord vergeten", + + "webb.ansokan.etikett": "Registratie", + "webb.ansokan.demo": + "Er wordt niets verzonden. Dit formulier indienen slaat niets op, meldt niemand iets en maakt geen aanvraag aan — er zit nog geen verwerking achter. De beschrijving hieronder zegt hoe de registratie bedoeld is te werken, niet wat er vandaag gebeurt.", + "webb.ansokan.avsikt": "Beoogde werking: aanvragen worden handmatig beoordeeld en in dit stadium wordt geen betaling geïnd.", + "webb.falt.foretag": "Bedrijf", + "webb.falt.orgnummer": "Registratienummer", + "webb.falt.kontakt": "Contactpersoon", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefoon", + "webb.falt.tekniker": "Aantal technici", + "webb.falt.bransch": "Branche", + "webb.falt.land": "Land", + "webb.falt.anvandare": "Verwacht aantal gebruikers", + "webb.falt.noteringar": "Opmerkingen", + "webb.falt.kravs": "(verplicht)", + "webb.ansokan.skicka": "Aanvraag indienen", + "webb.ansokan.mottagen.etikett": "Aanvraag", + "webb.ansokan.mottagen": "Ingediend", + "webb.ansokan.mottagen.demo": + "Er is niets verzonden en niemand is op de hoogte gebracht. De referentie hieronder is in uw browser berekend uit wat u typte en bestaat nergens anders — ze is weg zodra u deze pagina sluit.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Referentie", + "webb.ansokan.granskning": + "Aanvragen worden handmatig beoordeeld. Bij goedkeuring wordt een factuur verstuurd. De organisatie wordt geactiveerd zodra de betaling is geregistreerd.", + + "webb.sprak.etikett": "Lokalisatie", + "webb.sprak.rubrik": "Talen", + "webb.sprak.ingress": + "Engels is de standaard en de brontaal. Negen vertalingen volgen. Wat het platform niet doet, is beweren dat een vertaalde interface een vertaalde methode betekent.", + "webb.sprak.princip.etikett": "Principe", + "webb.sprak.princip.rubrik": "Twee soorten tekst", + "webb.sprak.granssnitt.rubrik": "Interfacetekst", + "webb.sprak.granssnitt.beteckning": "Valt stilzwijgend terug", + "webb.sprak.granssnitt.text": + "Labels, knoppen, statussen. Eindig, zelden gewijzigd. Een Engelse tekst die een Duitse gebruiker bereikt is een ergernis, geen gevaar — een ontbrekende vertaling valt dus zonder commentaar terug op het Engels.", + "webb.sprak.metodik.rubrik": "Proceduretekst", + "webb.sprak.metodik.beteckning": "Valt nooit stilzwijgend terug", + "webb.sprak.metodik.text": + "Instructies voor werk aan een voertuig. Hier is een ongecontroleerde vertaling erger dan een anderstalige — want Engels ziet er anderstalig uit, terwijl een slechte vertaling eruitziet als een instructie. De tekst wordt in het Engels getoond en gemarkeerd, met de taal benoemd.", + "webb.sprak.invariant": + "De methode zelf wordt nooit vertaald. Fasenamen en statuswoorden zijn de structuur van ALVA en luiden in elk land hetzelfde, zodat een auditor een Roemeens en een Duits dossier kan lezen zonder te weten in welke taal de werkplaats werkt.", + "webb.sprak.tackning.etikett": "Dekking", + "webb.sprak.tackning.rubrik": "Wat vertaald is, en wat gecontroleerd is", + "webb.sprak.kolumn.sprak": "Taal", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Methode gecontroleerd door een specialist", + "webb.sprak.granskat.ja": "Ja", + "webb.sprak.granskat.nej": "Nee — proceduretekst in het Engels", + "webb.sprak.matt": + "De dekking van de interface wordt gemeten, niet geschat: een test laat de build falen als een sleutel in een taal ontbreekt. De controlestatus is een uitspraak over mensen, niet over bestanden, en wordt handmatig ingesteld.", + "webb.sprak.kallsprak": + "De Engelse proceduretekst is een vertaling van de Zweedse bron en is nog niet gelezen door een specialist die het vak uitoefent. Hij wordt langs dezelfde meetlat gelegd als de andere negen talen, en hier wordt over hem dezelfde uitspraak gedaan in plaats van hem stilletjes uit te zonderen omdat hij de bron is.", + "webb.sprak.bevis.etikett": "Verificatie", + "webb.sprak.bevis.rubrik": "De zinnen die een zaak stoppen", + "webb.sprak.bevis.ingress": + "Dit zijn de zinnen die een technicus beletten een zaak af te sluiten. Een weigering die niemand begrijpt is een weigering zonder uitweg — dus zijn dit de juiste zinnen om een vertaling op te beoordelen.", + "webb.sprak.ej_granskad": "Niet gecontroleerd", + "webb.sprak.granskad": "Gecontroleerd", + "webb.sprak.granskad.text": + "De proceduretekst in {sprak} is gelezen door een specialist die het vak uitoefent. Stappen en controlepunten worden overal in {sprak} getoond.", + "webb.sprak.val.etikett": "Werking", + "webb.sprak.val.rubrik": "Hoe de taal wordt gekozen", + "webb.sprak.val.s1.rubrik": "Voorkeur van de gebruiker", + "webb.sprak.val.s1.text": "Wat de persoon zelf heeft geselecteerd.", + "webb.sprak.val.s2.rubrik": "Instelling van de organisatie", + "webb.sprak.val.s2.text": "De documentatietaal van de werkplaats.", + "webb.sprak.val.s3.rubrik": "Browsertaal", + "webb.sprak.val.s3.text": "De eerste taal die het platform herkent.", + "webb.sprak.val.s4.rubrik": "Engels", + "webb.sprak.val.s4.text": "De standaard, en de bron.", + "webb.sprak.val.notering": + "De organisatie gaat bewust vóór de browser. Een werkplaats in Duitsland met een Poolse technicus heeft één documentatietaal nodig — het dossier mag niet van taal wisselen afhankelijk van wie de tekst toevallig schreef.", +}; diff --git a/felsokning/app/services/gemensam/sprak/ord.mjs b/felsokning/app/services/gemensam/sprak/ord.mjs new file mode 100644 index 0000000..e2e34f3 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/ord.mjs @@ -0,0 +1,175 @@ +// ALVA-SPEC-060 · Ordlistorna bakom ALVA-RULE-200. +// +// ---- Varför den här filen finns ----------------------------------------- +// +// motivering.mjs granskar teknikerns slutsats med två ordlistor: fraser +// som ser ut som svar men inte är det ("klart", "trasig"), och ord som +// visar att en text faktiskt innehåller ett resonemang ("eftersom", +// "beror på"). Båda listorna var enbart svenska. +// +// Det var inte en översättningsbrist utan ett hål i regeln. En tysk +// tekniker som skrev "erledigt" i motiveringsfältet passerade filtret +// för icke-svar, därför att filtret bara kände igen "klart". Grinden +// släppte igenom avslutet och rapporten såg fullständig ut. Regeln som +// är hela produktens värde gällde i praktiken bara på svenska. +// +// ---- Union, inte uppslag per språk -------------------------------------- +// +// Listorna slås ihop över alla språk i stället för att väljas efter +// användarens språkinställning. Två skäl: +// +// Ett icke-svar är ett icke-svar oavsett språk. Jämförelsen görs mot +// HELA det normaliserade fältet, inte mot delsträngar, så "ok" fångas +// likadant vare sig verkstaden står i Kraków eller Köln — och en +// tekniker som skriver på ett annat språk än organisationens +// inställning ska inte kunna passera på den vägen. +// +// Orsaksorden går åt andra hållet: unionen gör kontrollen mer +// tillåtande, aldrig strängare. Ett felaktigt träffat orsaksord +// släpper igenom en text, det blockerar aldrig en riktig. Det är rätt +// riktning för ett fel att luta åt när alternativet är att neka en +// tekniker avslut på ett korrekt skrivet underlag. +// +// ---- Vad som medvetet INTE står i listorna ------------------------------- +// +// "inget", "keine", "none". Att ingenting kvarstår osäkert är ett giltigt +// och ofta korrekt svar (se motivering.mjs). Att göra det till ett +// icke-svar hade tvingat fram utfyllnad, och utfyllnad är sämre än ett +// kort sant svar. +// +// Orsaksord kortare än fyra tecken. De jämförs som delsträngar och skulle +// träffa inuti orelaterade ord på andra språk. +// +// ---- Granskningsstatus --------------------------------------------------- +// +// Svenska och engelska är genomgångna. Övriga språk är en första +// uppsättning och ska läsas av någon som arbetar i verkstad på språket +// innan de räknas som granskade — det är samma gräns som `granskat` i +// SPRAK, och den gäller här därför att listorna är metodik, inte +// gränssnitt. + +/** Fraser som ser ut som svar men inte är det. Jämförs mot hela fältet. */ +export const ICKESVAR_PER_SPRAK = { + sv: ["klart", "klar", "färdig", "fardig", "färdigt", "fardigt", "ok", "okej", "åtgärdat", "atgardat", "åtgärdad", "atgardad", + "fixat", "utfört", "utfort", "lagad", "lagat", "se ovan", "se logg", "enligt ovan", "som vanligt", + "trasig", "trasigt", "defekt", "sliten", "slitet", "utsliten", "behöver bytas", "behover bytas", "byttes", "bytt", "utbytt", + "enligt kund", "kunden sa", "vet ej", "ingen aning", "diverse", "allmänt slitage", "-", "n/a", "na"], + en: ["done", "ok", "okay", "fixed", "repaired", "see above", "see log", "as above", "as usual", + "broken", "defective", "faulty", "worn", "worn out", "needs replacing", "needs replacement", "replaced", + "per customer", "as per customer", "customer said", "don't know", "dont know", "no idea", "unknown", + "not sure", "dunno", "no clue", "various", "general wear"], + de: ["erledigt", "behoben", "repariert", "siehe oben", "siehe protokoll", "wie oben", "wie üblich", "wie ublich", + "kaputt", "verschlissen", "abgenutzt", "muss getauscht werden", "getauscht", "gewechselt", + "defekt", "fertig", "in ordnung", "i.o.", "erneuert", "ersetzt", + "laut kunde", "kunde sagte", "weiß nicht", "weiss nicht", "keine ahnung", "diverses", + "allgemeiner verschleiß", "allgemeiner verschleiss", "k.a."], + fr: ["fait", "terminé", "termine", "réparé", "repare", "voir ci-dessus", "voir journal", "comme ci-dessus", + "comme d'habitude", "cassé", "casse", "défectueux", "defectueux", "usé", "usagé", "usage", + "à remplacer", "a remplacer", "remplacé", "remplace", "selon le client", "le client a dit", + "use", "hors service", "hs", "en panne", "changé", "change", "comme d’habitude", + "je ne sais pas", "aucune idée", "aucune idee", "divers", "usure générale", "usure generale"], + es: ["hecho", "listo", "reparado", "ver arriba", "ver registro", "como arriba", "como siempre", + "roto", "defectuoso", "desgastado", "hay que cambiarlo", "cambiado", "sustituido", + "arreglado", "solucionado", "averiado", "ya está", "ya esta", + "según el cliente", "segun el cliente", "el cliente dijo", "no sé", "no se", "ni idea", + "varios", "desgaste general"], + it: ["fatto", "risolto", "riparato", "vedi sopra", "vedi registro", "come sopra", "come al solito", + "rotto", "difettoso", "usurato", "da sostituire", "sostituito", "cambiato", + "sistemato", "a posto", "guasto", "tutto ok", "boh", "non ne ho idea", + "secondo il cliente", "il cliente ha detto", "non so", "nessuna idea", "vari", "usura generale", "n/d"], + pl: ["zrobione", "gotowe", "naprawione", "patrz wyżej", "patrz wyzej", "patrz dziennik", "jak wyżej", "jak wyzej", + "jak zwykle", "zepsute", "uszkodzone", "zużyte", "zuzyte", "do wymiany", "wymienione", + "według klienta", "wedlug klienta", "klient powiedział", "klient powiedzial", + "jw", "jw.", "naprawiono", "wymieniono", "zrobiono", "usunięto", "usunieto", "bez uwag", + "uszkodzony", "zepsuty", "zużyty", "zuzyty", "wg klienta", + "nie wiem", "nie mam pojęcia", "nie mam pojecia", "różne", "rozne", "ogólne zużycie", "ogolne zuzycie"], + nl: ["klaar", "oké", "oke", "gedaan", "gerepareerd", "verholpen", "opgelost", "in orde", "zie boven", "zie logboek", "zoals boven", + "zoals gewoonlijk", "kapot", "defect", "stuk", "versleten", "moet vervangen worden", "vervangen", + "volgens klant", "klant zei", "weet niet", "geen idee", "diversen", "algemene slijtage", "n.v.t.", "nvt"], + pt: ["feito", "pronto", "reparado", "ver acima", "ver registo", "como acima", "como habitual", + "partido", "avariado", "defeituoso", "desgastado", "a substituir", "substituído", "substituido", + "resolvido", "arranjado", "concluído", "concluido", + "segundo o cliente", "o cliente disse", "não sei", "nao sei", "nenhuma ideia", "não faço ideia", "nao faco ideia", + "diversos", "desgaste geral"], + ro: ["gata", "rezolvat", "reparat", "vezi mai sus", "vezi jurnal", "ca mai sus", "ca de obicei", + "stricat", "stricată", "stricata", "defect", "defectă", "defecta", "uzat", "uzată", "uzata", + "de înlocuit", "de inlocuit", "înlocuit", "inlocuit", "schimbat", + "conform clientului", "clientul a spus", "nu știu", "nu stiu", "habar n-am", "habar nu am", "diverse", + "uzură generală", "uzura generala"], +}; + +/** + * Ord som visar att texten innehåller ett orsakssamband. + * + * Jämförs som delsträngar, därför minst fyra tecken — se filhuvudet. + */ +export const ORSAKSORD_PER_SPRAK = { + sv: ["eftersom", "därför", "darfor", "beror", "orsakas", "orsakade", "medför", "medfor", "leder till", + "ledde till", "vilket", "på grund av", "pga", "resulterar", "resulterade", "innebär", "innebar", + "orsakar", "orsakat", "pa grund av", "till foljd av", "tyder på", "tyder pa", "indikerar", + "följaktligen", "foljaktligen", "till följd av", "förklarar", "forklarar", "stämmer överens", + "stammer overens", "bekräftas av", "bekraftas av", "visar att", "utesluter"], + en: ["because", "since", "due to", "caused", "causes", "results in", "resulted in", "leads to", "led to", + "therefore", "hence", "consequently", "explain", "confirmed by", "confirms", "shows that", "rules out", + "consistent with", "indicat", "points to", "which means"], + de: ["weil", "aufgrund", "verursacht", "führt zu", "fuhrt zu", "führte zu", "fuhrte zu", "folglich", + "daher", "deshalb", "wegen", "zurückzuführen", "zuruckzufuhren", "bedingt durch", "dadurch", "somit", + "erklärt", "erklart", "bestätigt", "bestatigt", "zeigt, dass", + "zeigt dass", "schließt aus", "schliesst aus", "ausgeschlossen", "stimmt überein", "stimmt uberein", "deutet auf"], + fr: ["parce que", "puisque", "en raison de", "à cause de", "a cause de", "provoqué", "provoque", + "entraîne", "entraine", "entraîné", "conduit à", "conduit a", "par conséquent", "par consequent", + "donc", "prouve", "explique", "confirmé par", "confirme par", "montre que", "exclut", "correspond à", "correspond a", + "indique"], + es: ["porque", "ya que", "puesto que", "dado que", "debido a", "a causa de", "se debe a", "causado", + "provoca", "provocó", "provoco", "conduce a", + "por lo tanto", "por consiguiente", "explica", "confirmado por", "muestra que", "descarta", + "coincide con", "indica"], + it: ["perché", "perche", "poiché", "poiche", "dato che", "in quanto", "a causa di", "dovuto a", "causato", + "provoca", "comporta", "porta a", "deriva da", + "quindi", "pertanto", "di conseguenza", "spiega", "confermato da", "mostra che", "esclude", + "coincide con", "compatibile con", "coerente con", "indica"], + pl: ["ponieważ", "poniewaz", "gdyż", "gdyz", "dlatego", "z powodu", "spowodowa", "powoduj", "prowadzi do", + "w związku", "w zwiazku", "zatem", "wynika", "wskutek", "na skutek", "skutkuje", + "świadczy", "swiadczy", "wyjaśni", "wyjasni", "potwierdz", + "pokazuje, że", "pokazuje że", "pokazuje ze", "wyklucza", "zgodne z", "wskazuj"], + nl: ["omdat", "aangezien", "doordat", "vanwege", "waardoor", "als gevolg van", "te wijten aan", + "veroorzaakt", "leidt tot", "leidde tot", "daarom", + "bijgevolg", "verklaart", "bevestigd door", "blijkt uit", "toont aan", "wijst op", "sluit uit", "komt overeen", "duidt op"], + pt: ["porque", "dado que", "uma vez que", "visto que", "devido a", "deve-se a", "causad", "provoc", + "leva a", "resulta", "origina", "portanto", "por conseguinte", + "explica", "confirmado por", "mostra que", "exclui", "coincide com", "indica"], + ro: ["deoarece", "pentru că", "pentru ca", "fiindcă", "fiindca", "întrucât", "intrucat", "din cauza", + "se datorează", "se datoreaza", "cauzat", "provoacă", "provoaca", "duce la", "rezultă", "rezulta", "determinat", + "prin urmare", "așadar", "asadar", "explică", "explica", "confirmat de", "confirmată de", "confirmata de", + "arată că", "arata ca", "exclude", "corespunde cu", "indică", "indica"], +}; + +/** + * Ord som visar att texten hänvisar till dokumenterad evidens. + * + * Samma delsträngsjämförelse och samma unionsresonemang som ovan. Här + * är det extra tydligt varför unionen är rätt: engelskans "photo" + * innehåller inte "foto", så en engelsk tekniker som skrev "photo shows + * corrosion at pin 14" hänvisade till evidens som filtret inte såg. + */ +export const EVIDENSORD_PER_SPRAK = { + sv: ["mät", "matning", "matvarde", "uppmatt", "foto", "bild", "video", "felkod", "avläs", "avlas", "prov", "test", "dokument", "historik", "kontroll"], + en: ["measur", "photo", "image", "picture", "video", "fault code", "trouble code", "dtc", "reading", "voltage", "resistance", "inspect", "test", "document", "history", "check", "scan"], + de: ["mess", "foto", "bild", "video", "fehlercode", "fehlerspeicher", "ausles", "ablesung", "prüf", "pruf", "dokument", "historie", "kontrolle"], + fr: ["mesur", "photo", "image", "vidéo", "video", "code défaut", "code defaut", "relevé", "releve", "essai", "diag", "valise", "document", "historique", "contrôle", "controle"], + es: ["medici", "medida", "foto", "imagen", "vídeo", "video", "código de avería", "codigo de averia", "código de error", "codigo de error", "lectura", "prueba", "comprob", "diagnosis", "escáner", "escaner", "documento", "historial", "control"], + it: ["misur", "foto", "immagine", "video", "codice errore", "codice di errore", "codice guasto", "lettura", "prova", "documento", "storico", "controllo"], + pl: ["pomiar", "zmierzon", "zdjęci", "zdjeci", "foto", "obraz", "wideo", "film", "kod błędu", "kod bledu", "odczyt", "test", "prób", "skan", "dokument", "histori", "kontrol"], + nl: ["meting", "gemeten", "meetwaarde", "foto", "afbeelding", "video", "foutcode", "storingscode", "uitlezing", "uitgelezen", "test", "proef", "document", "historie", "controle"], + pt: ["medi", "foto", "imagem", "vídeo", "video", "código de avaria", "codigo de avaria", "código de erro", "codigo de erro", "leitura", "ensaio", "documento", "histórico", "historico", "controlo"], + ro: ["măsur", "masur", "foto", "poză", "poza", "imagine", "video", "cod de eroare", "cod eroare", "citire", "verific", "test", "document", "istoric", "control"], +}; + +const union = (per) => [...new Set(Object.values(per).flat())]; + +export const ICKESVAR = union(ICKESVAR_PER_SPRAK); +export const ORSAKSORD = union(ORSAKSORD_PER_SPRAK); +export const EVIDENSORD = union(EVIDENSORD_PER_SPRAK); + +/** Kortaste orsaksord som får jämföras som delsträng. Se filhuvudet. */ +export const MINSTA_ORSAKSORD = 4; diff --git a/felsokning/app/services/gemensam/sprak/pl.mjs b/felsokning/app/services/gemensam/sprak/pl.mjs new file mode 100644 index 0000000..34067e6 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/pl.mjs @@ -0,0 +1,329 @@ +// ALVA-SPEC-060 · Polski. +// +// ---- Odmiana nazwy języka ------------------------------------------------ +// +// {sprak} wstawia własną nazwę języka — «Polski» — w mianowniku. Po +// polsku «w języku Polski» jest błędem, a odmienić wstawianego wyrazu +// nie sposób bez tablicy przypadków dla dziesięciu języków. Dlatego +// zdania są tak sformułowane, by nazwa stała w nawiasie jako etykieta: +// «w tym języku ({sprak})». To wybór świadomy, nie niedopatrzenie. +// +// `granskat: false`: interfejs jest przetłumaczony, tekst procedur nie +// został sprawdzony przez specjalistę. Zob. index.mjs. + +export const PL = { + // ---- Stopnie dowodu ----------------------------------------------------- + "evidens.E1": "Obserwacja", + "evidens.E2": "Fotografia", + "evidens.E3": "Wideo z dźwiękiem", + "evidens.E4": "Pomiar, przyrząd wzorcowany", + "evidens.E5": "Dokument", + "evidens.ej_kalibrerad": "brak wzorcowania lub wzorcowanie wygasło", + "evidens.ej_angivet": "nie podano przyrządu", + "evidens.ej_fotograferad": "wprowadzono, nie sfotografowano", + + // ---- Kontrola zamknięcia ------------------------------------------------ + "grind.objekt": "Identyfikacja pojazdu lub obiektu zweryfikowana", + "grind.historik": "Historia pojazdu sprawdzona albo brak sprawdzenia uzasadniony", + "grind.historik.nekad": "Odmowa sprawdzenia historii wymaga podania powodu.", + "grind.historik.saknas": "Nie udokumentowano sprawdzenia historii.", + "grind.matarstallning.ingaende": "Stan licznika przy przyjęciu sfotografowany", + "grind.matarstallning.utgaende": "Stan licznika przy wydaniu sfotografowany", + "grind.matarstallning.saknas": "Nie udokumentowano stanu licznika.", + "grind.matarstallning.ej_foto": + "Stan licznika został wprowadzony, ale nie sfotografowany. Sfotografować licznik lub podać, dlaczego nie jest to możliwe.", + "grind.reproducering": "Weryfikacja objawu: odtworzony lub udokumentowany jako nieodtwarzalny", + "grind.felorsak": "Analiza przyczyny źródłowej udokumentowana", + "grind.atgard": "Działanie naprawcze udokumentowane albo jego brak uzasadniony", + "grind.atgard.saknas": "Nie udokumentowano ani wykonanego działania, ani powodu jego braku.", + "grind.kundbeslut": "Decyzja klienta w sprawie propozycji zarejestrowana", + "grind.kundbeslut.avbojt": "Praca wykonana mimo odrzuconej propozycji", + "grind.kundbeslut.avbojt.detalj": "Klient odrzucił propozycję, ale praca jest udokumentowana jako wykonana.", + "grind.kvalitetskontroll": "Kontrola jakości wykonana — potwierdzono ustąpienie objawu", + "grind.kontroller": "Punkty kontrolne metodyki: dowód lub udokumentowane odstępstwo", + "grind.foton": "Fotografie obecne przy punktach kontrolnych, które ich wymagają", + "grind.slutsats": "Wniosek końcowy (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Uprawnienia do prac przy wysokim napięciu potwierdzone", + "grind.hogvolt.spanningslos": "Pojazd pozbawiony napięcia zgodnie z procedurą producenta, brak napięcia potwierdzony pomiarem", + "grind.regelpaket": "Podpis pakietu reguł nie zgadza się — zamknięcie zablokowane.", + "grind.regelpaket.osignerat": "Używany jest zewnętrzny pakiet reguł bez podpisu — zamknięcie zablokowane.", + "grind.evidens": "Poziom dowodu powyżej E0", + "grind.evidens.saknas": "W dzienniku nie ma żadnego dowodu.", + "grind.foton.detalj": "Punktów kontrolnych wymagających fotografii: {kontroller}; fotografii w dzienniku: {foton}.", + "grind.sparr.ej_uppfyllt": "Wymóg bezpieczeństwa nie jest spełniony.", + "grind.arendetyp.okant": "Nieznany wymóg w pakiecie reguł: {krav}", + "grind.arendetyp.krav": "Wymóg dla tego rodzaju sprawy: {krav}", + "grind.sakerhet": "Poziom pewności w granicach tego, co potwierdzają dowody", + "grind.sakerhet.detalj": "Podany poziom pewności ({niva}) przekracza to, co potwierdzają dowody ({tak}). Uzupełnić dowody lub obniżyć poziom — uczciwa niepewność to informacja.", + "grind.eskalering": "Eskalacje techniczne z odpowiedzią", + "grind.eskalering.detalj": "Eskalacja jest otwarta ({referens}). Poczekać na odpowiedź i udokumentować ją przed zamknięciem sprawy.", + + + // ---- Wniosek końcowy (ALVA-RULE-200) ------------------------------------ + "slutsats.rubrik": "Wniosek końcowy", + "slutsats.konstaterat": "Co zostało ustalone", + "slutsats.evidens": "Jakie dowody to potwierdzają", + "slutsats.avfardat": "Które hipotezy odrzucono i dlaczego", + "slutsats.osakert": "Co pozostaje niepewne", + "slutsats.ickesvar": "To nie jest wniosek. Podać, co zostało ustalone i jakie dowody to potwierdzają.", + "slutsats.falt.motivering": "Uzasadnienie", + "slutsats.falt.motivering_ej": "Powód, dla którego nie ustalono przyczyny", + "slutsats.falt.uteslutet": "Odrzucone alternatywy", + "slutsats.falt.kvarstaende": "Pozostała niepewność", + "slutsats.falt.atgardsval": "Wybór działania", + "slutsats.saknas": "Brakuje: {falt}.", + "slutsats.ickesvar.falt": "{falt}: „{text}” nie jest uzasadnieniem. Podać, jaki jest stan faktyczny i dlaczego.", + "slutsats.for_kort": "Pole „{falt}” jest za krótkie ({langd} z co najmniej {minsta} znaków), aby dało się je później zweryfikować.", + "slutsats.utan_varfor": "{falt} podaje co, ale nie dlaczego. Powiązać wniosek z dowodami — co w nich sprawia, że to wynika?", + "slutsats.utan_slutsats": "Sprawy nie można zamknąć bez wniosku końcowego. Podać, dlaczego wniosek wynika z dowodów.", + "slutsats.hypotes_obemott": "Hipoteza „{text}” znajduje się w dzienniku, ale nie została omówiona. Podać, dlaczego ją odrzucono lub dlaczego pozostaje otwarta.", + + + // ---- Przebieg sprawy ---------------------------------------------------- + "arende.nytt": "Nowa sprawa", + "arende.oppna": "Sprawy otwarte", + "arende.avslutade": "Sprawy zamknięte", + "arende.avsluta": "Zamknij sprawę", + "arende.avslutat": "Sprawa zamknięta", + "arende.kan_ej_avslutas": "Sprawy nie można jeszcze zamknąć", + "arende.hinder": "Do wykonania przed zamknięciem", + "arende.overlamna": "Przekaż", + "arende.ansvarig": "Osoba odpowiedzialna", + + // ---- Kontrola wstępna --------------------------------------------------- + "pre.rubrik": "Diagnostyka wstępna — przed rozpoczęciem pracy", + "pre.historik.fraga": + "Czy historia pojazdu została sprawdzona? (wcześniejsze naprawy, powtarzające się usterki, TSB, akcje serwisowe)", + "pre.historik.ja": "Tak — sprawdzona", + "pre.historik.nej": "Nie", + "pre.historik.skal": "Powód niesprawdzenia historii (wymagane)", + "pre.historik.relevant": "Istotne wcześniejsze naprawy (opcjonalnie — łańcuch przyczynowy)", + "pre.matarstallning": "Stan licznika", + "pre.fotografera": "Sfotografuj zestaw wskaźników", + "pre.felbeskrivning": "Opis usterki podany przez klienta zweryfikowany", + "pre.observationer": "Coś jeszcze przy przyjęciu?", + + // ---- Czynności ---------------------------------------------------------- + "handling.spara": "Zapisz", + "handling.avbryt": "Anuluj", + "handling.fortsatt": "Dalej", + "handling.tillbaka": "Wstecz", + "handling.dokumentera": "Udokumentuj", + "handling.fotografera": "Sfotografuj", + "handling.spela_in": "Nagraj wideo", + "handling.undantag": "Udokumentuj odstępstwo", + "handling.undantag.skal": "Powód odstępstwa (wymagane)", + "handling.exportera": "Eksportuj", + "handling.skriv_ut": "Drukuj", + + // ---- Pomiar ------------------------------------------------------------- + "matning.varde": "Wartość", + "matning.enhet": "Jednostka", + "matning.matdon": "Przyrząd", + "matning.matdon.valj": "Wybierz przyrząd", + "matning.matdon.okant": "Nieznany przyrząd. Zarejestrować go przed zapisaniem pomiaru.", + "matning.kalibrerad_till": "Wzorcowanie ważne do", + + // ---- Raport ------------------------------------------------------------- + "rapport.rubrik": "Raport sprawy", + "rapport.sammanfattning": "Podsumowanie", + "rapport.evidens": "Dowody", + "rapport.atgarder": "Działania", + "rapport.harledd": + "Wyprowadzony z dziennika sprawy. Obserwacje i pomiary są przedstawiane bez wniosków pozbawionych podstaw.", + + // ---- Język -------------------------------------------------------------- + "sprak.valj": "Język", + "sprak.granskat": "Sprawdzony", + "sprak.ogranskat": "Niesprawdzony", + "sprak.tackning": "{procent} % interfejsu", + + "metodik.ogranskad": + "Tekst procedur nie został sprawdzony przez specjalistę technicznego w tym języku ({sprak}). Kroki i punkty kontrolne są wyświetlane po angielsku tam, gdzie nie ma sprawdzonego tłumaczenia — niesprawdzone tłumaczenie instrukcji bezpieczeństwa jest gorsze niż instrukcja w obcym języku, ponieważ nie wygląda obco.", + "metodik.pa_engelska": "Wyświetlane po angielsku — brak sprawdzonego tłumaczenia w tym języku ({sprak})", + + // ---- Publiczna witryna (webb.) ----------------------------------------- + "webb.nav.oversikt": "Przegląd", + "webb.ansok": "Wniosek o konto", + "webb.loggain": "Logowanie", + "webb.fot.impressum": "Nota prawna", + "webb.fot.dataskydd": "Prywatność", + "webb.fot.villkor": "Warunki", + "webb.fot.tillganglighet": "Dostępność", + "webb.fot.sprak": "Języki", + "webb.fot.utgavor": "Informacje o wydaniach", + + "webb.hero.position": "Platforma diagnostyki prowadzonej", + "webb.hero.definition": "Znormalizowane procedury diagnostyczne dla powtarzalnego i weryfikowalnego diagnozowania usterek.", + + "webb.metod.etikett": "Metodyka", + "webb.metod.rubrik": "Model ALVA", + "webb.metod.ingress": + "ALVA to znormalizowana metoda systematycznej analizy, lokalizacji, weryfikacji i działania. Każda decyzja jest identyfikowalna, każdy wniosek weryfikowalny, każde działanie powtarzalne.", + "webb.fas.analysis.syfte": "Zebrać dowody.", + "webb.fas.analysis.avgransning": "Wyłącznie fakty. W tej fazie nie zbiera się hipotez.", + "webb.fas.localization.syfte": "Zlokalizować usterkę.", + "webb.fas.localization.avgransning": "Ograniczyć obszar. Przyczyna nie jest jeszcze ustalona.", + "webb.fas.verification.syfte": "Potwierdzić przyczynę źródłową.", + "webb.fas.verification.avgransning": "Weryfikowana jest przyczyna — nie objaw.", + "webb.fas.action.syfte": "Wykonać działanie naprawcze.", + "webb.fas.action.avgransning": "Wynik zweryfikowany i udokumentowany. Inaczej faza jest niezakończona.", + + "webb.drift.etikett": "Sposób działania", + "webb.drift.rubrik": "Jak to działa", + "webb.drift.s1.rubrik": "Utworzenie organizacji", + "webb.drift.s1.text": "Wniosek rejestracyjny zostaje złożony i rozpatrzony.", + "webb.drift.s2.rubrik": "Użytkownicy otrzymują konta", + "webb.drift.s2.text": "Role przydziela administrator organizacji.", + "webb.drift.s3.rubrik": "Prowadzone procedury diagnostyczne", + "webb.drift.s3.text": "Każda sprawa przebiega według zdefiniowanej procedury.", + "webb.drift.s4.rubrik": "Weryfikacja", + "webb.drift.s4.text": "Przyczyna źródłowa jest potwierdzana przed działaniem naprawczym.", + "webb.drift.s5.rubrik": "Dokumentacja", + "webb.drift.s5.text": "Raport powstaje z dziennika sprawy.", + "webb.drift.s6.rubrik": "Ciągłe doskonalenie", + "webb.drift.s6.text": "Zweryfikowane wyniki udoskonalają kolejne procedury.", + + "webb.larande.etikett": "Możliwości", + "webb.larande.rubrik": "Uczenie się organizacji", + "webb.larande.p1": + "Platforma stale udoskonala procedury diagnostyczne na podstawie zweryfikowanego doświadczenia operacyjnego własnej organizacji.", + "webb.larande.p2": + "Każda ukończona diagnoza zasila kolejne wskazówki. Używane są wyłącznie zweryfikowane wyniki — sprawa zamknięta bez potwierdzonej przyczyny źródłowej z założenia nie wnosi niczego.", + "webb.larande.p3": + "Baza wiedzy należy do organizacji. Powstaje z jej własnych spraw, procedur i dokumentacji i nie jest agregowana między klientami.", + "webb.larande.block": "Pochodzi z", + "webb.larande.k1": "Zweryfikowane przyczyny źródłowe", + "webb.larande.k2": "Potwierdzone działania naprawcze", + "webb.larande.k3": "Powtarzające się kategorie usterek", + "webb.larande.k4": "Zapisy ukończonych procedur", + "webb.larande.k5": "Dokumentacja organizacji", + + "webb.rapport.etikett": "Raportowanie", + "webb.rapport.rubrik": "Kwartalny raport doskonalenia", + "webb.rapport.ingress": + "Raz na kwartał platforma odpowiada na sześć pytań o pracę samego warsztatu. Każda odpowiedź pochodzi z dziennika spraw — niczego się nie szacuje i nikogo o nic nie pyta.", + "webb.rapport.f1": "Które usterki diagnozujemy teraz poprawnie za pierwszym razem?", + "webb.rapport.f2": "Jak często podejrzewana przyczyna okazuje się rzeczywistą?", + "webb.rapport.f3": "Które usterki wciąż wracają we flocie?", + "webb.rapport.f4": "Które kroki procedury są pomijane i dlaczego?", + "webb.rapport.f5": "Co warsztat wie teraz, czego nie wiedział w zeszłym kwartale?", + "webb.rapport.f6": "Czy sprawy są zamykane, weryfikowane i dokumentowane — czy tylko zamykane?", + + "webb.pris.etikett": "Warunki handlowe", + "webb.pris.rubrik": "Licencjonowanie", + "webb.pris.komponent": "Składnik", + "webb.pris.grund": "Podstawa", + "webb.pris.plattform": "Licencja platformy", + "webb.pris.plattform.grund": "Na organizację, rocznie", + "webb.pris.anvandare": "Licencje użytkowników", + "webb.pris.anvandare.grund": "Na aktywnego użytkownika, miesięcznie", + "webb.pris.moduler": "Moduły korporacyjne", + "webb.pris.moduler.grund": "Opcjonalne, na moduł", + "webb.pris.betalning": "Bez płatności online. Bez samodzielnego wykupu subskrypcji. Fakturowanie następuje po rozpatrzeniu wniosku.", + "webb.pris.aktivering": "Kolejność aktywacji", + "webb.pris.a1": "Organizacja składa wniosek rejestracyjny.", + "webb.pris.a2": "Wniosek jest rozpatrywany.", + "webb.pris.a3": "Wystawiana jest faktura.", + "webb.pris.a4": "Płatność zostaje zaksięgowana.", + "webb.pris.a5": "Organizacja zostaje aktywowana.", + "webb.pris.a6": "Dodatkowi użytkownicy są rozliczani miesięcznie.", + + "webb.kallor.etikett": "Infrastruktura", + "webb.kallor.rubrik": "Operacyjna infrastruktura wiedzy", + "webb.kallor.p1": + "ALVA korzysta z własnych, autoryzowanych źródeł wiedzy organizacji. Architektura jest neutralna wobec dostawców: każde źródło implementuje ten sam interfejs i żadnego dostawcy nie zakłada się z góry.", + "webb.kallor.p2": + "Instalacja beta pracuje wyłącznie na dokumentacji wewnętrznej. Zewnętrzni dostawcy są włączani później przez osobne konektory, bez zmian w rdzeniu platformy.", + "webb.kallor.block": "Kolejność przeszukiwania źródeł", + "webb.kallor.k1": "Wewnętrzne procedury firmy", + "webb.kallor.k2": "Dokumentacja OEM", + "webb.kallor.k3": "Biuletyny techniczne", + "webb.kallor.k4": "Informacje gwarancyjne", + "webb.kallor.k5": "Podręczniki serwisowe", + "webb.kallor.k6": "Historyczne zweryfikowane diagnozy", + "webb.kallor.k7": "Dobre praktyki organizacji", + + "webb.login.etikett": "Dostęp", + "webb.login.demo": + "To logowanie niczego nie uwierzytelnia. Wpisz cokolwiek w polach E-mail i Hasło i naciśnij Zaloguj się — konto nie jest potrzebne, a portal za nim pokazuje stałe dane przykładowe. Połączona z instancją platformy strona uwierzytelnia się wobec niej, a bez ważnej sesji portal jest zamknięty.", + "webb.login.losenord": "Hasło", + "webb.login.ofullstandigt": "Uwierzytelnianie niekompletne. Wymagane są e-mail i hasło.", + "webb.login.misslyckades": "Uwierzytelnianie nie powiodło się.", + "webb.login.logga_in": "Zaloguj się", + "webb.login.loggar_in": "Logowanie w toku", + "webb.login.glomt": "Nie pamiętam hasła", + + "webb.ansokan.etikett": "Rejestracja", + "webb.ansokan.demo": + "Nic nie jest wysyłane. Wysłanie formularza niczego nie zapisuje, nikogo nie powiadamia i nie tworzy wniosku — nie stoi za nim jeszcze żaden proces przyjmowania zgłoszeń. Opis poniżej mówi, jak rejestracja ma działać, a nie co dzieje się dziś.", + "webb.ansokan.avsikt": "Zamierzone działanie: wnioski są rozpatrywane ręcznie i na tym etapie nie jest pobierana żadna płatność.", + "webb.falt.foretag": "Firma", + "webb.falt.orgnummer": "Numer rejestrowy", + "webb.falt.kontakt": "Osoba kontaktowa", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Liczba techników", + "webb.falt.bransch": "Branża", + "webb.falt.land": "Kraj", + "webb.falt.anvandare": "Przewidywana liczba użytkowników", + "webb.falt.noteringar": "Uwagi", + "webb.falt.kravs": "(wymagane)", + "webb.ansokan.skicka": "Wyślij wniosek", + "webb.ansokan.mottagen.etikett": "Wniosek", + "webb.ansokan.mottagen": "Złożono", + "webb.ansokan.mottagen.demo": + "Nic nie zostało przekazane i nikt nie został powiadomiony. Poniższy numer referencyjny został obliczony w przeglądarce z tego, co wpisano, i nie istnieje nigdzie indziej — zniknie po zamknięciu tej strony.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Numer referencyjny", + "webb.ansokan.granskning": + "Wnioski są rozpatrywane ręcznie. Po zatwierdzeniu wystawiana jest faktura. Organizacja zostaje aktywowana po zaksięgowaniu płatności.", + + "webb.sprak.etikett": "Lokalizacja", + "webb.sprak.rubrik": "Języki", + "webb.sprak.ingress": + "Angielski jest językiem domyślnym i źródłowym. Po nim następuje dziewięć tłumaczeń. Czego platforma nie robi: nie twierdzi, że przetłumaczony interfejs oznacza przetłumaczoną metodę.", + "webb.sprak.princip.etikett": "Zasada", + "webb.sprak.princip.rubrik": "Dwa rodzaje tekstu", + "webb.sprak.granssnitt.rubrik": "Tekst interfejsu", + "webb.sprak.granssnitt.beteckning": "Po cichu wraca do angielskiego", + "webb.sprak.granssnitt.text": + "Etykiety, przyciski, statusy. Zbiór skończony, rzadko zmieniany. Angielski tekst, który trafi do niemieckiego użytkownika, to irytacja, nie zagrożenie — brakujące tłumaczenie jest więc bez komentarza zastępowane angielskim.", + "webb.sprak.metodik.rubrik": "Tekst procedur", + "webb.sprak.metodik.beteckning": "Nigdy nie wraca po cichu", + "webb.sprak.metodik.text": + "Instrukcje pracy przy pojeździe. Tu niesprawdzone tłumaczenie jest gorsze niż tekst w obcym języku — bo angielski wygląda obco, a złe tłumaczenie wygląda jak instrukcja. Taki tekst jest pokazywany po angielsku i oznaczany, z podaną nazwą języka.", + "webb.sprak.invariant": + "Sama metoda nigdy nie jest tłumaczona. Nazwy faz i słowa statusów to struktura ALVA i czyta się je identycznie w każdym kraju, więc audytor może przeczytać rumuńskie i niemieckie akta sprawy, nie wiedząc, w jakim języku pracuje warsztat.", + "webb.sprak.tackning.etikett": "Pokrycie", + "webb.sprak.tackning.rubrik": "Co jest przetłumaczone, a co sprawdzone", + "webb.sprak.kolumn.sprak": "Język", + "webb.sprak.kolumn.granssnitt": "Interfejs", + "webb.sprak.kolumn.granskat": "Metodyka sprawdzona przez specjalistę", + "webb.sprak.granskat.ja": "Tak", + "webb.sprak.granskat.nej": "Nie — tekst procedur po angielsku", + "webb.sprak.matt": + "Pokrycie interfejsu jest mierzone, nie szacowane: test przerywa kompilację, jeśli w którymkolwiek języku brakuje jakiegokolwiek klucza. Status sprawdzenia to stwierdzenie o ludziach, nie o plikach, i ustawia się go ręcznie.", + "webb.sprak.kallsprak": + "Angielski tekst procedur jest tłumaczeniem szwedzkiego źródła i nie został jeszcze przeczytany przez specjalistę pracującego w zawodzie. Obowiązuje go ten sam standard co pozostałe dziewięć języków i tutaj pada o nim to samo stwierdzenie, zamiast po cichu robić wyjątek dlatego, że jest źródłem.", + "webb.sprak.bevis.etikett": "Weryfikacja", + "webb.sprak.bevis.rubrik": "Zdania, które zatrzymują sprawę", + "webb.sprak.bevis.ingress": + "To zdania, które odmawiają technikowi zamknięcia sprawy. Odmowa, której nikt nie rozumie, to odmowa bez wyjścia — dlatego to właściwe zdania do oceny tłumaczenia.", + "webb.sprak.ej_granskad": "Niesprawdzone", + "webb.sprak.granskad": "Sprawdzone", + "webb.sprak.granskad.text": + "Tekst procedur w tym języku ({sprak}) został przeczytany przez specjalistę pracującego w zawodzie. Kroki i punkty kontrolne są w całości pokazywane w tym języku ({sprak}).", + "webb.sprak.val.etikett": "Sposób działania", + "webb.sprak.val.rubrik": "Jak wybierany jest język", + "webb.sprak.val.s1.rubrik": "Wybór użytkownika", + "webb.sprak.val.s1.text": "To, co wybrała dana osoba.", + "webb.sprak.val.s2.rubrik": "Ustawienie organizacji", + "webb.sprak.val.s2.text": "Język dokumentacji warsztatu.", + "webb.sprak.val.s3.rubrik": "Język przeglądarki", + "webb.sprak.val.s3.text": "Pierwszy język, który platforma rozpoznaje.", + "webb.sprak.val.s4.rubrik": "Angielski", + "webb.sprak.val.s4.text": "Domyślny i źródłowy.", + "webb.sprak.val.notering": + "Organizacja celowo stoi wyżej niż przeglądarka. Warsztat w Niemczech z polskim technikiem potrzebuje jednego języka dokumentacji — akta sprawy nie mogą zmieniać języka w zależności od tego, kto akurat dokonał wpisu.", +}; diff --git a/felsokning/app/services/gemensam/sprak/pt.mjs b/felsokning/app/services/gemensam/sprak/pt.mjs new file mode 100644 index 0000000..8ef6ad6 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/pt.mjs @@ -0,0 +1,327 @@ +// ALVA-SPEC-060 · Português. +// +// Português europeu (pt-PT), sem o Acordo aplicado às formas em que as +// duas normas divergem de maneira visível para um técnico: «contacto», +// «registo», «conta-quilómetros». Se surgir um mercado brasileiro, é uma +// entrada nova no catálogo — «pt-BR» — e não uma emenda a esta. +// +// `granskat: false`: a interface está traduzida, o texto dos +// procedimentos não foi revisto por um especialista. Ver index.mjs. + +export const PT = { + // ---- Graus de evidência ------------------------------------------------- + "evidens.E1": "Observação", + "evidens.E2": "Fotografia", + "evidens.E3": "Vídeo com som", + "evidens.E4": "Medição, instrumento calibrado", + "evidens.E5": "Documento", + "evidens.ej_kalibrerad": "calibração em falta ou expirada", + "evidens.ej_angivet": "instrumento não indicado", + "evidens.ej_fotograferad": "introduzido, não fotografado", + + // ---- O controlo de encerramento ----------------------------------------- + "grind.objekt": "Identificação do veículo ou do objeto verificada", + "grind.historik": "Histórico do veículo verificado, ou a omissão da verificação justificada", + "grind.historik.nekad": "Uma verificação de histórico recusada exige a indicação de um motivo.", + "grind.historik.saknas": "Nenhuma verificação de histórico documentada.", + "grind.matarstallning.ingaende": "Quilometragem à entrada fotografada", + "grind.matarstallning.utgaende": "Quilometragem à saída fotografada", + "grind.matarstallning.saknas": "Nenhuma quilometragem documentada.", + "grind.matarstallning.ej_foto": + "A quilometragem está introduzida mas não fotografada. Fotografar o conta-quilómetros, ou indicar por que motivo não é possível.", + "grind.reproducering": "Verificação do sintoma: reproduzido, ou documentado como não reproduzível", + "grind.felorsak": "Análise da causa raiz documentada", + "grind.atgard": "Ação corretiva documentada, ou a sua ausência justificada", + "grind.atgard.saknas": "Não está documentada nem uma ação realizada nem um motivo para a sua ausência.", + "grind.kundbeslut": "Decisão do cliente sobre a proposta registada", + "grind.kundbeslut.avbojt": "Trabalho realizado apesar de uma proposta recusada", + "grind.kundbeslut.avbojt.detalj": "O cliente recusou a proposta, mas há trabalho documentado como realizado.", + "grind.kvalitetskontroll": "Controlo de qualidade efetuado — sintoma verificado como resolvido", + "grind.kontroller": "Pontos de controlo da metodologia: evidência, ou uma exceção documentada", + "grind.foton": "Fotografias presentes nos pontos de controlo que as exigem", + "grind.slutsats": "Conclusão (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Habilitação de alta tensão confirmada", + "grind.hogvolt.spanningslos": "Veículo colocado fora de tensão segundo o procedimento do fabricante, ausência de tensão verificada por medição", + "grind.regelpaket": "A assinatura do pacote de regras não corresponde — encerramento bloqueado.", + "grind.regelpaket.osignerat": + "Está a ser utilizado um pacote de regras externo sem assinatura — encerramento bloqueado.", + "grind.evidens": "Nível de evidência superior a E0", + "grind.evidens.saknas": "Não existe qualquer evidência no registo.", + "grind.foton.detalj": "{kontroller} pontos de controlo exigem fotografia, {foton} fotografias no registo.", + "grind.sparr.ej_uppfyllt": "O requisito de segurança não está cumprido.", + "grind.arendetyp.okant": "Requisito desconhecido no pacote de regras: {krav}", + "grind.arendetyp.krav": "Requisito para este tipo de processo: {krav}", + "grind.sakerhet": "Nível de confiança dentro do que a evidência sustenta", + "grind.sakerhet.detalj": "A confiança indicada ({niva}) excede o que a evidência sustenta ({tak}). Completar a evidência, ou baixar o nível — a incerteza honesta é informação.", + "grind.eskalering": "Escalonamentos técnicos respondidos", + "grind.eskalering.detalj": "Há um escalonamento em aberto ({referens}). Aguardar a resposta e documentá-la antes de encerrar o processo.", + + + // ---- A conclusão (ALVA-RULE-200) ---------------------------------------- + "slutsats.rubrik": "Conclusão", + "slutsats.konstaterat": "O que foi estabelecido", + "slutsats.evidens": "Que evidência o sustenta", + "slutsats.avfardat": "Que hipóteses foram afastadas, e porquê", + "slutsats.osakert": "O que permanece incerto", + "slutsats.ickesvar": "Isso não é uma conclusão. Indicar o que foi estabelecido e que evidência o sustenta.", + "slutsats.falt.motivering": "Fundamentação", + "slutsats.falt.motivering_ej": "Motivo pelo qual a causa não pôde ser estabelecida", + "slutsats.falt.uteslutet": "Alternativas afastadas", + "slutsats.falt.kvarstaende": "Incerteza remanescente", + "slutsats.falt.atgardsval": "Escolha da ação", + "slutsats.saknas": "Falta o campo «{falt}».", + "slutsats.ickesvar.falt": "{falt}: «{text}» não é uma fundamentação. Indicar o que se aplica realmente, e porquê.", + "slutsats.for_kort": "O campo «{falt}» é demasiado curto ({langd} de pelo menos {minsta} caracteres) para poder ser verificado posteriormente.", + "slutsats.utan_varfor": "{falt} indica o quê, mas não o porquê. Ligar a conclusão à evidência — o que nela faz com que isto decorra?", + "slutsats.utan_slutsats": "O processo não pode ser encerrado sem uma conclusão. Indicar por que motivo a conclusão decorre da evidência.", + "slutsats.hypotes_obemott": "A hipótese «{text}» consta do registo mas não é tratada. Indicar por que motivo foi afastada, ou por que motivo permanece em aberto.", + + + // ---- Fluxo do processo -------------------------------------------------- + "arende.nytt": "Novo processo", + "arende.oppna": "Processos abertos", + "arende.avslutade": "Processos encerrados", + "arende.avsluta": "Encerrar o processo", + "arende.avslutat": "Processo encerrado", + "arende.kan_ej_avslutas": "O processo ainda não pode ser encerrado", + "arende.hinder": "Pendente antes do encerramento", + "arende.overlamna": "Transferir", + "arende.ansvarig": "Responsável", + + // ---- Verificação prévia ------------------------------------------------- + "pre.rubrik": "Verificação prévia — antes do início do trabalho", + "pre.historik.fraga": + "O histórico do veículo foi verificado? (trabalhos anteriores, avarias recorrentes, TSB, campanhas)", + "pre.historik.ja": "Sim — verificado", + "pre.historik.nej": "Não", + "pre.historik.skal": "Motivo pelo qual o histórico não foi verificado (obrigatório)", + "pre.historik.relevant": "Trabalhos anteriores relevantes (opcional — cadeia causal)", + "pre.matarstallning": "Quilometragem", + "pre.fotografera": "Fotografar o painel de instrumentos", + "pre.felbeskrivning": "Descrição da avaria pelo cliente verificada", + "pre.observationer": "Mais alguma coisa na receção?", + + // ---- Ações -------------------------------------------------------------- + "handling.spara": "Guardar", + "handling.avbryt": "Cancelar", + "handling.fortsatt": "Continuar", + "handling.tillbaka": "Voltar", + "handling.dokumentera": "Documentar", + "handling.fotografera": "Fotografar", + "handling.spela_in": "Gravar vídeo", + "handling.undantag": "Documentar uma exceção", + "handling.undantag.skal": "Motivo da exceção (obrigatório)", + "handling.exportera": "Exportar", + "handling.skriv_ut": "Imprimir", + + // ---- Medição ------------------------------------------------------------ + "matning.varde": "Valor", + "matning.enhet": "Unidade", + "matning.matdon": "Instrumento", + "matning.matdon.valj": "Selecionar instrumento", + "matning.matdon.okant": "Instrumento desconhecido. Registá-lo antes de guardar a medição.", + "matning.kalibrerad_till": "Calibrado até", + + // ---- Relatório ---------------------------------------------------------- + "rapport.rubrik": "Relatório do processo", + "rapport.sammanfattning": "Resumo", + "rapport.evidens": "Evidência", + "rapport.atgarder": "Ações", + "rapport.harledd": + "Derivado do registo do processo. As observações e as medições são apresentadas sem conclusões que careçam de fundamento.", + + // ---- Idioma ------------------------------------------------------------- + "sprak.valj": "Idioma", + "sprak.granskat": "Revisto", + "sprak.ogranskat": "Não revisto", + "sprak.tackning": "{procent} % da interface", + + "metodik.ogranskad": + "O texto dos procedimentos não foi revisto por um especialista técnico em {sprak}. Os passos e os pontos de controlo são apresentados em inglês onde não existe tradução revista — uma tradução não revista de uma instrução de segurança é pior do que uma em língua estrangeira, porque não parece estrangeira.", + "metodik.pa_engelska": "Apresentado em inglês — sem tradução revista em {sprak}", + + // ---- Site público (webb.) ---------------------------------------------- + "webb.nav.oversikt": "Visão geral", + "webb.ansok": "Solicitar conta", + "webb.loggain": "Iniciar sessão", + "webb.fot.impressum": "Informação legal", + "webb.fot.dataskydd": "Privacidade", + "webb.fot.villkor": "Condições", + "webb.fot.tillganglighet": "Acessibilidade", + "webb.fot.sprak": "Idiomas", + "webb.fot.utgavor": "Notas de versão", + + "webb.hero.position": "Plataforma de diagnóstico guiado", + "webb.hero.definition": "Procedimentos de diagnóstico padronizados para uma deteção de avarias repetível e verificável.", + + "webb.metod.etikett": "Metodologia", + "webb.metod.rubrik": "O modelo ALVA", + "webb.metod.ingress": + "O ALVA é um método padronizado de análise, localização, verificação e ação sistemáticas. Cada decisão é rastreável, cada conclusão verificável, cada ação reproduzível.", + "webb.fas.analysis.syfte": "Reunir evidências.", + "webb.fas.analysis.avgransning": "Apenas factos. Nesta fase não se recolhem hipóteses.", + "webb.fas.localization.syfte": "Isolar a avaria.", + "webb.fas.localization.avgransning": "Estreitar a área. A causa ainda não está estabelecida.", + "webb.fas.verification.syfte": "Confirmar a causa raiz.", + "webb.fas.verification.avgransning": "Verifica-se a causa — não o sintoma.", + "webb.fas.action.syfte": "Executar a ação corretiva.", + "webb.fas.action.avgransning": "Resultado verificado e documentado. Caso contrário, a fase está incompleta.", + + "webb.drift.etikett": "Funcionamento", + "webb.drift.rubrik": "Como funciona", + "webb.drift.s1.rubrik": "Criar a organização", + "webb.drift.s1.text": "O registo é submetido e analisado.", + "webb.drift.s2.rubrik": "Os utilizadores recebem contas", + "webb.drift.s2.text": "As funções são atribuídas pelo administrador da organização.", + "webb.drift.s3.rubrik": "Procedimentos de diagnóstico guiados", + "webb.drift.s3.text": "Cada processo segue um procedimento definido.", + "webb.drift.s4.rubrik": "Verificação", + "webb.drift.s4.text": "A causa raiz é confirmada antes da ação corretiva.", + "webb.drift.s5.rubrik": "Documentação", + "webb.drift.s5.text": "O relatório é gerado a partir do registo do processo.", + "webb.drift.s6.rubrik": "Melhoria contínua", + "webb.drift.s6.text": "Resultados verificados refinam os procedimentos seguintes.", + + "webb.larande.etikett": "Capacidade", + "webb.larande.rubrik": "Aprendizagem da organização", + "webb.larande.p1": + "A plataforma refina continuamente os procedimentos de diagnóstico com a experiência operacional verificada da sua própria organização.", + "webb.larande.p2": + "Cada diagnóstico concluído alimenta a orientação seguinte. Só se usam resultados verificados — um processo encerrado sem causa raiz confirmada não contribui com nada, por conceção.", + "webb.larande.p3": + "A base de conhecimento pertence à organização. É derivada dos seus processos, dos seus procedimentos e da sua documentação, e não é agregada entre clientes.", + "webb.larande.block": "Derivada de", + "webb.larande.k1": "Causas raiz verificadas", + "webb.larande.k2": "Ações corretivas confirmadas", + "webb.larande.k3": "Categorias de avarias recorrentes", + "webb.larande.k4": "Registos de conclusão de procedimentos", + "webb.larande.k5": "Documentação da organização", + + "webb.rapport.etikett": "Relatórios", + "webb.rapport.rubrik": "Relatório trimestral de melhoria", + "webb.rapport.ingress": + "Uma vez por trimestre, a plataforma responde a seis perguntas sobre o próprio trabalho da oficina. Cada resposta é derivada do registo de processos — nada é estimado e nada é perguntado a ninguém.", + "webb.rapport.f1": "Que avarias diagnosticamos agora corretamente à primeira?", + "webb.rapport.f2": "Com que frequência uma causa suspeita se revela a verdadeira?", + "webb.rapport.f3": "Que avarias continuam a voltar na frota?", + "webb.rapport.f4": "Que passos do procedimento são saltados, e porquê?", + "webb.rapport.f5": "O que sabe a oficina agora que não sabia no trimestre passado?", + "webb.rapport.f6": "Os processos são encerrados, verificados e documentados — ou apenas encerrados?", + + "webb.pris.etikett": "Comercial", + "webb.pris.rubrik": "Licenciamento", + "webb.pris.komponent": "Componente", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licença de plataforma", + "webb.pris.plattform.grund": "Por organização, anual", + "webb.pris.anvandare": "Licenças de utilizador", + "webb.pris.anvandare.grund": "Por utilizador ativo, mensal", + "webb.pris.moduler": "Módulos empresariais", + "webb.pris.moduler.grund": "Opcionais, por módulo", + "webb.pris.betalning": "Sem pagamento online. Sem adesão a subscrição. A faturação segue-se à análise do pedido.", + "webb.pris.aktivering": "Sequência de ativação", + "webb.pris.a1": "A organização submete o registo.", + "webb.pris.a2": "O pedido é analisado.", + "webb.pris.a3": "A fatura é emitida.", + "webb.pris.a4": "O pagamento é registado.", + "webb.pris.a5": "A organização é ativada.", + "webb.pris.a6": "Os utilizadores adicionais são faturados mensalmente.", + + "webb.kallor.etikett": "Infraestrutura", + "webb.kallor.rubrik": "Infraestrutura operacional de conhecimento", + "webb.kallor.p1": + "O ALVA usa as fontes de conhecimento autorizadas da própria organização. A arquitetura é neutra quanto ao fornecedor: cada fonte implementa a mesma interface e nenhum fornecedor é pressuposto.", + "webb.kallor.p2": + "Uma instalação beta trabalha apenas com documentação interna. Fornecedores externos são ativados mais tarde através de conectores separados, sem alteração do núcleo da plataforma.", + "webb.kallor.block": "Ordem de resolução das fontes", + "webb.kallor.k1": "Procedimentos internos da empresa", + "webb.kallor.k2": "Documentação OEM", + "webb.kallor.k3": "Boletins técnicos", + "webb.kallor.k4": "Informação de garantia", + "webb.kallor.k5": "Manuais de oficina", + "webb.kallor.k6": "Diagnósticos históricos verificados", + "webb.kallor.k7": "Boas práticas da organização", + + "webb.login.etikett": "Acesso", + "webb.login.demo": + "Este início de sessão não autentica nada. Escreva qualquer coisa em E-mail e Palavra-passe e prima Entrar — não é preciso conta, e o portal por trás apresenta dados de exemplo fixos. Ligada a uma instância da plataforma, esta página autentica-se junto dela, e sem sessão válida o portal está fechado.", + "webb.login.losenord": "Palavra-passe", + "webb.login.ofullstandigt": "Autenticação incompleta. E-mail e palavra-passe são obrigatórios.", + "webb.login.misslyckades": "A autenticação falhou.", + "webb.login.logga_in": "Entrar", + "webb.login.loggar_in": "A entrar", + "webb.login.glomt": "Recuperar palavra-passe", + + "webb.ansokan.etikett": "Registo", + "webb.ansokan.demo": + "Nada é enviado. Submeter este formulário não guarda nada, não notifica ninguém e não cria nenhum pedido — ainda não há receção por trás. A descrição abaixo diz como o registo deve funcionar, não o que acontece hoje.", + "webb.ansokan.avsikt": "Funcionamento previsto: os pedidos são analisados manualmente e nesta fase não é cobrado qualquer pagamento.", + "webb.falt.foretag": "Empresa", + "webb.falt.orgnummer": "Número de registo comercial", + "webb.falt.kontakt": "Pessoa de contacto", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefone", + "webb.falt.tekniker": "Número de técnicos", + "webb.falt.bransch": "Setor", + "webb.falt.land": "País", + "webb.falt.anvandare": "Número previsto de utilizadores", + "webb.falt.noteringar": "Notas", + "webb.falt.kravs": "(obrigatório)", + "webb.ansokan.skicka": "Submeter o pedido", + "webb.ansokan.mottagen.etikett": "Pedido", + "webb.ansokan.mottagen": "Submetido", + "webb.ansokan.mottagen.demo": + "Nada foi transmitido e ninguém foi notificado. A referência abaixo foi calculada no seu navegador a partir do que escreveu e não existe em mais lado nenhum — desaparecerá quando fechar esta página.", + "webb.ansokan.status": "Estado", + "webb.ansokan.referens": "Referência", + "webb.ansokan.granskning": + "Os pedidos são analisados manualmente. A fatura é emitida após a aprovação. A organização é ativada quando o pagamento é registado.", + + "webb.sprak.etikett": "Localização", + "webb.sprak.rubrik": "Idiomas", + "webb.sprak.ingress": + "O inglês é o idioma predefinido e o idioma de origem. Seguem-se nove traduções. O que a plataforma não faz é afirmar que uma interface traduzida significa um método traduzido.", + "webb.sprak.princip.etikett": "Princípio", + "webb.sprak.princip.rubrik": "Dois tipos de texto", + "webb.sprak.granssnitt.rubrik": "Texto de interface", + "webb.sprak.granssnitt.beteckning": "Reverte em silêncio", + "webb.sprak.granssnitt.text": + "Etiquetas, botões, estados. Finito, raramente alterado. Uma cadeia de caracteres em inglês que chega a um utilizador alemão é uma irritação, não um perigo — por isso uma tradução em falta reverte para o inglês sem comentário.", + "webb.sprak.metodik.rubrik": "Texto de procedimento", + "webb.sprak.metodik.beteckning": "Nunca reverte em silêncio", + "webb.sprak.metodik.text": + "Instruções para trabalhar num veículo. Aqui uma tradução não revista é pior do que uma em língua estrangeira — porque o inglês parece estrangeiro, enquanto uma má tradução parece uma instrução. É mostrada em inglês e assinalada, com o idioma nomeado.", + "webb.sprak.invariant": + "O método em si nunca é traduzido. Os nomes das fases e as palavras de estado são a estrutura do ALVA e leem-se de forma idêntica em todos os países, para que um auditor possa ler um processo romeno e um alemão sem saber em que idioma a oficina trabalha.", + "webb.sprak.tackning.etikett": "Cobertura", + "webb.sprak.tackning.rubrik": "O que está traduzido, e o que está revisto", + "webb.sprak.kolumn.sprak": "Idioma", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Método revisto por um especialista", + "webb.sprak.granskat.ja": "Sim", + "webb.sprak.granskat.nej": "Não — texto de procedimento em inglês", + "webb.sprak.matt": + "A cobertura da interface é medida, não estimada: um teste faz falhar a compilação se faltar alguma chave em algum idioma. O estado de revisão é uma afirmação sobre pessoas, não sobre ficheiros, e é definido à mão.", + "webb.sprak.kallsprak": + "O texto de procedimento em inglês é uma tradução da fonte sueca e ainda não foi lido por um especialista que exerce o ofício. É sujeito ao mesmo padrão dos outros nove idiomas, e aqui faz-se sobre ele a mesma declaração em vez de o excetuar em silêncio por ser a fonte.", + "webb.sprak.bevis.etikett": "Verificação", + "webb.sprak.bevis.rubrik": "As frases que param um processo", + "webb.sprak.bevis.ingress": + "Estas são as frases que recusam a um técnico o encerramento de um processo. Uma recusa que ninguém entende é uma recusa sem saída — por isso são as frases certas para julgar uma tradução.", + "webb.sprak.ej_granskad": "Não revisto", + "webb.sprak.granskad": "Revisto", + "webb.sprak.granskad.text": + "O texto de procedimento em {sprak} foi lido por um especialista que exerce o ofício. Os passos e os pontos de controlo são mostrados em {sprak} em toda a plataforma.", + "webb.sprak.val.etikett": "Funcionamento", + "webb.sprak.val.rubrik": "Como o idioma é escolhido", + "webb.sprak.val.s1.rubrik": "Preferência do utilizador", + "webb.sprak.val.s1.text": "O que a pessoa selecionou.", + "webb.sprak.val.s2.rubrik": "Definição da organização", + "webb.sprak.val.s2.text": "O idioma de documentação da oficina.", + "webb.sprak.val.s3.rubrik": "Idioma do navegador", + "webb.sprak.val.s3.text": "O primeiro idioma que a plataforma reconhece.", + "webb.sprak.val.s4.rubrik": "Inglês", + "webb.sprak.val.s4.text": "O predefinido, e a origem.", + "webb.sprak.val.notering": + "A organização vem deliberadamente antes do navegador. Uma oficina na Alemanha com um técnico polaco precisa de um único idioma de documentação — o processo não pode mudar de idioma conforme quem escreveu a linha.", +}; diff --git a/felsokning/app/services/gemensam/sprak/ro.mjs b/felsokning/app/services/gemensam/sprak/ro.mjs new file mode 100644 index 0000000..ed51685 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/ro.mjs @@ -0,0 +1,333 @@ +// ALVA-SPEC-060 · Română. +// +// Imperativul persoanei a II-a singular este folosit consecvent, atât +// pe butoane («Salvează») cât și în instrucțiuni («Fotografiază +// kilometrajul»). Amestecul dintre singular și plural este cea mai +// frecventă inconsecvență din interfețele traduse în română, iar aici +// este evitat deliberat. +// +// Diacriticele sunt cele corecte — ș și ț cu virgulă (U+0219, U+021B), +// nu cu sedilă. Un raport tipărit cu sedile arată ca un document +// netradus. +// +// `granskat: false`: interfața este tradusă, textul procedurilor nu este +// verificat de un specialist. A se vedea index.mjs. + +export const RO = { + // ---- Grade de dovadă ---------------------------------------------------- + "evidens.E1": "Observație", + "evidens.E2": "Fotografie", + "evidens.E3": "Înregistrare video cu sunet", + "evidens.E4": "Măsurătoare, instrument etalonat", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "etalonare lipsă sau expirată", + "evidens.ej_angivet": "instrument neindicat", + "evidens.ej_fotograferad": "introdus, nefotografiat", + + // ---- Controlul de închidere --------------------------------------------- + "grind.objekt": "Identificarea vehiculului sau a obiectului verificată", + "grind.historik": "Istoricul vehiculului verificat, sau omiterea verificării justificată", + "grind.historik.nekad": "O verificare refuzată a istoricului necesită un motiv declarat.", + "grind.historik.saknas": "Nicio verificare a istoricului documentată.", + "grind.matarstallning.ingaende": "Kilometrajul la primire fotografiat", + "grind.matarstallning.utgaende": "Kilometrajul la predare fotografiat", + "grind.matarstallning.saknas": "Niciun kilometraj documentat.", + "grind.matarstallning.ej_foto": + "Kilometrajul este introdus, dar nu este fotografiat. Fotografiază odometrul sau indică de ce acest lucru nu este posibil.", + "grind.reproducering": "Verificarea simptomului: reprodus sau documentat ca nereproductibil", + "grind.felorsak": "Analiza cauzei rădăcină documentată", + "grind.atgard": "Acțiune corectivă documentată, sau absența ei justificată", + "grind.atgard.saknas": "Nu este documentată nici o acțiune efectuată, nici un motiv pentru absența acesteia.", + "grind.kundbeslut": "Decizia clientului cu privire la propunere, înregistrată", + "grind.kundbeslut.avbojt": "Lucrare efectuată în pofida unei propuneri refuzate", + "grind.kundbeslut.avbojt.detalj": + "Clientul a refuzat propunerea, dar există lucrări documentate ca fiind efectuate.", + "grind.kvalitetskontroll": "Control de calitate efectuat — simptom verificat ca rezolvat", + "grind.kontroller": "Punctele de control ale metodologiei: dovadă sau o excepție documentată", + "grind.foton": "Fotografii prezente pentru controalele care le impun", + "grind.slutsats": "Concluzie finală (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Autorizarea pentru înaltă tensiune confirmată", + "grind.hogvolt.spanningslos": "Vehicul scos de sub tensiune conform procedurii producătorului, absența tensiunii verificată prin măsurare", + "grind.regelpaket": "Semnătura pachetului de reguli nu corespunde — închiderea este blocată.", + "grind.regelpaket.osignerat": + "Se utilizează un pachet de reguli extern fără semnătură — închiderea este blocată.", + "grind.evidens": "Nivel de dovadă peste E0", + "grind.evidens.saknas": "În jurnal nu există nicio dovadă.", + "grind.foton.detalj": "Controale care necesită fotografie: {kontroller}; fotografii în jurnal: {foton}.", + "grind.sparr.ej_uppfyllt": "Cerința de siguranță nu este îndeplinită.", + "grind.arendetyp.okant": "Cerință necunoscută în pachetul de reguli: {krav}", + "grind.arendetyp.krav": "Cerință pentru acest tip de caz: {krav}", + "grind.sakerhet": "Nivel de încredere în limitele a ceea ce susțin dovezile", + "grind.sakerhet.detalj": "Încrederea indicată ({niva}) depășește ceea ce susțin dovezile ({tak}). Completează dovezile sau coboară nivelul — incertitudinea onestă este informație.", + "grind.eskalering": "Escaladări tehnice cu răspuns", + "grind.eskalering.detalj": "O escaladare este deschisă ({referens}). Așteaptă răspunsul și documentează-l înainte de închiderea cazului.", + + + // ---- Concluzia finală (ALVA-RULE-200) ----------------------------------- + "slutsats.rubrik": "Concluzie finală", + "slutsats.konstaterat": "Ce a fost stabilit", + "slutsats.evidens": "Ce dovezi susțin acest lucru", + "slutsats.avfardat": "Ce ipoteze au fost înlăturate și de ce", + "slutsats.osakert": "Ce rămâne incert", + "slutsats.ickesvar": "Acesta nu este un răspuns. Indică ce a fost stabilit și ce dovezi susțin acest lucru.", + "slutsats.falt.motivering": "Justificare", + "slutsats.falt.motivering_ej": "Motivul pentru care cauza nu a putut fi stabilită", + "slutsats.falt.uteslutet": "Alternative înlăturate", + "slutsats.falt.kvarstaende": "Incertitudine rămasă", + "slutsats.falt.atgardsval": "Alegerea acțiunii", + "slutsats.saknas": "Lipsește: {falt}.", + "slutsats.ickesvar.falt": "{falt}: „{text}” nu este o justificare. Indică ce se aplică în realitate și de ce.", + "slutsats.for_kort": "Câmpul {falt} este prea scurt pentru a putea fi verificat ulterior (caractere: {langd}, minim: {minsta}).", + "slutsats.utan_varfor": "{falt} spune ce, dar nu de ce. Leagă concluzia de dovezi — ce anume din ele face ca aceasta să decurgă?", + "slutsats.utan_slutsats": "Cazul nu poate fi închis fără o concluzie finală. Indică de ce concluzia decurge din dovezi.", + "slutsats.hypotes_obemott": "Ipoteza „{text}” se află în jurnal, dar nu este tratată. Indică de ce a fost înlăturată sau de ce rămâne deschisă.", + + + // ---- Parcursul cazului -------------------------------------------------- + "arende.nytt": "Caz nou", + "arende.oppna": "Cazuri deschise", + "arende.avslutade": "Cazuri închise", + "arende.avsluta": "Închide cazul", + "arende.avslutat": "Caz închis", + "arende.kan_ej_avslutas": "Cazul nu poate fi încă închis", + "arende.hinder": "Rămâne de rezolvat înainte de închidere", + "arende.overlamna": "Predă", + "arende.ansvarig": "Responsabil", + + // ---- Verificare prealabilă ---------------------------------------------- + "pre.rubrik": "Verificare prealabilă — înainte de începerea lucrării", + "pre.historik.fraga": + "A fost verificat istoricul vehiculului? (lucrări anterioare, defecte recurente, TSB, campanii)", + "pre.historik.ja": "Da — verificat", + "pre.historik.nej": "Nu", + "pre.historik.skal": "Motivul pentru care istoricul nu a fost verificat (obligatoriu)", + "pre.historik.relevant": "Lucrări anterioare relevante (opțional — lanț cauzal)", + "pre.matarstallning": "Kilometraj", + "pre.fotografera": "Fotografiază panoul de instrumente", + "pre.felbeskrivning": "Descrierea defectului de către client verificată", + "pre.observationer": "Altceva la primire?", + + // ---- Acțiuni ------------------------------------------------------------ + "handling.spara": "Salvează", + "handling.avbryt": "Anulează", + "handling.fortsatt": "Continuă", + "handling.tillbaka": "Înapoi", + "handling.dokumentera": "Documentează", + "handling.fotografera": "Fotografiază", + "handling.spela_in": "Înregistrează video", + "handling.undantag": "Documentează o excepție", + "handling.undantag.skal": "Motivul excepției (obligatoriu)", + "handling.exportera": "Exportă", + "handling.skriv_ut": "Tipărește", + + // ---- Măsurare ----------------------------------------------------------- + "matning.varde": "Valoare", + "matning.enhet": "Unitate", + "matning.matdon": "Instrument", + "matning.matdon.valj": "Selectează instrumentul", + "matning.matdon.okant": "Instrument necunoscut. Înregistrează-l înainte de salvarea măsurătorii.", + "matning.kalibrerad_till": "Etalonat până la", + + // ---- Raport ------------------------------------------------------------- + "rapport.rubrik": "Raportul cazului", + "rapport.sammanfattning": "Rezumat", + "rapport.evidens": "Dovezi", + "rapport.atgarder": "Acțiuni", + "rapport.harledd": + "Derivat din jurnalul cazului. Observațiile și măsurătorile sunt prezentate fără concluzii lipsite de temei.", + + // ---- Limbă -------------------------------------------------------------- + "sprak.valj": "Limbă", + "sprak.granskat": "Revizuit", + "sprak.ogranskat": "Nerevizuit", + "sprak.tackning": "{procent} % din interfață", + + "metodik.ogranskad": + "Textul procedurilor nu a fost verificat de un specialist tehnic în limba {sprak}. Pașii și punctele de control sunt afișate în engleză acolo unde nu există o traducere verificată — o traducere neverificată a unei instrucțiuni de siguranță este mai gravă decât una într-o limbă străină, pentru că nu arată ca fiind străină.", + "metodik.pa_engelska": "Afișat în engleză — nicio traducere verificată în limba {sprak}", + + // ---- Site-ul public (webb.) -------------------------------------------- + "webb.nav.oversikt": "Prezentare generală", + "webb.ansok": "Solicitați un cont", + "webb.loggain": "Autentificare", + "webb.fot.impressum": "Informații legale", + "webb.fot.dataskydd": "Confidențialitate", + "webb.fot.villkor": "Condiții", + "webb.fot.tillganglighet": "Accesibilitate", + "webb.fot.sprak": "Limbi", + "webb.fot.utgavor": "Note de versiune", + + "webb.hero.position": "Platformă de diagnosticare ghidată", + "webb.hero.definition": "Proceduri de diagnosticare standardizate pentru o depanare repetabilă și verificabilă.", + + "webb.metod.etikett": "Metodologie", + "webb.metod.rubrik": "Modelul ALVA", + "webb.metod.ingress": + "ALVA este o metodă standardizată de analiză, localizare, verificare și acțiune sistematice. Fiecare decizie este trasabilă, fiecare concluzie verificabilă, fiecare acțiune reproductibilă.", + "webb.fas.analysis.syfte": "Strânge dovezile.", + "webb.fas.analysis.avgransning": "Doar fapte. În această fază nu se adună ipoteze.", + "webb.fas.localization.syfte": "Izolează defectul.", + "webb.fas.localization.avgransning": "Restrânge zona. Cauza nu este încă stabilită.", + "webb.fas.verification.syfte": "Confirmă cauza rădăcină.", + "webb.fas.verification.avgransning": "Se verifică cauza — nu simptomul.", + "webb.fas.action.syfte": "Execută acțiunea corectivă.", + "webb.fas.action.avgransning": "Rezultat verificat și documentat. Altfel faza este incompletă.", + + "webb.drift.etikett": "Funcționare", + "webb.drift.rubrik": "Cum funcționează", + "webb.drift.s1.rubrik": "Crearea organizației", + "webb.drift.s1.text": "Înregistrarea este depusă și examinată.", + "webb.drift.s2.rubrik": "Utilizatorii primesc conturi", + "webb.drift.s2.text": "Rolurile sunt atribuite de administratorul organizației.", + "webb.drift.s3.rubrik": "Proceduri de diagnosticare ghidate", + "webb.drift.s3.text": "Fiecare caz urmează o procedură definită.", + "webb.drift.s4.rubrik": "Verificare", + "webb.drift.s4.text": "Cauza rădăcină este confirmată înaintea acțiunii corective.", + "webb.drift.s5.rubrik": "Documentare", + "webb.drift.s5.text": "Dosarul este generat din jurnalul cazului.", + "webb.drift.s6.rubrik": "Îmbunătățire continuă", + "webb.drift.s6.text": "Rezultatele verificate rafinează procedurile următoare.", + + "webb.larande.etikett": "Capacitate", + "webb.larande.rubrik": "Învățarea organizației", + "webb.larande.p1": + "Platforma rafinează continuu procedurile de diagnosticare cu experiența operațională verificată a propriei organizații.", + "webb.larande.p2": + "Fiecare diagnostic încheiat alimentează ghidarea ulterioară. Se folosesc doar rezultate verificate — un caz închis fără cauză rădăcină confirmată nu contribuie cu nimic, în mod intenționat.", + "webb.larande.p3": + "Baza de cunoștințe aparține organizației. Este derivată din cazurile, procedurile și documentația dumneavoastră și nu este agregată între clienți.", + "webb.larande.block": "Derivată din", + "webb.larande.k1": "Cauze rădăcină verificate", + "webb.larande.k2": "Acțiuni corective confirmate", + "webb.larande.k3": "Categorii de defecte recurente", + "webb.larande.k4": "Evidențe de finalizare a procedurilor", + "webb.larande.k5": "Documentația organizației", + + "webb.rapport.etikett": "Raportare", + "webb.rapport.rubrik": "Raport trimestrial de îmbunătățire", + "webb.rapport.ingress": + "O dată pe trimestru, platforma răspunde la șase întrebări despre munca atelierului însuși. Fiecare răspuns este derivat din jurnalul cazurilor — nimic nu se estimează și nimeni nu este întrebat nimic.", + "webb.rapport.f1": "Ce defecte diagnosticăm acum corect din prima?", + "webb.rapport.f2": "Cât de des o cauză bănuită se dovedește a fi cea reală?", + "webb.rapport.f3": "Ce defecte revin mereu în flotă?", + "webb.rapport.f4": "Ce pași de procedură sunt omiși, și de ce?", + "webb.rapport.f5": "Ce știe atelierul acum și nu știa trimestrul trecut?", + "webb.rapport.f6": "Cazurile sunt încheiate, verificate și documentate — sau doar încheiate?", + + "webb.pris.etikett": "Comercial", + "webb.pris.rubrik": "Licențiere", + "webb.pris.komponent": "Componentă", + "webb.pris.grund": "Bază", + "webb.pris.plattform": "Licență de platformă", + "webb.pris.plattform.grund": "Per organizație, anual", + "webb.pris.anvandare": "Licențe de utilizator", + "webb.pris.anvandare.grund": "Per utilizator activ, lunar", + "webb.pris.moduler": "Module pentru întreprinderi", + "webb.pris.moduler.grund": "Opționale, per modul", + "webb.pris.betalning": "Fără plată online. Fără înscriere la abonament. Facturarea urmează examinării cererii.", + "webb.pris.aktivering": "Secvența de activare", + "webb.pris.a1": "Organizația depune înregistrarea.", + "webb.pris.a2": "Cererea este examinată.", + "webb.pris.a3": "Se emite factura.", + "webb.pris.a4": "Plata este înregistrată.", + "webb.pris.a5": "Organizația este activată.", + "webb.pris.a6": "Utilizatorii suplimentari sunt facturați lunar.", + + "webb.kallor.etikett": "Infrastructură", + "webb.kallor.rubrik": "Infrastructură operațională de cunoștințe", + "webb.kallor.p1": + "ALVA folosește sursele de cunoștințe autorizate ale organizației. Arhitectura este neutră față de furnizor: fiecare sursă implementează aceeași interfață și niciun furnizor nu este presupus.", + "webb.kallor.p2": + "O instalare beta lucrează doar cu documentația internă. Furnizorii externi sunt activați mai târziu prin conectori separați, fără modificarea nucleului platformei.", + "webb.kallor.block": "Ordinea de rezolvare a surselor", + "webb.kallor.k1": "Proceduri interne ale firmei", + "webb.kallor.k2": "Documentație OEM", + "webb.kallor.k3": "Buletine tehnice", + "webb.kallor.k4": "Informații de garanție", + "webb.kallor.k5": "Manuale de atelier", + "webb.kallor.k6": "Diagnostice istorice verificate", + "webb.kallor.k7": "Bune practici ale organizației", + + "webb.login.etikett": "Acces", + "webb.login.demo": + "Această autentificare nu autentifică nimic. Scrieți orice în E-mail și Parolă și apăsați Conectare — nu este nevoie de cont, iar portalul din spate prezintă date de exemplu fixe. Conectată la o instanță a platformei, această pagină se autentifică față de ea, iar fără o sesiune validă portalul este închis.", + "webb.login.losenord": "Parolă", + "webb.login.ofullstandigt": "Autentificare incompletă. E-mailul și parola sunt obligatorii.", + "webb.login.misslyckades": "Autentificarea a eșuat.", + "webb.login.logga_in": "Conectare", + "webb.login.loggar_in": "Se conectează", + "webb.login.glomt": "Parolă uitată", + + "webb.ansokan.etikett": "Înregistrare", + "webb.ansokan.demo": + "Nu se trimite nimic. Trimiterea formularului nu salvează nimic, nu anunță pe nimeni și nu creează nicio cerere — în spate nu există încă niciun proces de preluare. Descrierea de mai jos arată cum ar urma să funcționeze înregistrarea, nu ce se întâmplă astăzi.", + "webb.ansokan.avsikt": "Funcționare prevăzută: cererile sunt examinate manual și în această etapă nu se percepe nicio plată.", + "webb.falt.foretag": "Firmă", + "webb.falt.orgnummer": "Număr de înregistrare", + "webb.falt.kontakt": "Persoană de contact", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Număr de tehnicieni", + "webb.falt.bransch": "Domeniu", + "webb.falt.land": "Țară", + "webb.falt.anvandare": "Număr estimat de utilizatori", + "webb.falt.noteringar": "Observații", + "webb.falt.kravs": "(obligatoriu)", + "webb.ansokan.skicka": "Trimiteți cererea", + "webb.ansokan.mottagen.etikett": "Cerere", + "webb.ansokan.mottagen": "Depusă", + "webb.ansokan.mottagen.demo": + "Nimic nu a fost transmis și nimeni nu a fost anunțat. Referința de mai jos a fost calculată în browserul dumneavoastră din ce ați scris și nu există nicăieri altundeva — va dispărea la închiderea acestei pagini.", + "webb.ansokan.status": "Stare", + "webb.ansokan.referens": "Referință", + "webb.ansokan.granskning": + "Cererile sunt examinate manual. La aprobare se emite o factură. Organizația este activată după înregistrarea plății.", + + "webb.sprak.etikett": "Localizare", + "webb.sprak.rubrik": "Limbi", + "webb.sprak.ingress": + "Engleza este limba implicită și limba sursă. Urmează nouă traduceri. Ce nu face platforma: nu pretinde că o interfață tradusă înseamnă o metodă tradusă.", + "webb.sprak.princip.etikett": "Principiu", + "webb.sprak.princip.rubrik": "Două feluri de text", + "webb.sprak.granssnitt.rubrik": "Text de interfață", + "webb.sprak.granssnitt.beteckning": "Revine în tăcere", + "webb.sprak.granssnitt.text": + "Etichete, butoane, stări. Finit, rar schimbat. Un șir englezesc care ajunge la un utilizator german este o iritare, nu un pericol — o traducere lipsă revine deci la engleză fără comentariu.", + "webb.sprak.metodik.rubrik": "Text de procedură", + "webb.sprak.metodik.beteckning": "Nu revine niciodată în tăcere", + "webb.sprak.metodik.text": + "Instrucțiuni pentru lucrul la un vehicul. Aici o traducere neverificată este mai rea decât una într-o limbă străină — pentru că engleza pare străină, în timp ce o traducere proastă pare o instrucțiune. Este afișat în engleză și marcat, cu indicarea limbii.", + "webb.sprak.invariant": + "Metoda în sine nu se traduce niciodată. Numele fazelor și cuvintele de stare sunt structura ALVA și se citesc identic în fiecare țară, astfel încât un auditor poate citi un dosar românesc și unul german fără să știe în ce limbă lucrează atelierul.", + "webb.sprak.tackning.etikett": "Acoperire", + "webb.sprak.tackning.rubrik": "Ce este tradus, și ce este verificat", + "webb.sprak.kolumn.sprak": "Limbă", + "webb.sprak.kolumn.granssnitt": "Interfață", + "webb.sprak.kolumn.granskat": "Metodă revizuită de un specialist", + "webb.sprak.granskat.ja": "Da", + "webb.sprak.granskat.nej": "Nu — textul de procedură afișat în engleză", + "webb.sprak.matt": + "Acoperirea interfeței se măsoară, nu se estimează: un test oprește compilarea dacă vreo cheie lipsește din vreo limbă. Starea de verificare este o afirmație despre oameni, nu despre fișiere, și se stabilește manual.", + "webb.sprak.kallsprak": + "Textul de procedură în engleză este o traducere a sursei suedeze și nu a fost încă citit de un specialist care practică meseria. I se aplică același standard ca celorlalte nouă limbi, iar aici se face despre el aceeași declarație în loc să fie exceptat în tăcere pentru că este sursa.", + "webb.sprak.bevis.etikett": "Verificare", + "webb.sprak.bevis.rubrik": "Frazele care opresc un caz", + "webb.sprak.bevis.ingress": + "Acestea sunt frazele care refuză unui tehnician închiderea unui caz. Un refuz pe care nimeni nu îl înțelege este un refuz fără ieșire — deci sunt frazele potrivite pentru a judeca o traducere.", + "webb.sprak.ej_granskad": "Nerevizuit", + "webb.sprak.granskad": "Revizuit", + "webb.sprak.granskad.text": + "Textul de procedură în limba {sprak} a fost revizuit de un specialist care practică meseria. Pașii și punctele de control sunt afișate peste tot în limba {sprak}.", + "webb.sprak.val.etikett": "Funcționare", + "webb.sprak.val.rubrik": "Cum se alege limba", + "webb.sprak.val.s1.rubrik": "Preferința utilizatorului", + "webb.sprak.val.s1.text": "Ce a selectat persoana.", + "webb.sprak.val.s2.rubrik": "Setarea organizației", + "webb.sprak.val.s2.text": "Limba de documentare a atelierului.", + "webb.sprak.val.s3.rubrik": "Limba browserului", + "webb.sprak.val.s3.text": "Prima limbă pe care platforma o recunoaște.", + "webb.sprak.val.s4.rubrik": "Engleza", + "webb.sprak.val.s4.text": "Limba implicită — și limba sursă.", + "webb.sprak.val.notering": + "Organizația trece intenționat înaintea browserului. Un atelier din Germania cu un tehnician polonez are nevoie de o singură limbă de documentare — dosarul nu trebuie să schimbe limba în funcție de cine a scris rândul.", +}; diff --git a/felsokning/app/services/gemensam/sprak/sv.mjs b/felsokning/app/services/gemensam/sprak/sv.mjs new file mode 100644 index 0000000..5630139 --- /dev/null +++ b/felsokning/app/services/gemensam/sprak/sv.mjs @@ -0,0 +1,325 @@ +// ALVA-SPEC-060 · Svenska. +// +// Hemmamarknadens språk och det enda utöver engelska där metodik- +// innehållet är läst av någon som kan yrket. Därför `granskat: true` i +// SPRAK — inte för att svenskan är bättre skriven än tyskan här, utan +// för att den är kontrollerad av en fackman och tyskan inte är det. +// +// Fas- och statusord saknas medvetet. De är ALVA:s struktur och skrivs +// likadant på varje språk (ALVA-SPEC-001 §2). Se index.mjs. + +export const SV = { + // ---- Evidensgrader ------------------------------------------------------ + "evidens.E1": "Observation", + "evidens.E2": "Fotografi", + "evidens.E3": "Video med ljud", + "evidens.E4": "Mätning, kalibrerat mätdon", + "evidens.E5": "Dokument", + "evidens.ej_kalibrerad": "kalibrering saknas eller har gått ut", + "evidens.ej_angivet": "mätdon ej angivet", + "evidens.ej_fotograferad": "inmatad, ej fotograferad", + + // ---- Kvalitetsgrinden --------------------------------------------------- + "grind.objekt": "Fordonets eller objektets identitet verifierad", + "grind.historik": "Fordonets historik kontrollerad, eller den uteblivna kontrollen motiverad", + "grind.historik.nekad": "En nekad historikkontroll kräver ett angivet skäl.", + "grind.historik.saknas": "Ingen historikkontroll dokumenterad.", + "grind.matarstallning.ingaende": "Ingående mätarställning fotograferad", + "grind.matarstallning.utgaende": "Utgående mätarställning fotograferad", + "grind.matarstallning.saknas": "Ingen mätarställning dokumenterad.", + "grind.matarstallning.ej_foto": + "Mätarställningen är inmatad men inte fotograferad. Fotografera mätaren, eller ange varför det inte är möjligt.", + "grind.reproducering": "Symtomverifiering: reproducerat, eller dokumenterat som ej reproducerbart", + "grind.felorsak": "Grundorsaksanalys dokumenterad", + "grind.atgard": "Åtgärd dokumenterad, eller den uteblivna åtgärden motiverad", + "grind.atgard.saknas": "Varken utförd åtgärd eller skäl till att den uteblivit är dokumenterat.", + "grind.kundbeslut": "Kundens beslut om förslaget registrerat", + "grind.kundbeslut.avbojt": "Arbete utfört trots avböjt förslag", + "grind.kundbeslut.avbojt.detalj": "Kunden avböjde förslaget, men arbete är dokumenterat som utfört.", + "grind.kvalitetskontroll": "Kvalitetskontroll utförd — symtomet verifierat avhjälpt", + "grind.kontroller": "Metodikens kontrollpunkter: evidens, eller dokumenterat undantag", + "grind.foton": "Foton finns för de kontrollpunkter som kräver det", + "grind.slutsats": "Slutsats (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Högvoltsbehörighet bekräftad", + "grind.hogvolt.spanningslos": "Fordonet spänningslöst enligt tillverkarens procedur, spänningsfrihet verifierad genom mätning", + "grind.regelpaket": "Regelpaketets signatur stämmer inte — avslut spärrat.", + "grind.regelpaket.osignerat": "Ett externt regelpaket används utan signatur — avslut spärrat.", + "grind.evidens": "Evidensnivå över E0", + "grind.evidens.saknas": "Ingen evidens av något slag finns i loggen.", + "grind.foton.detalj": "{kontroller} kontrollpunkter kräver foto, {foton} foton i loggen.", + "grind.sparr.ej_uppfyllt": "Säkerhetskravet är inte uppfyllt.", + "grind.arendetyp.okant": "Okänt krav i regelpaketet: {krav}", + "grind.arendetyp.krav": "Krav för ärendetypen: {krav}", + "grind.sakerhet": "Konfidensnivån inom vad underlaget bär", + "grind.sakerhet.detalj": "Angiven konfidens ({niva}) överstiger vad underlaget bär ({tak}). Komplettera underlaget, eller sänk nivån — ärlig osäkerhet är information.", + "grind.eskalering": "Tekniska eskaleringar besvarade", + "grind.eskalering.detalj": "En eskalering är öppen ({referens}). Invänta svaret och dokumentera det innan ärendet avslutas.", + + + // ---- Slutsatsen (ALVA-RULE-200) ----------------------------------------- + "slutsats.rubrik": "Slutsats", + "slutsats.konstaterat": "Vad som är konstaterat", + "slutsats.evidens": "Vilken evidens som bär det", + "slutsats.avfardat": "Vilka hypoteser som avfärdats, och varför", + "slutsats.osakert": "Vad som fortfarande är osäkert", + "slutsats.ickesvar": "Det är inte ett svar. Ange vad som är konstaterat och vilken evidens som bär det.", + "slutsats.falt.motivering": "Motivering", + "slutsats.falt.motivering_ej": "Skäl till att orsaken inte fastställts", + "slutsats.falt.uteslutet": "Uteslutna alternativ", + "slutsats.falt.kvarstaende": "Kvarstående osäkerhet", + "slutsats.falt.atgardsval": "Val av åtgärd", + "slutsats.saknas": "{falt} saknas.", + "slutsats.ickesvar.falt": "{falt}: ”{text}” är inget skäl. Beskriv vad som faktiskt gäller och varför.", + "slutsats.for_kort": "{falt} är för kort ({langd} av minst {minsta} tecken) för att gå att granska i efterhand.", + "slutsats.utan_varfor": "{falt} anger vad, men inte varför. Knyt slutsatsen till evidensen — vad i den gör att det här följer?", + "slutsats.utan_slutsats": "Ärendet kan inte avslutas utan en slutsats. Motivera varför slutsatsen följer av underlaget.", + "slutsats.hypotes_obemott": "Hypotesen ”{text}” finns i loggen men bemöts inte. Ange varför den avfärdades, eller varför den kvarstår.", + + + // ---- Ärendeflödet ------------------------------------------------------- + "arende.nytt": "Nytt ärende", + "arende.oppna": "Öppna ärenden", + "arende.avslutade": "Avslutade ärenden", + "arende.avsluta": "Avsluta ärendet", + "arende.avslutat": "Ärendet avslutat", + "arende.kan_ej_avslutas": "Ärendet kan ännu inte avslutas", + "arende.hinder": "Kvarstår före avslut", + "arende.overlamna": "Lämna över", + "arende.ansvarig": "Ansvarig", + + // ---- Förkontroll -------------------------------------------------------- + "pre.rubrik": "Förkontroll — innan arbetet börjar", + "pre.historik.fraga": "Är fordonets historik kontrollerad? (tidigare arbeten, återkommande fel, TSB:er, kampanjer)", + "pre.historik.ja": "Ja — kontrollerad", + "pre.historik.nej": "Nej", + "pre.historik.skal": "Skäl till att historiken inte kontrollerats (obligatoriskt)", + "pre.historik.relevant": "Relevanta tidigare arbeten (valfritt — orsakskedja)", + "pre.matarstallning": "Mätarställning", + "pre.fotografera": "Fotografera instrumentpanelen", + "pre.felbeskrivning": "Kundens felbeskrivning verifierad", + "pre.observationer": "Något annat vid mottagandet?", + + // ---- Handlingar --------------------------------------------------------- + "handling.spara": "Spara", + "handling.avbryt": "Avbryt", + "handling.fortsatt": "Fortsätt", + "handling.tillbaka": "Tillbaka", + "handling.dokumentera": "Dokumentera", + "handling.fotografera": "Fotografera", + "handling.spela_in": "Spela in video", + "handling.undantag": "Dokumentera ett undantag", + "handling.undantag.skal": "Skäl till undantaget (obligatoriskt)", + "handling.exportera": "Exportera", + "handling.skriv_ut": "Skriv ut", + + // ---- Mätning ------------------------------------------------------------ + "matning.varde": "Värde", + "matning.enhet": "Enhet", + "matning.matdon": "Mätdon", + "matning.matdon.valj": "Välj mätdon", + "matning.matdon.okant": "Okänt mätdon. Registrera det innan mätningen sparas.", + "matning.kalibrerad_till": "Kalibrerad t.o.m.", + + // ---- Rapport ------------------------------------------------------------ + "rapport.rubrik": "Ärenderapport", + "rapport.sammanfattning": "Sammanfattning", + "rapport.evidens": "Evidens", + "rapport.atgarder": "Åtgärder", + "rapport.harledd": + "Härledd ur ärendeloggen. Observationer och mätningar redovisas utan slutsatser som saknar stöd.", + + // ---- Språk -------------------------------------------------------------- + "sprak.valj": "Språk", + "sprak.granskat": "Granskat", + "sprak.ogranskat": "Ej granskat", + "sprak.tackning": "{procent} % av gränssnittet", + + "metodik.ogranskad": + "Metodiktexten är inte granskad av en fackman på {sprak}. Steg och kontrollpunkter visas på engelska där ingen granskad översättning finns — en ogranskad översättning av en säkerhetsinstruktion är sämre än en på främmande språk, därför att den inte ser främmande ut.", + "metodik.pa_engelska": "Visas på engelska — ingen granskad översättning på {sprak}", + + // ---- Publika webbplatsen (webb.) --------------------------------------- + "webb.nav.oversikt": "Översikt", + "webb.ansok": "Ansök om konto", + "webb.loggain": "Logga in", + "webb.fot.impressum": "Impressum", + "webb.fot.dataskydd": "Dataskydd", + "webb.fot.villkor": "Villkor", + "webb.fot.tillganglighet": "Tillgänglighet", + "webb.fot.sprak": "Språk", + "webb.fot.utgavor": "Versionsinformation", + + "webb.hero.position": "Guidad diagnostikplattform", + "webb.hero.definition": "Standardiserade diagnosprocedurer för repeterbar och verifierbar felsökning.", + + "webb.metod.etikett": "Metodik", + "webb.metod.rubrik": "ALVA-modellen", + "webb.metod.ingress": + "ALVA är en standardiserad metod för systematisk analys, lokalisering, verifiering och åtgärd. Varje beslut är spårbart, varje slutsats verifierbar, varje åtgärd reproducerbar.", + "webb.fas.analysis.syfte": "Samla evidens.", + "webb.fas.analysis.avgransning": "Enbart fakta. Hypoteser samlas inte i denna fas.", + "webb.fas.localization.syfte": "Ringa in felet.", + "webb.fas.localization.avgransning": "Avgränsa området. Orsaken är ännu inte fastställd.", + "webb.fas.verification.syfte": "Bekräfta grundorsaken.", + "webb.fas.verification.avgransning": "Orsaken verifieras — inte symtomet.", + "webb.fas.action.syfte": "Utför åtgärden.", + "webb.fas.action.avgransning": "Resultatet verifierat och dokumenterat. Annars är fasen inte avslutad.", + + "webb.drift.etikett": "Drift", + "webb.drift.rubrik": "Så fungerar det", + "webb.drift.s1.rubrik": "Skapa organisation", + "webb.drift.s1.text": "Registreringen skickas in och granskas.", + "webb.drift.s2.rubrik": "Användarna får konton", + "webb.drift.s2.text": "Roller tilldelas av organisationens administratör.", + "webb.drift.s3.rubrik": "Guidade diagnosprocedurer", + "webb.drift.s3.text": "Varje ärende följer en definierad procedur.", + "webb.drift.s4.rubrik": "Verifiering", + "webb.drift.s4.text": "Grundorsaken bekräftas före åtgärd.", + "webb.drift.s5.rubrik": "Dokumentation", + "webb.drift.s5.text": "Rapporten genereras ur ärendeloggen.", + "webb.drift.s6.rubrik": "Ständig förbättring", + "webb.drift.s6.text": "Verifierade utfall förfinar kommande procedurer.", + + "webb.larande.etikett": "Förmåga", + "webb.larande.rubrik": "Organisationens lärande", + "webb.larande.p1": + "Plattformen förfinar löpande diagnosprocedurerna med verifierad drifterfarenhet från den egna organisationen.", + "webb.larande.p2": + "Varje slutförd diagnos bidrar till kommande vägledning. Endast verifierade utfall används — ett ärende som stängts utan bekräftad grundorsak bidrar med ingenting, avsiktligt.", + "webb.larande.p3": + "Kunskapsbasen tillhör organisationen. Den härleds ur era ärenden, era procedurer och er dokumentation, och den aggregeras inte över kunder.", + "webb.larande.block": "Härledd ur", + "webb.larande.k1": "Verifierade grundorsaker", + "webb.larande.k2": "Bekräftade åtgärder", + "webb.larande.k3": "Återkommande felkategorier", + "webb.larande.k4": "Genomförda procedurer", + "webb.larande.k5": "Organisationens dokumentation", + + "webb.rapport.etikett": "Rapportering", + "webb.rapport.rubrik": "Kvartalsvis förbättringsrapport", + "webb.rapport.ingress": + "En gång i kvartalet besvarar plattformen sex frågor om verkstadens eget arbete. Varje svar härleds ur ärendeloggen — ingenting uppskattas, och ingen tillfrågas.", + "webb.rapport.f1": "Vilka fel diagnostiserar vi nu rätt på första försöket?", + "webb.rapport.f2": "Hur ofta visar sig en misstänkt orsak vara den verkliga?", + "webb.rapport.f3": "Vilka fel återkommer i flottan?", + "webb.rapport.f4": "Vilka procedursteg hoppas över, och varför?", + "webb.rapport.f5": "Vad vet verkstaden nu som den inte visste förra kvartalet?", + "webb.rapport.f6": "Avslutas, verifieras och dokumenteras ärendena — eller avslutas de bara?", + + "webb.pris.etikett": "Kommersiellt", + "webb.pris.rubrik": "Licensiering", + "webb.pris.komponent": "Komponent", + "webb.pris.grund": "Grund", + "webb.pris.plattform": "Plattformslicens", + "webb.pris.plattform.grund": "Per organisation, årsvis", + "webb.pris.anvandare": "Användarlicenser", + "webb.pris.anvandare.grund": "Per aktiv användare, månadsvis", + "webb.pris.moduler": "Företagsmoduler", + "webb.pris.moduler.grund": "Valfria, per modul", + "webb.pris.betalning": "Ingen onlinebetalning. Ingen prenumerationsregistrering. Fakturering följer på granskning av ansökan.", + "webb.pris.aktivering": "Aktiveringssekvens", + "webb.pris.a1": "Organisationen skickar in registreringen.", + "webb.pris.a2": "Ansökan granskas.", + "webb.pris.a3": "Faktura ställs ut.", + "webb.pris.a4": "Betalningen registreras.", + "webb.pris.a5": "Organisationen aktiveras.", + "webb.pris.a6": "Ytterligare användare faktureras månadsvis.", + + "webb.kallor.etikett": "Infrastruktur", + "webb.kallor.rubrik": "Operativ kunskapsinfrastruktur", + "webb.kallor.p1": + "ALVA använder organisationens egna auktoriserade kunskapskällor. Arkitekturen är leverantörsneutral: varje källa implementerar samma gränssnitt, och ingen leverantör förutsätts.", + "webb.kallor.p2": + "En betainstallation arbetar enbart mot intern dokumentation. Externa leverantörer aktiveras senare via separata anslutningar, utan ändring i plattformskärnan.", + "webb.kallor.block": "Källornas prioritetsordning", + "webb.kallor.k1": "Interna företagsprocedurer", + "webb.kallor.k2": "OEM-dokumentation", + "webb.kallor.k3": "Tekniska bulletiner", + "webb.kallor.k4": "Garantiinformation", + "webb.kallor.k5": "Verkstadshandböcker", + "webb.kallor.k6": "Historiska verifierade diagnoser", + "webb.kallor.k7": "Organisationens bästa praxis", + + "webb.login.etikett": "Åtkomst", + "webb.login.demo": + "Den här inloggningen autentiserar ingenting. Skriv vad som helst i E-post och Lösenord och tryck Logga in — inget konto behövs, och portalen bakom visar fasta exempeldata. Ansluten till en plattformsinstans autentiserar sidan mot den, och portalen är stängd utan giltig session.", + "webb.login.losenord": "Lösenord", + "webb.login.ofullstandigt": "Autentisering ofullständig. E-post och lösenord krävs.", + "webb.login.misslyckades": "Autentisering misslyckades.", + "webb.login.logga_in": "Logga in", + "webb.login.loggar_in": "Loggar in", + "webb.login.glomt": "Glömt lösenord", + + "webb.ansokan.etikett": "Registrering", + "webb.ansokan.demo": + "Ingenting skickas. Att skicka formuläret lagrar ingenting, meddelar ingen och skapar ingen ansökan — det finns ännu inget mottagande system bakom det. Beskrivningen nedan anger hur registreringen är avsedd att fungera, inte vad som sker i dag.", + "webb.ansokan.avsikt": "Avsedd drift: ansökningar granskas manuellt och ingen betalning tas i detta skede.", + "webb.falt.foretag": "Företag", + "webb.falt.orgnummer": "Organisationsnummer", + "webb.falt.kontakt": "Kontaktperson", + "webb.falt.epost": "E-post", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Antal tekniker", + "webb.falt.bransch": "Bransch", + "webb.falt.land": "Land", + "webb.falt.anvandare": "Förväntat antal användare", + "webb.falt.noteringar": "Noteringar", + "webb.falt.kravs": "(obligatoriskt)", + "webb.ansokan.skicka": "Skicka ansökan", + "webb.ansokan.mottagen.etikett": "Ansökan", + "webb.ansokan.mottagen": "Inskickad", + "webb.ansokan.mottagen.demo": + "Ingenting skickades och ingen underrättades. Referensen nedan beräknades i er webbläsare ur det ni skrev och finns ingen annanstans — den är borta när ni stänger sidan.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Referens", + "webb.ansokan.granskning": + "Ansökningar granskas manuellt. Faktura ställs ut vid godkännande. Organisationen aktiveras när betalningen registrerats.", + + "webb.sprak.etikett": "Lokalisering", + "webb.sprak.rubrik": "Språk", + "webb.sprak.ingress": + "Engelska är standard och källspråk. Nio översättningar följer. Vad plattformen inte gör är att påstå att ett översatt gränssnitt betyder en översatt metod.", + "webb.sprak.princip.etikett": "Princip", + "webb.sprak.princip.rubrik": "Två sorters text", + "webb.sprak.granssnitt.rubrik": "Gränssnittstext", + "webb.sprak.granssnitt.beteckning": "Faller tillbaka tyst", + "webb.sprak.granssnitt.text": + "Etiketter, knappar, statusar. Ändlig, sällan ändrad. En engelsk sträng som når en tysk användare är en irritation, inte en fara — så en saknad översättning faller tillbaka på engelska utan kommentar.", + "webb.sprak.metodik.rubrik": "Metodiktext", + "webb.sprak.metodik.beteckning": "Faller aldrig tillbaka tyst", + "webb.sprak.metodik.text": + "Instruktioner för arbete på ett fordon. Här är en ogranskad översättning sämre än en på främmande språk — därför att engelska ser främmande ut, medan en dålig översättning ser ut som en instruktion. Den visas på engelska och markeras, med språket namngivet.", + "webb.sprak.invariant": + "Metoden själv översätts aldrig. Fasnamn och statusord är ALVA:s struktur och läses likadant i varje land, så att en revisor kan läsa en rumänsk och en tysk ärendejournal utan att veta vilket språk verkstaden arbetar på.", + "webb.sprak.tackning.etikett": "Täckning", + "webb.sprak.tackning.rubrik": "Vad som är översatt, och vad som är granskat", + "webb.sprak.kolumn.sprak": "Språk", + "webb.sprak.kolumn.granssnitt": "Gränssnitt", + "webb.sprak.kolumn.granskat": "Metodik granskad av fackman", + "webb.sprak.granskat.ja": "Ja", + "webb.sprak.granskat.nej": "Nej — metodiktext visas på engelska", + "webb.sprak.matt": + "Gränssnittstäckningen mäts, inte uppskattas: ett test fäller bygget om någon nyckel saknas i något språk. Granskningsstatus är ett påstående om människor, inte om filer, och sätts för hand.", + "webb.sprak.kallsprak": + "Den engelska metodiktexten är en översättning av den svenska källan och har ännu inte lästs av en fackman i yrket. Den hålls till samma standard som de andra nio språken, och samma besked lämnas om den här i stället för att tyst undantas för att den är källan.", + "webb.sprak.bevis.etikett": "Verifiering", + "webb.sprak.bevis.rubrik": "Strängarna som stoppar ett ärende", + "webb.sprak.bevis.ingress": + "Detta är meningarna som nekar en tekniker att avsluta ett ärende. Ett avslag ingen förstår är ett avslag utan väg igenom — så det är rätt strängar att bedöma en översättning på.", + "webb.sprak.ej_granskad": "Ej granskad", + "webb.sprak.granskad": "Granskad", + "webb.sprak.granskad.text": + "Metodiktext på {sprak} har lästs av en fackman i yrket. Steg och kontrollpunkter visas på {sprak} genomgående.", + "webb.sprak.val.etikett": "Drift", + "webb.sprak.val.rubrik": "Hur språket väljs", + "webb.sprak.val.s1.rubrik": "Användarens val", + "webb.sprak.val.s1.text": "Vad den enskilda har valt.", + "webb.sprak.val.s2.rubrik": "Organisationens inställning", + "webb.sprak.val.s2.text": "Verkstadens dokumentationsspråk.", + "webb.sprak.val.s3.rubrik": "Webbläsarens språk", + "webb.sprak.val.s3.text": "Det första språk plattformen känner igen.", + "webb.sprak.val.s4.rubrik": "Engelska", + "webb.sprak.val.s4.text": "Standarden, och källan.", + "webb.sprak.val.notering": + "Organisationen går före webbläsaren avsiktligt. En verkstad i Tyskland med en polsk tekniker behöver ett gemensamt dokumentationsspråk — journalen får inte byta språk beroende på vem som råkade skriva raden.", +}; diff --git a/felsokning/app/services/gemensam/statistik.mjs b/felsokning/app/services/gemensam/statistik.mjs new file mode 100644 index 0000000..ac31139 --- /dev/null +++ b/felsokning/app/services/gemensam/statistik.mjs @@ -0,0 +1,208 @@ +// ALVA-REP-0100 · Operational metrics. +// +// Vilka siffror som visas avgör vad organisationen optimerar. Det är +// därför det här är ett produktbeslut och inte ett rapportjobb. +// +// Uteslutna med avsikt: +// +// Ärenden per tekniker Belönar snabbhet över noggrannhet. Den som +// hoppar över kontroller vinner. +// Genomsnittlig ledtid Samma sak, en gång till. Ett svårt fel +// SKA ta längre tid. +// Inloggningar, aktivitet Mäter användning, inte nytta. +// +// Medtagna, och varför var och en är handlingsbar: +// +// VERIFIERINGSGRAD Andel avslut med fastställd orsak. Sjunker den +// gissar organisationen mer. Det här är det enda +// måttet ett försäkringsbolag egentligen bryr sig +// om. +// REPRODUKTIONSGRAD Andel symptom som återskapats. Låg siffra +// förutsäger återkommande fordon — man kan inte +// åtgärda det man inte sett. +// OMARBETNING Samma fordon tillbaka med samma orsakskategori. +// Det dyraste felet i en verkstad, och det enda +// ingen mäter. +// UNDANTAGSFREKVENS Vilka kontroller hoppas över, per steg. Ett +// steg med hög frekvens är antingen fel skrivet +// eller kräver utrustning som saknas — bägge är +// åtgärdbara, ingetdera syns i dag. +// EVIDENSPROFIL Fördelning E0–E6. Visar om underlaget vilar på +// mätningar eller på observationer. +// FASFÖRDELNING Var tiden går i ALVA-termer. +// +// Alla funktioner är rena över (ärenden). Ingen databasåtkomst, ingen +// klocka — samma underlag ger samma siffra, vilket är förutsättningen +// för att en kvartalsrapport ska gå att granska i efterhand. + +import { fasFor } from "./faser.mjs"; + +const handelserI = (arende) => (arende.handelser ?? []).map((p) => p.handelse ?? p); +const av = (handelser, typ) => handelser.filter((h) => h?.typ === typ); +const arAvslutat = (arende) => handelserI(arende).some((h) => h?.typ === "arende_avslutat"); + +/** Andel med en decimal, eller null när underlaget saknas. */ +function andel(traffar, av_) { + return av_ === 0 ? null : Math.round((traffar / av_) * 1000) / 10; +} + +/** + * Verifieringsgrad — andel avslutade ärenden där orsaken fastställdes. + * + * Att den inte är 100 % är friskt. En organisation som aldrig skriver + * "orsaken kunde inte fastställas" ljuger antingen, eller får bara enkla + * fel. Talet är intressant över tid, inte som absolut nivå. + */ +export function verifieringsgrad(arenden) { + const avslutade = arenden.filter(arAvslutat); + const fastställda = avslutade.filter((a) => { + const s = av(handelserI(a), "slutsats").at(-1); + return s && s.orsakFastställd !== false; + }); + return { antal: avslutade.length, fastställda: fastställda.length, andel: andel(fastställda.length, avslutade.length) }; +} + +/** Reproduktionsgrad — andel ärenden där symptomet faktiskt återskapades. */ +export function reproduktionsgrad(arenden) { + const medSvar = arenden.filter((a) => av(handelserI(a), "reproducering").length > 0); + const fördelning = { ja: 0, delvis: 0, nej: 0 }; + for (const a of medSvar) { + const r = av(handelserI(a), "reproducering").at(-1); + if (r?.status in fördelning) fördelning[r.status] += 1; + } + return { antal: medSvar.length, ...fördelning, andel: andel(fördelning.ja, medSvar.length) }; +} + +/** + * Omarbetning — samma fordon tillbaka med samma orsakskategori. + * + * Det dyraste felet i en verkstad och det enda ingen mäter, eftersom det + * kräver att man kopplar ihop två ärenden som ser fristående ut. Här är + * kopplingen gratis: loggen bär fordonets identifierare och orsakens + * kategori. + * + * @param inomDagar fönster; 90 dagar är en rimlig utgångspunkt men + * varje verkstad har sin egen uppfattning. + */ +export function omarbetning(arenden, inomDagar = 90) { + const perFordon = new Map(); + for (const a of arenden) { + if (!arAvslutat(a)) continue; + const ident = av(handelserI(a), "objekt_identifierat").at(-1)?.objekt?.identifierare; + if (!ident) continue; + const orsak = av(handelserI(a), "felorsak").at(-1); + const avslut = (a.handelser ?? []).find((p) => (p.handelse ?? p)?.typ === "arende_avslutat")?.tidpunkt; + if (!orsak || !avslut) continue; + if (!perFordon.has(ident)) perFordon.set(ident, []); + perFordon.get(ident).push({ tid: Date.parse(avslut), kategorier: orsak.orsaker ?? [], arende: a.id }); + } + + const fall = []; + const fönster = inomDagar * 86400000; + for (const [ident, poster] of perFordon) { + poster.sort((a, b) => a.tid - b.tid); + for (let i = 1; i < poster.length; i += 1) { + const föregående = poster[i - 1]; + const nu = poster[i]; + const delad = nu.kategorier.filter((k) => föregående.kategorier.includes(k)); + if (delad.length > 0 && nu.tid - föregående.tid <= fönster) { + fall.push({ identifierare: ident, kategorier: delad, dagar: Math.round((nu.tid - föregående.tid) / 86400000) }); + } + } + } + const avslutade = arenden.filter(arAvslutat).length; + return { antal: fall.length, andel: andel(fall.length, avslutade), fall: fall.slice(0, 20) }; +} + +/** + * Undantagsfrekvens per metodiksteg. + * + * Ett steg som ofta hoppas över är antingen felskrivet eller kräver + * utrustning verkstaden inte har. Bägge går att åtgärda — men bara om + * man vet vilket steg det gäller, vilket ingen gör i dag. + */ +export function undantagsfrekvens(arenden) { + const per = new Map(); + for (const a of arenden) { + for (const h of av(handelserI(a), "kontroll_utford")) { + const nyckel = `${h.stegId}/${h.kontrollId}`; + if (!per.has(nyckel)) per.set(nyckel, { steg: h.stegId, kontroll: h.kontrollId, text: h.text, utforda: 0, undantag: 0 }); + const rad = per.get(nyckel); + rad.utforda += 1; + if ((h.undantag ?? "").trim()) rad.undantag += 1; + } + } + return [...per.values()] + .map((r) => ({ ...r, fas: fasFor(r.steg), andel: andel(r.undantag, r.utforda) })) + .filter((r) => r.undantag > 0) + .sort((a, b) => b.andel - a.andel || b.undantag - a.undantag); +} + +/** Evidensprofil — fördelning av starkaste evidenstyp per ärende. */ +export function evidensprofil(arenden) { + const nivå = { E0: 0, E1: 0, E2: 0, E3: 0, E4: 0, E5: 0, E6: 0 }; + for (const a of arenden) { + const h = handelserI(a); + const har = { + E5: av(h, "arbetsorder_skannad").length > 0, + // Ett mätvärde räknas som E4 bara med spårbart mätdon; utan det är + // det teknikerns observation av en siffra (QUALITY-AUDIT M-1). + E4: av(h, "matvarde").some((m) => m.matdonId), + E3: av(h, "video").length > 0, + E2: av(h, "foto").length > 0, + E1: av(h, "observation").length + av(h, "kontroll_utford").length > 0, + }; + const källor = ["E2", "E3", "E4", "E5"].filter((n) => har[n]).length; + const högsta = källor >= 2 ? "E6" : (["E5", "E4", "E3", "E2", "E1"].find((n) => har[n]) ?? "E0"); + nivå[högsta] += 1; + } + return nivå; +} + +/** Fasfördelning — var tiden går, i ALVA-termer. */ +export function fasfordelning(arenden) { + const per = { analysis: 0, localization: 0, verification: 0, action: 0 }; + for (const a of arenden) { + for (const h of handelserI(a)) { + if (h?.stegId) per[fasFor(h.stegId)] += 1; + } + } + const summa = Object.values(per).reduce((s, v) => s + v, 0); + return Object.fromEntries( + Object.entries(per).map(([fas, antal]) => [fas, { antal, andel: andel(antal, summa) }]), + ); +} + +/** Orsakskategorier över flottan. */ +export function orsakskategorier(arenden) { + const per = new Map(); + for (const a of arenden) { + for (const h of av(handelserI(a), "felorsak")) { + for (const k of h.orsaker ?? []) per.set(k, (per.get(k) ?? 0) + 1); + } + } + return [...per.entries()] + .map(([kategori, antal]) => ({ kategori, antal })) + .sort((a, b) => b.antal - a.antal); +} + +/** + * Hela underlaget för portalens analysvy och kvartalsrapporten. + * + * En enda funktion, så att skärmen och rapporten aldrig kan visa olika + * siffror för samma period — det är den vanligaste orsaken till att + * ingen litar på rapporten. + */ +export function oversikt(arenden, { omarbetningsfonster = 90 } = {}) { + return { + version: "ALVA-REP-0100", + antal: { totalt: arenden.length, avslutade: arenden.filter(arAvslutat).length }, + verifiering: verifieringsgrad(arenden), + reproduktion: reproduktionsgrad(arenden), + omarbetning: omarbetning(arenden, omarbetningsfonster), + undantag: undantagsfrekvens(arenden).slice(0, 10), + evidens: evidensprofil(arenden), + faser: fasfordelning(arenden), + orsaker: orsakskategorier(arenden).slice(0, 12), + }; +} diff --git a/felsokning/app/services/gemensam/support.mjs b/felsokning/app/services/gemensam/support.mjs new file mode 100644 index 0000000..36fcd31 --- /dev/null +++ b/felsokning/app/services/gemensam/support.mjs @@ -0,0 +1,108 @@ +// ALVA-PROC-0050 · Felanmälan och supportärenden. +// +// ---- Varför anmälan sitter i ärendet ------------------------------------ +// +// En felanmälan som skrivs någon annanstans än där felet uppstod tappar +// det som gör den användbar. Teknikern minns inte ärendenumret, vet inte +// vilken metodik som kördes, och kan inte plattformsversionen — så +// anmälan blir "det funkar inte" och supporten får börja med att fråga +// tillbaka. Två dagar går innan någon vet vad som hände. +// +// Sammanhanget HÄRLEDS därför i stället för att skrivas in: ärende, +// metodik, plattformsversion, spår-id och vilket steg som var öppet. Det +// är samma hållning som mot sammanfattningen och fakturan — det som går +// att härleda ska inte skrivas av. +// +// ---- Status är en projektion -------------------------------------------- +// +// Anmälan är oföränderlig. Det som händer med den — svar, statusbyten — +// är egna poster, och statusen härleds ur dem. Samma skäl som för +// fakturan: en anmälan vars historia kan skrivas om är inte ett underlag +// när någon senare frågar hur länge felet var känt. + +/** ALVA-SUP-0001 */ +export const supportbeteckning = (nummer) => `ALVA-SUP-${String(nummer).padStart(4, "0")}`; + +/** + * Vad anmälan gäller. + * + * Skilt åt därför att de har olika brådska och olika mottagare. En + * felanmälan om systemet mitt i ett ärende blockerar arbete just nu; + * ett förbättringsförslag gör det inte, och att blanda dem gör att + * bägge behandlas som det senare. + */ +export const TYPER = [ + { id: "felanmalan", rubrik: "Fault report", text: "The platform did something wrong, or stopped working." }, + { id: "fraga", rubrik: "Question", text: "The methodology or the rules are unclear in this case." }, + { id: "forbattring", rubrik: "Improvement", text: "Something works, but could work better." }, +]; + +/** + * Tillstånden. Ordningen är enkelriktad — utom att en stängd anmälan kan + * öppnas igen, vilket händer när felet visar sig inte vara borta. + */ +export const STATUSAR = ["mottagen", "under_arbete", "atgardad", "stangd"]; + +/** + * Statusen härleds ur inläggen, den lagras inte. + * + * Senaste statusposten gäller. Saknas statusposter är anmälan mottagen — + * det är vad den är i det ögonblick den skapas, och ingenting behöver + * skrivas för att det ska bli sant. + */ +export function supportstatus(inlagg = []) { + const senaste = [...inlagg].filter((i) => i?.typ === "status" && STATUSAR.includes(i.status)).pop(); + return senaste?.status ?? "mottagen"; +} + +/** + * Granskar en anmälan innan den tas emot. + * + * Kraven är låga med flit. En tröskel som gör det jobbigt att anmäla ger + * färre anmälningar, inte färre fel — och de fel som inte anmäls är de + * som lever längst. Men en rubrik som "fel" och en tom beskrivning gör + * anmälan obrukbar för den som ska svara, så det finns ett golv. + */ +export function granskaAnmalan({ typ, rubrik, beskrivning } = {}) { + if (!TYPER.some((t) => t.id === typ)) return "Ange vad anmälan gäller."; + if (typeof rubrik !== "string" || rubrik.trim().length < 5) { + return "Rubriken behöver säga vad det gäller — minst fem tecken."; + } + if (rubrik.trim().length > 200) return "Rubriken är för lång (max 200 tecken)."; + if (typeof beskrivning !== "string" || beskrivning.trim().length < 20) { + return "Beskriv vad som hände och vad du väntade dig — minst tjugo tecken. Den som ska svara ser inte din skärm."; + } + if (beskrivning.length > 20_000) return "Beskrivningen är för lång (max 20 000 tecken)."; + return null; +} + +/** + * Sammanhanget, härlett ur det som redan finns. + * + * Fälten är medvetet få och tekniska. Ingenting identifierande om + * fordonet eller kunden följer med: en supportanmälan är inte ett skäl + * att flytta personuppgifter till ett annat system, och den som svarar + * behöver dem inte för att läsa ett spår. + */ +export function sammanhang({ arendeId, metodikId, steg, plattformsversion, sparId, klient } = {}) { + const ut = {}; + if (arendeId) ut.arende = String(arendeId); + if (metodikId) ut.metodik = String(metodikId); + if (steg) ut.steg = String(steg); + if (plattformsversion) ut.plattform = String(plattformsversion); + if (sparId) ut.spar = String(sparId); + if (klient) ut.klient = String(klient).slice(0, 200); + return ut; +} + +/** Fälten som ALDRIG får följa med en anmälan. Låst av test. */ +export const FORBJUDET_I_SAMMANHANG = [ + "identifierare", + "regnr", + "vin", + "agare", + "kund", + "personnummer", + "telefon", + "epost", +]; diff --git a/felsokning/app/services/gemensam/version.mjs b/felsokning/app/services/gemensam/version.mjs new file mode 100644 index 0000000..94f8bdd --- /dev/null +++ b/felsokning/app/services/gemensam/version.mjs @@ -0,0 +1,201 @@ +// ALVA-SPEC-002 · Utgåvor. +// +// ---- Varför versionen är en egen modul --------------------------------- +// +// Den stod som en sträng i gränssnittskoden och räknades aldrig upp. +// Systemet påstod "ALVA 1.0" efter att kvalitetsgrinden flyttats till +// servern, schemat stängts, faktureringen byggts, portalen stängts och +// tre revisioner härdats. Versionen sa alltså ingenting om vad man fick. +// +// Historiken nedan är HÄRLEDD UR VAD SOM FAKTISKT LEVERERATS, inte +// skriven för att se ut som en produkt med lång historia. Varje post +// pekar på den commit där arbetet finns. En utgåva som inte går att peka +// på hör inte hemma i listan — en påhittad ändringslogg är samma sorts +// obefogade säkerhet som produkten finns för att undvika. +// +// ---- Vad som gör en utgåva stor ----------------------------------------- +// +// Andra siffran räcker för ny funktion. Första siffran höjs bara när +// GARANTIERNA ändras — vad systemet lovar om ett underlag. Det har hänt +// två gånger: när fakturan blev ett härlett dokument i systemet, och när +// TÜV-härdningen ändrade vad loggen garanterar. + +export const UTGAVOR = [ + { + version: "3.6", + datum: "2026-08-06", + commit: "4dea6d2", + rubrik: "Betalarspåret: beviskraven följer den som betalar", + andringar: [ + "FGS-1.0 (garantistandarden) inarbetad: betalaren är en händelse i loggen — spår, namn, claim-/skadereferens och godkännande — och grinden kräver den för de ärendetyper där någon annan än kunden betalar. Tre nya ärendetyper: vagnskadegaranti, förlängd garanti och leasing/fleet, med egna regelpaketskrav.", + "Eskaleringsspärren (DISS-mönstret): en öppnad teknisk förfrågan till tillverkare eller garantigivare utan dokumenterat svar spärrar avslutet, matchad per referens. Bulletinernas \"invänta svar\" är nu en grindregel, inte ett råd.", + "Ett verkligt fynd på vägen: serverns ärendetypskrav var en tyst no-op — servern matade in den distribuerade paketformen i en funktion som bara läste den slimmade, så garanti- och försäkringskraven fanns bara som råd i klienten. C-2-mönstret, återuppstånden genom ett format. Grinden läser nu båda formerna, med regressionstest.", + "Claim- och skadenummer kan komma ur den registrerade betalaren, inte bara ur skannad arbetsorder. Goodwill och externa garantigivare kräver dokumenterat godkännande före avslut.", + "Reservdelshändelsen: artikelnummer, serienummer, batch och markering att den demonterade delen sparas i väntan på garantibeslut. Betalare, eskalering och reservdel är interna i delningsfiltret — kunden ser åtgärden, inte förhandlingen.", + "Kartläggningen av hela FGS-1.0 mot plattformen bokförd i ALVA-DOC-0011, med ärlig gräns: ODIS/DISS/SAGA2 är tillverkarsystem — ALVA bär bevispaketet och eskaleringsspåret, den ersätter dem inte.", + ], + }, + { + version: "3.5", + datum: "2026-08-06", + commit: "8072ea6", + rubrik: "Fackgranskning av metodikinnehållet — säkerheten först, på riktigt", + andringar: [ + "Sex domängranskningar av metodikbiblioteket. Tio kritiska fynd åtgärdade: högvoltens skyddsutrustning kom SIST i säkerhetssekvensen och står nu först, spänningsfrihetsmätningen kräver kategoriserat instrument med prova–mät–prova, frånskiljaren låses mot återinkoppling, och metodiken säger nu vad som är förbjudet utan behörighet.", + "Klimatmetodiken fick sin saknade behörighetsgrind (f-gascertifikat) — samma mönster som högvolt. ADAS-provkörningen instruerade bokstavligen att provocera nödbromsingrepp i trafik; nu tillverkarens testmetod.", + "Ett klistringsfel gjorde att engelska styrningsklagomål ('pulls', 'steering') matchade högvoltsmetodiken — styrningens nyckelord låg inklistrade i högvoltens lista. Borttaget.", + "Säkerhet före ingrepp överallt: öppna aldrig varmt kylsystem, trycksänk bränslesystemet och rör aldrig högtryckssidan, deaktivera tändning och insprutning före kompressionsprov, avgasutsug vid mätning med motorn igång, ingen provkörning vid bränsle- eller bromsvätskeläckage, prova aldrig SRS-kretsar med mätare.", + "23 nya kontroller där granskningen fann diagnostiska hål (bromsvätskenivå, brutna fjädrar, drivaxlar, emissionsmätning, HVIL, vattenprov), mätvärden med referensvärden och villkor i stället för nakna uppmaningar, och felkodsavläsning i det generiska skyddsnätet.", + "Grindtexten för spänningslöshet kräver nu uttryckligen verifiering genom mätning — på alla tio språken. Protokoll för mänsklig fackgranskning per marknad: ALVA-DOC-0010. Granskat-flaggorna är oförändrade — detta är en teknisk granskning, inte marknadens.", + ], + }, + { + version: "3.4", + datum: "2026-08-06", + commit: "c1c56e6", + rubrik: "Kedjesvep, registret gäller alla vägar, och ett fynd svepet gjorde själv", + andringar: [ + "Kedjesvepet: driftens nattliga fråga \"är allt fortfarande sant?\". Varje kedja och försegling i varje organisation verifieras — på begäran av arbetsledaren, nattligen av driften, med felkod vid brott så larmet är gratis.", + "Svepet hittade en riktig bugg första gången det kördes: leverantörshändelser bröt sina egna kedjelänkar därför att den kanoniska formen behandlade undefined som null medan databasen släpper nyckeln. Kanonisk form är nu exakt den form som överlever rundresan.", + "Kalibreringsfaktumet fryses vid mottagandet: taket \"hög\" kräver ett mätdon vars kalibrering gällde när mätningen togs — inte bara ett mätdon.", + "Mätdonsregistret gäller även leverantörsprotokoll. Okänt instrument nedgraderas öppet i stället för att tros på.", + "Säkerhetshuvuden på varje API-svar, ärlig 413 för för stora kroppar, och startvarning vid oförseglad drift.", + ], + }, + { + version: "3.3", + datum: "2026-08-06", + commit: "9378089", + rubrik: "Härdning: kedjad logg, förseglat avslut, härledd säkerhetsnivå", + andringar: [ + "Varje händelse bär en hash av sitt innehåll och föregående händelses hash, beräknad av servern. Den som ändrar en rad i efterhand — även med full databasbehörighet — bryter varje efterföljande länk, och brottet pekar ut sig självt.", + "Avslut förseglas: kedjans rot och en HMAC med en nyckel som aldrig finns i databasen. En omräknad kedja kan inte förseglas om.", + "Verifieringen säger vad den bevisar och inte: innehållet är oförändrat sedan mottagandet — ingenting om tiden före, ingenting om sanningshalten.", + "Signaturen vid avslut skrivs ur verifierad token i stället för att tas emot som text. Fältet intygar nu exakt vad det kan intyga.", + "Säkerhetsnivån i felorsaksanalysen är ett tak härlett ur underlaget: hög kräver reproducerat symptom och spårbart mätvärde. Teknikern kan sänka — ärlig osäkerhet är information — men aldrig höja.", + ], + }, + { + version: "3.2", + datum: "2026-08-06", + commit: "af27275", + rubrik: "Engelska som standard, tio EU-språk, och en regel som gällde på ett", + andringar: [ + "Engelska är standard- och källspråk. Nio översättningar: tyska, franska, spanska, italienska, polska, nederländska, portugisiska, rumänska och svenska.", + "Gränssnittstext faller tillbaka på engelska tyst. Metodikinnehåll gör det aldrig tyst — en ogranskad översättning av en säkerhetsinstruktion är sämre än en engelsk, därför att engelska SYNS som ett annat språk.", + "Metodens struktur översätts inte. Fasnamn och statusord står likadant i varje land, så att en revisor kan läsa en rumänsk och en tysk rapport utan att veta vilket språk verkstaden arbetar på.", + "Kvalitetsgrindens hinder följer organisationens språk. En spärr på fel språk är en spärr utan väg förbi.", + "ALVA-RULE-200 gällde bara på svenska: en tekniker som skrev \"erledigt\" eller \"done\" i motiveringsfältet passerade filtret för icke-svar. Rättat på tio språk — det var ett hål i regeln, inte en översättningsbrist.", + ], + }, + { + version: "3.1", + datum: "2026-08-06", + commit: "a94c228", + rubrik: "Regelverk, försäkringsunderlag och support i ärendet", + andringar: [ + "Garantiregimer för EU och USA med källa och kontrolldatum per post — och skiljelinjen mellan lagstadgat och avtalat, som förväxlas åt bägge håll.", + "Försäkringsvillkor som underlag för ett samtal. Modulen avgör ingenting och kan inte avgöra något.", + "Felanmälan i varje ärende, med härlett sammanhang. Ett regnr kan inte smugglas med.", + "Mätarställningen ska vara fotograferad, inte bara inskriven — kravet fanns bara i gränssnittet och var därmed en vana, inte en spärr.", + ], + }, + { + version: "3.0", + datum: "2026-08-06", + commit: "b147694", + rubrik: "TÜV-revision och härdning av integritetsgarantierna", + stor: true, + andringar: [ + "Händelsenyckeln skopad till ärendet: en organisation kan inte längre hindra en annan från att skriva historik.", + "Mätkedjan slås upp i mätdonsregistret — E4 var tidigare ett påstående klienten gjorde om sig själv.", + "Personnycklarna kuverteras under en huvudnyckel utanför databasen.", + "Gallringen verkställs av ett eget jobb i stället för att bara vara ett datum ingen läste.", + "Åtkomstloggen och raderingsregistret är append-only.", + ], + }, + { + version: "2.1", + datum: "2026-08-06", + commit: "3b83fc9", + rubrik: "Portalen stängd på riktigt", + andringar: [ + "Inloggningen autentiserar mot plattformen och portalen är oåtkomlig utan giltig session.", + "Fakturavyn läser organisationens egna fakturor; ett misslyckat anrop visar felet, inte exemplet.", + ], + }, + { + version: "2.0", + datum: "2026-08-06", + commit: "b457543", + rubrik: "Fakturering i systemet", + stor: true, + andringar: [ + "Fakturan härleds ur organisationens tillstånd i stället för att skrivas in.", + "Utfärdad faktura är oföränderlig; betalning är en händelse och statusen en projektion.", + "Nummerserie utan luckor. Ingen betalleverantör är inblandad.", + ], + }, + { + version: "1.3", + datum: "2026-08-05", + commit: "6676262", + rubrik: "Egen typografi och läsbarhet på telefon", + andringar: [ + "Inbäddad typografi enligt ALVA-SPEC-001 — inga externa hämtningar.", + "Ritningsstämpel på rapporten, räta hörn, tätare spärrning.", + ], + }, + { + version: "1.2", + datum: "2026-08-05", + commit: "86d553b", + rubrik: "Sviten körs faktiskt", + andringar: [ + "ALVA:s tester lades in i CI. De hade passerat bara när någon körde dem för hand — ett test som ingen kör är dokumentation, inte en spärr.", + "Genomgången blev ett incheckat test.", + ], + }, + { + version: "1.1", + datum: "2026-08-05", + commit: "31727a4", + rubrik: "Leverantörsrevision 2 och härdning", + andringar: [ + "Händelseschemat stängt: okända fält avvisas i stället för att lagras.", + "Kvalitetsregeln finns på ett ställe — klienten frågar grinden i stället för att ha en egen åsikt.", + ], + }, + { + version: "1.0", + datum: "2026-08-04", + commit: "590fab3", + rubrik: "ALVA-metoden", + andringar: [ + "Analys · Lokalisering · Verifiering · Åtgärd som metod, inte som assistent.", + "Kvalitetsgrinden serverauktoritativ. Avslutssats enligt ALVA-RULE-200.", + ], + }, +]; + +/** Nuvarande utgåva. Enda stället siffran finns. */ +export const NUVARANDE = UTGAVOR[0]; + +/** Så versionen skrivs i gränssnittet och i ritningsstämpeln. */ +export const PLATTFORMSVERSION = `ALVA ${NUVARANDE.version}`; + +/** + * Versionen som gällde när ett ärende stängdes. + * + * Ritningsstämpeln ska visa vad som gällde VID AVSLUTET, inte vad som + * gäller den dag rapporten läses — det står i stämpelns egen beskrivning, + * och gällde ändå inte för versionen, som lästes ur den aktuella + * konstanten. Ett ärende stängt under 1.0 visade 3.1. + * + * Avslutshändelsen bär numera versionen. Äldre ärenden saknar den, och då + * sägs det uttryckligen i stället för att dagens siffra lånas ut. + */ +export function versionVidAvslut(avslutshandelse) { + const v = avslutshandelse?.plattformsversion; + return typeof v === "string" && v.trim() ? v : "Ej registrerad (stängt före ALVA 3.1)"; +} diff --git a/felsokning/services-backup/gemensam/abonnemang.mjs b/felsokning/services-backup/gemensam/abonnemang.mjs new file mode 100644 index 0000000..fb245bf --- /dev/null +++ b/felsokning/services-backup/gemensam/abonnemang.mjs @@ -0,0 +1,211 @@ +// ALVA-PROC-0002 · Abonnemang. +// +// ---- Hållningen --------------------------------------------------------- +// +// Vi litar på kunden. Det är inte en artighet utan ett designbeslut med +// följder: inget kort krävs, ingen förskottsbetalning, ingen spärr innan +// någon fått veta att något är obetalt. Kunden är en verkstad med +// anställda och kunder i sin tur — den försvinner inte över natten, och +// att behandla den som om den kunde det gör produkten sämre för alla som +// betalar i tid. +// +// Vid utebliven betalning varnar vi, räknar ned synligt, och låser sedan. +// +// ---- Vad en låsning ALDRIG får göra ------------------------------------- +// +// Låsning stänger nya ärenden. Den rör inte historiken, och den får inte +// göra det. +// +// Skälet är inte generositet. Ärendeloggen är verkstadens underlag i en +// garanti- eller försäkringstvist som gäller DERAS kund — en tredje part +// som inte har någon del i vår obetalda faktura. Att göra det underlaget +// oåtkomligt vore att använda någon annans rättsliga ställning som +// påtryckningsmedel. Läsning och export förblir därför öppna i låst läge, +// och det är inte förhandlingsbart. +// +// ---- Gratis är en nivå, inte en provperiod ------------------------------ +// +// Kontot startar på Free och stannar där tills någon väljer annat. En +// provperiod som tyst börjar kosta är samma sorts fälla som produkten +// finns för att undvika. Fakturan skapas ändå vid registreringen — på +// noll kronor — därför att den etablerar serien och visar kunden exakt +// vad den inte betalar för. + +import { PRISLISTA } from "./fakturering.mjs"; + +/** Dagar efter förfallodagen innan låsning. Synlig nedräkning hela tiden. */ +export const RESPITDAGAR = 14; + +/** + * Nivåerna. + * + * `basavgift` och `anvandaravgift` är i öre, som resten av faktureringen. + * Värdena är PLATSHÅLLARE på samma villkor som PRISLISTA — mekanismen är + * oberoende av dem. + */ +export const NIVAER = [ + { + id: "free", + namn: "Free", + basavgift: 0, + anvandaravgift: 0, + maxAnvandare: 2, + text: "Full method, full evidence model, full report. Two accounts.", + ingar: [ + "The complete ALVA methodology and quality gate", + "Append-only case log and evidence report", + "Two active accounts", + ], + saknas: ["Fleet analytics", "Outgoing integrations", "Knowledge source connectors"], + }, + { + id: "standard", + namn: "Standard", + basavgift: PRISLISTA.plattform_ar / 12, + anvandaravgift: PRISLISTA.anvandare_manad, + maxAnvandare: null, + text: "Platform fee plus a fee per active account.", + ingar: [ + "Everything in Free, without the account limit", + "Fleet analytics and fault-cause statistics", + "Live Share with customer, partner and internal levels", + "Instrument register with calibration tracking", + ], + saknas: ["Outgoing integrations", "Knowledge source connectors"], + }, + { + id: "premium", + namn: "Premium", + basavgift: (PRISLISTA.plattform_ar / 12) * 2, + anvandaravgift: PRISLISTA.anvandare_manad, + maxAnvandare: null, + text: "For workshops that report into someone else's system.", + ingar: [ + "Everything in Standard", + "Outgoing integrations and event subscriptions", + "Protocol ingest from external test equipment", + "Knowledge source connectors", + ], + saknas: ["Dedicated instance", "Custom methodology authoring"], + }, + { + id: "enterprise", + namn: "Enterprise", + basavgift: null, // Avtalas. Inte "kontakta oss" som prissignal — det finns inget listpris. + anvandaravgift: null, + maxAnvandare: null, + text: "Chains and fleets. Priced per agreement because the scope is.", + ingar: [ + "Everything in Premium", + "Own methodologies and own rule package", + "Dedicated instance, own key management", + "Signed rule package and audit support", + ], + saknas: [], + }, +]; + +export const nivaFor = (id) => NIVAER.find((n) => n.id === id) ?? NIVAER[0]; + +/** Abonnemangets tillstånd. Härlett, aldrig lagrat. */ +export const TILLSTAND = ["aktiv", "varning", "last"]; + +/** + * Tillståndet, härlett ur fakturorna. + * + * Tre utfall och inget fjärde: + * + * aktiv Inget är förfallet. + * varning Något är förfallet men respiten löper. Nedräkningen visas. + * last Respiten är slut. + * + * En faktura på noll kronor kan inte försätta någon i varning. Free-nivån + * fakturerar noll, och en obetald nollfaktura är inte en skuld — den är + * en bokföringspost. Att låsa någon för den vore absurt, och det är + * precis den sortens absurditet som uppstår när tillståndet räknas fram + * utan att någon tänkt på specialfallet. + * + * @param fakturor [{ forfaller, totalt, status }] + * @param nu tidpunkt; injicerad för testbarhet + */ +export function abonnemangstillstand(fakturor = [], nu = new Date()) { + const obetalda = fakturor.filter( + (f) => f.status === "utfardad" && Number(f.totalt) > 0 && new Date(f.forfaller) < nu, + ); + if (obetalda.length === 0) return { tillstand: "aktiv", dagarKvar: null, aldsta: null }; + + // Den äldsta förfallna styr. Betalar kunden den senaste men inte den + // första har respiten ändå löpt sedan den första. + const aldsta = obetalda.reduce((a, b) => (new Date(a.forfaller) < new Date(b.forfaller) ? a : b)); + const forfalloDygn = Math.floor((nu - new Date(aldsta.forfaller)) / 86_400_000); + const dagarKvar = RESPITDAGAR - forfalloDygn; + + return { + tillstand: dagarKvar > 0 ? "varning" : "last", + dagarKvar: Math.max(0, dagarKvar), + aldsta: aldsta.beteckning ?? null, + belopp: obetalda.reduce((s, f) => s + Number(f.totalt), 0), + }; +} + +/** + * Vad ett tillstånd tillåter. + * + * Läsning och export är sanna i ALLA tillstånd. Se filhuvudet — det är + * verkstadens kunds underlag, inte verkstadens, och inte vårt. + */ +export function behorighet(tillstand) { + const last = tillstand === "last"; + return { + lasa: true, + exportera: true, + nyttArende: !last, + dokumentera: !last, + avsluta: !last, + }; +} + +/** + * Beskedet till kunden. Rak text, ingen förhandling, inget hot. + * + * Statusspråket gäller även här: ett konstaterande och vad som händer + * härnäst. + */ +export function besked(status) { + if (status.tillstand === "aktiv") return null; + if (status.tillstand === "varning") { + return ( + `Invoice ${status.aldsta} is past due. New cases can be started for ${status.dagarKvar} more ` + + `day${status.dagarKvar === 1 ? "" : "s"}. Reading and export are never affected.` + ); + } + return ( + `Invoice ${status.aldsta} is past due and the grace period has ended. New cases cannot be started. ` + + "Existing cases remain readable and exportable — that record belongs to your customer, not to us." + ); +} + +/** + * Nästa fakturaperiod. + * + * Månadsvis från registreringsdagen, inte från den första i månaden: en + * organisation som registrerar sig den 20:e ska inte få en faktura på + * elva dagar som sin första upplevelse av produkten. + */ +export function nastaPeriod(registrerad, senastFakturerad = null) { + const start = new Date(senastFakturerad ?? registrerad); + if (Number.isNaN(start.getTime())) return null; + const fran = new Date(start); + if (senastFakturerad) fran.setMonth(fran.getMonth() + 1); + const till = new Date(fran); + till.setMonth(till.getMonth() + 1); + till.setDate(till.getDate() - 1); + return { fran: fran.toISOString().slice(0, 10), till: till.toISOString().slice(0, 10) }; +} + +/** Är det dags att fakturera perioden? */ +export function forfallenFakturering(registrerad, senastFakturerad, nu = new Date()) { + const period = nastaPeriod(registrerad, senastFakturerad); + if (!period) return null; + return new Date(period.fran) <= nu ? period : null; +} diff --git a/felsokning/services-backup/gemensam/fakturapdf.mjs b/felsokning/services-backup/gemensam/fakturapdf.mjs new file mode 100644 index 0000000..a5ea39b --- /dev/null +++ b/felsokning/services-backup/gemensam/fakturapdf.mjs @@ -0,0 +1,200 @@ +// ALVA-PROC-0001 · Fakturan som PDF. +// +// ---- Varför den är skriven för hand ------------------------------------- +// +// Kravet var inga beroenden. En PDF-generator drar in ett bibliotek som +// drar in fem till, och varje sådant är en försörjningskedja vi inte +// granskar — för att rita text i två spalter. +// +// PDF är ett textformat. En ensidig faktura med inbyggda typsnitt är +// omkring tvåhundra rader, och de raderna går att läsa och förstå av den +// som ska lita på dem. Det är samma resonemang som gjorde +// plattformstjänsten ramverkslös. +// +// ---- Vad som INTE görs --------------------------------------------------- +// +// Inga bilder, ingen egen typografi, ingen radbrytning av godtycklig +// bredd. De 14 basfonterna finns i varje PDF-läsare och behöver inte +// bäddas in; Helvetica räcker för en faktura, och en faktura som ser +// tråkig ut är fortfarande en faktura. +// +// WinAnsi täcker svenska tecken. Det som ligger utanför ersätts hellre än +// att ge en trasig glyf — en faktura med kråkfötter ser förfalskad ut. + +/** Sidmått i punkter. A4. */ +const BREDD = 595.28; +const HOJD = 841.89; +const MARGINAL = 56; + +/** Tecken PDF:ens strängsyntax måste få undantagna. */ +const flykta = (s) => String(s).replace(/([\\()])/g, "\\$1"); + +/** + * WinAnsi-kodning av det vi faktiskt använder. + * + * Latin-1 räcker till 0xFF och täcker åäöÅÄÖ. Utanför det ersätts + * tecknet — hellre en punkt än en trasig glyf. + */ +function winansi(text) { + let ut = ""; + for (const tecken of String(text)) { + const k = tecken.codePointAt(0); + if (k === 0x2013 || k === 0x2014) ut += "-"; // tankstreck + else if (k === 0x00a0) ut += " "; // hårt mellanslag + else if (k === 0x2212) ut += "-"; // minustecken + else if (k <= 0xff) ut += tecken; + else ut += "."; + } + return ut; +} + +/** En textinstruktion i innehållsströmmen. */ +function text(x, y, storlek, innehall, font = "F1") { + return `BT /${font} ${storlek} Tf 1 0 0 1 ${x.toFixed(2)} ${y.toFixed(2)} Tm (${flykta(winansi(innehall))}) Tj ET\n`; +} + +function linje(x1, y1, x2, y2, tjocklek = 0.5) { + return `${tjocklek} w ${x1.toFixed(2)} ${y1.toFixed(2)} m ${x2.toFixed(2)} ${y2.toFixed(2)} l S\n`; +} + +/** + * Bygger fakturans PDF. + * + * @param faktura dokumentet ur fakturering.mjs + * @param avsandare valfria uppgifter om utfärdaren (ALVA-SPEC-050) + * @returns Buffer + */ +export function fakturaPdf(faktura, avsandare = {}) { + const rader = []; + let y = HOJD - MARGINAL; + + const skriv = (storlek, innehall, font = "F1", hopp = null) => { + rader.push(text(MARGINAL, y, storlek, innehall, font)); + y -= hopp ?? storlek + 6; + }; + + // ---- Huvud ------------------------------------------------------------ + skriv(20, "ALVA", "F2", 28); + skriv(9, "GUIDED DIAGNOSTIC PLATFORM", "F1", 24); + + skriv(15, `Invoice ${faktura.beteckning}`, "F2", 22); + + // ---- Parter och datum ------------------------------------------------- + const uppgifter = [ + ["Organization", faktura.organisation], + ["Period", `${faktura.period.fran} - ${faktura.period.till}`], + ["Issued", faktura.utfardad], + ["Due", faktura.forfaller], + ]; + for (const [etikett, varde] of uppgifter) { + rader.push(text(MARGINAL, y, 9, etikett, "F1")); + rader.push(text(MARGINAL + 120, y, 9, String(varde), "F2")); + y -= 14; + } + y -= 10; + + // ---- Rader ------------------------------------------------------------ + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y, 1)); + y -= 16; + for (const [x, etikett] of [ + [MARGINAL, "ITEM"], + [MARGINAL + 250, "QTY"], + [MARGINAL + 330, "UNIT PRICE"], + [BREDD - MARGINAL - 80, "AMOUNT"], + ]) { + rader.push(text(x, y, 8, etikett, "F2")); + } + y -= 6; + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y)); + y -= 18; + + const belopp = (ore) => `${(Math.abs(ore) / 100).toFixed(2)}${ore < 0 ? "-" : ""} ${faktura.valuta}`; + + for (const r of faktura.rader) { + rader.push(text(MARGINAL, y, 10, r.benamning, "F2")); + rader.push(text(MARGINAL + 250, y, 10, `${r.antal}`)); + rader.push(text(MARGINAL + 330, y, 10, belopp(r.apris))); + rader.push(text(BREDD - MARGINAL - 80, y, 10, belopp(r.belopp))); + y -= 13; + // Underlaget under raden: varje rad ska bära var antalet kommer ifrån, + // och det gäller på papper lika mycket som på skärm. + rader.push(text(MARGINAL + 8, y, 8, r.underlag)); + y -= 18; + } + + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y)); + y -= 18; + + // ---- Summering -------------------------------------------------------- + for (const [etikett, varde, fet] of [ + ["Net", belopp(faktura.netto), false], + [`VAT ${Math.round(faktura.momssats * 100)} %`, belopp(faktura.moms), false], + ["Total", belopp(faktura.totalt), true], + ]) { + rader.push(text(MARGINAL + 330, y, fet ? 11 : 10, etikett, fet ? "F2" : "F1")); + rader.push(text(BREDD - MARGINAL - 80, y, fet ? 11 : 10, varde, fet ? "F2" : "F1")); + y -= fet ? 20 : 15; + } + + // ---- Betalning -------------------------------------------------------- + y -= 10; + rader.push(linje(MARGINAL, y, BREDD - MARGINAL, y)); + y -= 18; + skriv(9, faktura.betalningssatt, "F2", 14); + skriv(8, "Payment is registered by an administrator when the funds arrive.", "F1", 20); + + if (faktura.krediterar) { + skriv(9, `Credit note for ${faktura.krediterar}`, "F2", 14); + if (faktura.orsak) skriv(8, faktura.orsak, "F1", 20); + } + + // ---- Utfärdarens uppgifter ------------------------------------------- + // + // Tomma fält utelämnas. En faktura med "— ej satt" i adressfältet är + // sämre än en utan adressfält: den påstår att någon fyllt i det. + const fot = [avsandare.foretagsnamn, avsandare.adress, avsandare.momsnummer, avsandare.epost].filter(Boolean); + let fy = MARGINAL + 40; + for (const rad of fot.reverse()) { + rader.push(text(MARGINAL, fy, 8, rad)); + fy += 11; + } + rader.push(text(BREDD - MARGINAL - 120, MARGINAL, 8, faktura.version ?? "ALVA-PROC-0001")); + + return montera(rader.join("")); +} + +/** + * Sätter ihop PDF-filen med korrekt korsreferenstabell. + * + * Byteoffsetarna i xref måste stämma på byten. Det är den enda delen av + * formatet som inte förlåter slarv — en läsare som inte hittar objektet + * visar ingenting alls, inte en trasig sida. + */ +function montera(innehall) { + const objekt = [ + "<< /Type /Catalog /Pages 2 0 R >>", + "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", + `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${BREDD.toFixed(2)} ${HOJD.toFixed(2)}] ` + + "/Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> /Contents 4 0 R >>", + null, // 4: innehållsströmmen, sätts nedan + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>", + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>", + ]; + + const strom = Buffer.from(innehall, "latin1"); + objekt[3] = `<< /Length ${strom.length} >>\nstream\n${innehall}endstream`; + + let pdf = "%PDF-1.4\n"; + const offsetar = []; + objekt.forEach((kropp, i) => { + offsetar.push(Buffer.byteLength(pdf, "latin1")); + pdf += `${i + 1} 0 obj\n${kropp}\nendobj\n`; + }); + + const xrefOffset = Buffer.byteLength(pdf, "latin1"); + pdf += `xref\n0 ${objekt.length + 1}\n0000000000 65535 f \n`; + for (const o of offsetar) pdf += `${String(o).padStart(10, "0")} 00000 n \n`; + pdf += `trailer\n<< /Size ${objekt.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`; + + return Buffer.from(pdf, "latin1"); +} diff --git a/felsokning/services-backup/gemensam/fakturering.mjs b/felsokning/services-backup/gemensam/fakturering.mjs new file mode 100644 index 0000000..ba1c5ef --- /dev/null +++ b/felsokning/services-backup/gemensam/fakturering.mjs @@ -0,0 +1,257 @@ +// ALVA-PROC-0001 · Fakturering. +// +// ---- Varför fakturan skapas här och inte hos en betalleverantör ------- +// +// ALVA säljs inte över disk. Aktiveringssekvensen på webbplatsen säger +// det redan: ansökan granskas, faktura utfärdas, betalning registreras, +// organisationen aktiveras. Det finns ingen kassa att gå igenom, och +// därför ingen betalleverantör att fråga vad kunden ska betala. +// +// Det betyder att beloppet måste komma någonstans ifrån, och det enda +// hållbara stället är organisationens faktiska tillstånd: hur många +// aktiva användare den har, vilken licensperiod som gäller, vilka +// moduler som är påslagna. En summa någon skriver in för hand kan säga +// emot verkligheten. En summa som härleds kan inte det. +// +// Samma resonemang som sammanfattningen (ALVA-PROC-0030): härledd, inte +// inmatad. Det är också det som gör fakturan granskbar i efterhand — +// underlaget står i raderna, inte i någons minne. +// +// ---- Vad som INTE finns här ------------------------------------------- +// +// Ingen betalningshantering, inga kortuppgifter, ingen extern tjänst. +// Betalning registreras av en administratör när pengarna kommit in. +// Systemet påstår aldrig att något är betalt utan att en människa sagt +// det, av samma skäl som en kontroll aldrig är verifierad utan evidens. +// +// ---- Oföränderlighet --------------------------------------------------- +// +// En utfärdad faktura ändras aldrig. Blev den fel utfärdas en +// kreditfaktura som pekar på den, och en ny. Det är samma regel som för +// händelseloggen: en felaktig post rättas inte, den bemöts. + +/** + * Prislistan. + * + * Beloppen är i minsta valutaenhet (öre) för att undvika flyttalsfel — + * en faktura får aldrig avrunda fel åt något håll. + * + * VÄRDENA NEDAN ÄR PLATSHÅLLARE och ska sättas av produktägaren per + * marknad. Mekanismen är oberoende av dem: byt siffror, och varje + * kommande faktura följer med. + */ +export const PRISLISTA = { + valuta: "SEK", + /** Plattformslicens, per organisation och år. */ + plattform_ar: 2_400_000, + /** Användarlicens, per aktiv användare och månad. */ + anvandare_manad: 39_000, + /** Tillval, per modul och år. */ + moduler: { + forsakringsrapportering: { namn: "Insurance reporting", ar: 600_000 }, + flottanalys: { namn: "Fleet analytics", ar: 960_000 }, + kunskapskallor_oem: { namn: "OEM knowledge sources", ar: 1_440_000 }, + }, + /** Mervärdesskatt. 25 % är svensk normalskattesats. */ + momssats: 0.25, + /** Betalningsvillkor i dagar. */ + betalningsvillkor: 30, +}; + +/** Fakturans tillstånd. Ordningen är enkelriktad utom kredit. */ +export const FAKTURASTATUS = ["utfardad", "betald", "krediterad"]; + +/** + * Statusen är en projektion, inte en kolumn. + * + * Följden av att en utfärdad faktura aldrig ändras: "betald" kan inte + * skrivas ovanpå "utfärdad". Betalningen är en händelse som inträffar + * efteråt, och tillståndet härleds ur händelserna — samma förhållande + * som mellan ärendets tillstånd och dess logg. + * + * Kreditering väger tyngst. En krediterad faktura som också hunnit bli + * betald är fortfarande krediterad; det är återbetalningen som är kvar + * att göra, och den frågan besvaras inte av ett statusfält. + * + * @param poster [{ typ: "betald" | "krediterad", ... }] + */ +export function fakturastatus(poster = []) { + if (poster.some((p) => p?.typ === "krediterad")) return "krediterad"; + if (poster.some((p) => p?.typ === "betald")) return "betald"; + return "utfardad"; +} + +/** + * Granskar en fakturaperiod innan något härleds ur den. + * + * En bakvänd period ger noll månader i manaderMellan(), vilket tyst + * skulle bli en faktura utan användarrad i stället för ett fel. Den + * sortens tystnad är värre än ett avslag: fakturan ser rimlig ut. + * + * Returnerar en felsträng, eller null när perioden duger. + */ +export function granskaPeriod(period) { + const datum = (v) => typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v) && !Number.isNaN(Date.parse(v)); + if (!period || !datum(period.fran) || !datum(period.till)) { + return "Perioden kräver fran och till som datum (ÅÅÅÅ-MM-DD)."; + } + if (new Date(period.till) < new Date(period.fran)) { + return "Perioden slutar före den börjar."; + } + // Ett tak finns för att ett skrivfel i årtalet inte ska bli en faktura + // på nittio år. Fem år rymmer varje rimlig licensperiod. + if (manaderMellan(period.fran, period.till) > 60) { + return "Perioden är längre än fem år — kontrollera datumen."; + } + return null; +} + +/** + * Beräknar fakturarader ur organisationens tillstånd. + * + * Varje rad bär sitt underlag — antal, à-pris och vad antalet kommer + * ifrån. En granskare ska kunna se varför summan blev den den blev utan + * att fråga någon. + * + * @param org { namn, licensstart, moduler: string[] } + * @param aktiva antal aktiva användare i organisationen + * @param period { fran: "2026-01-01", till: "2026-12-31" } + * @param prislista valfri; standard är PRISLISTA + */ +export function fakturarader(org, aktiva, period, prislista = PRISLISTA) { + const rader = []; + const månader = manaderMellan(period.fran, period.till); + + rader.push({ + benamning: "Platform license", + underlag: `${org?.namn ?? "Organization"} · ${period.fran} – ${period.till}`, + antal: 1, + enhet: "organization/year", + apris: prislista.plattform_ar, + belopp: prislista.plattform_ar, + }); + + if (aktiva > 0 && månader > 0) { + rader.push({ + benamning: "User licenses", + // Antalet är inte en uppskattning: det är de konton som faktiskt + // kan logga in när fakturan utfärdas. + underlag: `${aktiva} active users × ${månader} months`, + antal: aktiva * månader, + enhet: "user/month", + apris: prislista.anvandare_manad, + belopp: aktiva * månader * prislista.anvandare_manad, + }); + } + + for (const nyckel of org?.moduler ?? []) { + const modul = prislista.moduler[nyckel]; + if (!modul) continue; + rader.push({ + benamning: modul.namn, + underlag: `Enterprise module · ${period.fran} – ${period.till}`, + antal: 1, + enhet: "module/year", + apris: modul.ar, + belopp: modul.ar, + }); + } + + return rader; +} + +/** + * Sätter ihop en faktura. + * + * Returnerar ett objekt som är fullständigt i sig själv: raderna, summan, + * momsen, förfallodagen och vad allt härleddes ur. Den som läser den om + * två år ska inte behöva systemet för att förstå den. + */ +export function fakturera({ nummer, org, aktiva, period, utfardad, prislista = PRISLISTA }) { + const rader = fakturarader(org, aktiva, period, prislista); + const netto = rader.reduce((summa, r) => summa + r.belopp, 0); + // Momsen räknas på nettosumman, inte per rad: radvis avrundning ger + // ören som inte stämmer mot totalen. + const moms = Math.round(netto * prislista.momssats); + + return { + version: "ALVA-PROC-0001", + beteckning: fakturabeteckning(nummer), + organisation: org?.namn ?? "Organization", + period, + utfardad, + forfaller: adderaDagar(utfardad, prislista.betalningsvillkor), + valuta: prislista.valuta, + rader, + netto, + momssats: prislista.momssats, + moms, + totalt: netto + moms, + status: "utfardad", + betalningssatt: "Invoice. No online payment is accepted.", + }; +} + +/** + * Kreditering. + * + * En utfärdad faktura ändras aldrig. Krediteringen är en egen post som + * pekar tillbaka, med samma rader och omvänt tecken, och den kräver ett + * skäl — av samma anledning som ett avslut kräver ett varför. + */ +export function kreditera(faktura, { nummer, utfardad, orsak }) { + if (!orsak || orsak.trim().length < 10) { + return { fel: "En kreditering kräver ett skäl som går att granska i efterhand." }; + } + return { + kredit: { + ...faktura, + beteckning: fakturabeteckning(nummer), + utfardad, + krediterar: faktura.beteckning, + orsak: orsak.trim(), + rader: faktura.rader.map((r) => ({ ...r, antal: -r.antal, belopp: -r.belopp })), + netto: -faktura.netto, + moms: -faktura.moms, + totalt: -faktura.totalt, + status: "krediterad", + }, + }; +} + +/** ALVA-INV-0001 */ +export const fakturabeteckning = (nummer) => `ALVA-INV-${String(nummer).padStart(4, "0")}`; + +/** Hela månader mellan två datum, inklusive startmånaden. */ +export function manaderMellan(fran, till) { + const a = new Date(fran); + const b = new Date(till); + if (Number.isNaN(a.getTime()) || Number.isNaN(b.getTime()) || b < a) return 0; + return (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth()) + 1; +} + +function adderaDagar(datum, dagar) { + const d = new Date(datum); + if (Number.isNaN(d.getTime())) return datum; + d.setDate(d.getDate() + dagar); + return d.toISOString().slice(0, 10); +} + +/** + * Belopp för läsning. Öre in, sträng ut. + * + * Fast antal decimaler och hårt mellanslag som tusentalsavskiljare — + * belopp jämförs oftare än de läses, precis som beteckningar. + */ +/** Hårt mellanslag: ett belopp får aldrig brytas över en radbrytning. */ +const TUSENTAL = "\u00A0"; + +export function formateraBelopp(ore, valuta = PRISLISTA.valuta) { + const negativt = ore < 0; + const kronor = Math.abs(ore) / 100; + const text = kronor + .toFixed(2) + .replace(".", ",") + .replace(/\B(?=(\d{3})+(?!\d))/g, TUSENTAL); + return `${negativt ? "−" : ""}${text} ${valuta}`; +} diff --git a/felsokning/services-backup/gemensam/faser.mjs b/felsokning/services-backup/gemensam/faser.mjs new file mode 100644 index 0000000..d140330 --- /dev/null +++ b/felsokning/services-backup/gemensam/faser.mjs @@ -0,0 +1,107 @@ +// ALVA-modellen tillämpad på metodikbiblioteket. +// +// ALVA är en metod, inte ett gränssnitt. Om metoden bara fanns i +// marknadsföringen vore den en etikett på en wizard. Här är den den +// struktur som varje procedur faktiskt följer: +// +// A Analysis Samla fakta. Inte hypoteser. +// L Localization Avgränsa. Identifiera exakt område. +// V Verification Verifiera orsaken. Inte symptomet. +// A Action Utför korrigering. Verifiera resultat. Dokumentera. +// +// Varje steg i varje metodik hör till exakt en fas. Klassificeringen är +// data och inte en gissning i vyn, av två skäl: den ska gå att granska, +// och en procedur vars faser inte går ihop ska falla i test i stället +// för att se rätt ut på skärmen. +// +// ---- Varför just den här indelningen --------------------------------- +// +// Gränsen mellan L och V är den enda som är svår, och den är också den +// som bär hela metoden. Att mäta spänning vid en komponent är +// lokalisering — den avgränsar var felet finns. Att mäta spänningsfall +// över samma komponent under last är verifiering — den fastställer +// varför. Samma instrument, samma komponent, olika fas. +// +// Regeln vi tillämpar: ett steg är Verification när det kan avfärda en +// kandidatorsak. Kan det bara flytta uppmärksamheten är det Localization. + +/** Steg-id → fas. Uttömmande; ett okänt steg-id faller i test. */ +export const STEGFAS = { + // ---- Analysis: vad hände, när, var. Inga slutsatser. ---------------- + sakerhet: "analysis", // Förutsättningen för att få samla fakta alls. + symptom: "analysis", + forutsattningar: "analysis", + grund: "analysis", + + // ---- Localization: avgränsa området --------------------------------- + visuell: "localization", + felkoder: "localization", + avlasning: "localization", + koder: "localization", + buss: "localization", + inspelning: "localization", + lokalisering: "localization", + batteri: "localization", + glapp: "localization", + metod: "localization", + reproduktion: "localization", // vägprovet som pekar ut hjulet — avgränsning, inte åtgärd + + // ---- Verification: fastställ orsaken -------------------------------- + matningar: "verification", + kontroller: "verification", + grundkontroller: "verification", + rela: "verification", + start: "verification", + laddning: "verification", + krypstrom: "verification", + mekanik: "verification", + tandning_bransle: "verification", + packning: "verification", + system: "verification", + styrning: "verification", + orsak: "verification", + installning: "verification", + + // ---- Action: korrigera och verifiera resultatet --------------------- + provkorning: "action", + funktionstest: "action", + kalibrering: "action", +}; + +export const FASORDNING = ["analysis", "localization", "verification", "action"]; + +/** Fasen för ett steg. Okänt steg → analysis, och testet faller. */ +export const fasFor = (stegId) => STEGFAS[stegId] ?? "analysis"; + +/** + * Faserna en metodik faktiskt innehåller, i ALVA-ordning. + * + * Alla metodiker innehåller inte alla fyra. En läckagemetodik som slutar + * med lokalisering är inte ofullständig — läckan är hittad, åtgärden är + * ett annat arbete. Att tvinga in ett Action-steg där hade varit att + * fylla ut modellen i stället för att tillämpa den. + */ +export function faserI(metodik) { + const funna = new Set((metodik?.steg ?? []).map((s) => fasFor(s.id))); + return FASORDNING.filter((f) => funna.has(f)); +} + +/** + * Klara faser givet loggen: en fas är klar när metodikens samtliga steg i + * den fasen är dokumenterade. + */ +export function klaraFaser(metodik, handelser) { + const besvarade = new Set(); + const utforda = new Set(); + for (const h of handelser) { + if (h?.typ === "fraga_besvarad") besvarade.add(`${h.stegId}/${h.frageId}`); + if (h?.typ === "kontroll_utford") utforda.add(`${h.stegId}/${h.kontrollId}`); + } + const klar = (steg) => + (steg.fragor ?? []).every((f) => besvarade.has(`${steg.id}/${f.id}`)) && + (steg.kontroller ?? []).every((k) => utforda.has(`${steg.id}/${k.id}`)); + + return faserI(metodik).filter((fas) => + (metodik.steg ?? []).filter((s) => fasFor(s.id) === fas).every(klar), + ); +} diff --git a/felsokning/services-backup/gemensam/foretagsuppgifter.mjs b/felsokning/services-backup/gemensam/foretagsuppgifter.mjs new file mode 100644 index 0000000..3cc2204 --- /dev/null +++ b/felsokning/services-backup/gemensam/foretagsuppgifter.mjs @@ -0,0 +1,198 @@ +// ALVA-SPEC-050 · Företagsuppgifter och lagstadgad information. +// +// ---- Varför tyskt krav som golv ---------------------------------------- +// +// Tyskland har den strängaste kravbilden på vad en näringsidkare måste +// hålla synligt: Impressumspflicht enligt § 5 DDG är namngiven, +// utkrävbar och avmahnbar på ett sätt som saknar motsvarighet i övriga +// EU. Bygger man för den listan uppfyller man i praktiken de andra +// marknadernas krav på köpet, och man slipper en andra genomgång per +// land. Det är därför listan nedan är tysk och inte svensk. +// +// ---- Vad modulen INTE gör ----------------------------------------------- +// +// Den hittar inte på uppgifter. Ett Impressum med påhittad adress eller +// påhittat organisationsnummer är inte ett halvfärdigt Impressum utan ett +// vilseledande, och skadan är större än den tomma rutans. +// +// Osatta fält märks därför som osatta, och `saknade()` returnerar dem så +// att driften kan spärra en driftsättning på listan i stället för att +// upptäcka den när brevet kommer. +// +// ---- Två fällor som är aktuella just nu --------------------------------- +// +// ODR-LÄNKEN SKA BORT. Förordning (EU) 524/2013 upphävdes genom +// förordning (EU) 2024/3228 och plattformen stängdes 20 juli 2025. +// Länken till ec.europa.eu/odr har legat i tiotusentals fotnoter sedan +// 2016 och pekar numera ingenstans. Den som låtit den ligga kvar visar +// upp en död hänvisning som en lagstadgad upplysning. +// +// § 5 TMG HETER § 5 DDG. Telemediengesetz ersattes av +// Digitale-Dienste-Gesetz den 14 maj 2024. Innehållet i plikten är +// oförändrat; hänvisningen är det inte. + +/** Fälten, med sin rättsliga grund. `kravs` styr vad `saknade()` larmar om. */ +export const FALT = [ + { + id: "foretagsnamn", + rubrik: "Firma", + grund: "§ 5 Abs. 1 Nr. 1 DDG", + kravs: true, + hjalp: "Fullständigt firmanamn inklusive bolagsform (GmbH, AB, Ltd).", + }, + { + id: "adress", + rubrik: "Anschrift", + grund: "§ 5 Abs. 1 Nr. 1 DDG", + kravs: true, + hjalp: "Gatuadress. En postbox räcker inte — adressen ska gå att delge på.", + }, + { + id: "foretradare", + rubrik: "Vertretungsberechtigte", + grund: "§ 5 Abs. 1 Nr. 1 DDG", + kravs: true, + hjalp: "Behörig företrädare, t.ex. verkställande direktör eller Geschäftsführer.", + }, + { + id: "epost", + rubrik: "E-Mail", + grund: "§ 5 Abs. 1 Nr. 2 DDG", + kravs: true, + hjalp: "En adress som läses. Ett kontaktformulär ersätter den inte.", + }, + { + id: "telefon", + rubrik: "Telefon", + grund: "§ 5 Abs. 1 Nr. 2 DDG", + kravs: true, + hjalp: "Snabb elektronisk kontakt ska vara möjlig; telefon är det som normalt godtas.", + }, + { + id: "register", + rubrik: "Registergericht und -nummer", + grund: "§ 5 Abs. 1 Nr. 4 DDG", + kravs: true, + hjalp: "Handelsregister och HRB-nummer, eller motsvarande register i hemlandet.", + }, + { + id: "momsnummer", + rubrik: "Umsatzsteuer-Identifikationsnummer", + grund: "§ 27a UStG", + kravs: true, + hjalp: "VAT-nummer, om ett sådant finns.", + }, + { + id: "tillsyn", + rubrik: "Aufsichtsbehörde", + grund: "§ 5 Abs. 1 Nr. 3 DDG", + kravs: false, + hjalp: "Endast om verksamheten kräver tillstånd. Diagnostikverktyg gör normalt inte det.", + }, + { + id: "ansvarig_innehall", + rubrik: "Inhaltlich Verantwortlicher", + grund: "§ 18 Abs. 2 MStV", + kravs: false, + hjalp: "Krävs för journalistiskt-redaktionellt innehåll. En produktwebbplats behöver det sällan.", + }, + { + id: "dataskyddsombud", + rubrik: "Datenschutzbeauftragter", + grund: "Art. 37 DSGVO", + kravs: false, + hjalp: "Krävs vid regelbunden och systematisk övervakning i stor omfattning, eller känsliga uppgifter i stor omfattning.", + }, +]; + +/** + * Handlingar som ska finnas och nås från varje sida. + * + * `nar` säger när kravet inträder — flera av dem är nya, och en fot som + * byggdes för två år sedan saknar dem utan att någon märkt det. + */ +export const HANDLINGAR = [ + { + id: "impressum", + rubrik: "Impressum", + grund: "§ 5 DDG (ersatte § 5 TMG den 14 maj 2024)", + kravs: true, + nar: "Alltid, för kommersiella digitala tjänster.", + }, + { + id: "dataskydd", + rubrik: "Datenschutzerklärung", + grund: "Art. 13–14 DSGVO", + kravs: true, + nar: "Alltid när personuppgifter behandlas.", + }, + { + id: "villkor", + rubrik: "Allgemeine Geschäftsbedingungen", + grund: "§§ 305 ff. BGB", + kravs: true, + nar: "När standardvillkor används.", + }, + { + id: "tillganglighet", + rubrik: "Erklärung zur Barrierefreiheit", + grund: "BFSG (Barrierefreiheitsstärkungsgesetz), i kraft 28 juni 2025", + kravs: true, + nar: "Tjänster till konsument. Mikroföretag som endast tillhandahåller tjänster är undantagna.", + }, + { + id: "tvistlosning", + rubrik: "Verbraucherstreitbeilegung", + grund: "§ 36 VSBG", + kravs: true, + nar: "Besked om man deltar i tvistlösning inför konsumentskiljenämnd — även ett nej ska stå.", + }, + { + id: "visselblasare", + rubrik: "Hinweisgeberkanal", + grund: "HinSchG", + kravs: false, + nar: "Från 50 anställda.", + }, +]; + +/** + * Vad som INTE ska stå i foten längre. + * + * En upphävd hänvisning är inte harmlös: den ser ut som en upplysning och + * pekar ingenstans. + */ +export const AVSKAFFAT = [ + { + id: "odr", + rubrik: "Länk till EU:s ODR-plattform", + grund: "Förordning (EU) 524/2013 upphävd genom förordning (EU) 2024/3228", + text: + "Plattformen togs ur drift den 20 juli 2025 och tog inte emot nya ärenden efter 20 mars 2025. Länken ska tas bort helt, inte bytas ut.", + }, + { + id: "tmg", + rubrik: 'Hänvisning till "§ 5 TMG"', + grund: "Telemediengesetz ersatt av Digitale-Dienste-Gesetz 14 maj 2024", + text: "Plikten är oförändrad, men paragrafen heter § 5 DDG. Enklast är att inte hänvisa till paragrafen alls.", + }, +]; + +/** + * Vilka obligatoriska fält som saknas. + * + * Driften kan spärra en driftsättning på den här listan i stället för att + * upptäcka bristen när ett avmahnungsbrev kommer. + */ +export function saknade(uppgifter = {}) { + return FALT.filter((f) => f.kravs && !String(uppgifter[f.id] ?? "").trim()).map((f) => ({ + id: f.id, + rubrik: f.rubrik, + grund: f.grund, + })); +} + +/** Är uppgifterna kompletta nog för att visas som ett riktigt Impressum? */ +export function komplett(uppgifter = {}) { + return saknade(uppgifter).length === 0; +} diff --git a/felsokning/services-backup/gemensam/forsakring.mjs b/felsokning/services-backup/gemensam/forsakring.mjs new file mode 100644 index 0000000..f615863 --- /dev/null +++ b/felsokning/services-backup/gemensam/forsakring.mjs @@ -0,0 +1,227 @@ +// ALVA-SPEC-041 · Försäkringsvillkor som underlag. +// +// ---- Varför den här modulen aldrig svarar ja eller nej ------------------ +// +// Den avgör ingenting. Den kan inte avgöra något, och en version som såg +// ut att göra det vore skadlig på ett sätt som inte märks förrän någon +// förlitat sig på den. +// +// Skälet är inte försiktighet utan sakförhållandet: vad som gäller +// bestäms av KUNDENS EGET försäkringsbrev och de villkor som gällde när +// avtalet tecknades — inte av vad ett bolag skriver på sin webbplats i +// dag, och inte av vad vi noterat. Samma bolag har flera produkter med +// olika gränser, gränserna ändras, och ett tecknat avtal följer sina egna +// villkor även efter att bolaget ändrat dem. +// +// Modulen ger därför tre saker och inte ett fjärde: +// +// 1. VAD SOM AVGÖR frågan — de villkorstyper som styr maskinskada, +// oberoende av bolag. Den delen är stabil. +// 2. VAD REGISTRET SÄGER — noterade gränser per bolag och produkt, med +// källa och datum. Underlag för ett samtal, inte ett besked. +// 3. VAD SOM MÅSTE KONTROLLERAS i det enskilda fallet. +// +// Det fjärde — ett utfall — finns inte, och `bedom()` returnerar med +// avsikt inget fält som liknar ett. +// +// ---- Registrets hållbarhet ---------------------------------------------- +// +// Uppgifterna nedan är avlästa från bolagens egna sidor vid det datum som +// står på varje post. De åldras. En post äldre än GILTIGHETSTID_DAGAR +// märks som föråldrad i gränssnittet i stället för att tigas ihjäl — +// en gammal siffra som ser aktuell ut är värre än ingen siffra. +// +// Organisationen kan lägga till bolag och produkter. Ett register som +// bara vi fyller kommer aldrig att täcka de mindre bolagen. + +/** En post äldre än så här märks som föråldrad. */ +export const GILTIGHETSTID_DAGAR = 180; + +/** + * Vad som avgör en maskinskadefråga, oavsett bolag. + * + * Den här listan är modulens stabila del. Gränserna nedanför ändras; + * frågorna gör det inte. + */ +export const AVGORANDE_VILLKOR = [ + { + id: "alder", + rubrik: "Vehicle age", + fraga: "Is the vehicle within the age limit of the policy in force?", + text: "Machine damage cover ends at an age limit that differs by insurer and by product tier within the same insurer.", + }, + { + id: "stracka", + rubrik: "Odometer", + fraga: "Is the recorded distance within the limit?", + text: "Age and distance are alternative ceilings — whichever is reached first ends the cover. The odometer reading therefore has to be documented, not estimated.", + }, + { + id: "plotsligt", + rubrik: "Sudden and unforeseen", + fraga: "Is the failure sudden and unforeseen, rather than gradual?", + text: "Machine damage cover is written for sudden internal failure. Gradual deterioration, wear and ageing are normally excluded — and this is the exclusion most claims turn on.", + }, + { + id: "komponent", + rubrik: "Component in scope", + fraga: "Is the failed component named in the policy's list?", + text: "Cover is usually limited to an enumerated list — powertrain, steering, brake and electrical systems are common; wear parts, exhaust and bodywork usually are not.", + }, + { + id: "underhall", + rubrik: "Maintenance history", + fraga: "Has the vehicle been serviced according to the manufacturer's schedule?", + text: "Neglected or undocumented servicing is a common ground for reduction or refusal. The service record is evidence, and its absence is also evidence.", + }, + { + id: "orsak", + rubrik: "Excluded cause", + fraga: "Was the failure caused by something the policy excludes?", + text: "Wrong fuel, lack of oil or coolant, frost damage, tuning or modification, competition use and consequential damage from an earlier unrepaired fault are typical exclusions.", + }, + { + id: "sjalvrisk", + rubrik: "Deductible", + fraga: "What is the deductible, and does it exceed the repair cost?", + text: "Deductibles are often graded by vehicle age. A claim below the deductible is not worth filing, and saying so early is part of the service.", + }, +]; + +/** + * Noterade gränser per bolag och produkt. + * + * `kontrollerad` är avläsningsdatum, inte giltighetsdatum. Ingen post här + * uttalar sig om vad som gäller för en enskild kund. + */ +export const BOLAG = [ + { + id: "if", + namn: "If", + marknad: "SE", + produkter: [ + { namn: "Maskinskadeförsäkring", maxAlderAr: 10, maxMil: 12_000 }, + { namn: "Stor bilförsäkring", maxAlderAr: 10, maxMil: 15_000 }, + ], + kalla: "https://www.if.se/privat/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "folksam", + namn: "Folksam", + marknad: "SE", + produkter: [ + { namn: "Maskinskadeförsäkring", maxAlderAr: 8, maxMil: 15_000 }, + { namn: "Bilförsäkring Stor", maxAlderAr: 10, maxMil: 15_000 }, + ], + kalla: "https://www.folksam.se/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "trygghansa", + namn: "Trygg-Hansa", + marknad: "SE", + produkter: [ + { namn: "Maskinskadeförsäkring", maxAlderAr: 8, maxMil: 12_000 }, + { namn: "Bilförsäkring stor / märkesförsäkring", maxAlderAr: 10, maxMil: 15_000 }, + ], + kalla: "https://www.trygghansa.se/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "svedea", + namn: "Svedea", + marknad: "SE", + produkter: [{ namn: "Maskinskadeförsäkring", maxAlderAr: 10, maxMil: 15_000 }], + kalla: "https://www.svedea.se/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, + { + id: "watercircles", + namn: "WaterCircles", + marknad: "SE", + produkter: [{ namn: "Maskinskadeförsäkring", maxAlderAr: 12, maxMil: 17_000 }], + kalla: "https://www.watercircles.se/tips-och-rad/maskinskadeforsakring/10925/", + kontrollerad: "2026-08-06", + }, + { + id: "hedvig", + namn: "Hedvig", + marknad: "SE", + produkter: [{ namn: "Maskinskadeskydd", maxAlderAr: 8, maxMil: 12_000 }], + kalla: "https://www.hedvig.com/se/forsakringar/bilforsakring/maskinskadeforsakring", + kontrollerad: "2026-08-06", + }, +]; + +/** Är posten äldre än giltighetstiden? */ +export function foraldrad(post, nu = new Date()) { + const d = new Date(post.kontrollerad); + if (Number.isNaN(d.getTime())) return true; + return (nu - d) / 86_400_000 > GILTIGHETSTID_DAGAR; +} + +/** Bredaste och smalaste noterade gräns — spannet, inte ett svar. */ +export function spann(bolag = BOLAG) { + const produkter = bolag.flatMap((b) => b.produkter); + if (produkter.length === 0) return null; + return { + alderAr: { minsta: Math.min(...produkter.map((p) => p.maxAlderAr)), storsta: Math.max(...produkter.map((p) => p.maxAlderAr)) }, + mil: { minsta: Math.min(...produkter.map((p) => p.maxMil)), storsta: Math.max(...produkter.map((p) => p.maxMil)) }, + }; +} + +/** + * Underlag för ett samtal med kunden. INTE ett utfall. + * + * Returnerar avsiktligt inget fält som säger täckt eller ej. Det som + * returneras är: vilka noterade produkter fordonet ligger inom respektive + * utanför, vilka frågor som återstår, och det obligatoriska nästa steget + * — att läsa kundens eget försäkringsbrev. + * + * Ett fordon som ligger utanför SAMTLIGA noterade gränser får ändå inget + * "nej". Registret kan vara ofullständigt, produkten kan vara en annan, + * och villkoren vid tecknandet kan ha varit andra. + */ +export function bedom({ alderAr, mil, bolagId } = {}, register = BOLAG, nu = new Date()) { + const urval = bolagId ? register.filter((b) => b.id === bolagId) : register; + + const inom = []; + const utanfor = []; + const oklart = []; + + for (const b of urval) { + for (const p of b.produkter) { + const post = { bolag: b.namn, produkt: p.namn, gransAr: p.maxAlderAr, gransMil: p.maxMil, kalla: b.kalla, foraldrad: foraldrad(b, nu) }; + const alderKant = typeof alderAr === "number"; + const milKant = typeof mil === "number"; + if (!alderKant && !milKant) { + oklart.push(post); + continue; + } + const overAlder = alderKant && alderAr > p.maxAlderAr; + const overMil = milKant && mil > p.maxMil; + if (overAlder || overMil) utanfor.push(post); + else if (alderKant && milKant) inom.push(post); + else oklart.push(post); + } + } + + return { + inom, + utanfor, + oklart, + // Frågorna som återstår oavsett vad gränserna säger. Ålder och + // sträcka är bara två av sju, och de fem andra avgör oftare. + kvarstaende: AVGORANDE_VILLKOR.filter((v) => !["alder", "stracka"].includes(v.id)), + nastaSteg: + "Read the customer's own policy document and the terms in force when it was taken out. The register is a starting point for the conversation, never the answer.", + }; +} + +/** Förbehållet. Ska stå i varje vy som visar registret. */ +export const FORBEHALL = + "ALVA does not decide whether a claim is covered and cannot do so. Cover is determined by the customer's own " + + "policy and the terms in force when it was taken out — not by this register, which records what insurers " + + "published on the dates shown. Figures age, insurers differ between their own product tiers, and an existing " + + "policy keeps its original terms. Use this to frame the conversation and the questions; the insurer decides."; diff --git a/felsokning/services-backup/gemensam/garantier.mjs b/felsokning/services-backup/gemensam/garantier.mjs new file mode 100644 index 0000000..18fde23 --- /dev/null +++ b/felsokning/services-backup/gemensam/garantier.mjs @@ -0,0 +1,396 @@ +// ALVA-SPEC-040 · Garantiregimer, EU och USA. +// +// ---- Vad den här modulen är, och inte är ------------------------------- +// +// Den är beslutsstöd för verkstaden, inte juridisk rådgivning. Varje regel +// bär sin källa och det datum uppgiften kontrollerades, så att den som +// lutar sig mot den kan gå till originaltexten i stället för att tro på +// oss. En regel utan källa hör inte hemma här. +// +// ---- Den skiljelinje allt annat hänger på ------------------------------ +// +// LAGSTADGAT och AVTALAT är inte samma sak, och blandas ihop dagligen i +// verkstadsledet. +// +// Lagstadgat Följer av lag eller förordning. Gäller oavsett vad någon +// skrivit i ett garantihäfte. Går inte att avtala bort mot +// en konsument. +// +// Avtalat Tillverkarens frivilliga löfte. Villkoren är dess egna +// och varierar mellan märken, marknader och årsmodeller. +// +// Det som förvånar är hur lite av det verkstaden dagligen kallar "garanti" +// som faktiskt är lagstadgat: +// +// NYBILSGARANTI avtalad. Ingen lag i EU eller USA kräver den. +// ROSTSKYDDSGARANTI avtalad. Ingen lag kräver den, någonstans. +// LACKGARANTI avtalad. +// DRIVLINEGARANTI avtalad. +// AVGASGARANTI LAGSTADGAD i bägge jurisdiktionerna, med olika +// längd, och den är den enda av dem som är det. +// +// Att påstå att en rostskyddsgaranti är lagstadgad är alltså fel, och att +// tro att en avgasgaranti bara är en tillverkarutfästelse är fel åt andra +// hållet — det andra felet kostar kunden pengar. +// +// ---- Vad som INTE finns här -------------------------------------------- +// +// Inga märkesvillkor. De är tusentals, ändras per årsmodell och marknad, +// och en föråldrad siffra här vore värre än ingen siffra alls. Modulen +// säger vad LAGEN kräver som golv och vilka kategorier av avtalade +// garantier som brukar finnas — inte vad ett visst märke lovar. + +/** Datum då uppgifterna nedan senast kontrollerades mot källorna. */ +export const KONTROLLERAD = "2026-08-06"; + +/** + * Regimerna. + * + * `grund` skiljer lagstadgat från avtalat. `bärare` säger VEM som svarar — + * en fråga verkstaden behöver besvara innan den ringer någon: säljaren + * (EU:s konsumentköp) är sällan tillverkaren. + */ +export const REGIMER = [ + // ---- EU, lagstadgat --------------------------------------------------- + { + id: "EU-KONFORMITET", + jurisdiktion: "EU", + grund: "lag", + bärare: "säljaren", + rubrik: "Legal guarantee of conformity", + kortform: "2 years minimum, seller liable", + källa: "Directive (EU) 2019/771, Art. 10–11", + källänk: "https://eur-lex.europa.eu/eli/dir/2019/771/oj/eng", + period: { år: 2 }, + text: + "The seller is liable for any lack of conformity existing at delivery that becomes apparent within two years. A lack of conformity appearing within one year is presumed to have existed at delivery — the seller must prove otherwise. Member States may set longer periods and may extend the reversed burden of proof to two years.", + // Det här är det fel verkstaden gör oftast: skickar kunden till + // tillverkaren för något som är säljarens ansvar. + anmärkning: + "This is a claim against the SELLER, not the manufacturer, and it exists independently of any manufacturer warranty.", + avvikelser: [ + { marknad: "Sverige", text: "Three years (konsumentköplagen 2022:260), reversed burden of proof two years." }, + { marknad: "Frankrike, Portugal", text: "Reversed burden of proof extended to the full two years." }, + ], + }, + { + id: "EU-MVBER", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Servicing outside the authorised network does not void the warranty", + kortform: "Independent servicing is protected", + källa: + "Regulation (EU) 461/2010, prolonged to 31 May 2028 by Regulation (EU) 2023/822; Supplementary Guidelines 2010/C 138/05", + källänk: "https://eur-lex.europa.eu/eli/reg/2023/822/oj/eng", + period: null, + text: + "A manufacturer may not make its warranty conditional on the vehicle being serviced within the authorised network for work not covered by the warranty, nor on the exclusive use of its own branded parts for such work. Warranty cover therefore survives servicing by an independent workshop, provided the work follows the manufacturer's specification and uses parts of matching quality.", + anmärkning: + "The rule that matters most to an independent workshop. Document the specification followed and the part quality — that documentation is what makes the protection usable if the claim is later contested.", + avvikelser: [], + }, + { + id: "EU-AVGAS-EURO6", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Emissions durability — Euro 5/6", + kortform: "5 years / 100 000 km in-service conformity", + källa: "Regulation (EC) No 715/2007, Art. 4(2) and Annex VII", + källänk: "https://eur-lex.europa.eu/eli/reg/2007/715/oj/eng", + period: { år: 5, km: 100_000 }, + text: + "In-service conformity is checked for up to 5 years or 100 000 km, whichever comes first. Durability testing of pollution control devices for type approval covers 160 000 km.", + anmärkning: + "A durability requirement on the manufacturer's type approval — not a consumer warranty in the US sense. It gives no direct claim to the owner; the corresponding owner-facing cover is contractual.", + avvikelser: [], + }, + { + id: "EU-AVGAS-EURO7", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Emissions durability — Euro 7", + kortform: "8 years / 160 000 km, plus 25 % additional lifetime", + källa: "Regulation (EU) 2024/1257, Art. 5 and Annex IV", + källänk: "https://eur-lex.europa.eu/eli/reg/2024/1257/oj/eng", + period: { år: 8, km: 160_000 }, + text: + "The main lifetime for M1 and N1 rises from 5 years/100 000 km to 8 years/160 000 km, with an additional lifetime of a further 25 % (to 10 years/200 000 km) governed by durability multipliers.", + anmärkning: + "Applies to new types from 29 November 2026 and to all new vehicles from 29 November 2027. A vehicle registered before those dates keeps the Euro 6 regime.", + avvikelser: [], + }, + { + id: "EU-BATTERI-EURO7", + jurisdiktion: "EU", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Battery durability — Euro 7", + kortform: "≥80 % at 5 y/100 000 km, ≥72 % at 8 y/160 000 km", + källa: "Regulation (EU) 2024/1257, Art. 8 and Annex VII", + källänk: "https://eur-lex.europa.eu/eli/reg/2024/1257/oj/eng", + period: { år: 8, km: 160_000 }, + text: + "For passenger cars (M1) the battery must retain at least 80 % of its energy storage capability after 5 years or 100 000 km, and not fall below 72 % after 8 years or 160 000 km.", + anmärkning: + "A type-approval durability requirement. A measured State of Health below the threshold is evidence in a claim, not a claim in itself — and it must be measured with a traceable instrument to carry that weight.", + avvikelser: [], + }, + + // ---- USA, lagstadgat -------------------------------------------------- + { + id: "US-AVGAS-PRESTANDA", + jurisdiktion: "US", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Federal emissions performance warranty", + kortform: "2 years / 24 000 miles", + källa: "Clean Air Act § 207, 42 U.S.C. § 7541; 40 CFR Part 85 Subpart V", + källänk: "https://www.ecfr.gov/current/title-40/chapter-I/subchapter-C/part-85/subpart-V", + period: { år: 2, miles: 24_000 }, + text: + "The manufacturer must repair, at no cost, a vehicle that fails an approved emissions test during the first 2 years or 24 000 miles because of a defect in an emission control component.", + anmärkning: + "Runs from the date of sale to the first owner and follows the vehicle — a later owner inherits the remaining period.", + avvikelser: [], + }, + { + id: "US-AVGAS-HUVUDDELAR", + jurisdiktion: "US", + grund: "lag", + bärare: "tillverkaren", + rubrik: "Federal warranty — specified major emission control components", + kortform: "8 years / 80 000 miles", + källa: "Clean Air Act § 207(i); 40 CFR § 85.2103", + källänk: "https://www.ecfr.gov/current/title-40/chapter-I/subchapter-C/part-85/subpart-V/section-85.2103", + period: { år: 8, miles: 80_000 }, + text: + "Specified major emission control components carry 8 years or 80 000 miles. For light-duty vehicles, light-duty trucks and medium-duty passenger vehicles these include catalytic converters, SCR catalysts and related components, and particulate filters and traps, on both spark-ignition and compression-ignition engines.", + anmärkning: + "Routinely missed. A catalytic converter on a six-year-old vehicle is frequently quoted to the customer as a paid repair when it is not one.", + avvikelser: [], + }, + { + id: "US-MMWA", + jurisdiktion: "US", + grund: "lag", + bärare: "tillverkaren", + rubrik: "No tie-in: aftermarket parts and independent service", + kortform: "Warranty cannot require branded parts or dealer service", + källa: "Magnuson–Moss Warranty Act, 15 U.S.C. § 2302(c)", + källänk: "https://www.law.cornell.edu/uscode/text/15/2302", + period: null, + text: + "A warrantor may not condition a written or implied warranty on the consumer using an article or service identified by brand or corporate name, unless it is provided free of charge or the FTC has granted a waiver.", + anmärkning: + "The US counterpart to the MVBER rule. Denying a claim requires the manufacturer to show that a specific part actually caused the failure — which is a question of evidence, and therefore a question of what the workshop documented.", + avvikelser: [], + }, + { + id: "US-CA-AVGAS", + jurisdiktion: "US-CA", + grund: "lag", + bärare: "tillverkaren", + rubrik: "California emissions warranty", + kortform: "3 y/50 000 mi, high-priced parts 7 y/70 000 mi", + källa: "13 CCR §§ 2035–2039 (CARB)", + källänk: "https://www.law.cornell.edu/regulations/california/13-CCR-2039", + period: { år: 3, miles: 50_000 }, + text: + "Emission-related parts are warranted 3 years or 50 000 miles. A single warranted part above an annually indexed cost threshold is covered 7 years or 70 000 miles. The threshold was USD 770 for model year 2025.", + anmärkning: + "Applies in California and in the states that have adopted CARB standards — always check which regime the individual vehicle was certified to before quoting the federal figures.", + avvikelser: [], + }, + { + id: "US-CA-BATTERI", + jurisdiktion: "US-CA", + grund: "lag", + bärare: "tillverkaren", + rubrik: "California ZEV battery warranty — Advanced Clean Cars II", + kortform: "≥70 % SoH at 8 y/100 000 mi (MY2026–2030)", + källa: "Advanced Clean Cars II, 13 CCR § 1962.4 (CARB)", + källänk: "https://ww2.arb.ca.gov/resources/fact-sheets/california-vehicle-and-emissions-warranty-periods", + period: { år: 8, miles: 100_000 }, + text: + "For model years 2026–2030 the battery must be warranted to retain at least 70 % of certified State of Health for 8 years or 100 000 miles; from model year 2031 the floor rises to 75 %. Propulsion-related parts follow the 3 y/50 000 mi and 7 y/70 000 mi structure.", + anmärkning: "Separate from the durability requirement that range be retained over 10 years/150 000 miles.", + avvikelser: [], + }, + + // ---- Avtalade kategorier ---------------------------------------------- + // + // Ingen av dem har lagstöd. De tas med därför att de är vad kunden MENAR + // när den säger garanti, och verkstaden måste kunna säga vilket slag av + // löfte den talar om. Spannen är vad som förekommer på marknaden, inte + // vad något märke utfäster. + { + id: "AVTAL-NYBIL", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "New-vehicle warranty", + kortform: "Typically 2–7 years — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "The manufacturer's general warranty against defects. Length, mileage cap and exclusions are set by the manufacturer and vary by brand, market and model year.", + anmärkning: + "No law in the EU or the US requires it. In the EU it sits on top of the statutory conformity right against the seller and cannot reduce it.", + avvikelser: [], + }, + { + id: "AVTAL-ROST", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Corrosion / anti-perforation warranty", + kortform: "Typically 6–12 years — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "Cover against rust perforation from the inside out. Usually excludes surface corrosion, stone-chip damage and rust caused by unrepaired accident damage, and is commonly conditioned on documented periodic inspection.", + anmärkning: + "There is no statutory corrosion warranty anywhere in the EU or the US. The inspection condition is the usual reason a claim is refused — which makes the inspection record the decisive evidence.", + avvikelser: [], + }, + { + id: "AVTAL-LACK", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Paint warranty", + kortform: "Typically 2–3 years — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: "Cover against paint defects. Normally separate from, and shorter than, the corrosion warranty.", + anmärkning: "", + avvikelser: [], + }, + { + id: "AVTAL-DRIVLINA", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Powertrain warranty", + kortform: "Varies — contractual, not statutory", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "Engine, transmission and driveline cover, often longer than the general new-vehicle warranty and frequently conditioned on servicing at the prescribed intervals.", + anmärkning: + "The service-interval condition is legitimate — what is not legitimate is requiring that the service be performed by the authorised network (MVBER; Magnuson–Moss § 2302(c)).", + avvikelser: [], + }, + { + id: "AVTAL-BATTERI", + jurisdiktion: "EU/US", + grund: "avtal", + bärare: "tillverkaren", + rubrik: "Traction battery warranty", + kortform: "Commonly 8 years / 160 000 km with a State of Health floor", + källa: "Manufacturer's own warranty terms", + källänk: null, + period: null, + text: + "Commercial cover for the traction battery, usually expressed as a period plus a retained State of Health, often around 70 %.", + anmärkning: + "Sits alongside the statutory durability requirements (Euro 7 in the EU, ACC II in California) and is frequently more generous than them.", + avvikelser: [], + }, +]; + +/** Jurisdiktioner som går att välja. `US-CA` ärver de federala reglerna. */ +export const JURISDIKTIONER = [ + { id: "EU", namn: "European Union" }, + { id: "US", namn: "United States (federal)" }, + { id: "US-CA", namn: "United States — California / CARB states" }, +]; + +/** + * Regimer som gäller för en jurisdiktion. + * + * Kalifornien ÄRVER de federala reglerna och lägger sina egna ovanpå. + * Att visa enbart de kaliforniska hade utelämnat Magnuson–Moss, som är + * den regel verkstaden behöver mest av alla. + */ +export function regimerFor(jurisdiktion) { + const gäller = (r) => { + if (r.jurisdiktion === "EU/US") return true; + if (jurisdiktion === "US-CA") return r.jurisdiktion === "US" || r.jurisdiktion === "US-CA"; + return r.jurisdiktion === jurisdiktion; + }; + return REGIMER.filter(gäller); +} + +/** + * Vilka LAGSTADGADE regimer ett visst fordon fortfarande ligger inom. + * + * Returnerar tre listor i stället för ett filtrerat urval, därför att + * "utanför" och "går inte att avgöra" är olika besked och bara det ena + * betyder att kunden ska betala. Saknas ålder eller mätarställning + * hamnar regimen under `oklart` — aldrig under `utanfor`. Att påstå att + * en garanti har gått ut när underlaget inte räcker är precis den sortens + * obefogade säkerhet produkten finns för att undvika. + * + * @param jurisdiktion "EU" | "US" | "US-CA" + * @param fordon { alderAr?, km?, miles? } + */ +export function mojligenGallande(jurisdiktion, fordon = {}) { + const inom = []; + const utanfor = []; + const oklart = []; + + for (const r of regimerFor(jurisdiktion)) { + if (r.grund !== "lag") continue; + if (!r.period) { + // Regler utan tidsgräns — MVBER, Magnuson–Moss — gäller alltid. + inom.push(r); + continue; + } + + const bedömningar = []; + if (typeof r.period.år === "number") { + bedömningar.push(typeof fordon.alderAr === "number" ? fordon.alderAr <= r.period.år : null); + } + if (typeof r.period.km === "number") { + const km = fordon.km ?? (typeof fordon.miles === "number" ? fordon.miles * 1.609344 : null); + bedömningar.push(typeof km === "number" ? km <= r.period.km : null); + } + if (typeof r.period.miles === "number") { + const miles = fordon.miles ?? (typeof fordon.km === "number" ? fordon.km / 1.609344 : null); + bedömningar.push(typeof miles === "number" ? miles <= r.period.miles : null); + } + + // Perioderna är "whichever comes first": ett överskridet mått räcker + // för att regimen ska vara passerad, oavsett vad det andra säger. + if (bedömningar.some((b) => b === false)) utanfor.push(r); + else if (bedömningar.some((b) => b === null)) oklart.push(r); + else inom.push(r); + } + + return { inom, utanfor, oklart }; +} + +/** + * Det verkstaden behöver få veta oavsett ärende. + * + * Bägge jurisdiktionerna har en regel som säger samma sak: att arbetet + * utförs här upphäver inte garantin. Den är värd att lyfta separat, + * eftersom motsatsen ofta påstås — och ibland av kunden själv. + */ +export function skyddadReparation(jurisdiktion) { + return regimerFor(jurisdiktion).find((r) => r.id === (jurisdiktion === "EU" ? "EU-MVBER" : "US-MMWA")) ?? null; +} + +/** Förbehållet. Står i varje vy som visar regimerna. */ +export const FORBEHALL = + "Decision support, not legal advice. Figures are statutory minimums verified against the cited sources on " + + `${KONTROLLERAD}; contractual warranties are set by the manufacturer and are not reproduced here. ` + + "Verify against the current text of the cited instrument before relying on it in a dispute."; diff --git a/felsokning/services-backup/gemensam/grind.mjs b/felsokning/services-backup/gemensam/grind.mjs new file mode 100644 index 0000000..490b95f --- /dev/null +++ b/felsokning/services-backup/gemensam/grind.mjs @@ -0,0 +1,313 @@ +// Kvalitetsgrinden på serversidan. +// +// Revisionen (docs/QUALITY-AUDIT.md, C-2) fann att grinden bara fanns i +// webbläsaren. Ett ärende kunde därmed avslutas utan evidens genom ett +// anrop till API:t, och rapporten presenterade det som komplett — trots +// att dokumentationen påstod att systemet aldrig kan skriva en slutsats +// ECM inte godkänt. +// +// Modulen utvärderar de obligatoriska raderna direkt mot händelseloggen. +// Klientens ecm.ts har fler rader (råd, rekommendationer, härledd +// evidensnivå per påstående) och fortsätter vara vägledningen i +// gränssnittet. Det som blockerar ett avslut bor här. +// +// Avsiktligt: reglerna är rena funktioner av (händelser, metodik). Ingen +// databasåtkomst, ingen tid, inget slumpmässigt — samma logg ger alltid +// samma utfall, vilket är vad som gör en spärr granskbar. + +import { granskaSlutsats } from "./motivering.mjs"; +import { inomTak, sakerhetstak } from "./sakerhet.mjs"; +import { STANDARD, arJakande, arendetypNu, t } from "./sprak/index.mjs"; + +const UNDANTAG_MOTIVERAT = (h) => typeof h.undantag === "string" && h.undantag.trim().length > 0; + +// Frågor som måste besvaras jakande för att arbetet ska få avslutas. +// Speglar SPARRFRAGOR i app/src/felsokning/metodik.ts; ett test jämför +// listorna så de inte kan glida isär. +export const SPARRFRAGOR = { + "hogvolt/sakerhet/behorighet": "grind.hogvolt.behorighet", + "hogvolt/sakerhet/avstangt": "grind.hogvolt.spanningslos", +}; + +/** Händelser av en viss typ. */ +const av = (handelser, typ) => handelser.filter((h) => h.typ === typ); + +/** + * Utvärderar avslutsgrinden. + * + * @returns lista med hinder; tom lista betyder att ärendet får avslutas. + */ +export function grinda(handelser, metodik, sprak = STANDARD) { + const hinder = []; + + // Hindret bär både nyckeln och den färdiga texten. Nyckeln därför att + // en klient som byter språk ska kunna rendera om utan att fråga + // servern; texten därför att ett hinder som passerar genom en logg + // eller en PDF måste vara läsbart utan katalogen. + const krav = (id, nyckel, ok, detaljNyckel, variabler) => { + if (ok) return; + const post = { id, nyckel, rubrik: t(sprak, nyckel, variabler) }; + if (detaljNyckel) { + post.detaljNyckel = detaljNyckel; + post.detalj = t(sprak, detaljNyckel, variabler); + } + hinder.push(post); + }; + + // Fritext som kommer ur data, inte ur katalogen — namnen på metodikens + // kontroller. De står på metodikens språk och kan inte översättas här. + const kravMedData = (id, nyckel, ok, detalj) => { + if (ok) return; + const post = { id, nyckel, rubrik: t(sprak, nyckel) }; + if (detalj) post.detalj = detalj; + hinder.push(post); + }; + + krav("objekt", "grind.objekt", handelser.some((h) => h.typ === "objekt_identifierat")); + + // Historiken får besvaras nekande, men då krävs en motivering — annars + // vore "Nej" ett sätt att hoppa över kontrollen utan att det syns. + const historik = av(handelser, "historik_kontrollerad").at(-1); + krav( + "historik", + "grind.historik", + Boolean(historik) && (historik.kontrollerad || (historik.kommentar ?? "").trim().length > 0), + historik ? "grind.historik.nekad" : "grind.historik.saknas", + ); + + // Mätarställningen ska vara FOTOGRAFERAD, inte bara inskriven. + // + // Skillnaden är hela dess bevisvärde. En inskriven siffra är teknikerns + // påstående om vad som stod på mätaren; ett foto visar vad som stod + // där. Mätarställningen är samtidigt det som avgör garanti- och + // försäkringsfrågor i efterhand, och den enda uppgift i ärendet som + // någon kan ha ett intresse av att skriva fel. + // + // Klienten krävde redan fotot — men bara klienten, och en regel som + // bara finns i gränssnittet är en vana, inte en spärr. Det är samma + // förhållande som QUALITET C-2 gällde, fast åt andra hållet: här var + // det servern som var svagare, vilket är värre, eftersom servern är + // den som är auktoritativ. + // + // Undantag finns och måste finnas — en trasig display, en timräknare + // som sitter oåtkomligt, en spegling som inte går att fota bort. Men + // undantaget kräver en motivering, av samma skäl som ett nekat + // historiksvar gör det. + for (const [lage, nyckel] of [ + ["ingaende", "grind.matarstallning.ingaende"], + ["utgaende", "grind.matarstallning.utgaende"], + ]) { + const poster = av(handelser, "matarstallning").filter((h) => h.lage === lage); + const dokumenterad = poster.length > 0; + const fotograferad = poster.some((h) => h.bilagaId || h.dataUrl); + const motiverat = poster.some((h) => (h.undantag ?? "").trim().length > 0); + krav( + `matarstallning_${lage}`, + nyckel, + dokumenterad && (fotograferad || motiverat), + dokumenterad ? "grind.matarstallning.ej_foto" : "grind.matarstallning.saknas", + ); + } + + krav("reproducering", "grind.reproducering", handelser.some((h) => h.typ === "reproducering")); + + krav("felorsak", "grind.felorsak", handelser.some((h) => h.typ === "felorsak")); + + // Säkerhetsnivån är ett tak, inte ett val (ALVA-SPEC-071). En felorsak + // som påstår högre säkerhet än underlaget bär spärrar avslutet — inte + // för att slutsatsen är fel, utan för att PÅSTÅENDET om den är det. + // Sänkning prövas aldrig: ärlig osäkerhet är information. + const felorsaken = av(handelser, "felorsak").at(-1); + if (felorsaken?.sakerhet) { + const tak = sakerhetstak(handelser); + krav( + "sakerhetstak", + "grind.sakerhet", + inomTak(felorsaken.sakerhet, tak), + "grind.sakerhet.detalj", + { niva: felorsaken.sakerhet, tak }, + ); + } + + // Åtgärdskedjan. En utebliven åtgärd är ett giltigt utfall — men bara + // när skälet står i loggen. + const atgarder = av(handelser, "atgard_utford"); + const utfordArbete = atgarder.some((h) => h.utford === true); + krav("atgard", "grind.atgard", atgarder.length > 0, "grind.atgard.saknas"); + + if (utfordArbete) { + const beslut = av(handelser, "kundbeslut"); + krav("kundbeslut", "grind.kundbeslut", beslut.length > 0); + + // Hårt fel, inte en varning: arbete utfört trots att kunden avböjt. + krav( + "avbojt_men_utfort", + "grind.kundbeslut.avbojt", + !beslut.some((h) => h.beslut === "avbojt"), + "grind.kundbeslut.avbojt.detalj", + ); + + krav("kvalitetskontroll", "grind.kvalitetskontroll", handelser.some((h) => h.typ === "kvalitetskontroll")); + } + + // Metodikens kontroller: varje kontroll ska bära evidens eller ett + // dokumenterat undantag. Det är den rad som gör metodiken bindande. + const utforda = new Map(); + for (const h of av(handelser, "kontroll_utford")) utforda.set(`${h.stegId}/${h.kontrollId}`, h); + + const saknade = []; + const fotokravande = []; + const foton = av(handelser, "foto").length; + for (const steg of metodik?.steg ?? []) { + for (const kontroll of steg.kontroller ?? []) { + const h = utforda.get(`${steg.id}/${kontroll.id}`); + if (!h) { + saknade.push(`${steg.rubrik} · ${kontroll.text}`); + continue; + } + if (UNDANTAG_MOTIVERAT(h)) continue; + + // En fotokontroll verifieras av fotot. Regeln krävde tidigare ett + // textresultat även här — trots att gränssnittet märker fältet + // "Observation (valfritt)". Grinden nekade därmed avslut på nästan + // varje riktigt ärende, vilket inte syntes så länge klienten hade + // ett eget och mildare villkor (QUALITY-AUDIT-2 · M-7). + if (kontroll.krav === "foto") { + fotokravande.push(kontroll.text); + continue; + } + if (!(h.resultat ?? "").trim()) saknade.push(`${steg.rubrik} · ${kontroll.text} (utan resultat)`); + } + } + kravMedData("metodik_kontroller", "grind.kontroller", saknade.length === 0, saknade.slice(0, 5).join(" · ")); + krav("foton", "grind.foton", foton >= fotokravande.length, fotokravande.length ? "grind.foton.detalj" : undefined, { + kontroller: fotokravande.length, + foton, + }); + + // ALVA-RULE-200 · Slutsatsen. Ett ärende stängs aldrig utan att + // teknikern lämnat ett varför — motiveringen som knyter slutsatsen + // till underlaget, vad som uteslöts, och vad som kvarstår osäkert. + // + // Detta är den rad som saknas i varje verkstadsprotokoll: underlaget + // säger vad som mättes, slutsatsen vad som är fel, men ingenting + // varför det ena medför det andra. Det är precis det steget en + // garantihandläggare eller försäkringsbedömare behöver granska. + const slutsats = av(handelser, "slutsats").at(-1); + for (const brist of granskaSlutsats(slutsats, handelser, sprak)) { + hinder.push({ id: `slutsats_${brist.falt}`, nyckel: brist.nyckel, rubrik: brist.text }); + } + + // Säkerhetsspärrar. Ett nekande svar på en spärrfråga får aldrig + // passera som "besvarad" — se SPARRFRAGOR i klientens metodik.ts. + // Klientens spärr är vägledning; den här är hindret (QUALITET M-2). + for (const [fraga, nyckel] of Object.entries(SPARRFRAGOR)) { + const [metodikId, stegId, frageId] = fraga.split("/"); + if (metodik?.id !== metodikId) continue; + const svaret = av(handelser, "fraga_besvarad").findLast( + (h) => h.stegId === stegId && h.frageId === frageId, + ); + krav(`sparr_${frageId}`, nyckel, arJakande(svaret?.svar), "grind.sparr.ej_uppfyllt"); + } + + // Teknisk eskalering (FGS-1.0 §8, DISS-mönstret): en öppnad förfrågan + // till tillverkare eller garantigivare utan dokumenterat svar spärrar + // avslutet. Bulletinerna kräver att svaret inväntas innan ytterligare + // åtgärder görs — ett ärende som stängs med öppen eskalering har inte + // hela orsakskedjan dokumenterad, och garantianspråket kan ogiltigas. + { + const oppna = new Map(); + for (const h of av(handelser, "eskalering")) { + const nyckel = String(h.referens ?? "").trim(); + if (h.status === "oppnad") oppna.set(nyckel, h); + else if (oppna.has(nyckel)) oppna.delete(nyckel); + else if (nyckel === "" && oppna.size === 1) oppna.clear(); + } + krav("eskalering", "grind.eskalering", oppna.size === 0, "grind.eskalering.detalj", { + referens: [...oppna.keys()].map((r) => r || "—").join(", "), + }); + } + + // Evidensnivån måste överstiga E0 — annars avslutas ett ärende utan + // att någonting alls har dokumenterats. + krav( + "evidens", + "grind.evidens", + handelser.some((h) => ["foto", "video", "matvarde", "observation", "kontroll_utford"].includes(h.typ)), + "grind.evidens.saknas", + ); + + return hinder; +} + +/** + * Extra krav som följer av ärendetypen. Reglerna kommer från regelpaketet + * (ECM Knowledge Library) och är alltså data, inte kod. + */ +export function grindaArendetyp(handelser, regelpaket, sprak = STANDARD) { + // Ärendetypen slås upp i sin nuvarande form. Ett ärende som öppnades + // när typerna hette "Garanti" ska få garantins extra krav även efter + // att de bytt språk — se ARENDETYP_ARV. + const typ = arendetypNu(av(handelser, "arendetyp_satt").at(-1)?.arendetyp); + // Paketet finns i två former: den distribuerade filens rika form + // (arendetypRegler med id/rubrik/detaljVidBrist) och den slimmade + // (arendetyper[typ].krav). Servern matade tidigare in den rika formen + // i en funktion som bara läste den slimmade — varje ärendetypskrav + // blev en tyst no-op på servern, och spärren fanns bara som råd i + // klienten. Det är exakt C-2-mönstret, återuppstått genom ett format. + const regler = [ + ...(regelpaket?.arendetyper?.[typ]?.krav ?? []), + ...(regelpaket?.arendetypRegler?.[typ] ?? []).map((r) => ({ typ: r.krav, rubrik: r.rubrik, detalj: r.detaljVidBrist })), + ]; + const hinder = []; + + const betalareReferens = () => av(handelser, "betalare").some((h) => String(h.referens ?? "").trim()); + const uppfyllt = { + miltal: () => av(handelser, "matarstallning").length > 0, + historik: () => av(handelser, "historik_kontrollerad").some((h) => h.kontrollerad), + foto: () => av(handelser, "foto").length > 0, + // Referensen kan komma ur den skannade arbetsordern ELLER ur en + // registrerad betalare — alla verkstäder skannar inte (FGS-1.0 §4). + claim: () => harReferens(handelser, ["ao_claim", "claim"]) || betalareReferens(), + skadenummer: () => harReferens(handelser, ["ao_skadenummer", "skadenummer"]) || betalareReferens(), + // FGS-1.0 §1/§18: betalaren fastställd — spår och namn — innan + // ärendet kan stängas i de spår där någon annan än kunden betalar. + betalare: () => av(handelser, "betalare").some((h) => String(h.namn ?? "").trim() && String(h.spar ?? "").trim()), + // Goodwill och externa garantigivare kräver godkännande/claim + // authorization INNAN reparationen ersätts (FGS-1.0 §12). + godkannande: () => av(handelser, "betalare").some((h) => String(h.godkannande ?? "").trim()), + }; + + for (const regel of regler) { + const kontroll = uppfyllt[regel.typ ?? regel]; + // En okänd kravtyp får aldrig tolkas som uppfylld. Ett regelpaket som + // inte går att utvärdera ska stoppa avslutet, inte släppa igenom det. + const krav = regel.typ ?? regel; + if (!kontroll) { + hinder.push({ + id: `arendetyp_${krav}`, + nyckel: "grind.arendetyp.okant", + rubrik: t(sprak, "grind.arendetyp.okant", { krav }), + }); + } else if (!kontroll()) { + // Regelpaketets egen rubrik står på regelpaketets språk och går + // inte att översätta här — den är data från kunden, inte vår text. + hinder.push({ + id: `arendetyp_${krav}`, + nyckel: regel.rubrik ? undefined : "grind.arendetyp.krav", + rubrik: regel.rubrik ?? t(sprak, "grind.arendetyp.krav", { krav }), + ...(regel.detalj ? { detalj: regel.detalj } : {}), + }); + } + } + return hinder; +} + +function harReferens(handelser, nycklar) { + for (const h of av(handelser, "arbetsorder_skannad")) { + for (const f of h.falt ?? []) { + if (nycklar.includes(f.id) && String(f.varde ?? "").trim()) return true; + } + } + return false; +} diff --git a/felsokning/services-backup/gemensam/handelser.mjs b/felsokning/services-backup/gemensam/handelser.mjs new file mode 100644 index 0000000..87738f5 --- /dev/null +++ b/felsokning/services-backup/gemensam/handelser.mjs @@ -0,0 +1,226 @@ +// Händelsevalidering vid API-gränsen. +// +// Bakgrund: revisionen (docs/QUALITY-AUDIT.md, C-1 och M-3) fann att servern +// bara kontrollerade att `anvandare` var *en sträng* och `tidpunkt` *sanningsvärd* +// — inte att de var sanna — och att `handelse` togs emot som godtycklig JSON. +// +// Två skilda problem med samma botemedel: +// +// Härkomst Vem som utförde arbetet och när är det som gör loggen till en +// bevis­kedja. Sätts de av anroparen är loggen en signerad behållare +// för overifierade påståenden. Båda härleds nu ur den verifierade +// token respektive serverns klocka. +// +// Form Loggen är append-only. En felaktig post kan aldrig rättas, bara +// kommenteras. Därför måste formen kontrolleras *innan* skrivning — +// efteråt är det för sent för alltid. +// +// Klientens klocka kastas inte bort. Vid offline-arbete är den det enda som +// finns, så den bevaras som `registrerad_tidpunkt` bredvid serverns +// mottagningstid. Glappet blir synligt i stället för osynligt. + +/** Fält som aldrig får komma från klienten. */ +export const SERVERÄGDA_FÄLT = ["anvandare", "tidpunkt"]; + +// Varje händelsetyp med sina obligatoriska fält. Listan är avsiktligt +// fullständig: en typ som inte står här avvisas, eftersom en okänd typ i en +// append-only-logg är permanent skräp. +export const HÄNDELSESCHEMA = { + objekt_identifierat: { objekt: "objekt" }, + arbetsorder_skannad: { falt: "lista" }, + felbeskrivning: { text: "text" }, + arendetyp_satt: { arendetyp: "text" }, + fraga_besvarad: { stegId: "text", frageId: "text", fraga: "text", svar: "text" }, + kontroll_utford: { stegId: "text", kontrollId: "text", text: "text" }, + observation: { text: "text" }, + matvarde: { beskrivning: "text", varde: "text" }, // matdonId valfritt — se M-1 + hypotes: { text: "text", niva: ["medel", "lag"] }, + foto: { beskrivning: "text" }, + video: { beskrivning: "text" }, + matarstallning: { lage: ["ingaende", "utgaende"], varde: "text" }, + historik_kontrollerad: { kontrollerad: "boolean" }, + reproducering: { status: ["ja", "delvis", "nej"], beskrivning: "text" }, + felorsak: { + avvikelse: "text", + orsaker: "lista", + underlag: "lista", + sakerhet: ["hog", "medel", "lag"], + atgard: "text", + }, + atgardsforslag: { beskrivning: "text" }, + kundbeslut: { beslut: ["godkant", "avbojt", "delvis"], kanal: "text" }, + atgard_utford: { beskrivning: "text", utford: "boolean" }, + kvalitetskontroll: { + resultat: ["symptomet_borta", "kvarstar", "delvis", "ej_verifierbar"], + beskrivning: "text", + }, + // FGS-1.0: betalarspåret, eskaleringen och reservdelen är händelser i + // loggen — inte fritext i en kommentar — så att grinden kan räkna på dem. + betalare: { + spar: ["fabriksgaranti", "vagnskadegaranti", "forsakring", "extern_garanti", "leasing", "goodwill", "kund"], + namn: "text", + }, + eskalering: { status: ["oppnad", "besvarad"], beskrivning: "text" }, + reservdel: { artikelnummer: "text", beskrivning: "text" }, + kommentar: { text: "text" }, + kategori_byte: { kategori: "text" }, + inaktivitet_forklarad: { text: "text", minuter: "tal" }, + overlamning: { fran: "text" }, + ansvarig_satt: { ansvarig: "text" }, + ai_svar: { rader: "lista", nastaSteg: "text", modell: "text" }, + export_skapad: { format: "text", version: "tal" }, + // ALVA-RULE-200: teknikerns varför. Fälten valideras utöver formen i + // services/gemensam/motivering.mjs — här kontrolleras bara att de finns. + slutsats: { motivering: "text", uteslutet: "text", kvarstaende: "text" }, + arende_avslutat: {}, +}; + +// ---- Valfria fält (QUALITY-AUDIT-2 · C-5) ------------------------------ +// +// Schemat kontrollerade tidigare bara att de OBLIGATORISKA fälten fanns. +// Det itererade schemats nycklar, aldrig händelsens, så vilket okänt fält +// som helst passerade och sparades ordagrant. +// +// Två garantier vilade på motsatsen. Krypto-shreddingen skyddar en fast +// lista av fältNAMN: ett registreringsnummer på en vanlig observation +// kom aldrig in på listan, krypterades aldrig, och överlevde därför att +// nyckeln förstördes — medan raderingskvittot ändå sa att subjektet var +// raderat. Och delningsfiltret arbetar på typnivå, inte fältnivå, så +// samma fält gick ut i kundens delningslänk. +// +// Därför är listan nedan uttömmande och avvisningen hård. Ett fält som +// inte står här eller bland de obligatoriska finns inte, och en anropare +// som tror sig ha sparat något får aldrig veta att det gick bra. +export const VALFRIA_FÄLT = { + // Bilagefälten ärvs via intersektion med Bilaga i domänmodellen. + arbetsorder_skannad: ["bilagaId", "bilagaHash", "dataUrl"], + foto: ["bilagaId", "bilagaHash", "dataUrl"], + video: ["bilagaId", "bilagaHash", "dataUrl"], + matarstallning: ["undantag", "bilagaId", "bilagaHash", "dataUrl"], + + kontroll_utford: ["resultat", "undantag"], + betalare: ["referens", "godkannande"], + eskalering: ["referens", "kanal"], + reservdel: ["serienummer", "batch", "sparad"], + // `kalla` bär härkomsten för värden som kommit in via ett + // diagnosprotokoll (ALVA-SPEC-020). Fältet måste vara deklarerat: + // integrationen fungerade tidigare bara därför att schemat var öppet. + observation: ["kalla"], + // `kalibreradVidMatning` är serverns stämpel av ett FAKTUM vid + // mottagandet: gällde mätdonets kalibrering då? Grinden får inte + // fråga klockan (samma logg ska alltid ge samma utfall), så faktumet + // måste frysas när det fortfarande är ett faktum. Klientens värde + // skrivs alltid över av mätdonsFakta (TÜV-2 T-13). + matvarde: ["enhet", "matdonId", "matdonBeteckning", "matdonKalibreradTill", "kalibreradVidMatning", "kalla"], + overlamning: ["till"], + historik_kontrollerad: ["kommentar"], + felorsak: ["motivering", "ytterligareKontroller"], + atgardsforslag: ["uppskattadKostnad", "uppskattadTid"], + kundbeslut: ["kommentar", "kontaktperson"], + atgard_utford: ["delar", "motivering"], + slutsats: ["atgardsval", "orsakFastställd"], + // Versionen som gällde VID AVSLUTET. Ritningsstämpeln läste den ur + // den aktuella konstanten, så ett ärende stängt under 1.0 visade 3.1 — + // precis det stämpelns egen beskrivning säger att den inte ska göra. + arende_avslutat: ["signatur", "plattformsversion"], +}; + +/** + * Fält som systemet självt sätter efter valideringen. + * + * `anvandarId` skrivs av tillPost ur den verifierade token, och + * `registrerad_tidpunkt` bevarar klientens klocka vid offline-arbete. + * Bägge måste passera när en redan skriven händelse valideras om. + */ +export const SYSTEMFÄLT = ["typ", "anvandarId", "registrerad_tidpunkt"]; + +// Hypotesen får aldrig anta hög tillförlitlighet — samma regel som +// typsystemet upprätthåller i klienten, upprepad här därför att servern inte +// kan lita på att klienten är vår. +const GILTIG = { + text: (v) => typeof v === "string" && v.length > 0 && v.length <= 20000, + tal: (v) => typeof v === "number" && Number.isFinite(v), + boolean: (v) => typeof v === "boolean", + lista: (v) => Array.isArray(v) && v.length <= 500, + objekt: (v) => v !== null && typeof v === "object" && !Array.isArray(v), +}; + +function fältFel(typ, nyckel, regel, värde) { + if (Array.isArray(regel)) { + return regel.includes(värde) ? null : `${typ}.${nyckel} måste vara en av: ${regel.join(", ")}`; + } + return GILTIG[regel](värde) ? null : `${typ}.${nyckel} har fel form (väntade ${regel})`; +} + +/** + * Validerar en händelses form. Returnerar null när den duger, annars en + * förklaring avsedd att läsas av en människa som felsöker en integration. + */ +export function granskaHändelse(handelse) { + if (handelse === null || typeof handelse !== "object" || Array.isArray(handelse)) { + return "handelse måste vara ett objekt"; + } + const schema = HÄNDELSESCHEMA[handelse.typ]; + if (!schema) { + return `okänd händelsetyp: ${JSON.stringify(handelse.typ)}`; + } + for (const [nyckel, regel] of Object.entries(schema)) { + const fel = fältFel(handelse.typ, nyckel, regel, handelse[nyckel]); + if (fel) return fel; + } + + // C-5: schemat är stängt. Allt som inte är deklarerat avvisas — annars + // kan personuppgifter hängas på en vanlig händelse, undgå krypteringen + // och följa med ut i delningslänken. + const tillåtna = new Set([ + ...SYSTEMFÄLT, + ...Object.keys(schema), + ...(VALFRIA_FÄLT[handelse.typ] ?? []), + ]); + const okända = Object.keys(handelse).filter((n) => !tillåtna.has(n)); + if (okända.length > 0) { + return `${handelse.typ}: okända fält avvisas (${okända.join(", ")})`; + } + return null; +} + +/** + * Bygger den post som faktiskt skrivs. + * + * Härkomsten kommer härifrån och ingen annanstans: `anvandare` ur den + * verifierade token, `tidpunkt` ur serverns klocka. Klientens tidsstämpel + * bevaras separat när den finns, eftersom den är det enda som finns vid + * offline-arbete — men den avgör aldrig när något anses ha skett. + */ +export function tillPost(post, anspr, nu = new Date()) { + const fel = granskaHändelse(post?.handelse); + if (fel) return { fel }; + if (typeof post.id !== "string" || !/^[A-Za-z0-9_:.-]{1,128}$/.test(post.id)) { + return { fel: "id saknas eller har otillåtna tecken" }; + } + + const klient = typeof post.tidpunkt === "string" ? post.tidpunkt : null; + const klientTid = klient && !Number.isNaN(Date.parse(klient)) ? klient : null; + + return { + post: { + id: post.id, + tidpunkt: nu.toISOString(), + anvandare: anspr.namn, + handelse: { + ...post.handelse, + // Fälten nedan går inte att sätta utifrån; skrivs de över här är det + // just poängen. + anvandarId: anspr.sub, + // Signaturen vid avslut var teknikerns egen text — ett fält som + // HETER signatur men inte var en (panelgranskningen, Volvo-sätet). + // Nu skrivs den ur verifierad token, precis som `anvandare`. + // Vad den intygar är därmed exakt vad den kan intyga: vem som var + // inloggad när avslutet togs emot. Kedjans försegling intygar + // resten — att loggen är den som förelåg då. + ...(post.handelse.typ === "arende_avslutat" ? { signatur: anspr.namn } : {}), + ...(klientTid && klientTid !== nu.toISOString() ? { registrerad_tidpunkt: klientTid } : {}), + }, + }, + }; +} diff --git a/felsokning/services-backup/gemensam/integration.mjs b/felsokning/services-backup/gemensam/integration.mjs new file mode 100644 index 0000000..aa50486 --- /dev/null +++ b/felsokning/services-backup/gemensam/integration.mjs @@ -0,0 +1,188 @@ +// ALVA-SPEC-020 · Integration interface. +// +// Verkstaden har redan system: ett DMS, ett diagnosinstrument som +// producerar protokoll, ett videooffertsystem. ALVA ska koppla in sig i +// dem — inte ersätta dem, och inte kräva att de anpassar sig. +// +// ---- Vad jag INTE bygger --------------------------------------------- +// +// Färdiga klienter mot namngivna leverantörer. Jag känner inte Beonodes +// eller ServiceCams faktiska API:er, och en uppfunnen endpoint som ser +// färdig ut är sämre än en tom: den ser ut att fungera tills någon +// försöker, och då är felet dyrare att hitta. +// +// I stället: ett stabilt gränssnitt med PROFILER. En profil beskriver +// vad en kategori av system förväntar sig — fältnamn, format, riktning — +// och valideras mot leverantören innan den märks som `validated`. Tills +// dess står den som `draft`, och det syns i gränssnittet. +// +// Det är samma princip som Knowledge Sources: leverantören är data. +// +// ---- Riktningar -------------------------------------------------------- +// +// IN Diagnosprotokoll, arbetsorder, fordonsdata. Blir händelser i +// loggen med källa och tidsstämpel bevarade. +// UT Ärendet, rapporten, slutsatsen, mediet. Levereras som webhook +// eller hämtas via API. +// +// Riktningen är inte symmetrisk. Inkommande data blir evidens och måste +// därför bära sin härkomst hela vägen; utgående data är en projektion +// och kan formas fritt. + +/** Kategorier av system. Namnen är generiska med avsikt. */ +export const KATEGORIER = { + diagnosprotokoll: { + namn: "Diagnostic protocol", + riktning: "in", + beskrivning: "Fault codes, live data and readouts from a diagnostic instrument.", + // Vad ALVA gör av det: varje avläsning blir evidens, inte en bilaga. + blir: ["matvarde", "observation", "foto"], + }, + dms: { + namn: "Dealer management system", + riktning: "bada", + beskrivning: "Work orders in, completed cases and time records out.", + blir: ["arbetsorder_skannad", "objekt_identifierat"], + }, + videooffert: { + namn: "Video quotation", + riktning: "bada", + beskrivning: "Video evidence in, the case record and closing statement out.", + blir: ["video", "foto"], + }, + fordonsdata: { + namn: "Vehicle data", + riktning: "in", + beskrivning: "Identification, equipment level, service campaigns.", + blir: ["objekt_identifierat"], + }, + garanti: { + namn: "Warranty administration", + riktning: "ut", + beskrivning: "Claim substantiation: evidence chain, closing statement, traceability package.", + blir: [], + }, + forsakring: { + namn: "Insurance assessment", + riktning: "ut", + beskrivning: "Assessor-facing record: what was checked, what was ruled out, and why.", + blir: [], + }, + skadekalkyl: { + namn: "Damage calculation", + riktning: "bada", + beskrivning: + "Damage estimation systems used between workshops and insurers — CABAS is the Nordic standard. " + + "In: damage assessment and vehicle identification. Out: the diagnostic record substantiating that " + + "the damage was investigated rather than assumed.", + blir: ["objekt_identifierat", "observation", "foto"], + // Det ALVA tillför en skadekalkyl är inte fler poster utan + // beviskedjan bakom dem: vad som kontrollerades, vad som uteslöts + // och varför. Det är den enda del av kalkylen som i dag inte går + // att granska i efterhand. + tillfor: ["slutsats", "felorsak", "reproducering"], + }, +}; + +/** + * Profilens mognad. Samma resonemang som konnektorernas livscykel: + * "det finns kod" är inte samma sak som "det fungerar mot leverantören". + */ +export const MOGNAD = ["draft", "tested", "validated"]; + +/** + * Utgående händelser. En integration prenumererar på det den behöver — + * ett videooffertsystem bryr sig inte om varje mätvärde. + */ +export const UTGAENDE = { + "arende.skapat": "A case was opened.", + "arende.fas": "The case entered a new ALVA phase.", + "arende.slutsats": "A closing statement was recorded.", + "arende.avslutat": "The case was closed and passed the quality gate.", + "media.tillagt": "Photo or video evidence was added.", + "atgardsforslag.lamnat": "A repair proposal was issued to the customer.", + "kundbeslut.registrerat": "The customer's decision was recorded.", +}; + +/** + * Signerar en utgående leverans. + * + * HMAC över tidsstämpel och kropp, i det format nästan alla + * webhook-mottagare redan förstår. Tidsstämpeln ingår i signaturen så + * att en avlyssnad leverans inte går att spela upp igen. + */ +export function signeraLeverans(kropp, hemlighet, tidsstampel, hmac) { + const underlag = `${tidsstampel}.${kropp}`; + return `t=${tidsstampel},v1=${hmac("sha256", hemlighet).update(underlag).digest("hex")}`; +} + +/** + * Verifierar en inkommande leverans från oss. + * + * Exponerad så att mottagaren kan använda exakt samma kod som avsändaren + * — de flesta integrationsfel uppstår i glappet mellan två + * implementationer av samma signatur. + */ +export function verifieraLeverans(kropp, huvud, hemlighet, hmac, timingSafeEqual, nu = Date.now()) { + const delar = Object.fromEntries( + String(huvud ?? "") + .split(",") + .map((d) => d.split("=")), + ); + if (!delar.t || !delar.v1) return { ok: false, orsak: "Malformed signature header." }; + // Fem minuter. Nog för klockglapp, kort nog att en fångad leverans + // inte går att spela upp i morgon. + if (Math.abs(nu - Number(delar.t) * 1000) > 300_000) return { ok: false, orsak: "Timestamp outside tolerance." }; + + const väntad = hmac("sha256", hemlighet).update(`${delar.t}.${kropp}`).digest("hex"); + const a = Buffer.from(delar.v1); + const b = Buffer.from(väntad); + if (a.length !== b.length || !timingSafeEqual(a, b)) return { ok: false, orsak: "Signature mismatch." }; + return { ok: true }; +} + +/** + * Normaliserar ett inkommande diagnosprotokoll till händelser. + * + * Profilen beskriver var värdena ligger i leverantörens format; den här + * funktionen gör dem till evidens. Härkomsten bevaras i varje händelse — + * ett värde som kommit utifrån får aldrig se ut som något teknikern + * själv mätt. + */ +export function protokollTillHandelser(protokoll, profil, kalla) { + const ut = []; + // Vägen kommer från leverantörsprofilen, alltså utifrån. Uppslaget + // begränsas till egna, uppräkningsbara egenskaper: annars når en + // profil med `__proto__` eller `constructor.prototype` fram till + // prototypkedjan. Läsningen är visserligen ofarlig i sig, men en + // extern indata ska inte ha den friheten (QUALITY-AUDIT-2 · m-8). + const plocka = (objekt, vag) => + String(vag ?? "") + .split(".") + .reduce((o, n) => (o !== null && typeof o === "object" && Object.hasOwn(o, n) ? o[n] : undefined), objekt); + + for (const kod of plocka(protokoll, profil?.felkoder?.vag) ?? []) { + ut.push({ + typ: "observation", + text: `${plocka(kod, profil.felkoder.kod) ?? "?"} — ${plocka(kod, profil.felkoder.text) ?? "utan beskrivning"}`, + kalla, + }); + } + + for (const värde of plocka(protokoll, profil?.matvarden?.vag) ?? []) { + ut.push({ + typ: "matvarde", + beskrivning: String(plocka(värde, profil.matvarden.beskrivning) ?? "Avläst värde"), + varde: String(plocka(värde, profil.matvarden.varde) ?? ""), + enhet: plocka(värde, profil.matvarden.enhet) ?? undefined, + // Instrumentets identitet följer med när leverantören lämnar den. + // Utan den nedgraderas värdet till E1 — ett avläst tal utan känt + // instrument är inte en spårbar mätning (QUALITY-AUDIT M-1). + matdonId: plocka(värde, profil.matvarden.instrumentId) ?? undefined, + matdonBeteckning: plocka(värde, profil.matvarden.instrument) ?? undefined, + kalla, + }); + } + + return ut.filter((h) => h.typ !== "matvarde" || h.varde !== ""); +} diff --git a/felsokning/services-backup/gemensam/kedja.mjs b/felsokning/services-backup/gemensam/kedja.mjs new file mode 100644 index 0000000..c742b9a --- /dev/null +++ b/felsokning/services-backup/gemensam/kedja.mjs @@ -0,0 +1,108 @@ +// ALVA-SPEC-070 · Hashkedjan. +// +// ---- Varför triggrar inte räcker ---------------------------------------- +// +// Loggen är append-only, genomdrivet av databastriggrar. Panelgranskningen +// (docs/PANELGRANSKNING-2026-08.md, datasäkerhetssätet) satte fingret på +// gränsen för det skyddet: en trigger skyddar mot APPLIKATIONEN, inte mot +// den som äger databasen. Den som har superuser släpper triggern, ändrar +// raden och återskapar triggern, och ingenting syns. +// +// För ett system vars hela värde är bevisvärde är det fel viloläge. En +// hashkedja flyttar skyddet från behörighet till matematik: varje händelse +// bär en hash av sitt eget innehåll OCH föregående händelses hash. Den som +// ändrar en rad i efterhand bryter varje efterföljande länk, och brottet +// går att peka ut — inte bara konstatera. +// +// ---- Vad kedjan bevisar, och inte --------------------------------------- +// +// Kedjan bevisar att loggen inte ändrats SEDAN DEN SKREVS, i den ordning +// den skrevs. Den bevisar inte att det som skrevs var sant — det är +// grindens och evidensmodellens uppgift — och den bevisar ingenting om +// tiden FÖRE serverns mottagande. Rapporten ska säga just det och inte +// mer: "innehållet är oförändrat sedan mottagandet". +// +// Förseglingen vid avslut (HMAC över roten med en nyckel utanför +// databasen) skyddar mot nästa angrepp i ordningen: den som äger databasen +// och räknar OM hela kedjan efter sin ändring. Utan nyckeln kan en +// omräknad kedja inte förseglas om, och en försegling som inte stämmer +// mot roten är beviset. +// +// ---- Konstruktion -------------------------------------------------------- +// +// länk_n = SHA256(länk_{n-1} · id · tidpunkt · anvandare · digest) +// +// `digest` är innehållshashen av den kanoniska händelsen — samma +// `klientdigest` som kollisionsprövningen redan använder, så kedjan och +// dubblettskyddet kan aldrig ha olika uppfattning om vad en händelse +// "är". Fälten skiljs med radbrytning: utan avskiljare vore +// ("ab","c") och ("a","bc") samma länk. +// +// Första länken utgår från ärende-id:t i stället för en tom sträng, så +// att två ärenden med identisk första händelse ändå får olika kedjor — +// en kedja ska inte gå att lyfta från ett ärende till ett annat. + +import { createHash, createHmac, timingSafeEqual } from "node:crypto"; + +/** Kedjans startvärde för ett ärende. */ +export const grund = (arendeId) => + createHash("sha256").update(`alva-kedja\n${arendeId}`).digest("hex"); + +/** Nästa länk. Alla fält är strängar; digest är händelsens innehållshash. */ +export function lank(foregaende, { id, tidpunkt, anvandare, digest }) { + return createHash("sha256") + .update(`${foregaende}\n${id}\n${tidpunkt}\n${anvandare}\n${digest ?? ""}`) + .digest("hex"); +} + +/** + * Verifierar en hel kedja mot lagrade länkar. + * + * @param arendeId ärendet kedjan tillhör + * @param rader [{id, tidpunkt, anvandare, digest, kedjehash}] i kedjeordning + * @returns {ok, rot, brott} — brott pekar ut FÖRSTA raden som inte + * stämmer. Allt efter den är följdfel och räknas inte upp: + * den som läser ska laga ett brott i taget, inte skrämmas av + * hundra. + * + * Rader utan lagrad kedjehash (skrivna innan kolumnen fanns) bryter inte + * kedjan men märks som `okedjade` — tystnad hade låtit gammal data se + * starkare ut än den är, och det är precis den sortens övertro kedjan + * finns för att förhindra. + */ +export function verifiera(arendeId, rader) { + let h = grund(arendeId); + let okedjade = 0; + for (let i = 0; i < rader.length; i++) { + const rad = rader[i]; + h = lank(h, rad); + if (rad.kedjehash == null) { + okedjade++; + continue; + } + if (rad.kedjehash !== h) { + return { ok: false, rot: null, okedjade, brott: { index: i, id: rad.id } }; + } + } + return { ok: true, rot: rader.length > 0 ? h : grund(arendeId), okedjade }; +} + +/** + * Förseglar en kedjerot med en nyckel som hålls utanför databasen. + * + * HMAC, inte hash: poängen är att den som äger databasen inte kan räkna + * om förseglingen efter en ändring. Nyckeln är serverns, inte + * teknikerns — förseglingen intygar att PLATTFORMEN såg denna rot vid + * denna tid, ingenting om vem som tryckte på knappen. Det intyget står + * händelsens serverägda `anvandare`-fält för. + */ +export function forsegla(nyckel, arendeId, rot, tidpunkt) { + return createHmac("sha256", nyckel).update(`${arendeId}\n${rot}\n${tidpunkt}`).digest("hex"); +} + +/** Prövar en försegling. Konstanttid — en försegling är en hemlighet. */ +export function provaForsegling(nyckel, arendeId, rot, tidpunkt, forsegling) { + const ratt = Buffer.from(forsegla(nyckel, arendeId, rot, tidpunkt), "hex"); + const given = Buffer.from(String(forsegling ?? ""), "hex"); + return ratt.length === given.length && given.length > 0 && timingSafeEqual(ratt, given); +} diff --git a/felsokning/services-backup/gemensam/metodiker.mjs b/felsokning/services-backup/gemensam/metodiker.mjs new file mode 100644 index 0000000..cfc5b11 --- /dev/null +++ b/felsokning/services-backup/gemensam/metodiker.mjs @@ -0,0 +1,967 @@ +// Metodikbiblioteket — delad kod. +// +// Låg tidigare bara i klienten, vilket gjorde att servern inte kunde +// utvärdera kvalitetsgrindens metodikrader (QUALITET C-2). Datan bor nu +// här; app/src/felsokning/metodiker.ts återexporterar den med typer, så +// det finns fortfarande exakt en sanning. +// +// En metodik är inte en checklista över vad som brukar vara fel — det +// vore att gissa åt teknikern. Den är en ordning för att utesluta: +// symptomet verifieras först, sedan det billiga och synliga, sedan det +// som kräver mätning, sist det som kräver demontering. +// +// Tre regler gäller alla metodiker här: +// +// 1. Varje kontroll har ett minimikrav (mätvärde, foto eller +// observation). En kryssruta är inte evidens. +// 2. Symptomsteget frågar aldrig "vad är fel" utan "vad händer, när, +// var". Kundens ord blir ett verifierat symptom först när det +// reproducerats. +// 3. Där arbetet kan skada någon kommer säkerhetssteget först och kan +// inte hoppas över — se HÖGVOLT. +// +// Att "täcka allt" går inte att lova. Det som går är att täcka fordonets +// system systematiskt och låta GENERISK vara ett strukturellt komplett +// skyddsnät: den fungerar även för det ingen förutsett. +// +// ---- Språk och granskningsstatus (ALVA-SPEC-060) ------------------------ +// +// Texten är skriven på svenska och översatt till engelska när engelska +// blev källspråk. Det är alltså en ÖVERSÄTTNING som ännu inte lästs av en +// fackman på målspråket, och den ska behandlas som en sådan tills den är +// det — samma krav som ställs på de nio andra språken. +// +// Det spelar roll här och ingen annanstans i produkten: det här är den +// enda text i systemet som säger åt någon vad den ska göra med ett +// fordon. "Mät och dokumentera spänningsfrihet före ingrepp" är inte en +// etikett. +// +// NYCKELORDEN är ett undantag och är avsiktligt tvåspråkiga. De matchas +// mot teknikerns egen fritext; att byta ut de svenska mot engelska hade +// gjort metodikvalet blint för allt en svensk verkstad skriver. Fler +// nyckelord kan bara ge fler träffar, aldrig färre. + +export const VIBRATION = { + id: "vibration", + namn: "Vibration while driving", + omrade: "Wheels and balance", + nyckelord: ["vibration", "vibrer", "skak", "obalans", "ojämn gång", "rattvibration", "vibrat", "shak", "shimm", "imbalance", "unbalance", "wheel wobble", "rough running"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + beskrivning: + "The questions are answered from a road test — drive before the vehicle is lifted. Vibration under braking is brake judder — use the braking system methodology for it.", + fragor: [ + { id: "hastighetsberoende", text: "Is the vibration speed-dependent?", svarstyp: "janej" }, + { id: "var", text: "Where is the vibration felt?", svarstyp: "val", val: ["In the steering wheel", "In the seat", "Throughout the vehicle"] }, + { + id: "nar", + text: "When does the vibration occur?", + svarstyp: "val", + val: ["Under acceleration", "At steady speed", "Under braking", "Always while driving"], + }, + { id: "intervall", text: "Does it disappear above or below a certain speed range?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + beskrivning: "Photograph every wheel. Keep observation separate from conclusion.", + kontroller: [ + { id: "foto_vf", text: "Photograph the left front wheel", krav: "foto" }, + { id: "foto_hf", text: "Photograph the right front wheel", krav: "foto" }, + { id: "foto_vb", text: "Photograph the left rear wheel", krav: "foto" }, + { id: "foto_hb", text: "Photograph the right rear wheel", krav: "foto" }, + { id: "dot", text: "Document the DOT / manufacturing date", krav: "kommentar" }, + { id: "slitage", text: "Check tyre wear and the wear pattern", krav: "kommentar" }, + { id: "vikter", text: "Check for missing balance weights and deposits (mud, snow) inside the rim", krav: "kommentar" }, + ], + }, + { + id: "kontroller", + rubrik: "Measurements", + kontroller: [ + { id: "lufttryck", text: "Check tyre pressure", krav: "matvarde" }, + { id: "hjulmoment", text: "Check wheel torque", krav: "matvarde" }, + { id: "kast", text: "Measure radial and lateral runout at the rim with a dial gauge; measure at the tyre if the rim is within limits", krav: "matvarde" }, + { id: "balansering", text: "Measure wheel balance on the balancer — residual imbalance in grams per plane (road force where available)", krav: "matvarde" }, + { id: "bussningar", text: "Check bushings and joints", krav: "kommentar" }, + { id: "drivaxlar", text: "Check drive shafts and inner CV joints when the vibration is load-dependent", krav: "kommentar" }, + ], + }, + { + id: "provkorning", + rubrik: "Road test", + beskrivning: + "Check that the wheels are visibly secure before the test; provoke the vibration only within legal speed and where traffic allows.", + kontroller: [ + { id: "pk_hastighet", text: "Speed at which the vibration occurs", krav: "matvarde" }, + { id: "pk_acc", text: "Change under acceleration", krav: "kommentar" }, + { id: "pk_motorbroms", text: "Change under engine braking", krav: "kommentar" }, + { id: "pk_kurva", text: "Change when cornering", krav: "kommentar" }, + { id: "pk_var", text: "Whether the vibration is felt in the steering wheel or the body", krav: "kommentar" }, + ], + }, + ], +}; + +export const ELSYSTEM = { + id: "elsystem", + namn: "Electrical system and power supply", + omrade: "Electrical", + nyckelord: ["relä", "rela", "säkring", "elektr", "spänning", "volt", "lampa", "belysning", "ström", "kortslut", "kabel", "kabla", "kontaktdon", "jordfel", "relay", "fuse", "electric", "voltage", "lamp", "light", "current", "short circuit", "wiring", "harness", "connector", "earth fault", "ground fault"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { id: "reproducerbart", text: "Can the fault be reproduced?", svarstyp: "janej" }, + { + id: "omfattning", + text: "Is the function completely dead or partly working?", + svarstyp: "val", + val: ["Completely dead", "Partly working", "Intermittent"], + }, + { id: "funktion", text: "Which function is missing or faulty?", svarstyp: "text" }, + { id: "forandring", text: "Has anything been repaired or changed recently?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "foto_komponent", text: "Photograph the component and its connections", krav: "foto" }, + { id: "skador", text: "Check for visible damage, corrosion and loose connectors", krav: "kommentar" }, + { id: "vicktest", text: "Wiggle test — flex the harness and connectors while monitoring the function; document where the fault can be provoked", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + beskrivning: + "Measure under load. A voltage without load says almost nothing about a poor connection. Never probe airbag or other pyrotechnic (SRS) circuits with a meter — deployment risk; use the diagnostic tool and the manufacturer's procedure for those.", + kontroller: [ + { id: "batterispanning", text: "Measure battery voltage", krav: "matvarde" }, + { id: "sakringar", text: "Measure voltage on both sides of the fuses concerned, circuit under load", krav: "matvarde" }, + { id: "matningsspanning", text: "Measure supply voltage at the component under load and compare with battery voltage — more than about 0.5 V lower indicates a supply-side voltage drop", krav: "matvarde" }, + { id: "jord", text: "Measure voltage drop to earth under load — more than about 0.2 V indicates a poor earth point or connection", krav: "matvarde" }, + ], + }, + { + id: "rela", + rubrik: "Relay and control signal", + kontroller: [ + { id: "rela_klick", text: "Check whether the relay clicks when energized — a click only proves the coil circuit; verify voltage on the load side, or substitute a known-good identical relay", krav: "kommentar" }, + { id: "styrsignal", text: "Measure the control signal to the relay", krav: "matvarde" }, + ], + }, + { + id: "funktionstest", + rubrik: "Function test", + kontroller: [{ id: "test", text: "Perform a function test and document the result", krav: "kommentar" }], + }, + ], +}; + +export const START_LADDNING = { + id: "start_laddning", + namn: "Starting and charging system", + omrade: "Electrical", + nyckelord: ["startar inte", "startproblem", "startmotor", "generator", "laddning", "batteriet dör", "urladdat", "batterilampa", "drar inte runt", "klick vid start", "will not start", "wont start", "no start", "starter", "alternator", "charging", "battery dies", "flat battery", "battery light", "does not crank", "click when starting"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "beteende", + text: "What happens when starting is attempted?", + svarstyp: "val", + val: ["Nothing at all", "A single click", "Cranks slowly", "Cranks normally but does not fire", "Starts intermittently"], + }, + { id: "kallt_varmt", text: "Does the fault appear cold, warm or always?", svarstyp: "val", val: ["Cold", "Warm", "Always"] }, + { id: "stillestand", text: "How long has the vehicle stood between attempts?", svarstyp: "text" }, + { id: "laddat", text: "Has the battery been charged or replaced recently?", svarstyp: "text" }, + ], + }, + { + id: "batteri", + rubrik: "Battery condition", + beskrivning: + "A weak battery causes consequential faults across the vehicle. Rule it out first. Battery safety: remove rings and metal watch straps, ventilate — a charging battery gives off hydrogen; never break a high-current connection at the post.", + kontroller: [ + { id: "vilospanning", text: "Measure resting voltage after at least one hour at rest, surface charge removed — about 12.6 V or more is fully charged, 12.4 V about 75 %, below 12.0 V discharged", krav: "matvarde" }, + { id: "batteritest", text: "Load or conductance test with the battery charged to at least 75 % state of charge — photograph the result, measured versus rated CCA", krav: "foto" }, + { id: "poler", text: "Check battery terminals and earth straps", krav: "kommentar" }, + ], + }, + { + id: "start", + rubrik: "Starting system", + beskrivning: + "Before any cranking test: gearbox in park or neutral, parking brake applied. Disable starting (fuel or ignition) where the test requires cranking without the engine firing, and keep hands and test leads clear of belt drives and hot surfaces. Cranks normally but does not fire — use the engine running methodology.", + kontroller: [ + { id: "startspanning", text: "Measure voltage at the starter motor during cranking — should stay above about 9.6 V at normal temperature; lower points to the battery, the cables or a dragging starter", krav: "matvarde" }, + { id: "spanningsfall_plus", text: "Measure voltage drop in the positive lead during cranking — no more than about 0.5 V", krav: "matvarde" }, + { id: "spanningsfall_jord", text: "Measure voltage drop in the earth lead during cranking — no more than about 0.5 V", krav: "matvarde" }, + { id: "startsignal", text: "Check the control signal to the starter relay", krav: "matvarde" }, + ], + }, + { + id: "laddning", + rubrik: "Charging system", + kontroller: [ + { id: "laddspanning", text: "Measure charging voltage at idle and at raised engine speed with consumers on — conventional systems 13.8–14.8 V; with smart charging, compare against the control unit's requested voltage instead of a fixed limit", krav: "matvarde" }, + { id: "rippel", text: "Measure AC ripple at the battery with the engine running and loads on — more than about 0.5 V indicates failed diodes", krav: "matvarde" }, + { id: "rem", text: "Check the drive belt and its tension", krav: "kommentar" }, + ], + }, + { + id: "krypstrom", + rubrik: "Parasitic drain", + beskrivning: "Only relevant when the battery discharges while the vehicle stands.", + kontroller: [ + { id: "vilostrom", text: "Measure quiescent current with a clamp, or in series without breaking continuity, once the vehicle has gone to sleep — typically below 30–50 mA; note the vehicle's specified value", krav: "matvarde" }, + { id: "sakringsdrag", text: "Localize the circuit by measuring the millivolt drop across each fuse with the vehicle still asleep; pull fuses only as a last resort — removing one wakes modules", krav: "kommentar" }, + ], + }, + ], +}; + +export const MOTOR_DRIFT = { + id: "motor_drift", + namn: "Engine running and power", + omrade: "Engine", + nyckelord: ["ryck", "tappar kraft", "effektbrist", "misständ", "ojämn tomgång", "tomgång", "motorlampa", "kraftlös", "hack", "går på tre", "jerk", "hesitat", "loses power", "power loss", "misfire", "rough idle", "idle", "engine light", "check engine", "gutless", "running on three"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "nar", + text: "When does the fault appear?", + svarstyp: "val", + val: ["On a cold start", "At idle", "Under load", "At a particular engine speed", "Always"], + }, + { id: "varvtal", text: "At what engine speed or load?", svarstyp: "text" }, + { id: "varningslampa", text: "Is the engine warning lamp on or flashing?", svarstyp: "val", val: ["On steady", "Flashing", "Off"] }, + { id: "brankslebyte", text: "Was fuel added or service performed shortly before?", svarstyp: "text" }, + ], + }, + { + id: "felkoder", + rubrik: "Fault codes and measured data", + beskrivning: "Read before anything is dismantled. A fault code points to a circuit, not to a broken component.", + kontroller: [ + { id: "dtc", text: "Read fault codes — photograph the diagnostic screen", krav: "foto" }, + { id: "frysdata", text: "Document the freeze frame data for the code", krav: "foto" }, + { id: "misstandning", text: "Read the misfire counter per cylinder", krav: "matvarde" }, + { id: "branslekorrigering", text: "Read short- and long-term fuel trim", krav: "matvarde" }, + ], + }, + { + id: "mekanik", + rubrik: "Basic engine condition", + beskrivning: "Without sound basic condition, every sensor value is hard to interpret.", + kontroller: [ + { id: "kompression", text: "Measure compression or relative compression — deactivate ignition and injection first; document per cylinder, and locate a low cylinder with a leak-down test", krav: "matvarde" }, + { id: "vevhustryck", text: "Measure crankcase pressure at idle", krav: "matvarde" }, + { id: "insugslackage", text: "Check the intake system for unmetered air", krav: "kommentar" }, + ], + }, + { + id: "tandning_bransle", + rubrik: "Ignition and fuel", + kontroller: [ + { id: "tandstift", text: "Check the spark plugs — photograph all of them", krav: "foto" }, + { id: "spolar", text: "Swap ignition coils between cylinders and re-read the misfire counter — document whether the fault moves with the coil", krav: "matvarde" }, + { id: "bransletryck", text: "Measure fuel pressure under load — depressurize before opening the system, never open the high-pressure side (read it via the control unit), and keep ignition sources away", krav: "matvarde" }, + { id: "insprutare", text: "Check injector back-leakage (leak-off) quantity or injector control", krav: "matvarde" }, + ], + }, + { + id: "provkorning", + rubrik: "Road test with logging", + kontroller: [ + { id: "logg", text: "Log measured data while driving where the fault appears — with a flashing warning lamp, do not drive under load; log at idle or light load", krav: "foto" }, + { id: "reproducerat", text: "Document the conditions under which the fault appeared", krav: "kommentar" }, + ], + }, + ], +}; + +export const BROMSAR = { + id: "bromsar", + namn: "Braking system", + omrade: "Chassis", + nyckelord: ["broms", "bromspedal", "skevhet", "gnisslar vid inbromsning", "abs", "bromsvätska", "handbroms", "drar snett vid inbromsning", "brake", "brake pedal", "judder", "squeal when braking", "brake fluid", "handbrake", "parking brake", "pulls when braking"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Judder or pulsation", "Abnormal noise", "Long pedal travel", "Soft pedal", "Pulls to one side", "The brake is binding"], + }, + { id: "nar", text: "At what speed and braking force?", svarstyp: "text" }, + { id: "varningslampa", text: "Is the brake or ABS warning lamp on?", svarstyp: "janej" }, + { id: "senaste", text: "When were brake components last replaced?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "vatskeniva", text: "Check the brake fluid level and condition in the reservoir before the vehicle is moved", krav: "foto" }, + { id: "foto_belagg", text: "Photograph pads and discs on every wheel", krav: "foto" }, + { id: "belaggtjocklek", text: "Measure pad thickness", krav: "matvarde" }, + { id: "skivtjocklek", text: "Measure disc thickness and compare with the minimum", krav: "matvarde" }, + { id: "lackage", text: "Check hoses, pipes and calipers for leaks", krav: "kommentar" }, + { id: "dtc", text: "Read fault codes from the ABS control unit", krav: "foto" }, + ], + }, + { + id: "reproduktion", + rubrik: "Road test", + beskrivning: "Only after the fluid level and pedal feel are checked. Provoke braking only where traffic allows.", + kontroller: [ + { id: "pk_temp", text: "Compare disc temperature side to side with an IR thermometer after the road test", krav: "matvarde" }, + { id: "pk_beteende", text: "Document judder, pull or binding during the test", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + kontroller: [ + { id: "skivkast", text: "Measure disc runout with a dial gauge — if out of limits, clean the hub face and measure hub runout before condemning the disc", krav: "matvarde" }, + { id: "tjockleksvariation", text: "Measure disc thickness variation", krav: "matvarde" }, + { id: "vatska", text: "Measure the brake fluid's boiling point or water content", krav: "matvarde" }, + { id: "okens_rorlighet", text: "Check that calipers and slide pins move freely", krav: "kommentar" }, + ], + }, + { + id: "system", + rubrik: "System and ABS", + kontroller: [ + { id: "hjulgivare", text: "Check the wheel speed sensor signals", krav: "matvarde" }, + { id: "parkeringsbroms", text: "Check the parking brake's function and travel", krav: "kommentar" }, + { id: "bromsprov", text: "Perform a brake test on a test bench — observe the restrictions for four-wheel drive and use service mode for an electronic parking brake; photograph the report", krav: "foto" }, + ], + }, + ], +}; + +export const STYRNING_FJADRING = { + id: "styrning_fjadring", + namn: "Steering and suspension", + omrade: "Chassis", + nyckelord: ["drar åt", "styrning", "ratt", "glapp", "stötdämpar", "fjäder", "fjädr", "hjulinställning", "spårar", "gungar", "knäpper i kurva", "servostyrning", "pulls", "steering", "steering wheel", "play", "damper", "shock absorber", "spring", "suspension", "wheel alignment", "tracking", "wallow", "clunk in corners", "power steering"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Pulls to one side", "Play in the steering wheel", "Abnormal noise", "Poor road feel", "Uneven tyre wear", "Wallows or bounces"], + }, + { id: "underlag", text: "Does it appear on a particular surface or in a particular corner?", svarstyp: "text" }, + { id: "handelse", text: "Has the vehicle struck something or hit a kerb?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "foto_slitage", text: "Photograph the tyres' wear pattern", krav: "foto" }, + { id: "dampare", text: "Check dampers for leakage", krav: "kommentar" }, + { id: "damask", text: "Check boots on track rod ends and drive joints", krav: "kommentar" }, + { id: "fjadrar", text: "Check springs for breakage and compare ride height side to side", krav: "matvarde" }, + { id: "servo", text: "Check power steering — fluid level and leaks (hydraulic) or fault codes (EPS)", krav: "kommentar" }, + ], + }, + { + id: "glapp", + rubrik: "Play check", + beskrivning: + "With the vehicle securely supported, unload the joint being checked according to the suspension design — support the lower arm where the spring acts on it. Rock the wheel at 12 and 6 o'clock for wheel bearings, at 3 and 9 o'clock for steering joints. Document where the play is, not only that it exists.", + kontroller: [ + { id: "hjullager", text: "Check wheel bearing play — state location and estimated magnitude", krav: "kommentar" }, + { id: "styrleder", text: "Check the outer tie rod ends and ball joints", krav: "kommentar" }, + { id: "bussningar", text: "Check bushings", krav: "kommentar" }, + { id: "kuggstang", text: "Check the steering rack mountings and the inner tie rods (axial joints)", krav: "kommentar" }, + ], + }, + { + id: "installning", + rubrik: "Wheel alignment", + kontroller: [ + { id: "dacktryck", text: "Check tyre pressures before measuring", krav: "matvarde" }, + { id: "dackskifte", text: "When the vehicle pulls: swap the front tyres side to side to rule out tyre pull before adjusting", krav: "kommentar" }, + { id: "matning", text: "Measure wheel alignment — photograph the report before adjustment", krav: "foto" }, + { id: "avvikelse", text: "Document deviations from the manufacturer's values", krav: "matvarde" }, + ], + }, + ], +}; + +export const KYLSYSTEM = { + id: "kylsystem", + namn: "Cooling system and overheating", + omrade: "Engine", + nyckelord: ["överhett", "koka", "kylvätska", "temperatur", "termostat", "kylare", "vattenpump", "värme i kupén", "expansionskärl", "overheat", "boiling", "coolant", "temperature", "thermostat", "radiator", "water pump", "cabin heat", "expansion tank"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "nar", + text: "When does the temperature rise?", + svarstyp: "val", + val: ["At idle", "At low speed", "At high speed", "Under load or on a hill", "Always"], + }, + { id: "kylvatskeforlust", text: "Is coolant being lost? How much and how often?", svarstyp: "text" }, + { id: "varme", text: "Does the cabin heater work?", svarstyp: "janej" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "niva", text: "Check the coolant level cold — photograph the expansion tank", krav: "foto" }, + { id: "lackage", text: "Check for visible leaks at hoses, radiator and pump", krav: "kommentar" }, + { id: "kylarpaket", text: "Check the radiator core and the A/C condenser for external blockage", krav: "foto" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + beskrivning: "Never open a hot, pressurized system — scalding risk. Work on a cooled-down engine.", + kontroller: [ + { id: "trycktest", text: "Pressure test the cooling system cold — pressurize to the cap's rated pressure, never above", krav: "matvarde" }, + { id: "lock", text: "Pressure test the expansion tank cap against its rated pressure", krav: "matvarde" }, + { id: "temperatur", text: "Measure inlet and outlet temperature at the radiator with an IR thermometer", krav: "matvarde" }, + { id: "flakt", text: "Measure the temperature at which the cooling fan cuts in — keep clear of the fan, it can start without warning", krav: "matvarde" }, + { id: "givare", text: "Compare the control unit's temperature value with the measured one", krav: "matvarde" }, + { id: "termostat", text: "Measure the upper radiator hose temperature at the thermostat's opening point — document the opening temperature", krav: "matvarde" }, + ], + }, + { + id: "packning", + rubrik: "Cylinder head gasket", + beskrivning: "Performed when coolant is lost without an external leak.", + kontroller: [ + { id: "avgastest", text: "Combustion leak test (block tester) in the coolant", krav: "foto" }, + { id: "kompression_kyl", text: "Connect a pressure tester to the cold system and document the pressure build-up over time with the engine running — never open hot", krav: "matvarde" }, + ], + }, + ], +}; + +export const DRIVLINA = { + id: "drivlina", + namn: "Transmission and driveline", + omrade: "Driveline", + nyckelord: ["växellåda", "växlar", "koppling", "automatlåda", "rycker vid växling", "slir", "drivknut", "kardan", "differential", "kärvar i växeln", "gearbox", "transmission", "shift", "clutch", "automatic", "jerks when shifting", "slip", "drive joint", "cv joint", "cv boot", "driveshaft", "axle", "diff whine", "propshaft", "sticks in gear"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Slips", "Jerks when shifting", "Will not engage a gear", "Abnormal noise", "Vibration under acceleration", "Leakage"], + }, + { id: "vaxel", text: "Which gear or engine speed?", svarstyp: "text" }, + { id: "kall_varm", text: "Cold or warm transmission?", svarstyp: "val", val: ["Cold", "Warm", "No difference"] }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "olja", text: "Check the oil level and the oil's condition — photograph", krav: "foto" }, + { id: "lackage", text: "Check for leaks at seals and the sump", krav: "kommentar" }, + { id: "fasten", text: "Check the transmission mounts", krav: "kommentar" }, + { id: "drivaxlar", text: "Check drive shaft boots and CV joints — clicking on full lock points to an outer joint", krav: "kommentar" }, + { id: "kardan", text: "Check the propshaft, centre bearing and universal joints; document driveline lash", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements and readout", + kontroller: [ + { id: "dtc", text: "Read fault codes from the transmission control unit", krav: "foto" }, + { id: "adaptioner", text: "Document adaptation values and clutch wear", krav: "matvarde" }, + { id: "oljetemp", text: "Measure oil temperature during the road test", krav: "matvarde" }, + ], + }, + { + id: "provkorning", + rubrik: "Road test", + kontroller: [ + { id: "vaxlingspunkter", text: "Document shift points (engine speed at up- and downshift) and behaviour", krav: "matvarde" }, + { id: "belastning", text: "Road test under the load where the fault appears", krav: "kommentar" }, + { id: "koppling_slir", text: "Clutch slip test on a manual gearbox: high gear, low speed, full throttle — document whether engine speed rises without road speed; check pedal free play", krav: "kommentar" }, + ], + }, + ], +}; + +export const AVGAS_EMISSION = { + id: "avgas_emission", + namn: "Exhaust system and emissions", + omrade: "Engine", + nyckelord: ["partikelfilt", "dpf", "adblue", "katalysator", "lambda", "avgas", "emission", "rökutveckling", "svart rök", "blå rök", "os", "egr", "regenerer", "utsläpp", "particulate filt", "catalyst", "exhaust", "smoke", "black smoke", "blue smoke", "regenerat"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Warning lamp", "Smoke", "Power limitation", "Odour", "Failed inspection"], + }, + { id: "rokfarg", text: "If smoke — what colour and when?", svarstyp: "text" }, + { id: "korprofil", text: "Describe the vehicle's usage profile (mostly short or long journeys)?", svarstyp: "text" }, + ], + }, + { + id: "avlasning", + rubrik: "Readout", + kontroller: [ + { id: "dtc", text: "Read fault codes — photograph the diagnostic screen", krav: "foto" }, + { id: "sotmangd", text: "Read the calculated and measured soot load in the particulate filter", krav: "matvarde" }, + { id: "regenerering", text: "Document the number and outcome of the most recent regenerations", krav: "kommentar" }, + { id: "lambda", text: "Read the lambda sensor signals before and after the catalyst", krav: "matvarde" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + beskrivning: "Run the engine only with exhaust extraction connected, or outdoors.", + kontroller: [ + { id: "mottryck", text: "Measure exhaust back pressure", krav: "matvarde" }, + { id: "differenstryck", text: "Measure differential pressure across the particulate filter at idle and at raised engine speed — state the engine speed with each value", krav: "matvarde" }, + { id: "adblue_niva", text: "Check AdBlue level and quality where fitted", krav: "matvarde" }, + { id: "egr", text: "Check the EGR valve's function and position", krav: "matvarde" }, + { id: "emissioner", text: "Measure emissions at idle and raised idle (CO/HC/lambda, or smoke opacity for diesel) — document against the limit", krav: "matvarde" }, + ], + }, + { + id: "orsak", + rubrik: "Underlying cause", + beskrivning: "A blocked filter is usually a consequence, not the cause. Document what produced the soot.", + kontroller: [ + { id: "grundorsak", text: "Check injection, turbocharger and air flow", krav: "kommentar" }, + { id: "korstrackor", text: "Assess whether the usage profile allows regeneration", krav: "kommentar" }, + ], + }, + ], +}; + +export const KLIMAT = { + id: "klimat", + namn: "Climate system", + omrade: "Comfort", + nyckelord: ["ac", "luftkonditionering", "klimatanläggning", "kyler inte", "kompressor", "köldmedi", "imma", "immig", "fläkt", "lukt i kupén", "värmepaket", "air conditioning", "climate", "no cooling", "compressor", "refrigerant", "misting", "fan", "smell in the cabin", "heater matrix"], + steg: [ + { + id: "sakerhet", + rubrik: "Safety — performed first", + beskrivning: + "Work on the refrigerant circuit requires certification for mobile air-conditioning (EU F-gas regulation). Refrigerant under pressure causes cold burns — protective glasses and gloves.", + fragor: [ + { id: "certifikat", text: "Are you certified for mobile air-conditioning work?", svarstyp: "janej" }, + ], + kontroller: [ + { id: "skydd", text: "Document the protective equipment for refrigerant handling", krav: "kommentar" }, + ], + }, + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["No cooling at all", "Cools poorly", "Cools intermittently", "Abnormal noise", "Odour", "Misting on the windows"], + }, + { id: "sidor", text: "Does it differ between the right and left sides?", svarstyp: "janej" }, + { id: "service", text: "When was the AC last serviced?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "kondensor", text: "Photograph the condenser — check for blockage and damage", krav: "foto" }, + { id: "kupefilter", text: "Check the cabin filter", krav: "foto" }, + { id: "kompressor_rem", text: "Check the compressor drive and the magnetic clutch", krav: "kommentar" }, + ], + }, + { + id: "matningar", + rubrik: "Measurements", + kontroller: [ + { id: "tryck", text: "Measure high and low pressure at the prescribed engine speed (about 1500–2000 rpm), AC at maximum, after stabilization — document the ambient temperature", krav: "matvarde" }, + { id: "utblastemp", text: "Measure the outlet temperature at the centre vent — AC at maximum, recirculation on, after a few minutes' stabilization", krav: "matvarde" }, + { id: "mangd", text: "Weigh the refrigerant recovered when evacuating and compare with the nominal charge on the vehicle's label", krav: "matvarde" }, + { id: "lackagesok", text: "Trace the leak with tracer gas or UV — photograph the leak point", krav: "foto" }, + ], + }, + { + id: "styrning", + rubrik: "Climate control", + kontroller: [ + { id: "dtc", text: "Read fault codes from the climate control unit", krav: "foto" }, + { id: "spjall", text: "Check the movement of the flap actuators", krav: "kommentar" }, + ], + }, + ], +}; + +export const HOGVOLT = { + id: "hogvolt", + namn: "High-voltage system — electric and hybrid", + omrade: "High voltage", + nyckelord: ["högvolt", "elbil", "hybrid", "traktionsbatteri", "hv", "laddning av bil", "räckvidd", "laddbox", "orange kabel", "sköldpadda", "high voltage", "high-voltage", "ev", "electric vehicle", "traction battery", "charging the car", "range", "wallbox", "orange cable", "turtle"], + steg: [ + { + id: "sakerhet", + rubrik: "Safety — performed first", + beskrivning: + "High-voltage systems can be lethal. No work on the system begins until the steps below are documented. Requires authorization for work on high-voltage systems — without it, stop after non-intrusive readout: no removal of covers or connectors, no insulation testing with applied test voltage, and never any work on an energized system. The readout and charging steps are non-intrusive; de-energization is completed and documented before any physical intervention.", + fragor: [ + { id: "behorighet", text: "Are you authorized to work on high-voltage systems?", svarstyp: "janej" }, + { id: "avstangt", text: "Is the vehicle de-energized per the manufacturer's procedure, with absence of voltage verified by measurement?", svarstyp: "janej" }, + ], + kontroller: [ + { id: "skyddsutrustning", text: "Document before starting: the work area cordoned off and signed, and the protective equipment in use — insulated gloves (EN 60903, within retest date), face shield, insulated tools (EN 60900)", krav: "foto" }, + { id: "service_disconnect", text: "Document that the ignition is off, the key out of range, and the service disconnect removed and kept under the sole control of the person working — locked out against re-energization", krav: "foto" }, + { id: "vantetid", text: "Document the manufacturer's prescribed capacitor discharge waiting time and the actual time waited (minutes)", krav: "matvarde" }, + { id: "spanningsfrihet", text: "Verify absence of voltage at the manufacturer's designated measuring points (HV+ to HV−, and each to chassis) with a CAT III rated tester — prove the tester against a known source immediately before and after; document the readings", krav: "matvarde" }, + ], + }, + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "karaktar", + text: "How does the fault present?", + svarstyp: "val", + val: ["Will not charge", "Reduced range", "Power limitation", "Warning message", "The vehicle will not start"], + }, + { id: "laddtyp", text: "At which charge type does the fault appear (AC, DC, which charge point)?", svarstyp: "text" }, + { id: "temperatur", text: "At what ambient and battery temperature?", svarstyp: "text" }, + ], + }, + { + id: "avlasning", + rubrik: "Readout", + beskrivning: "Performed with a diagnostic tool — no intervention in the system is required.", + kontroller: [ + { id: "dtc", text: "Read fault codes from every high-voltage control unit", krav: "foto" }, + { id: "soh", text: "Read the battery's state of health (SOH) and state of charge (SOC)", krav: "matvarde" }, + { id: "cellspanning", text: "Read cell voltages and document the largest deviation", krav: "matvarde" }, + { id: "isolationsvarde", text: "Read the system's insulation value", krav: "matvarde" }, + { id: "temperaturer", text: "Read the battery temperature sensors", krav: "matvarde" }, + { id: "hvil", text: "Read the status of the high-voltage interlock loop (HVIL)", krav: "kommentar" }, + ], + }, + { + id: "laddning", + rubrik: "Charging chain", + kontroller: [ + { id: "laddkabel", text: "Check the charging cable and connector — photograph the pins", krav: "foto" }, + { id: "laddlogg", text: "Read the charging history and the reasons for interruptions", krav: "kommentar" }, + { id: "extern", text: "Test against another charge point to rule out an external cause", krav: "kommentar" }, + ], + }, + ], +}; + +export const DIAGNOS_NATVERK = { + id: "diagnos_natverk", + namn: "Fault codes and communication", + omrade: "Diagnostics", + nyckelord: ["felkod", "dtc", "can", "canbuss", "kommunikationsfel", "ingen kontakt", "styrenhet", "obd", "diagnos", "flera lampor", "fault code", "can bus", "communication fault", "no communication", "control unit", "ecu", "several lamps"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "omfattning", + text: "How many systems are affected?", + svarstyp: "val", + val: ["One control unit", "Several control units", "The whole vehicle", "The diagnostic tool only"], + }, + { id: "nar", text: "Did the fault appear after work, a battery change or an update?", svarstyp: "text" }, + ], + }, + { + id: "grund", + rubrik: "Basic preconditions", + beskrivning: "Communication faults are more often due to supply and earth than to the control unit.", + kontroller: [ + { id: "batteri", text: "Measure battery voltage during the readout", krav: "matvarde" }, + { id: "obd_matning", text: "Measure supply (pin 16) and earth (pins 4 and 5) at the diagnostic socket — supply near battery voltage", krav: "matvarde" }, + { id: "sakringar", text: "Check the fuses for the control units concerned", krav: "kommentar" }, + ], + }, + { + id: "koder", + rubrik: "Fault codes in context", + beskrivning: "A fault code points to a circuit, not to a broken component. Read every system before any code is cleared.", + kontroller: [ + { id: "helavlasning", text: "Read every control unit — photograph the list", krav: "foto" }, + { id: "frysdata", text: "Document the freeze frame data before clearing", krav: "kommentar" }, + { id: "efter_radering", text: "Clear, road test, and document which codes returned", krav: "kommentar" }, + ], + }, + { + id: "buss", + rubrik: "Bus measurement", + kontroller: [ + { id: "terminering", text: "Measure the bus termination resistance between CAN-H and CAN-L with the battery disconnected — expect about 60 ohm; on gateway vehicles measure on the affected segment, not only at the diagnostic socket", krav: "matvarde" }, + { id: "bussignal", text: "Measure the CAN signal with an oscilloscope — photograph the trace. High-speed CAN: about 2.5 to 3.5 V (CAN-H) and 2.5 to 1.5 V (CAN-L); note error frames or bus off", krav: "foto" }, + { id: "avdelning", text: "Split the bus to localize the disturbing node", krav: "kommentar" }, + ], + }, + ], +}; + +export const LACKAGE = { + id: "lackage", + namn: "Leakage", + omrade: "General", + nyckelord: ["läck", "läckage", "oljefläck", "dropp", "vatten i bilen", "fukt", "blöt matta", "oljeförbrukning", "leak", "leakage", "oil stain", "drip", "water in the car", "damp", "wet carpet", "oil consumption"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { id: "vatska", text: "What does the fluid look like — colour, consistency, smell — and where does it appear?", svarstyp: "text" }, + { id: "mangd", text: "How much and how often?", svarstyp: "text" }, + { id: "plats", text: "Where on the ground is the stain relative to the vehicle?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Localization", + beskrivning: "Clean first. An old leak hides where the new one comes from.", + kontroller: [ + { id: "foto_fore", text: "Photograph the area before cleaning", krav: "foto" }, + { id: "rengoring", text: "Clean the area and document that it is dry", krav: "foto" }, + { id: "identifiering", text: "Identify the fluid on white paper — photograph", krav: "foto" }, + { id: "provkorning", text: "Road test, or let the engine run, and photograph where the fluid appears — no road test on a suspected fuel or brake fluid leak: fuel means no ignition sources and engine off; brakes are verified and repaired before any driving", krav: "foto" }, + ], + }, + { + id: "metod", + rubrik: "Leak tracing", + kontroller: [ + { id: "uv", text: "Leak test with UV dye where applicable — photograph the fluorescence", krav: "foto" }, + { id: "trycktest", text: "Pressure test the system", krav: "matvarde" }, + { id: "niva", text: "Measure the change in level over time", krav: "matvarde" }, + { id: "vattenprov", text: "For water ingress: hose test section by section and check the drains — photograph the entry point", krav: "foto" }, + ], + }, + ], +}; + +export const MISSLJUD = { + id: "missljud", + namn: "Abnormal noise", + omrade: "General", + nyckelord: ["missljud", "ljud", "knack", "gnissel", "gnissl", "vinande", "brumm", "skram", "väsande", "tjut", "noise", "sound", "knock", "squeak", "squeal", "whine", "drone", "rattle", "hiss", "screech"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + beskrivning: "An abnormal noise that has not been reproduced cannot be repaired. Record it.", + fragor: [ + { + id: "karaktar", + text: "What is the character of the noise?", + svarstyp: "val", + val: ["Knock", "Squeal", "Whine", "Drone", "Rattle", "Hissing", "Screech"], + }, + { + id: "nar", + text: "When is it heard?", + svarstyp: "val", + val: ["On starting", "At idle", "While driving", "Under braking", "When cornering", "Over bumps"], + }, + { id: "beroende", text: "Does the noise follow engine speed or road speed?", svarstyp: "val", val: ["Engine speed", "Road speed", "Neither"] }, + { id: "temperatur", text: "Any difference cold versus warm?", svarstyp: "text" }, + ], + }, + { + id: "inspelning", + rubrik: "Document the noise", + beskrivning: + "Video with sound is evidence. A description in text is not. On a road test one person drives, one operates the equipment.", + kontroller: [ + { id: "video", text: "Record the noise on video under the conditions in which it occurs", krav: "foto" }, + { id: "forhallanden", text: "Document road speed, engine speed and surface during the recording", krav: "matvarde" }, + ], + }, + { + id: "lokalisering", + rubrik: "Localization", + kontroller: [ + { id: "stetoskop", text: "Localize with a stethoscope or sound probe when stationary — keep the probe clear of rotating parts; use chassis ears on a road test for speed-dependent noise", krav: "kommentar" }, + { id: "avlastning", text: "Check whether the noise changes when unloaded or loaded", krav: "kommentar" }, + { id: "frikoppling", text: "Document whether the noise changes in neutral or when coasting, and when cornering left versus right", krav: "kommentar" }, + { id: "uteslutning", text: "Document which sources were ruled out, and how", krav: "kommentar" }, + ], + }, + ], +}; + +export const ADAS = { + id: "adas", + namn: "Driver assistance and calibration", + omrade: "Diagnostics", + nyckelord: ["adas", "kamera", "radar", "filhållning", "adaptiv farthållare", "kalibrer", "vindruta", "parkeringssensor", "döda vinkeln", "nödbroms", "camera", "lane keep", "adaptive cruise", "calibrat", "windscreen", "windshield", "parking sensor", "blind spot", "emergency brak"], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { + id: "funktion", + text: "Which function is faulty?", + svarstyp: "val", + val: ["Lane keeping", "Adaptive cruise control", "Automatic emergency braking", "Blind spot", "Parking aid", "Reversing camera"], + }, + { id: "ingrepp", text: "Have the windscreen, body or suspension been worked on recently?", svarstyp: "text" }, + { id: "forhallanden", text: "Does the fault appear in particular weather or light?", svarstyp: "text" }, + ], + }, + { + id: "forutsattningar", + rubrik: "Preconditions for calibration", + beskrivning: + "A calibration performed under the wrong preconditions produces a fault that looks repaired. Document that the requirements are met before it is carried out. Dynamic calibration has its own requirements — speed, clear lane markings, weather and light — per the manufacturer.", + kontroller: [ + { id: "dtc", text: "Read fault codes from the control units concerned", krav: "foto" }, + { id: "hjulinstallning", text: "Check wheel alignment — rear axle tracking affects the camera", krav: "matvarde" }, + { id: "dacktryck", text: "Check tyre pressure and ride height", krav: "matvarde" }, + { id: "fordonskondition", text: "Document that vehicle condition meets the manufacturer's requirements — load, fuel level, battery support, steering wheel centred", krav: "kommentar" }, + { id: "sikt", text: "Check the camera's and radar's field of view for dirt or damage — after a windscreen replacement, verify correct glass and camera mount", krav: "foto" }, + { id: "yta", text: "Document that the calibration area meets the requirements for flatness and lighting", krav: "foto" }, + ], + }, + { + id: "kalibrering", + rubrik: "Calibration", + kontroller: [ + { id: "utford", text: "Perform the calibration by the manufacturer's method — photograph the result", krav: "foto" }, + { id: "provkorning", text: "Verify the function by the manufacturer's test method — never provoke emergency braking or steering interventions on public roads; confirm the system is active and free of warnings", krav: "kommentar" }, + ], + }, + ], +}; + +export const GENERISK = { + id: "generisk", + namn: "General structured diagnosis", + omrade: "General", + nyckelord: [], + steg: [ + { + id: "symptom", + rubrik: "Verify the symptom", + fragor: [ + { id: "energikallor", text: "Does the work involve high voltage, fuel, gas or stored energy? If yes — switch to the appropriate methodology, or document the safety measures first", svarstyp: "janej" }, + { id: "reproducerbart", text: "Can the fault be reproduced?", svarstyp: "janej" }, + { + id: "nar", + text: "When does the fault appear?", + svarstyp: "val", + val: ["Always", "Intermittently", "Only while running", "Only when starting"], + }, + { + id: "var", + text: "Where is the fault experienced?", + svarstyp: "val", + val: ["Front", "Rear", "Right", "Left", "Engine and power source", "Cabin and driver's position", "Unknown"], + }, + { + id: "hur", + text: "How is the fault experienced?", + svarstyp: "val", + val: ["Shakes or vibrates", "Abnormal noise (knock, squeal, whine)", "Jerking", "Loses power", "Stalls completely", "Other"], + }, + { id: "forlopp", text: "Describe the sequence when the fault occurs", svarstyp: "text" }, + { id: "forandring", text: "Has anything changed recently (service, repair, environment)?", svarstyp: "text" }, + ], + }, + { + id: "visuell", + rubrik: "Visual inspection", + kontroller: [ + { id: "foto_objekt", text: "Photograph the object and the area concerned", krav: "foto" }, + { id: "skador", text: "Check for visible damage, leaks or loose connections", krav: "kommentar" }, + { id: "typskylt", text: "Document the type plate or marking", krav: "foto" }, + ], + }, + { + id: "grundkontroller", + rubrik: "Basic checks", + kontroller: [ + { + id: "matning", + text: "Take basic measurements (voltage, pressure, temperature — whatever is relevant)", + krav: "matvarde", + }, + { id: "dtc", text: "Read fault codes where the object has diagnostics — photograph", krav: "foto" }, + { id: "sakringar", text: "Check fuses and protective devices", krav: "kommentar" }, + { id: "anslutningar", text: "Check connectors and terminations", krav: "kommentar" }, + ], + }, + { + id: "funktionstest", + rubrik: "Function test", + kontroller: [{ id: "test", text: "Perform a function test and document the result", krav: "kommentar" }], + }, + ], +}; + +// Ordningen styr bara hur de visas. Valet görs på nyckelord, inte på +// position — GENERISK ligger sist eftersom den är fallback. +export const ALLA_METODIKER = [ + VIBRATION, + BROMSAR, + STYRNING_FJADRING, + ELSYSTEM, + START_LADDNING, + MOTOR_DRIFT, + KYLSYSTEM, + DRIVLINA, + AVGAS_EMISSION, + KLIMAT, + HOGVOLT, + DIAGNOS_NATVERK, + LACKAGE, + MISSLJUD, + ADAS, + GENERISK, +]; diff --git a/felsokning/services-backup/gemensam/motivering.mjs b/felsokning/services-backup/gemensam/motivering.mjs new file mode 100644 index 0000000..0bba7a0 --- /dev/null +++ b/felsokning/services-backup/gemensam/motivering.mjs @@ -0,0 +1,178 @@ +// ALVA-RULE-200 · Closing statement. +// +// Ett ärende stängs aldrig utan att teknikern har lämnat ett varför. +// +// Det låter enkelt och är det inte. En obligatorisk fritextruta blir +// "klart" på tredje ärendet, och då har vi bara gjort dokumentationen +// långsammare utan att göra den bättre. Ett varför som ska hålla i en +// garantitvist måste vara strukturerat, kopplat till evidensen, och +// kvalitetsgranskat mot det som faktiskt skrivs när någon har bråttom. +// +// ---- Vad ett varför faktiskt består av ------------------------------- +// +// Fyra frågor med olika adressat. De blandas i dag ihop till en enda +// rad i en arbetsorder, och det är därför verkstadsprotokoll är svåra +// att använda i efterhand: +// +// MOTIVERING Varför följer slutsatsen av evidensen? +// Läses av: nästa tekniker, försäkringsbedömaren. +// Detta är den rad som saknas i varje system jag sett. +// Underlaget säger VAD som mättes. Slutsatsen säger VAD +// som är fel. Ingenting säger varför det ena medför det +// andra — och det är precis det steget en bedömare +// behöver granska. +// +// UTESLUTET Vad övervägdes och varför föll det bort? +// Läses av: försäkringsbolaget, garantihandläggaren. +// En felsökning utan uteslutna alternativ är en gissning +// som råkade stämma. Härleds delvis ur loggen: en +// hypotes som dokumenterats och inte blev slutsatsen +// MÅSTE bemötas. +// +// ÅTGÄRDSVAL Varför denna åtgärd och inte en annan? +// Läses av: kunden, kostnadsgranskaren. +// Byte eller justering? Varför hela enheten? +// +// KVARSTÅENDE Vad är fortfarande osäkert? +// Läses av: nästa tekniker, flottansvarig. +// Får vara "inget" — men det måste sägas aktivt. +// +// ---- Den ärliga vägen ------------------------------------------------- +// +// Ibland vet teknikern inte varför. Systemet måste ha en väg för det som +// inte är en lögn: orsaken kunde inte fastställas, och varför den inte +// kunde det. Det är också ett varför, och ofta ett mer användbart än en +// påhittad orsak. + +import { EVIDENSORD, ICKESVAR, MINSTA_ORSAKSORD, ORSAKSORD } from "./sprak/ord.mjs"; +import { STANDARD, t } from "./sprak/index.mjs"; + +export const MINSTA_MOTIVERING = 40; +export const MINSTA_KORT = 15; + +const normalisera = (text) => String(text ?? "").trim().toLowerCase().replace(/\s+/g, " "); + +/** Är texten ett icke-svar? Jämför mot hela fältet, inte delsträngar. */ +function ärIckesvar(text) { + const n = normalisera(text).replace(/[.!]+$/, ""); + return ICKESVAR.includes(n); +} + +// Ett resonemang innehåller ett därför. Utan orsakssamband är texten en +// omformulering av slutsatsen, inte ett skäl för den. +// +// Orden jämförs som delsträngar och kommer från alla språk samtidigt — +// se sprak/ord.mjs för varför unionen är rätt riktning att fela åt. +function harOrsakssamband(text) { + const n = normalisera(text); + return ORSAKSORD.some((ord) => ord.length >= MINSTA_ORSAKSORD && n.includes(ord)); +} + +/** + * Refererar texten till dokumenterad evidens? Godtar både uttryckliga + * ord för underlag och konkreta mätvärden med enhet — den som skriver + * "12,4 V vid stift 14" hänvisar till en mätning även utan att säga + * ordet "mätning". + */ +function refererarEvidens(text) { + const n = normalisera(text); + const värde = /\d+([.,]\d+)?\s*(v|volt|a|ampere|ω|ohm|bar|kpa|mpa|nm|°c|c|mm|km|mil|%|g|hz|rpm|varv)\b/; + return EVIDENSORD.some((o) => n.includes(o)) || värde.test(n); +} + +/** + * Granskar ett fält i slutsatsen. + * @returns null när fältet duger, annars en förklaring till teknikern. + */ +export function granskaFalt(faltNyckel, text, { minsta = MINSTA_KORT, kravOrsak = false, sprak = STANDARD } = {}) { + const rå = String(text ?? "").trim(); + const falt = t(sprak, faltNyckel); + if (!rå) return t(sprak, "slutsats.saknas", { falt }); + if (ärIckesvar(rå)) return t(sprak, "slutsats.ickesvar.falt", { falt, text: rå }); + if (rå.length < minsta) { + return t(sprak, "slutsats.for_kort", { falt, langd: rå.length, minsta }); + } + if (kravOrsak && !harOrsakssamband(rå) && !refererarEvidens(rå)) { + return t(sprak, "slutsats.utan_varfor", { falt }); + } + return null; +} + +/** + * Hypoteser som dokumenterats men inte blivit slutsatsen. + * + * Härleds ur loggen: den som skrivit ned en misstanke och sedan landat i + * något annat måste säga varför den föll bort. Det är den enskilda regel + * som gör underlaget användbart för en försäkringsbedömare — utan den är + * en felsökning en gissning som råkade stämma. + */ +export function obemottaHypoteser(handelser, slutsats) { + const hypoteser = handelser.filter((h) => h?.typ === "hypotes").map((h) => h.text); + if (hypoteser.length === 0) return []; + + const uteslutet = normalisera(slutsats?.uteslutet); + const orsak = normalisera( + [slutsats?.motivering, ...(handelser.filter((h) => h?.typ === "felorsak").map((h) => h.avvikelse) ?? [])].join(" "), + ); + + // En hypotes är bemött om något meningsbärande ord ur den återkommer i + // uteslutandet eller i den fastställda orsaken. Trubbigt, men det + // fångar den som skriver "uteslutet: inget" med tre öppna hypoteser i + // loggen. + return hypoteser.filter((text) => { + const ord = normalisera(text) + .split(/[^a-zåäöéü0-9]+/) + .filter((o) => o.length >= 5); + if (ord.length === 0) return false; + return !ord.some((o) => uteslutet.includes(o) || orsak.includes(o)); + }); +} + +/** + * Granskar hela slutsatsen inför avslut. + * + * @param slutsats händelsen av typ "slutsats" + * @param handelser hela loggen, för de regler som härleds ur den + * @returns lista med brister; tom lista betyder godkänd + */ +export function granskaSlutsats(slutsats, handelser = [], sprak = STANDARD) { + const brister = []; + const lägg = (fält, text) => text && brister.push({ falt: fält, text }); + const g = (nyckel, text, val) => granskaFalt(nyckel, text, { ...val, sprak }); + + if (!slutsats) { + return [{ falt: "slutsats", nyckel: "slutsats.utan_slutsats", text: t(sprak, "slutsats.utan_slutsats") }]; + } + + const fastställd = slutsats.orsakFastställd !== false; + + if (fastställd) { + lägg("motivering", g("slutsats.falt.motivering", slutsats.motivering, { minsta: MINSTA_MOTIVERING, kravOrsak: true })); + } else { + // Den ärliga vägen. Att orsaken inte kunnat fastställas är ett + // giltigt utfall — men varför den inte kunnat det är fortfarande ett + // varför, och det måste stå där. + lägg( + "motivering", + g("slutsats.falt.motivering_ej", slutsats.motivering, { minsta: MINSTA_MOTIVERING, kravOrsak: true }), + ); + } + + lägg("uteslutet", g("slutsats.falt.uteslutet", slutsats.uteslutet, { minsta: MINSTA_KORT })); + lägg("kvarstaende", g("slutsats.falt.kvarstaende", slutsats.kvarstaende, { minsta: 5 })); + + const utfördArbete = handelser.some((h) => h?.typ === "atgard_utford" && h.utford === true); + if (utfördArbete) { + lägg("atgardsval", g("slutsats.falt.atgardsval", slutsats.atgardsval, { minsta: MINSTA_KORT, kravOrsak: true })); + } + + for (const text of obemottaHypoteser(handelser, slutsats)) { + brister.push({ + falt: "uteslutet", + nyckel: "slutsats.hypotes_obemott", + text: t(sprak, "slutsats.hypotes_obemott", { text }), + }); + } + + return brister; +} diff --git a/felsokning/services-backup/gemensam/observation.mjs b/felsokning/services-backup/gemensam/observation.mjs new file mode 100644 index 0000000..83be12e --- /dev/null +++ b/felsokning/services-backup/gemensam/observation.mjs @@ -0,0 +1,166 @@ +// Observation — spårning och mätvärden utan nya beroenden. +// +// Tjänsterna har medvetet nästan inga beroenden: plattformen har +// pg-drivrutinen, orkestern har Claude-klienten. Att dra in ett +// OpenTelemetry-SDK med trettio paket för att mäta fyra saker vore fel +// avvägning. +// +// I stället två standarder som båda bara är text på stdout: +// +// W3C Trace Context traceparent-huvudet följer med genom hela +// kedjan, så en teknikers anrop går att följa +// från klienten via plattformen till orkestern. +// +// CloudWatch EMF strukturerad JSON som CloudWatch själv plockar +// mätvärden ur. Ingen agent, ingen SDK, inget +// som kan sluta fungera tyst. +// +// Det som mäts är valt efter en fråga: vad vill man veta klockan tre på +// natten när något är långsamt? Svaret är var tiden gick — inte hur +// många anrop som skett. + +import { randomBytes } from "node:crypto"; + +export const NAMNRYMD = "GuidadFelsokning"; + +const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/; + +/** + * Läser inkommande traceparent eller startar ett nytt spår. + * Ett anrop utan huvud är inte ett fel — de flesta kommer utifrån. + */ +export function spårFrån(huvud) { + const träff = typeof huvud === "string" ? huvud.trim().match(TRACEPARENT) : null; + return { + spårId: träff ? träff[1] : randomBytes(16).toString("hex"), + förälder: träff ? träff[2] : null, + spanId: randomBytes(8).toString("hex"), + }; +} + +// Skickas vidare till nästa tjänst i kedjan. +export function traceparent(spår) { + return `00-${spår.spårId}-${spår.spanId}-01`; +} + +/** + * Ett spann. Mäter tid och samlar barn, så att en logg-rad kan svara på + * "var tog tiden vägen" utan att någon behöver korrelera flera rader. + */ +export function starta(namn, spår) { + const början = process.hrtime.bigint(); + const barn = []; + + return { + namn, + spår, + + // Tidtagning på det som faktiskt kan vara långsamt: databasen, + // modellanropet, objektlagringen, kundens leverantör. + async mät(delnamn, arbete) { + const start = process.hrtime.bigint(); + try { + return await arbete(); + } finally { + barn.push({ namn: delnamn, ms: Number(process.hrtime.bigint() - start) / 1e6 }); + } + }, + + ms() { + return Number(process.hrtime.bigint() - början) / 1e6; + }, + + delar() { + // Samma delnamn flera gånger (t.ex. tre databasfrågor) slås ihop: + // antalet och summan säger mer än en lista. + const summa = {}; + for (const d of barn) { + summa[d.namn] ??= { antal: 0, ms: 0 }; + summa[d.namn].antal += 1; + summa[d.namn].ms += d.ms; + } + for (const d of Object.values(summa)) d.ms = Math.round(d.ms * 100) / 100; + return summa; + }, + }; +} + +// En rad JSON per händelse. CloudWatch Logs Insights kan fråga på +// vilket fält som helst utan att någon behöver skriva ett regex. +export function logga(nivå, meddelande, fält = {}) { + process.stdout.write( + `${JSON.stringify({ + nivå, + meddelande, + tid: new Date().toISOString(), + ...fält, + })}\n`, + ); +} + +/** + * Mätvärden i CloudWatch Embedded Metric Format. + * + * Formatet är en logg-rad som CloudWatch känner igen och extraherar + * mätvärden ur. Poängen är att mätvärdet och sammanhanget står i samma + * rad: när latensen är hög går det att gå direkt till anropen som + * orsakade den, i stället för att gissa utifrån en graf. + * + * Dimensioner hålls medvetet få. Varje unik kombination är en egen + * tidsserie som kostar, så organisation eller ärende-id får aldrig bli + * en dimension — de ligger som vanliga fält. + */ +export function mätvärde(namn, värde, enhet, dimensioner = {}, extra = {}) { + const nycklar = Object.keys(dimensioner); + + process.stdout.write( + `${JSON.stringify({ + _aws: { + Timestamp: Date.now(), + CloudWatchMetrics: [ + { + Namespace: NAMNRYMD, + Dimensions: nycklar.length > 0 ? [nycklar] : [[]], + Metrics: [{ Name: namn, Unit: enhet }], + }, + ], + }, + ...dimensioner, + [namn]: värde, + ...extra, + })}\n`, + ); +} + +/** + * Avslutar ett spann: en strukturerad logg-rad med hela nedbrytningen, + * och ett mätvärde för latensen. + * + * Fel loggas som fel men mäts på samma ställe — annars syns inte att + * felen är snabba och de lyckade anropen långsamma, vilket är precis + * den sortens sak som förvirrar en felsökning. + */ +export function avsluta(spann, { status, väg, extra = {} }) { + const ms = spann.ms(); + const delar = spann.delar(); + + logga(status >= 500 ? "fel" : "info", spann.namn, { + spårId: spann.spår.spårId, + spanId: spann.spår.spanId, + väg, + status, + ms: Math.round(ms * 100) / 100, + delar, + ...extra, + }); + + mätvärde("Svarstid", ms, "Milliseconds", { Tjänst: spann.namn, Väg: väg }, { status }); + + // En egen serie för fel gör larmet enkelt: larma på summan, inte på + // ett förhållande som måste räknas ut. + if (status >= 500) { + mätvärde("Fel", 1, "Count", { Tjänst: spann.namn, Väg: väg }, { spårId: spann.spår.spårId }); + } + + return ms; +} diff --git a/felsokning/services-backup/gemensam/personuppgifter.mjs b/felsokning/services-backup/gemensam/personuppgifter.mjs new file mode 100644 index 0000000..997aad2 --- /dev/null +++ b/felsokning/services-backup/gemensam/personuppgifter.mjs @@ -0,0 +1,162 @@ +// Personuppgifter i en append-only-logg. +// +// Revisionen (docs/QUALITY-AUDIT.md, C-3) fann att systemet inte kunde +// uppfylla artikel 17: databastriggern hindrar aktivt radering, och +// kundens namn, telefonnummer, e-post, registreringsnummer och VIN +// ligger i klartext i loggen. +// +// "Append-only" och "radering på begäran" är inte oförenliga, men de +// måste förenas medvetet. Lösningen här är krypto-shredding: +// +// Identifierande fält lagras krypterade med en nyckel som är unik per +// registrerad person. Radering sker genom att nyckeln förstörs. Loggen +// förblir intakt och hashverifierbar — det som blir oåtkomligt är +// identifieringen, inte protokollet över vad som kontrollerades. +// +// Det är den enda konstruktion jag känner till där bevisvärdet överlever +// en raderingsbegäran. Ett ärende där kunden begärt radering kan +// fortfarande visa att lufttrycket mättes till 2,4 bar av en behörig +// tekniker klockan 08:42 — bara inte längre vem bilen tillhörde. +// +// Vad som INTE krypteras: mätvärden, observationer, kontrollresultat, +// tidsstämplar, teknikerns roll. De är verksamhetsdata och bär inget +// personvärde i sig. Att kryptera allt vore enklare att beskriva och +// sämre i praktiken — då raderas beviset tillsammans med personuppgiften. + +import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto"; +import { arendetypNu } from "./sprak/index.mjs"; + +/** + * Fält som räknas som identifierande och därför krypteras. + * + * Registreringsnummer och VIN står med avsikt med. De pekar ut ett + * fordon, men ett fordon pekar i praktiken ut en ägare — EDPB behandlar + * VIN som personuppgift när kopplingen finns, och här finns den. + */ +export const IDENTIFIERANDE = { + objekt_identifierat: ["identifierare", "vin", "agare", "kund"], + arbetsorder_skannad: [ + "kund_namn", + "kund_foretag", + "kund_telefon", + "kund_epost", + "fordon_regnr", + "fordon_vin", + ], + kundbeslut: ["kontaktperson"], +}; + +export const MASKERAT = "[raderat på begäran]"; + +/** + * Krypterar ett värde med den registrerades nyckel. + * Formatet bär nyckelns id, så att en post går att koppla till rätt + * nyckel utan att nyckeln behöver finnas. + */ +export function skydda(värde, nyckelId, nyckel) { + const iv = randomBytes(12); + const chiffer = createCipheriv("aes-256-gcm", nyckel, iv); + const ut = Buffer.concat([chiffer.update(String(värde), "utf8"), chiffer.final()]); + return `pd1:${nyckelId}:${iv.toString("base64url")}:${chiffer.getAuthTag().toString("base64url")}:${ut.toString("base64url")}`; +} + +/** + * Öppnar ett skyddat värde. + * + * Saknas nyckeln returneras MASKERAT — det är det normala utfallet efter + * en radering, inte ett fel. Ett kastat undantag här skulle göra hela + * ärendet oläsbart, vilket vore precis fel: resten av bevisningen ska + * överleva. + */ +export function öppna(skyddat, nycklar) { + if (typeof skyddat !== "string" || !skyddat.startsWith("pd1:")) return skyddat; + const [, nyckelId, iv, tagg, data] = skyddat.split(":"); + const nyckel = nycklar.get(nyckelId); + if (!nyckel) return MASKERAT; + try { + const chiffer = createDecipheriv("aes-256-gcm", nyckel, Buffer.from(iv, "base64url")); + chiffer.setAuthTag(Buffer.from(tagg, "base64url")); + return chiffer.update(Buffer.from(data, "base64url"), undefined, "utf8") + chiffer.final("utf8"); + } catch { + // Fel nyckel eller manipulerad chiffertext. Att skilja de två fallen + // åt i ett felmeddelande vore ett orakel; behandla dem lika. + return MASKERAT; + } +} + +/** Krypterar de identifierande fälten i en händelse. */ +export function skyddaHändelse(handelse, nyckelId, nyckel) { + const fält = IDENTIFIERANDE[handelse.typ]; + if (!fält) return handelse; + + if (handelse.typ === "arbetsorder_skannad") { + return { + ...handelse, + falt: (handelse.falt ?? []).map((f) => + fält.includes(f.id) && f.varde ? { ...f, varde: skydda(f.varde, nyckelId, nyckel) } : f, + ), + }; + } + if (handelse.typ === "objekt_identifierat") { + const objekt = { ...handelse.objekt }; + for (const n of fält) if (objekt[n]) objekt[n] = skydda(objekt[n], nyckelId, nyckel); + return { ...handelse, objekt }; + } + const ut = { ...handelse }; + for (const n of fält) if (ut[n]) ut[n] = skydda(ut[n], nyckelId, nyckel); + return ut; +} + +/** Öppnar de identifierande fälten igen för visning. */ +export function öppnaHändelse(handelse, nycklar) { + const fält = IDENTIFIERANDE[handelse?.typ]; + if (!fält) return handelse; + + if (handelse.typ === "arbetsorder_skannad") { + return { ...handelse, falt: (handelse.falt ?? []).map((f) => ({ ...f, varde: öppna(f.varde, nycklar) })) }; + } + if (handelse.typ === "objekt_identifierat") { + const objekt = { ...handelse.objekt }; + for (const n of fält) if (objekt[n]) objekt[n] = öppna(objekt[n], nycklar); + return { ...handelse, objekt }; + } + const ut = { ...handelse }; + for (const n of fält) if (ut[n]) ut[n] = öppna(ut[n], nycklar); + return ut; +} + +/** + * Gallringsbeslut. + * + * Retention är inte en teknisk detalj utan ett juridiskt ställningstagande, + * och det skiljer sig mellan ärendetyper: ett garantiärende måste kunna + * visas upp under garantitiden, ett kontantärende inte. Standardvärdena + * nedan är utgångspunkter — varje organisation ska sätta sina egna, och + * den som inte gör ett aktivt val får det försiktigaste. + */ +export const GALLRING_MANADER = { + Warranty: 120, // Följer den längsta rimliga garantitiden på en drivlina. + Goodwill: 120, + Insurance: 120, // Preskription för försäkringskrav. + Complaint: 36, // Konsumentköplagens reklamationsrätt. + "Used-vehicle warranty": 60, + standard: 36, +}; + +/** + * Gallringsdatum för ett avslutat ärende. + * + * Ärendetypen normaliseras genom ARENDETYP_ARV innan den slås upp. Det + * är inte en artighet mot gamla data utan en spärr: när typerna bytte + * språk hade ett garantiärende vars logg säger "Garanti" fallit ur + * tabellen och tagit standardvärdet 36 månader i stället för 120 — + * alltså raderat garantiunderlaget sju år för tidigt, tyst, och + * oåterkalleligt. Ett uppslag som missar ska ge det FÖRSIKTIGASTE + * utfallet; här gav det det farligaste. + */ +export function gallringsdatum(arendetyp, avslutat, manader = GALLRING_MANADER) { + const antal = manader[arendetypNu(arendetyp)] ?? manader.standard; + const d = new Date(avslutat); + d.setMonth(d.getMonth() + antal); + return d; +} diff --git a/felsokning/services-backup/gemensam/sakerhet.mjs b/felsokning/services-backup/gemensam/sakerhet.mjs new file mode 100644 index 0000000..774d175 --- /dev/null +++ b/felsokning/services-backup/gemensam/sakerhet.mjs @@ -0,0 +1,74 @@ +// ALVA-SPEC-071 · Säkerhetsnivåns tak. +// +// ---- Bakgrund (panelgranskningen, garantisätet) -------------------------- +// +// Säkerhetsnivån i felorsaksanalysen — hög, medel, låg — valdes fritt av +// teknikern. Ett självskattat värde som ser ut som en mätning är värre än +// inget värde: en garantihandläggare kan inte prissätta det, och den som +// har bråttom väljer "hög" av samma skäl som den skriver "klart". +// +// Panelens lösning, som blev regeln här: nivån HÄRLEDS ur underlaget och +// sätter ett TAK. Teknikern kan sänka — den som vet något om fordonets +// historia som underlaget inte visar ska kunna vara ärligt osäker — men +// aldrig höja. Asymmetrin är poängen: ingen kan påstå högre säkerhet än +// evidensen bär, och en sänkning är information i stället för misstanke. +// +// ---- Vad taket räknar på ------------------------------------------------- +// +// HÖG kräver att orsakskedjan är sluten i båda ändar: symptomet +// reproducerat (inte "delvis" — delvis är ett annat ord för att felet +// inte är förstått) OCH minst ett spårbart mätvärde (mätdon ur registret, +// alltså E4). En slutsats utan mätning kan vara rätt, men den kan inte +// vara HÖG — det är precis skillnaden mellan att veta och att känna igen. +// +// MEDEL kräver evidens över E1: foto, video eller mätvärde. Enbart +// observationer — teknikerns egna påståenden — bär inte medel. +// +// LÅG är alltid tillåten. Ett golv vore att tvinga fram falsk säkerhet. +// +// Taket är avsiktligt ETT MÅTT PÅ UNDERLAGET, inte på resonemanget. +// Professorssätet varnade för att en formel som räknar foton utger sig +// för att mäta diagnostisk säkerhet — det gör den inte, och det står +// därför i både namn och dokumentation att det är ett TAK, inte ett +// betyg. Formeln säger vad underlaget som mest kan bära; om slutsatsen +// förtjänar det avgörs fortfarande av en människa. + +const ORDNING = ["lag", "medel", "hog"]; + +/** Rangordnar nivåer; okänd nivå räknas som lägst. */ +const rang = (niva) => Math.max(0, ORDNING.indexOf(niva)); + +/** + * Högsta säkerhetsnivå underlaget bär. + * + * @param handelser ärendets händelselogg (klartextform) + * @returns "hog" | "medel" | "lag" + */ +export function sakerhetstak(handelser) { + const av = (typ) => handelser.filter((h) => h?.typ === typ); + + const reproducerat = av("reproducering").some((h) => h.status === "ja"); + // Spårbart = mätdonet kommer ur registret OCH kalibreringen gällde + // när mätningen togs emot. Stämpeln `kalibreradVidMatning` sätts av + // servern vid uppslaget (TÜV T-2, TÜV-2 T-13) — utan den räknade + // taket ett instrument med utgången kalibrering som spårbart, och + // "hög" gick att nå med ett mätdon som evidensmodellen själv graderar + // E1. Fältet går inte att dikta från klienten: mätdonsFakta skriver + // alltid över det. + // + // Strikt även bakåt: ett mätvärde utan stämpel (skrivet före + // regeln) räknas INTE som spårbart. Samma princip som för påstådda + // beteckningar — det som inte kan styrkas bär inte "hög". + const sparbartMatvarde = av("matvarde").some((h) => h.matdonId && h.kalibreradVidMatning === true); + const evidensOverE1 = + sparbartMatvarde || av("matvarde").length > 0 || av("foto").length > 0 || av("video").length > 0; + + if (reproducerat && sparbartMatvarde) return "hog"; + if (evidensOverE1) return "medel"; + return "lag"; +} + +/** Är den påstådda nivån inom taket? Sänkning är alltid tillåten. */ +export function inomTak(pastadd, tak) { + return rang(pastadd) <= rang(tak); +} diff --git a/felsokning/services-backup/gemensam/sammanfattning.mjs b/felsokning/services-backup/gemensam/sammanfattning.mjs new file mode 100644 index 0000000..eeeb58e --- /dev/null +++ b/felsokning/services-backup/gemensam/sammanfattning.mjs @@ -0,0 +1,150 @@ +// ALVA-PROC-0030 · Case summary. +// +// Några få meningar som ger vem som helst en bild av ärendet: kunden i +// telefon, försäkringshandläggaren som öppnar en akt, teknikern som tar +// över efter helgen. +// +// ---- Varför den är härledd och inte genererad ------------------------- +// +// Frestelsen är att låta modellen skriva den. Det vore fel här, och +// skälet är inte försiktighet utan funktion: +// +// Den ska gå att lita på. En sammanfattning som en bedömare läser +// blir en del av beslutsunderlaget. Är den +// genererad måste den granskas mot loggen +// varje gång — och då är den inte längre en +// genväg. +// +// Den ska vara stabil. Samma ärende måste ge samma text i dag och +// om två år. En sammanfattning som ändrar sig +// mellan visningar är oanvändbar i en tvist. +// +// Den ska fungera offline. Verkstadsgolvet har dålig täckning. +// +// Sammanfattningen är därför en projektion, precis som briefen och +// rapporten. 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. +// +// Modellen har fortfarande en roll — men i andra änden: den kan föreslå +// en språkligt jämnare formulering som teknikern GODKÄNNER, och då blir +// godkännandet en händelse i loggen. Det som visas här är alltid det +// härledda. + +const handelserI = (arende) => (arende.handelser ?? []).map((p) => p.handelse ?? p); +const av = (handelser, typ) => handelser.filter((h) => h?.typ === typ); +const sista = (handelser, typ) => av(handelser, typ).at(-1); + +const REPRODUKTION = { + ja: "Symptomet reproducerades.", + delvis: "Symptomet kunde delvis reproduceras.", + nej: "Symptomet kunde inte reproduceras under de förhållanden som rådde vid undersökningen.", +}; + +const KVALITET = { + symptomet_borta: "Vid kvalitetskontrollen var symptomet borta.", + kvarstar: "Vid kvalitetskontrollen kvarstod symptomet.", + delvis: "Vid kvalitetskontrollen kvarstod symptomet delvis.", + ej_verifierbar: "Kvalitetskontrollen kunde inte verifiera resultatet.", +}; + +/** Klipper en mening vid en rimlig gräns utan att kapa mitt i ett ord. */ +function korta(text, max = 220) { + const rå = String(text ?? "").trim().replace(/\s+/g, " "); + if (rå.length <= max) return rå; + const brytpunkt = rå.lastIndexOf(" ", max); + return `${rå.slice(0, brytpunkt > max * 0.6 ? brytpunkt : max)}…`; +} + +/** + * Bygger sammanfattningen. + * + * Ordningen är den en läsare faktiskt behöver: vad gällde det, vad sade + * kunden, kunde vi se det, vad blev orsaken, vad gjorde vi, höll det, + * vad är kvar. Varje mening utelämnas när underlaget saknas — utom de + * som handlar om att något saknas, för de är själva poängen. + * + * @returns { meningar[], text, fullstandig, saknas[] } + */ +export function sammanfatta(arende) { + const h = handelserI(arende); + const meningar = []; + const saknas = []; + + // 1 · Objektet. + const objekt = sista(h, "objekt_identifierat")?.objekt; + if (objekt) { + const delar = [objekt.beskrivning, objekt.identifierare].filter(Boolean).join(", "); + meningar.push(`Ärendet gäller ${delar || "ett identifierat objekt"}.`); + } else { + saknas.push("objektidentifiering"); + meningar.push("Objektet är inte identifierat i loggen."); + } + + // 2 · Kundens ord, ordagrant men kortat. + const fel = sista(h, "felbeskrivning")?.text; + if (fel) meningar.push(`Kunden beskrev: ”${korta(fel, 160)}”`); + else saknas.push("felbeskrivning"); + + // 3 · Kunde vi se det? Detta är den mening en bedömare läser först. + const repro = sista(h, "reproducering"); + if (repro) meningar.push(REPRODUKTION[repro.status] ?? "Reproduktionen är dokumenterad."); + else saknas.push("symptomverifiering"); + + // 4 · Orsaken, med teknikerns eget varför när det finns. + const orsak = sista(h, "felorsak"); + const slutsats = sista(h, "slutsats"); + if (slutsats && slutsats.orsakFastställd === false) { + meningar.push(`Orsaken kunde inte fastställas: ${korta(slutsats.motivering, 200)}`); + } else if (orsak) { + const kategori = (orsak.orsaker ?? []).join(", "); + meningar.push(`Konstaterad avvikelse: ${korta(orsak.avvikelse, 180)}${kategori ? ` Bedömd orsak: ${kategori}.` : ""}`); + if (slutsats?.motivering) meningar.push(`Motivering: ${korta(slutsats.motivering, 220)}`); + } else { + saknas.push("felorsaksanalys"); + } + + // 5 · Vad som gjordes. + const atgard = sista(h, "atgard_utford"); + if (atgard) { + meningar.push( + atgard.utford + ? `Utförd åtgärd: ${korta(atgard.beskrivning, 160)}` + : `Ingen åtgärd utfördes: ${korta(atgard.motivering || atgard.beskrivning, 160)}`, + ); + } else saknas.push("åtgärd"); + + // 6 · Höll det? + const kvalitet = sista(h, "kvalitetskontroll"); + if (kvalitet) meningar.push(KVALITET[kvalitet.resultat] ?? "Kvalitetskontrollen är dokumenterad."); + + // 7 · Vad som är kvar. Utelämnas aldrig när den finns — det är den + // enda meningen som talar om vad läsaren själv behöver göra. + if (slutsats?.kvarstaende) meningar.push(`Kvarstående: ${korta(slutsats.kvarstaende, 160)}`); + + return { + version: "ALVA-PROC-0030", + meningar, + text: meningar.join(" "), + /** Sant när inget obligatoriskt underlag saknas. */ + fullstandig: saknas.length === 0, + saknas, + }; +} + +/** + * Kortformen: en enda mening för en lista eller ett API-svar där + * utrymmet är en rad. + */ +export function enrading(arende) { + const h = handelserI(arende); + const objekt = sista(h, "objekt_identifierat")?.objekt; + const slutsats = sista(h, "slutsats"); + const orsak = sista(h, "felorsak"); + const avslutat = h.some((x) => x?.typ === "arende_avslutat"); + + const vad = objekt?.identifierare ?? "Oidentifierat objekt"; + if (!avslutat) return `${vad} — pågående.`; + if (slutsats?.orsakFastställd === false) return `${vad} — avslutat utan fastställd orsak.`; + if (orsak) return `${vad} — ${korta(orsak.avvikelse, 90)}`; + return `${vad} — avslutat.`; +} diff --git a/felsokning/services-backup/gemensam/sprak/de.mjs b/felsokning/services-backup/gemensam/sprak/de.mjs new file mode 100644 index 0000000..46a5344 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/de.mjs @@ -0,0 +1,329 @@ +// ALVA-SPEC-060 · Deutsch. +// +// Anweisungen stehen im Infinitiv, nicht in der Anrede: "Kilometerzähler +// fotografieren", nicht "Fotografieren Sie den Kilometerzähler". Das ist +// der Registerton technischer Anweisungen im deutschen Sprachraum und +// entspricht ALVA-SPEC-001 §7 — das System meldet einen Zustand, es +// wendet sich nicht an den Benutzer. +// +// `granskat: false`: die Oberfläche ist übersetzt, der Verfahrenstext +// ist nicht von einer Fachkraft geprüft. Siehe index.mjs. + +export const DE = { + // ---- Evidenzgrade ------------------------------------------------------- + "evidens.E1": "Beobachtung", + "evidens.E2": "Fotografie", + "evidens.E3": "Video mit Ton", + "evidens.E4": "Messung, kalibriertes Messmittel", + "evidens.E5": "Dokument", + "evidens.ej_kalibrerad": "Kalibrierung fehlt oder ist abgelaufen", + "evidens.ej_angivet": "Messmittel nicht angegeben", + "evidens.ej_fotograferad": "eingetragen, nicht fotografiert", + + // ---- Qualitätsschranke -------------------------------------------------- + "grind.objekt": "Identität des Fahrzeugs oder Objekts verifiziert", + "grind.historik": "Fahrzeughistorie geprüft, oder das Unterlassen begründet", + "grind.historik.nekad": "Eine abgelehnte Historienprüfung erfordert eine Begründung.", + "grind.historik.saknas": "Keine Historienprüfung dokumentiert.", + "grind.matarstallning.ingaende": "Kilometerstand bei Annahme fotografiert", + "grind.matarstallning.utgaende": "Kilometerstand bei Rückgabe fotografiert", + "grind.matarstallning.saknas": "Kein Kilometerstand dokumentiert.", + "grind.matarstallning.ej_foto": + "Der Kilometerstand ist eingetragen, aber nicht fotografiert. Kilometerzähler fotografieren, oder angeben, warum dies nicht möglich ist.", + "grind.reproducering": "Symptomverifizierung: reproduziert oder als nicht reproduzierbar dokumentiert", + "grind.felorsak": "Grundursachenanalyse dokumentiert", + "grind.atgard": "Korrekturmaßnahme dokumentiert, oder ihr Ausbleiben begründet", + "grind.atgard.saknas": "Weder eine durchgeführte Maßnahme noch ein Grund für deren Ausbleiben ist dokumentiert.", + "grind.kundbeslut": "Kundenentscheidung zum Vorschlag erfasst", + "grind.kundbeslut.avbojt": "Arbeit trotz abgelehntem Vorschlag durchgeführt", + "grind.kundbeslut.avbojt.detalj": + "Der Kunde hat den Vorschlag abgelehnt, es ist jedoch durchgeführte Arbeit dokumentiert.", + "grind.kvalitetskontroll": "Qualitätsprüfung durchgeführt — Symptom nachweislich behoben", + "grind.kontroller": "Prüfpunkte der Methodik: Evidenz oder dokumentierte Ausnahme", + "grind.foton": "Fotos für die Prüfpunkte vorhanden, die sie erfordern", + "grind.slutsats": "Abschlussfeststellung (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Hochvolt-Qualifikation bestätigt", + "grind.hogvolt.spanningslos": "Fahrzeug nach Herstellerverfahren spannungsfrei geschaltet, Spannungsfreiheit durch Messung festgestellt", + "grind.regelpaket": "Signatur des Regelpakets stimmt nicht — Abschluss gesperrt.", + "grind.regelpaket.osignerat": "Ein externes Regelpaket wird ohne Signatur verwendet — Abschluss gesperrt.", + "grind.evidens": "Evidenzgrad über E0", + "grind.evidens.saknas": "Es liegt keinerlei Evidenz im Protokoll vor.", + "grind.foton.detalj": "{kontroller} Prüfpunkte erfordern ein Foto, {foton} Fotos im Protokoll.", + "grind.sparr.ej_uppfyllt": "Die Sicherheitsanforderung ist nicht erfüllt.", + "grind.arendetyp.okant": "Unbekannte Anforderung im Regelpaket: {krav}", + "grind.arendetyp.krav": "Anforderung für diese Vorgangsart: {krav}", + "grind.sakerhet": "Konfidenzniveau innerhalb dessen, was die Evidenz trägt", + "grind.sakerhet.detalj": "Die angegebene Konfidenz ({niva}) übersteigt, was die Evidenz trägt ({tak}). Evidenz ergänzen oder das Niveau senken — ehrliche Unsicherheit ist Information.", + "grind.eskalering": "Technische Eskalationen beantwortet", + "grind.eskalering.detalj": "Eine Eskalation ist offen ({referens}). Die Antwort abwarten und dokumentieren, bevor der Vorgang abgeschlossen wird.", + + + // ---- Abschlussfeststellung (ALVA-RULE-200) ------------------------------ + "slutsats.rubrik": "Abschlussfeststellung", + "slutsats.konstaterat": "Was festgestellt wurde", + "slutsats.evidens": "Welche Evidenz dies trägt", + "slutsats.avfardat": "Welche Hypothesen verworfen wurden, und warum", + "slutsats.osakert": "Was unsicher bleibt", + "slutsats.ickesvar": + "Das ist keine Antwort. Angeben, was festgestellt wurde und welche Evidenz dies trägt.", + "slutsats.falt.motivering": "Begründung", + "slutsats.falt.motivering_ej": "Grund, warum die Ursache nicht festgestellt werden konnte", + "slutsats.falt.uteslutet": "Verworfene Alternativen", + "slutsats.falt.kvarstaende": "Verbleibende Unsicherheit", + "slutsats.falt.atgardsval": "Wahl der Maßnahme", + "slutsats.saknas": "{falt} fehlt.", + "slutsats.ickesvar.falt": "{falt}: „{text}“ ist keine Begründung. Angeben, was tatsächlich gilt, und warum.", + "slutsats.for_kort": "{falt} ist zu kurz ({langd} von mindestens {minsta} Zeichen), um im Nachhinein prüfbar zu sein.", + "slutsats.utan_varfor": "{falt} nennt das Was, nicht das Warum. Die Feststellung an die Evidenz binden — was darin begründet diese Folgerung?", + "slutsats.utan_slutsats": "Der Vorgang kann ohne Abschlussfeststellung nicht abgeschlossen werden. Angeben, warum die Feststellung aus der Evidenz folgt.", + "slutsats.hypotes_obemott": "Die Hypothese „{text}“ steht im Protokoll, wird aber nicht behandelt. Angeben, warum sie verworfen wurde oder warum sie offen bleibt.", + + + // ---- Vorgangsablauf ----------------------------------------------------- + "arende.nytt": "Neuer Vorgang", + "arende.oppna": "Offene Vorgänge", + "arende.avslutade": "Abgeschlossene Vorgänge", + "arende.avsluta": "Vorgang abschließen", + "arende.avslutat": "Vorgang abgeschlossen", + "arende.kan_ej_avslutas": "Der Vorgang kann noch nicht abgeschlossen werden", + "arende.hinder": "Vor dem Abschluss offen", + "arende.overlamna": "Übergeben", + "arende.ansvarig": "Verantwortlich", + + // ---- Vorprüfung --------------------------------------------------------- + "pre.rubrik": "Vorprüfung — vor Arbeitsbeginn", + "pre.historik.fraga": + "Ist die Fahrzeughistorie geprüft? (frühere Arbeiten, wiederkehrende Fehler, TSB, Rückruf- und Serviceaktionen)", + "pre.historik.ja": "Ja — geprüft", + "pre.historik.nej": "Nein", + "pre.historik.skal": "Grund, warum die Historie nicht geprüft wurde (erforderlich)", + "pre.historik.relevant": "Relevante frühere Arbeiten (optional — Ursachenkette)", + "pre.matarstallning": "Kilometerstand", + "pre.fotografera": "Kombiinstrument fotografieren", + "pre.felbeskrivning": "Fehlerbeschreibung des Kunden verifiziert", + "pre.observationer": "Sonstiges bei der Annahme?", + + // ---- Handlungen --------------------------------------------------------- + "handling.spara": "Speichern", + "handling.avbryt": "Abbrechen", + "handling.fortsatt": "Weiter", + "handling.tillbaka": "Zurück", + "handling.dokumentera": "Dokumentieren", + "handling.fotografera": "Fotografieren", + "handling.spela_in": "Video aufnehmen", + "handling.undantag": "Ausnahme dokumentieren", + "handling.undantag.skal": "Begründung der Ausnahme (erforderlich)", + "handling.exportera": "Exportieren", + "handling.skriv_ut": "Drucken", + + // ---- Messung ------------------------------------------------------------ + "matning.varde": "Wert", + "matning.enhet": "Einheit", + "matning.matdon": "Messmittel", + "matning.matdon.valj": "Messmittel wählen", + "matning.matdon.okant": "Unbekanntes Messmittel. Das Messmittel vor dem Speichern der Messung registrieren.", + "matning.kalibrerad_till": "Kalibriert bis", + + // ---- Bericht ------------------------------------------------------------ + "rapport.rubrik": "Vorgangsbericht", + "rapport.sammanfattning": "Zusammenfassung", + "rapport.evidens": "Evidenz", + "rapport.atgarder": "Maßnahmen", + "rapport.harledd": + "Aus dem Vorgangsprotokoll abgeleitet. Beobachtungen und Messungen werden ohne Schlussfolgerungen wiedergegeben, denen die Grundlage fehlt.", + + // ---- Sprache ------------------------------------------------------------ + "sprak.valj": "Sprache", + "sprak.granskat": "Geprüft", + "sprak.ogranskat": "Nicht geprüft", + "sprak.tackning": "{procent} % der Oberfläche", + + "metodik.ogranskad": + "Der Verfahrenstext wurde nicht von einer Fachkraft auf {sprak} geprüft. Schritte und Prüfpunkte werden auf Englisch angezeigt, wo keine geprüfte Übersetzung vorliegt — eine ungeprüfte Übersetzung einer Sicherheitsanweisung ist schlechter als eine fremdsprachige, weil sie nicht fremd aussieht.", + "metodik.pa_engelska": "Auf Englisch angezeigt — keine geprüfte Übersetzung auf {sprak}", + + // ---- Öffentliche Website (webb.) --------------------------------------- + "webb.nav.oversikt": "Überblick", + "webb.ansok": "Konto beantragen", + "webb.loggain": "Anmeldung", + "webb.fot.impressum": "Impressum", + "webb.fot.dataskydd": "Datenschutz", + "webb.fot.villkor": "Nutzungsbedingungen", + "webb.fot.tillganglighet": "Barrierefreiheit", + "webb.fot.sprak": "Sprachen", + "webb.fot.utgavor": "Versionshinweise", + + "webb.hero.position": "Geführte Diagnoseplattform", + "webb.hero.definition": "Standardisierte Diagnoseprozeduren für wiederholbare und verifizierbare Fehlersuche.", + + "webb.metod.etikett": "Methodik", + "webb.metod.rubrik": "Das ALVA-Modell", + "webb.metod.ingress": + "ALVA ist eine standardisierte Methode für systematische Analyse, Lokalisierung, Verifikation und Maßnahme. Jede Entscheidung ist nachvollziehbar, jede Schlussfolgerung verifizierbar, jede Maßnahme reproduzierbar.", + "webb.fas.analysis.syfte": "Evidenz sammeln.", + "webb.fas.analysis.avgransning": "Nur Fakten. Hypothesen werden in dieser Phase nicht erfasst.", + "webb.fas.localization.syfte": "Den Fehler isolieren.", + "webb.fas.localization.avgransning": "Den Bereich eingrenzen. Die Ursache ist noch nicht festgestellt.", + "webb.fas.verification.syfte": "Grundursache bestätigen.", + "webb.fas.verification.avgransning": "Verifiziert wird die Ursache — nicht das Symptom.", + "webb.fas.action.syfte": "Korrekturmaßnahme durchführen.", + "webb.fas.action.avgransning": "Ergebnis verifiziert und dokumentiert. Andernfalls ist die Phase unvollständig.", + + "webb.drift.etikett": "Betrieb", + "webb.drift.rubrik": "So funktioniert es", + "webb.drift.s1.rubrik": "Organisation anlegen", + "webb.drift.s1.text": "Die Registrierung wird eingereicht und geprüft.", + "webb.drift.s2.rubrik": "Benutzer erhalten Konten", + "webb.drift.s2.text": "Rollen werden vom Administrator der Organisation vergeben.", + "webb.drift.s3.rubrik": "Geführte Diagnoseprozeduren", + "webb.drift.s3.text": "Jeder Vorgang folgt einer definierten Prozedur.", + "webb.drift.s4.rubrik": "Verifikation", + "webb.drift.s4.text": "Die Grundursache wird vor der Korrekturmaßnahme bestätigt.", + "webb.drift.s5.rubrik": "Dokumentation", + "webb.drift.s5.text": "Der Bericht wird aus dem Vorgangsprotokoll erzeugt.", + "webb.drift.s6.rubrik": "Kontinuierliche Verbesserung", + "webb.drift.s6.text": "Verifizierte Ergebnisse verfeinern nachfolgende Prozeduren.", + + "webb.larande.etikett": "Fähigkeit", + "webb.larande.rubrik": "Lernende Organisation", + "webb.larande.p1": + "Die Plattform verfeinert Diagnoseprozeduren fortlaufend mit verifizierter Betriebserfahrung aus der eigenen Organisation.", + "webb.larande.p2": + "Jede abgeschlossene Diagnose fließt in die nachfolgende Anleitung ein. Nur verifizierte Ergebnisse werden verwendet — ein Vorgang, der ohne bestätigte Grundursache geschlossen wurde, trägt bewusst nichts bei.", + "webb.larande.p3": + "Die Wissensbasis gehört der Organisation. Sie wird aus Ihren Vorgängen, Ihren Prozeduren und Ihrer Dokumentation abgeleitet und nicht über Kunden hinweg zusammengeführt.", + "webb.larande.block": "Abgeleitet aus", + "webb.larande.k1": "Verifizierte Grundursachen", + "webb.larande.k2": "Bestätigte Korrekturmaßnahmen", + "webb.larande.k3": "Wiederkehrende Fehlerkategorien", + "webb.larande.k4": "Nachweise abgeschlossener Prozeduren", + "webb.larande.k5": "Dokumentation der Organisation", + + "webb.rapport.etikett": "Berichtswesen", + "webb.rapport.rubrik": "Vierteljährlicher Verbesserungsbericht", + "webb.rapport.ingress": + "Einmal im Quartal beantwortet die Plattform sechs Fragen zur eigenen Arbeit des Betriebs. Jede Antwort wird aus dem Vorgangsprotokoll abgeleitet — nichts wird geschätzt, und niemand wird befragt.", + "webb.rapport.f1": "Welche Fehler diagnostizieren wir jetzt beim ersten Mal richtig?", + "webb.rapport.f2": "Wie oft erweist sich eine vermutete Ursache als die tatsächliche?", + "webb.rapport.f3": "Welche Fehler kehren in der Flotte immer wieder?", + "webb.rapport.f4": "Welche Prozedurschritte werden übersprungen, und warum?", + "webb.rapport.f5": "Was weiß der Betrieb jetzt, was er im letzten Quartal nicht wusste?", + "webb.rapport.f6": "Werden Vorgänge abgeschlossen, verifiziert und dokumentiert — oder nur abgeschlossen?", + + "webb.pris.etikett": "Kommerzielles", + "webb.pris.rubrik": "Lizenzierung", + "webb.pris.komponent": "Komponente", + "webb.pris.grund": "Grundlage", + "webb.pris.plattform": "Plattformlizenz", + "webb.pris.plattform.grund": "Je Organisation, jährlich", + "webb.pris.anvandare": "Benutzerlizenzen", + "webb.pris.anvandare.grund": "Pro aktivem Benutzer, monatlich", + "webb.pris.moduler": "Unternehmensmodule", + "webb.pris.moduler.grund": "Optional, je Modul", + "webb.pris.betalning": "Keine Online-Zahlung. Keine Abo-Anmeldung. Die Rechnungsstellung folgt auf die Prüfung des Antrags.", + "webb.pris.aktivering": "Aktivierungsablauf", + "webb.pris.a1": "Die Organisation reicht die Registrierung ein.", + "webb.pris.a2": "Der Antrag wird geprüft.", + "webb.pris.a3": "Die Rechnung wird gestellt.", + "webb.pris.a4": "Die Zahlung wird verbucht.", + "webb.pris.a5": "Die Organisation wird aktiviert.", + "webb.pris.a6": "Weitere Benutzer werden monatlich abgerechnet.", + + "webb.kallor.etikett": "Infrastruktur", + "webb.kallor.rubrik": "Operative Wissensinfrastruktur", + "webb.kallor.p1": + "ALVA nutzt die eigenen autorisierten Wissensquellen der Organisation. Die Architektur ist anbieterneutral: Jede Quelle implementiert dieselbe Schnittstelle, und kein Anbieter wird vorausgesetzt.", + "webb.kallor.p2": + "Eine Beta-Installation arbeitet allein mit interner Dokumentation. Externe Anbieter werden später über separate Konnektoren angebunden, ohne Änderung am Plattformkern.", + "webb.kallor.block": "Auflösungsreihenfolge der Quellen", + "webb.kallor.k1": "Interne Unternehmensprozeduren", + "webb.kallor.k2": "OEM-Dokumentation", + "webb.kallor.k3": "Technische Bulletins", + "webb.kallor.k4": "Garantieinformationen", + "webb.kallor.k5": "Werkstatthandbücher", + "webb.kallor.k6": "Historische verifizierte Diagnosen", + "webb.kallor.k7": "Bewährte Praxis der Organisation", + + "webb.login.etikett": "Zugang", + "webb.login.demo": + "Diese Anmeldung authentifiziert nichts. Geben Sie Beliebiges in E-Mail und Passwort ein und drücken Sie Anmelden — kein Konto ist nötig, und das Portal dahinter zeigt feste Beispieldaten. Mit einer Plattforminstanz verbunden authentifiziert sich diese Seite gegen sie, und ohne gültige Sitzung ist das Portal geschlossen.", + "webb.login.losenord": "Passwort", + "webb.login.ofullstandigt": "Authentifizierung unvollständig. E-Mail und Passwort erforderlich.", + "webb.login.misslyckades": "Authentifizierung fehlgeschlagen.", + "webb.login.logga_in": "Anmelden", + "webb.login.loggar_in": "Anmeldung läuft", + "webb.login.glomt": "Passwort vergessen", + + "webb.ansokan.etikett": "Registrierung", + "webb.ansokan.demo": + "Nichts wird gesendet. Das Absenden dieses Formulars speichert nichts, benachrichtigt niemanden und erzeugt keinen Antrag — dahinter steht noch keine Antragsentgegennahme. Die Beschreibung unten sagt, wie die Registrierung funktionieren soll, nicht was heute geschieht.", + "webb.ansokan.avsikt": "Vorgesehener Betrieb: Anträge werden manuell geprüft, und in diesem Schritt wird keine Zahlung erhoben.", + "webb.falt.foretag": "Unternehmen", + "webb.falt.orgnummer": "Handelsregisternummer", + "webb.falt.kontakt": "Ansprechpartner", + "webb.falt.epost": "E-Mail", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Anzahl Techniker", + "webb.falt.bransch": "Branche", + "webb.falt.land": "Land", + "webb.falt.anvandare": "Erwartete Benutzerzahl", + "webb.falt.noteringar": "Anmerkungen", + "webb.falt.kravs": "(erforderlich)", + "webb.ansokan.skicka": "Antrag absenden", + "webb.ansokan.mottagen.etikett": "Antrag", + "webb.ansokan.mottagen": "Eingereicht", + "webb.ansokan.mottagen.demo": + "Nichts wurde übermittelt, und niemand wurde benachrichtigt. Die Referenz unten wurde in Ihrem Browser aus Ihren Eingaben berechnet und existiert nirgendwo sonst — sie ist weg, wenn Sie diese Seite schließen.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Referenz", + "webb.ansokan.granskning": + "Anträge werden manuell geprüft. Bei Genehmigung wird eine Rechnung gestellt. Die Organisation wird aktiviert, sobald die Zahlung verbucht ist.", + + "webb.sprak.etikett": "Lokalisierung", + "webb.sprak.rubrik": "Sprachen", + "webb.sprak.ingress": + "Englisch ist Standard- und Quellsprache. Neun Übersetzungen folgen. Was die Plattform nicht tut: behaupten, dass eine übersetzte Oberfläche eine übersetzte Methode bedeutet.", + "webb.sprak.princip.etikett": "Prinzip", + "webb.sprak.princip.rubrik": "Zwei Arten von Text", + "webb.sprak.granssnitt.rubrik": "Oberflächentext", + "webb.sprak.granssnitt.beteckning": "Fällt still zurück", + "webb.sprak.granssnitt.text": + "Beschriftungen, Schaltflächen, Status. Begrenzter Bestand, selten geändert. Eine englische Zeichenkette bei einem deutschen Benutzer ist ein Ärgernis, keine Gefahr — eine fehlende Übersetzung fällt daher kommentarlos auf Englisch zurück.", + "webb.sprak.metodik.rubrik": "Verfahrenstext", + "webb.sprak.metodik.beteckning": "Fällt nie still zurück", + "webb.sprak.metodik.text": + "Anweisungen für Arbeiten an einem Fahrzeug. Hier ist eine ungeprüfte Übersetzung schlimmer als eine fremdsprachige — denn Englisch sieht fremd aus, während eine schlechte Übersetzung wie eine Anweisung aussieht. Sie wird auf Englisch gezeigt und gekennzeichnet, mit benannter Sprache.", + "webb.sprak.invariant": + "Die Methode selbst wird nie übersetzt. Phasennamen und Statuswörter sind ALVAs Struktur und lesen sich in jedem Land gleich, sodass ein Prüfer eine rumänische und eine deutsche Vorgangsakte lesen kann, ohne zu wissen, in welcher Sprache der Betrieb arbeitet.", + "webb.sprak.tackning.etikett": "Abdeckung", + "webb.sprak.tackning.rubrik": "Was übersetzt ist, und was geprüft ist", + "webb.sprak.kolumn.sprak": "Sprache", + "webb.sprak.kolumn.granssnitt": "Oberfläche", + "webb.sprak.kolumn.granskat": "Methodik von einer Fachkraft geprüft", + "webb.sprak.granskat.ja": "Ja", + "webb.sprak.granskat.nej": "Nein — Verfahrenstext auf Englisch", + "webb.sprak.matt": + "Die Abdeckung der Oberfläche wird gemessen, nicht geschätzt: Ein Test lässt den Build scheitern, wenn irgendein Schlüssel in irgendeiner Sprache fehlt. Der Prüfstatus ist eine Aussage über Menschen, nicht über Dateien, und wird von Hand gesetzt.", + "webb.sprak.kallsprak": + "Der englische Verfahrenstext ist eine Übersetzung der schwedischen Quelle und wurde noch nicht von einer im Handwerk tätigen Fachkraft gelesen. Er wird am selben Maßstab gemessen wie die anderen neun Sprachen, und dieselbe Aussage steht hier über ihn, statt ihn still auszunehmen, weil er die Quelle ist.", + "webb.sprak.bevis.etikett": "Verifikation", + "webb.sprak.bevis.rubrik": "Die Sätze, die einen Vorgang stoppen", + "webb.sprak.bevis.ingress": + "Dies sind die Sätze, die einem Techniker den Abschluss eines Vorgangs verweigern. Eine Verweigerung, die niemand versteht, ist eine Verweigerung ohne Ausweg — also sind es die richtigen Sätze, um eine Übersetzung zu beurteilen.", + "webb.sprak.ej_granskad": "Nicht geprüft", + "webb.sprak.granskad": "Geprüft", + "webb.sprak.granskad.text": + "Der Verfahrenstext auf {sprak} wurde von einer im Handwerk tätigen Fachkraft gelesen. Schritte und Prüfpunkte werden durchgehend auf {sprak} angezeigt.", + "webb.sprak.val.etikett": "Betrieb", + "webb.sprak.val.rubrik": "Wie die Sprache gewählt wird", + "webb.sprak.val.s1.rubrik": "Wahl des Benutzers", + "webb.sprak.val.s1.text": "Was die einzelne Person gewählt hat.", + "webb.sprak.val.s2.rubrik": "Einstellung der Organisation", + "webb.sprak.val.s2.text": "Die Dokumentationssprache des Betriebs.", + "webb.sprak.val.s3.rubrik": "Browsersprache", + "webb.sprak.val.s3.text": "Die erste Sprache, die die Plattform erkennt.", + "webb.sprak.val.s4.rubrik": "Englisch", + "webb.sprak.val.s4.text": "Der Standard, und die Quelle.", + "webb.sprak.val.notering": + "Die Organisation steht bewusst über dem Browser. Ein Betrieb in Deutschland mit einem polnischen Techniker braucht eine gemeinsame Dokumentationssprache — die Sprache der Vorgangsakte darf nicht davon abhängen, wer die Zeile gerade geschrieben hat.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/en.mjs b/felsokning/services-backup/gemensam/sprak/en.mjs new file mode 100644 index 0000000..472e92d --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/en.mjs @@ -0,0 +1,362 @@ +// ALVA-SPEC-060 · English — the source language. +// +// Every key that exists lives here first. A key added to another +// catalogue but not to this one is not a translation; it is a string +// with no original, and the test refuses it. +// +// ---- Naming -------------------------------------------------------------- +// +// `område.sak` — the prefix says where the string is used, so a +// translator can read the file in the order the user meets it rather +// than alphabetically. +// +// ---- Tone --------------------------------------------------------------- +// +// The status language of ALVA-SPEC-001 §7 applies to every string here: +// the system reports state, it does not address the user. "Verification +// passed", not "Well done!". A translator who makes it friendlier has +// made it wrong. + +export const EN = { + // ---- Phases (ALVA) ---------------------------------------------------- + "fas.analys": "Analysis", + "fas.lokalisering": "Localization", + "fas.verifiering": "Verification", + "fas.atgard": "Action", + + // ---- Status ------------------------------------------------------------ + "status.pending": "Pending", + "status.in_progress": "In progress", + "status.complete": "Complete", + "status.passed": "Passed", + "status.failed": "Failed", + "status.blocked": "Blocked", + "status.incomplete": "Incomplete", + "status.not_applicable": "Not applicable", + + // ---- Evidence grades ---------------------------------------------------- + "evidens.E1": "Observation", + "evidens.E2": "Photograph", + "evidens.E3": "Video with sound", + "evidens.E4": "Measurement, calibrated instrument", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "calibration missing or expired", + "evidens.ej_angivet": "instrument not stated", + "evidens.ej_fotograferad": "entered, not photographed", + + // ---- The quality gate --------------------------------------------------- + // + // These are the highest-stakes strings in the product. Each one is the + // reason a technician cannot close a case, so it has to say what is + // missing — not that something is missing. + "grind.objekt": "Vehicle or object identification verified", + "grind.historik": "Vehicle history checked, or the omission justified", + "grind.historik.nekad": "A declined history check requires a stated reason.", + "grind.historik.saknas": "No history check documented.", + "grind.matarstallning.ingaende": "Incoming odometer reading photographed", + "grind.matarstallning.utgaende": "Outgoing odometer reading photographed", + "grind.matarstallning.saknas": "No odometer reading documented.", + "grind.matarstallning.ej_foto": + "The reading is entered but not photographed. Photograph the odometer, or state why that is not possible.", + "grind.reproducering": "Symptom verification: reproduced, or documented as not reproducible", + "grind.felorsak": "Root cause analysis documented", + "grind.atgard": "Corrective action documented, or its absence justified", + "grind.atgard.saknas": "Neither a performed action nor a reason for its absence is documented.", + "grind.kundbeslut": "Customer decision on the proposal recorded", + "grind.kundbeslut.avbojt": "Work performed despite a declined proposal", + "grind.kundbeslut.avbojt.detalj": "The customer declined the proposal, but work has been documented as performed.", + "grind.kvalitetskontroll": "Quality check performed — symptom verified as resolved", + "grind.kontroller": "Methodology checks: evidence, or a documented exemption", + "grind.foton": "Photographs present for checks that require them", + "grind.slutsats": "Closing statement (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "High-voltage authorization confirmed", + "grind.hogvolt.spanningslos": "Vehicle de-energized per the manufacturer’s procedure, absence of voltage verified by measurement", + "grind.regelpaket": "Rule package signature does not match — closing blocked.", + "grind.regelpaket.osignerat": "An external rule package is in use without a signature — closing blocked.", + "grind.evidens": "Evidence above E0", + "grind.evidens.saknas": "No evidence of any kind is present in the log.", + "grind.foton.detalj": "{kontroller} checks require a photograph; {foton} photographs are in the log.", + "grind.sparr.ej_uppfyllt": "The safety requirement is not met.", + "grind.arendetyp.okant": "Unknown requirement in the rule package: {krav}", + "grind.arendetyp.krav": "Requirement for this case type: {krav}", + "grind.sakerhet": "Confidence level within what the evidence supports", + "grind.sakerhet.detalj": "The stated confidence ({niva}) exceeds what the evidence supports ({tak}). Add supporting evidence, or lower the confidence — honest uncertainty is information.", + "grind.eskalering": "Technical escalations answered", + "grind.eskalering.detalj": "An escalation is open ({referens}). Await the answer and document it before the case is closed.", + + + // ---- The closing statement (ALVA-RULE-200) ------------------------------ + "slutsats.rubrik": "Closing statement", + "slutsats.konstaterat": "What was established", + "slutsats.evidens": "Which evidence carries it", + "slutsats.avfardat": "Which hypotheses were dismissed, and why", + "slutsats.osakert": "What remains uncertain", + "slutsats.ickesvar": "That is not an answer. State what was established and which evidence carries it.", + "slutsats.falt.motivering": "Rationale", + "slutsats.falt.motivering_ej": "Reason the cause could not be established", + "slutsats.falt.uteslutet": "Dismissed alternatives", + "slutsats.falt.kvarstaende": "Remaining uncertainty", + "slutsats.falt.atgardsval": "Choice of action", + "slutsats.saknas": "{falt} is missing.", + "slutsats.ickesvar.falt": "{falt}: “{text}” is not a reason. State what actually applies, and why.", + "slutsats.for_kort": "{falt} is too short ({langd} of at least {minsta} characters) to be reviewable afterwards.", + "slutsats.utan_varfor": "{falt} states what, but not why. Tie the conclusion to the evidence — what in it makes this follow?", + "slutsats.utan_slutsats": "The case cannot be closed without a closing statement. State why the conclusion follows from the evidence.", + "slutsats.hypotes_obemott": "The hypothesis “{text}” is in the log but is not addressed. State why it was dismissed, or why it remains open.", + + + // ---- Case flow ----------------------------------------------------------- + "arende.nytt": "New case", + "arende.oppna": "Open cases", + "arende.avslutade": "Closed cases", + "arende.avsluta": "Close case", + "arende.avslutat": "Case closed", + "arende.kan_ej_avslutas": "The case cannot be closed yet", + "arende.hinder": "Outstanding before closing", + "arende.overlamna": "Hand over", + "arende.ansvarig": "Assigned to", + + // ---- Pre-diagnostics ------------------------------------------------------ + "pre.rubrik": "Pre-diagnostics — before the work begins", + "pre.historik.fraga": "Has the vehicle’s history been checked? (previous work, recurring faults, TSBs, campaigns)", + "pre.historik.ja": "Yes — checked", + "pre.historik.nej": "No", + "pre.historik.skal": "Reason the history was not checked (required)", + "pre.historik.relevant": "Relevant previous work (optional — causal chain)", + "pre.matarstallning": "Odometer reading", + "pre.fotografera": "Photograph the instrument cluster", + "pre.felbeskrivning": "Customer’s fault description verified", + "pre.observationer": "Anything else noted at vehicle reception?", + + // ---- Common actions -------------------------------------------------------- + "handling.spara": "Save", + "handling.avbryt": "Cancel", + "handling.fortsatt": "Continue", + "handling.tillbaka": "Back", + "handling.dokumentera": "Document", + "handling.fotografera": "Photograph", + "handling.spela_in": "Record video", + "handling.undantag": "Document an exemption", + "handling.undantag.skal": "Reason for the exemption (required)", + "handling.exportera": "Export", + "handling.skriv_ut": "Print", + + // ---- Measurement ------------------------------------------------------------- + "matning.varde": "Value", + "matning.enhet": "Unit", + "matning.matdon": "Instrument", + "matning.matdon.valj": "Select instrument", + "matning.matdon.okant": "Unknown instrument. Register it before saving the measurement.", + "matning.kalibrerad_till": "Calibration valid until", + + // ---- Report -------------------------------------------------------------------- + "rapport.rubrik": "Case report", + "rapport.sammanfattning": "Summary", + "rapport.evidens": "Evidence", + "rapport.atgarder": "Actions", + "rapport.harledd": "Generated from the case log. Observations and measurements are reported without conclusions that lack support.", + + // ---- Language ---------------------------------------------------------------------- + "sprak.valj": "Language", + "sprak.granskat": "Reviewed", + "sprak.ogranskat": "Not reviewed", + "sprak.tackning": "{procent}% of the interface", + + // Shown when the methodology content has not been reviewed by a + // specialist in the user’s language. Deliberately specific: a general + // disclaimer is read by nobody. + "metodik.ogranskad": + "Procedure text has not been reviewed by a technical specialist in {sprak}. Steps and checks are shown in English where no reviewed translation exists — an unreviewed translation of a safety instruction is worse than a foreign-language one, because it does not look foreign.", + "metodik.pa_engelska": "Shown in English — no reviewed translation in {sprak}", + + // ---- Public website (webb.) --------------------------------------------- + // + // The marketing site and the platform are the same system, so the site + // speaks through the same catalogue. Keys follow the page order. The + // portal chrome is deliberately absent: the portal is the product, and + // its language is governed by account and organization, not by the + // visitor’s footer choice. + "webb.nav.oversikt": "Overview", + "webb.ansok": "Request account", + "webb.loggain": "Sign in", + "webb.fot.impressum": "Legal notice", + "webb.fot.dataskydd": "Privacy", + "webb.fot.villkor": "Terms", + "webb.fot.tillganglighet": "Accessibility", + "webb.fot.sprak": "Languages", + "webb.fot.utgavor": "Release notes", + + "webb.hero.position": "Guided Diagnostic Platform", + "webb.hero.definition": "Standardized diagnostic procedures for repeatable and verifiable troubleshooting.", + + "webb.metod.etikett": "Methodology", + "webb.metod.rubrik": "The ALVA model", + "webb.metod.ingress": + "ALVA is a standardized method for systematic analysis, localization, verification and action. Every decision is traceable, every conclusion verifiable, every action reproducible.", + "webb.fas.analysis.syfte": "Collect evidence.", + "webb.fas.analysis.avgransning": "Facts only. Hypotheses are not collected in this phase.", + "webb.fas.localization.syfte": "Isolate the fault.", + "webb.fas.localization.avgransning": "Narrow the area. The cause is not yet established.", + "webb.fas.verification.syfte": "Confirm root cause.", + "webb.fas.verification.avgransning": "The cause is verified — not the symptom.", + "webb.fas.action.syfte": "Execute corrective action.", + "webb.fas.action.avgransning": "Result verified and documented. Otherwise the phase is incomplete.", + + "webb.drift.etikett": "Operation", + "webb.drift.rubrik": "How it works", + "webb.drift.s1.rubrik": "Create organization", + "webb.drift.s1.text": "An application is submitted and reviewed.", + "webb.drift.s2.rubrik": "Users receive accounts", + "webb.drift.s2.text": "Roles are assigned by the organization administrator.", + "webb.drift.s3.rubrik": "Guided diagnostic procedures", + "webb.drift.s3.text": "Each case follows a defined procedure.", + "webb.drift.s4.rubrik": "Verification", + "webb.drift.s4.text": "Root cause is confirmed before corrective action.", + "webb.drift.s5.rubrik": "Documentation", + "webb.drift.s5.text": "The record is generated from the case log.", + "webb.drift.s6.rubrik": "Continuous improvement", + "webb.drift.s6.text": "Verified outcomes refine subsequent procedures.", + + "webb.larande.etikett": "Capability", + "webb.larande.rubrik": "Enterprise learning", + "webb.larande.p1": + "The platform continuously refines diagnostic procedures using verified operational experience from your own organization.", + "webb.larande.p2": + "Every completed diagnostic contributes to subsequent guidance. Only verified outcomes are used — a case closed without a confirmed root cause contributes nothing, by design.", + "webb.larande.p3": + "The knowledge base belongs to the organization. It is derived from your cases, your procedures and your documentation, and it is not aggregated across customers.", + "webb.larande.block": "Derived from", + "webb.larande.k1": "Verified root causes", + "webb.larande.k2": "Confirmed corrective actions", + "webb.larande.k3": "Recurring fault categories", + "webb.larande.k4": "Procedure completion records", + "webb.larande.k5": "Organization documentation", + + "webb.rapport.etikett": "Reporting", + "webb.rapport.rubrik": "Quarterly improvement report", + "webb.rapport.ingress": + "Once a quarter, the platform answers six questions about the workshop’s own work. Every answer is derived from the case log — nothing is estimated, and nothing is asked of anyone.", + "webb.rapport.f1": "Which faults are we now diagnosing correctly the first time?", + "webb.rapport.f2": "How often does a suspected cause turn out to be the actual one?", + "webb.rapport.f3": "Which faults keep coming back across the fleet?", + "webb.rapport.f4": "Which procedure steps are being skipped, and why?", + "webb.rapport.f5": "What does the workshop know now that it did not know last quarter?", + "webb.rapport.f6": "Are cases being completed, verified and documented — or only completed?", + + "webb.pris.etikett": "Commercial", + "webb.pris.rubrik": "Licensing", + "webb.pris.komponent": "Component", + "webb.pris.grund": "Basis", + "webb.pris.plattform": "Platform license", + "webb.pris.plattform.grund": "Per organization, annually", + "webb.pris.anvandare": "User licenses", + "webb.pris.anvandare.grund": "Per active user, monthly", + "webb.pris.moduler": "Enterprise modules", + "webb.pris.moduler.grund": "Optional, per module", + "webb.pris.betalning": "No online payment. No subscription sign-up. Invoicing follows review of the application.", + "webb.pris.aktivering": "Activation sequence", + "webb.pris.a1": "Organization submits an application.", + "webb.pris.a2": "Application is reviewed.", + "webb.pris.a3": "Invoice is issued.", + "webb.pris.a4": "Payment is registered.", + "webb.pris.a5": "Organization is activated.", + "webb.pris.a6": "Additional users are billed monthly.", + + "webb.kallor.etikett": "Infrastructure", + "webb.kallor.rubrik": "Operational Knowledge Infrastructure", + "webb.kallor.p1": + "ALVA uses the organization’s own authorized knowledge sources. The architecture is provider-agnostic: every source implements the same interface, and no provider is assumed.", + "webb.kallor.p2": + "A beta installation operates on internal documentation alone. External providers are enabled later through separate connectors, without change to the platform core.", + "webb.kallor.block": "Source resolution order", + "webb.kallor.k1": "Internal company procedures", + "webb.kallor.k2": "OEM documentation", + "webb.kallor.k3": "Technical bulletins", + "webb.kallor.k4": "Warranty information", + "webb.kallor.k5": "Service manuals", + "webb.kallor.k6": "Historical verified diagnostics", + "webb.kallor.k7": "Organization best practices", + + "webb.login.etikett": "Access", + "webb.login.demo": + "This login authenticates nothing. Type anything into Email and Password and press Sign in — no account is needed, and the portal behind it presents fixed example data. When connected to a platform instance, this page authenticates against it, and the portal is closed without a valid session.", + "webb.login.losenord": "Password", + "webb.login.ofullstandigt": "Authentication incomplete. Email and password required.", + "webb.login.misslyckades": "Authentication failed.", + "webb.login.logga_in": "Sign in", + "webb.login.loggar_in": "Signing in", + "webb.login.glomt": "Forgot password", + + "webb.ansokan.etikett": "Registration", + "webb.ansokan.demo": + "Nothing is sent. Submitting this form stores nothing, notifies no one, and creates no application — there is no intake behind it yet. The description below states how registration is intended to work, not what happens today.", + "webb.ansokan.avsikt": "Intended operation: applications are reviewed manually and no payment is taken at this stage.", + "webb.falt.foretag": "Company", + "webb.falt.orgnummer": "Company registration number", + "webb.falt.kontakt": "Contact person", + "webb.falt.epost": "Email", + "webb.falt.telefon": "Phone", + "webb.falt.tekniker": "Number of technicians", + "webb.falt.bransch": "Industry", + "webb.falt.land": "Country", + "webb.falt.anvandare": "Expected number of users", + "webb.falt.noteringar": "Notes", + "webb.falt.kravs": "(required)", + "webb.ansokan.skicka": "Submit application", + "webb.ansokan.mottagen.etikett": "Application", + "webb.ansokan.mottagen": "Submitted", + "webb.ansokan.mottagen.demo": + "Nothing was submitted and no one was notified. The reference below was computed in your browser from what you typed, and exists nowhere else — it will be gone when you close this page.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Reference", + "webb.ansokan.granskning": + "Applications are reviewed manually. An invoice is issued on approval. The organization is activated once payment is registered.", + + "webb.sprak.etikett": "Translation", + "webb.sprak.rubrik": "Languages", + "webb.sprak.ingress": + "English is the default and the source language. Nine translations follow. What the platform does not do is claim that a translated interface means a translated method.", + "webb.sprak.princip.etikett": "Principle", + "webb.sprak.princip.rubrik": "Two kinds of text", + "webb.sprak.granssnitt.rubrik": "Interface text", + "webb.sprak.granssnitt.beteckning": "Falls back silently", + "webb.sprak.granssnitt.text": + "Labels, buttons, statuses. Finite, rarely changed. An English string reaching a German user is an irritation, not a hazard — so a missing translation falls back to English without comment.", + "webb.sprak.metodik.rubrik": "Procedure text", + "webb.sprak.metodik.beteckning": "Never falls back silently", + "webb.sprak.metodik.text": + "Instructions for work on a vehicle. Here an unreviewed translation is worse than a foreign-language one — because English looks foreign, while a bad translation looks like an instruction. It is shown in English and marked, with the language named.", + "webb.sprak.invariant": + "The method itself is never translated. Phase names and status words are ALVA’s structure and read identically in every country, so an auditor can read a Romanian and a German case record without knowing which language the workshop works in.", + "webb.sprak.tackning.etikett": "Coverage", + "webb.sprak.tackning.rubrik": "What is translated, and what is reviewed", + "webb.sprak.kolumn.sprak": "Language", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Method reviewed by a specialist", + "webb.sprak.granskat.ja": "Yes", + "webb.sprak.granskat.nej": "No — procedure text shown in English", + "webb.sprak.matt": + "Interface coverage is measured, not estimated: a test fails the build if any key is missing from any language. Review status is a statement about people, not about files, and is set by hand.", + "webb.sprak.kallsprak": + "The English procedure text is a translation of the Swedish source and has not yet been read by a specialist working in the trade. It is held to the same standard as the other nine languages, and the same statement is made about it here rather than quietly excepted because it is the source.", + "webb.sprak.bevis.etikett": "Verification", + "webb.sprak.bevis.rubrik": "The strings that stop a case", + "webb.sprak.bevis.ingress": + "These are the sentences that refuse to let a technician close a case. A refusal nobody understands is a refusal with no way through — so they are the right strings to judge a translation by.", + "webb.sprak.ej_granskad": "Not reviewed", + "webb.sprak.granskad": "Reviewed", + "webb.sprak.granskad.text": + "Procedure text in {sprak} has been read by a specialist working in the trade. Steps and checks are shown in {sprak} throughout.", + "webb.sprak.val.etikett": "Operation", + "webb.sprak.val.rubrik": "How the language is chosen", + "webb.sprak.val.s1.rubrik": "User preference", + "webb.sprak.val.s1.text": "What the individual has selected.", + "webb.sprak.val.s2.rubrik": "Organization setting", + "webb.sprak.val.s2.text": "The workshop’s documentation language.", + "webb.sprak.val.s3.rubrik": "Browser language", + "webb.sprak.val.s3.text": "The first language the platform recognizes.", + "webb.sprak.val.s4.rubrik": "English", + "webb.sprak.val.s4.text": "The default, and the source.", + "webb.sprak.val.notering": + "The organization ranks above the browser deliberately. A workshop in Germany with a Polish technician needs one documentation language — the case record must not change language depending on who happened to write the line.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/es.mjs b/felsokning/services-backup/gemensam/sprak/es.mjs new file mode 100644 index 0000000..cebc9c2 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/es.mjs @@ -0,0 +1,327 @@ +// ALVA-SPEC-060 · Español. +// +// Las instrucciones van en infinitivo, sin dirigirse al usuario: +// «Fotografiar el cuentakilómetros», no «Fotografíe el cuentakilómetros». +// Es el registro de las instrucciones técnicas y respeta ALVA-SPEC-001 §7 +// — el sistema informa de un estado, no se dirige al operario. +// +// `granskat: false`: la interfaz está traducida; el texto de +// procedimiento no está revisado por un especialista. Véase index.mjs. + +export const ES = { + // ---- Grados de evidencia ------------------------------------------------ + "evidens.E1": "Observación", + "evidens.E2": "Fotografía", + "evidens.E3": "Vídeo con sonido", + "evidens.E4": "Medición, instrumento calibrado", + "evidens.E5": "Documento", + "evidens.ej_kalibrerad": "calibración ausente o caducada", + "evidens.ej_angivet": "instrumento no indicado", + "evidens.ej_fotograferad": "introducido, no fotografiado", + + // ---- El control de cierre ----------------------------------------------- + "grind.objekt": "Identificación del vehículo u objeto verificada", + "grind.historik": "Historial del vehículo comprobado, o su omisión justificada", + "grind.historik.nekad": "Una comprobación de historial rechazada exige que se indique el motivo.", + "grind.historik.saknas": "No hay ninguna comprobación de historial documentada.", + "grind.matarstallning.ingaende": "Kilometraje de entrada fotografiado", + "grind.matarstallning.utgaende": "Kilometraje de salida fotografiado", + "grind.matarstallning.saknas": "No hay ningún kilometraje documentado.", + "grind.matarstallning.ej_foto": + "El kilometraje está introducido pero no fotografiado. Fotografiar el cuentakilómetros, o indicar por qué no es posible.", + "grind.reproducering": "Verificación del síntoma: reproducido, o documentado como no reproducible", + "grind.felorsak": "Análisis de la causa raíz documentado", + "grind.atgard": "Acción correctiva documentada, o su ausencia justificada", + "grind.atgard.saknas": "No se ha documentado ni una acción realizada ni un motivo de su ausencia.", + "grind.kundbeslut": "Decisión del cliente sobre la propuesta: registrada", + "grind.kundbeslut.avbojt": "Trabajo realizado pese a una propuesta rechazada", + "grind.kundbeslut.avbojt.detalj": + "El cliente rechazó la propuesta, pero hay trabajo documentado como realizado.", + "grind.kvalitetskontroll": "Control de calidad realizado — síntoma verificado como resuelto", + "grind.kontroller": "Puntos de control de la metodología: evidencia, o exención documentada", + "grind.foton": "Hay fotografías para los puntos de control que las exigen", + "grind.slutsats": "Conclusión (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Habilitación de alta tensión confirmada", + "grind.hogvolt.spanningslos": "Vehículo sin tensión según el procedimiento del fabricante, ausencia de tensión verificada mediante medición", + "grind.regelpaket": "La firma del paquete de reglas no coincide — cierre bloqueado.", + "grind.regelpaket.osignerat": "Se utiliza un paquete de reglas externo sin firma — cierre bloqueado.", + "grind.evidens": "Nivel de evidencia superior a E0", + "grind.evidens.saknas": "No hay ninguna evidencia de ningún tipo en el registro.", + "grind.foton.detalj": "{kontroller} puntos de control exigen fotografía, {foton} fotografías en el registro.", + "grind.sparr.ej_uppfyllt": "El requisito de seguridad no se cumple.", + "grind.arendetyp.okant": "Requisito desconocido en el paquete de reglas: {krav}", + "grind.arendetyp.krav": "Requisito para este tipo de expediente: {krav}", + "grind.sakerhet": "Nivel de confianza dentro de lo que sustenta la evidencia", + "grind.sakerhet.detalj": "La confianza indicada ({niva}) supera lo que sustenta la evidencia ({tak}). Añadir evidencia que la sustente, o bajar el nivel — la incertidumbre honesta es información.", + "grind.eskalering": "Escalaciones técnicas respondidas", + "grind.eskalering.detalj": "Hay una escalación abierta ({referens}). Esperar la respuesta y documentarla antes de cerrar el expediente.", + + + // ---- La conclusión (ALVA-RULE-200) -------------------------------------- + "slutsats.rubrik": "Conclusión", + "slutsats.konstaterat": "Qué se ha establecido", + "slutsats.evidens": "Qué evidencia lo sustenta", + "slutsats.avfardat": "Qué hipótesis se han descartado, y por qué", + "slutsats.osakert": "Qué sigue siendo incierto", + "slutsats.ickesvar": "Eso no es una respuesta. Indicar qué se ha establecido y qué evidencia lo sustenta.", + "slutsats.falt.motivering": "Justificación", + "slutsats.falt.motivering_ej": "Motivo por el que no se pudo establecer la causa", + "slutsats.falt.uteslutet": "Alternativas descartadas", + "slutsats.falt.kvarstaende": "Incertidumbre restante", + "slutsats.falt.atgardsval": "Elección de la acción", + "slutsats.saknas": "Falta el campo «{falt}».", + "slutsats.ickesvar.falt": "{falt}: «{text}» no es un motivo. Indicar qué se aplica realmente, y por qué.", + "slutsats.for_kort": "El campo «{falt}» es demasiado corto ({langd} de al menos {minsta} caracteres) para poder revisarse después.", + "slutsats.utan_varfor": "{falt} indica el qué, pero no el porqué. Vincular la conclusión con la evidencia — ¿qué hay en ella que la sustente?", + "slutsats.utan_slutsats": "El expediente no se puede cerrar sin una conclusión. Indicar por qué la conclusión se desprende de la evidencia.", + "slutsats.hypotes_obemott": "La hipótesis «{text}» figura en el registro pero no se aborda. Indicar por qué se descartó, o por qué sigue abierta.", + + + // ---- Flujo del expediente ----------------------------------------------- + "arende.nytt": "Nuevo expediente", + "arende.oppna": "Expedientes abiertos", + "arende.avslutade": "Expedientes cerrados", + "arende.avsluta": "Cerrar el expediente", + "arende.avslutat": "Expediente cerrado", + "arende.kan_ej_avslutas": "El expediente todavía no se puede cerrar", + "arende.hinder": "Pendiente antes del cierre", + "arende.overlamna": "Transferir", + "arende.ansvarig": "Responsable", + + // ---- Comprobación previa ------------------------------------------------ + "pre.rubrik": "Comprobación previa — antes de iniciar el trabajo", + "pre.historik.fraga": + "¿Se ha comprobado el historial del vehículo? (trabajos anteriores, averías recurrentes, TSB, campañas)", + "pre.historik.ja": "Sí — comprobado", + "pre.historik.nej": "No", + "pre.historik.skal": "Motivo por el que no se ha comprobado el historial (obligatorio)", + "pre.historik.relevant": "Trabajos anteriores relevantes (opcional — cadena causal)", + "pre.matarstallning": "Kilometraje", + "pre.fotografera": "Fotografiar el cuadro de instrumentos", + "pre.felbeskrivning": "Descripción de la avería según el cliente verificada", + "pre.observationer": "¿Algo más en la recepción?", + + // ---- Acciones ----------------------------------------------------------- + "handling.spara": "Guardar", + "handling.avbryt": "Cancelar", + "handling.fortsatt": "Continuar", + "handling.tillbaka": "Atrás", + "handling.dokumentera": "Documentar", + "handling.fotografera": "Fotografiar", + "handling.spela_in": "Grabar vídeo", + "handling.undantag": "Documentar una exención", + "handling.undantag.skal": "Motivo de la exención (obligatorio)", + "handling.exportera": "Exportar", + "handling.skriv_ut": "Imprimir", + + // ---- Medición ----------------------------------------------------------- + "matning.varde": "Valor", + "matning.enhet": "Unidad", + "matning.matdon": "Instrumento", + "matning.matdon.valj": "Seleccionar instrumento", + "matning.matdon.okant": "Instrumento desconocido. Registrarlo antes de guardar la medición.", + "matning.kalibrerad_till": "Calibrado hasta", + + // ---- Informe ------------------------------------------------------------ + "rapport.rubrik": "Informe del expediente", + "rapport.sammanfattning": "Resumen", + "rapport.evidens": "Evidencia", + "rapport.atgarder": "Acciones", + "rapport.harledd": + "Derivado del registro del expediente. Las observaciones y las mediciones se presentan sin conclusiones que carezcan de sustento.", + + // ---- Idioma ------------------------------------------------------------- + "sprak.valj": "Idioma", + "sprak.granskat": "Revisado", + "sprak.ogranskat": "Sin revisar", + "sprak.tackning": "{procent} % de la interfaz", + + "metodik.ogranskad": + "El texto de procedimiento no ha sido revisado por un especialista técnico en {sprak}. Los pasos y los puntos de control se muestran en inglés cuando no existe una traducción revisada — una traducción sin revisar de una instrucción de seguridad es peor que una en lengua extranjera, porque no parece extranjera.", + "metodik.pa_engelska": "Mostrado en inglés — no hay traducción revisada en {sprak}", + + // ---- Sitio público (webb.) --------------------------------------------- + "webb.nav.oversikt": "Visión general", + "webb.ansok": "Solicitar cuenta", + "webb.loggain": "Iniciar sesión", + "webb.fot.impressum": "Aviso legal", + "webb.fot.dataskydd": "Privacidad", + "webb.fot.villkor": "Condiciones", + "webb.fot.tillganglighet": "Accesibilidad", + "webb.fot.sprak": "Idiomas", + "webb.fot.utgavor": "Notas de versión", + + "webb.hero.position": "Plataforma de diagnóstico guiado", + "webb.hero.definition": "Procedimientos de diagnóstico estandarizados para una localización de averías repetible y verificable.", + + "webb.metod.etikett": "Metodología", + "webb.metod.rubrik": "El modelo ALVA", + "webb.metod.ingress": + "ALVA es un método estandarizado de análisis, localización, verificación y acción sistemáticos. Cada decisión es trazable, cada conclusión verificable, cada acción reproducible.", + "webb.fas.analysis.syfte": "Reunir evidencia.", + "webb.fas.analysis.avgransning": "Solo hechos. En esta fase no se recogen hipótesis.", + "webb.fas.localization.syfte": "Aislar la avería.", + "webb.fas.localization.avgransning": "Delimitar la zona. La causa aún no está establecida.", + "webb.fas.verification.syfte": "Confirmar la causa raíz.", + "webb.fas.verification.avgransning": "Se verifica la causa — no el síntoma.", + "webb.fas.action.syfte": "Ejecutar la acción correctiva.", + "webb.fas.action.avgransning": "Resultado verificado y documentado. De lo contrario, la fase está incompleta.", + + "webb.drift.etikett": "Operativa", + "webb.drift.rubrik": "Cómo funciona", + "webb.drift.s1.rubrik": "Crear la organización", + "webb.drift.s1.text": "El registro se envía y se revisa.", + "webb.drift.s2.rubrik": "Los usuarios reciben cuentas", + "webb.drift.s2.text": "El administrador de la organización asigna los roles.", + "webb.drift.s3.rubrik": "Procedimientos de diagnóstico guiados", + "webb.drift.s3.text": "Cada expediente sigue un procedimiento definido.", + "webb.drift.s4.rubrik": "Verificación", + "webb.drift.s4.text": "La causa raíz se confirma antes de la acción correctiva.", + "webb.drift.s5.rubrik": "Documentación", + "webb.drift.s5.text": "El informe se genera a partir del registro del expediente.", + "webb.drift.s6.rubrik": "Mejora continua", + "webb.drift.s6.text": "Los resultados verificados refinan los procedimientos posteriores.", + + "webb.larande.etikett": "Capacidad", + "webb.larande.rubrik": "Aprendizaje de la organización", + "webb.larande.p1": + "La plataforma refina continuamente los procedimientos de diagnóstico con la experiencia operativa verificada de su propia organización.", + "webb.larande.p2": + "Cada diagnóstico completado alimenta la guía posterior. Solo se usan resultados verificados — un expediente cerrado sin causa raíz confirmada no aporta nada, por diseño.", + "webb.larande.p3": + "La base de conocimiento pertenece a la organización. Se deriva de sus casos, sus procedimientos y su documentación, y no se agrega entre clientes.", + "webb.larande.block": "Derivada de", + "webb.larande.k1": "Causas raíz verificadas", + "webb.larande.k2": "Acciones correctivas confirmadas", + "webb.larande.k3": "Categorías de averías recurrentes", + "webb.larande.k4": "Registros de procedimientos completados", + "webb.larande.k5": "Documentación de la organización", + + "webb.rapport.etikett": "Informes", + "webb.rapport.rubrik": "Informe trimestral de mejora", + "webb.rapport.ingress": + "Una vez al trimestre, la plataforma responde seis preguntas sobre el propio trabajo del taller. Cada respuesta se deriva del registro de expedientes — nada se estima y no se pregunta a nadie.", + "webb.rapport.f1": "¿Qué averías diagnosticamos ahora bien a la primera?", + "webb.rapport.f2": "¿Con qué frecuencia la causa sospechada resulta ser la real?", + "webb.rapport.f3": "¿Qué averías vuelven una y otra vez en la flota?", + "webb.rapport.f4": "¿Qué pasos del procedimiento se omiten, y por qué?", + "webb.rapport.f5": "¿Qué sabe el taller ahora que no sabía el trimestre pasado?", + "webb.rapport.f6": "¿Se cierran, verifican y documentan los expedientes — o solo se cierran?", + + "webb.pris.etikett": "Comercial", + "webb.pris.rubrik": "Licencias", + "webb.pris.komponent": "Componente", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licencia de plataforma", + "webb.pris.plattform.grund": "Por organización, anual", + "webb.pris.anvandare": "Licencias de usuario", + "webb.pris.anvandare.grund": "Por usuario activo, mensual", + "webb.pris.moduler": "Módulos de empresa", + "webb.pris.moduler.grund": "Opcionales, por módulo", + "webb.pris.betalning": "Sin pago en línea. Sin alta de suscripción. La facturación sigue a la revisión de la solicitud.", + "webb.pris.aktivering": "Secuencia de activación", + "webb.pris.a1": "La organización envía el registro.", + "webb.pris.a2": "La solicitud se revisa.", + "webb.pris.a3": "Se emite la factura.", + "webb.pris.a4": "Se registra el pago.", + "webb.pris.a5": "La organización se activa.", + "webb.pris.a6": "Los usuarios adicionales se facturan mensualmente.", + + "webb.kallor.etikett": "Infraestructura", + "webb.kallor.rubrik": "Infraestructura operativa de conocimiento", + "webb.kallor.p1": + "ALVA usa las fuentes de conocimiento autorizadas propias de la organización. La arquitectura es neutral respecto al proveedor: cada fuente implementa la misma interfaz y no se presupone ningún proveedor.", + "webb.kallor.p2": + "Una instalación beta trabaja solo con documentación interna. Los proveedores externos se habilitan más adelante mediante conectores separados, sin cambios en el núcleo de la plataforma.", + "webb.kallor.block": "Orden de resolución de fuentes", + "webb.kallor.k1": "Procedimientos internos de la empresa", + "webb.kallor.k2": "Documentación OEM", + "webb.kallor.k3": "Boletines técnicos", + "webb.kallor.k4": "Información de garantía", + "webb.kallor.k5": "Manuales de taller", + "webb.kallor.k6": "Histórico de diagnósticos verificados", + "webb.kallor.k7": "Buenas prácticas de la organización", + + "webb.login.etikett": "Acceso", + "webb.login.demo": + "Este acceso no autentica nada. Escriba cualquier cosa en Correo y Contraseña y pulse Iniciar sesión — no hace falta cuenta, y el portal detrás presenta datos de ejemplo fijos. Conectada a una instancia de la plataforma, esta página se autentica contra ella, y el portal queda cerrado sin una sesión válida.", + "webb.login.losenord": "Contraseña", + "webb.login.ofullstandigt": "Autenticación incompleta. Se requieren correo y contraseña.", + "webb.login.misslyckades": "Autenticación fallida.", + "webb.login.logga_in": "Iniciar sesión", + "webb.login.loggar_in": "Iniciando sesión", + "webb.login.glomt": "Contraseña olvidada", + + "webb.ansokan.etikett": "Registro", + "webb.ansokan.demo": + "No se envía nada. Enviar este formulario no guarda nada, no avisa a nadie y no crea ninguna solicitud — todavía no hay ningún proceso de recepción detrás. La descripción de abajo indica cómo está previsto que funcione el registro, no lo que ocurre hoy.", + "webb.ansokan.avsikt": "Funcionamiento previsto: las solicitudes se revisan manualmente y en esta fase no se cobra ningún pago.", + "webb.falt.foretag": "Empresa", + "webb.falt.orgnummer": "Número de registro mercantil", + "webb.falt.kontakt": "Persona de contacto", + "webb.falt.epost": "Correo electrónico", + "webb.falt.telefon": "Teléfono", + "webb.falt.tekniker": "Número de técnicos", + "webb.falt.bransch": "Sector", + "webb.falt.land": "País", + "webb.falt.anvandare": "Número previsto de usuarios", + "webb.falt.noteringar": "Observaciones", + "webb.falt.kravs": "(obligatorio)", + "webb.ansokan.skicka": "Enviar la solicitud", + "webb.ansokan.mottagen.etikett": "Solicitud", + "webb.ansokan.mottagen": "Enviada", + "webb.ansokan.mottagen.demo": + "No se transmitió nada y no se avisó a nadie. La referencia de abajo se calculó en su navegador a partir de lo que escribió y no existe en ningún otro lugar — desaparecerá al cerrar esta página.", + "webb.ansokan.status": "Estado", + "webb.ansokan.referens": "Referencia", + "webb.ansokan.granskning": + "Las solicitudes se revisan manualmente. La factura se emite al aprobarse. La organización se activa cuando se registra el pago.", + + "webb.sprak.etikett": "Localización", + "webb.sprak.rubrik": "Idiomas", + "webb.sprak.ingress": + "El inglés es el idioma predeterminado y el idioma fuente. Le siguen nueve traducciones. Lo que la plataforma no hace es afirmar que una interfaz traducida signifique un método traducido.", + "webb.sprak.princip.etikett": "Principio", + "webb.sprak.princip.rubrik": "Dos clases de texto", + "webb.sprak.granssnitt.rubrik": "Texto de interfaz", + "webb.sprak.granssnitt.beteckning": "Recurre al inglés en silencio", + "webb.sprak.granssnitt.text": + "Etiquetas, botones, estados. Finito, rara vez cambia. Una cadena en inglés que llega a un usuario alemán es una molestia, no un peligro — así que una traducción ausente recurre al inglés sin comentario.", + "webb.sprak.metodik.rubrik": "Texto de procedimiento", + "webb.sprak.metodik.beteckning": "Nunca recurre al inglés en silencio", + "webb.sprak.metodik.text": + "Instrucciones para trabajar en un vehículo. Aquí una traducción sin revisar es peor que una en lengua extranjera — porque el inglés parece extranjero, mientras que una mala traducción parece una instrucción. Se muestra en inglés y se marca, con el idioma nombrado.", + "webb.sprak.invariant": + "El método en sí nunca se traduce. Los nombres de fase y las palabras de estado son la estructura de ALVA y se leen igual en todos los países, de modo que un auditor puede leer un expediente rumano y uno alemán sin saber en qué idioma trabaja el taller.", + "webb.sprak.tackning.etikett": "Cobertura", + "webb.sprak.tackning.rubrik": "Qué está traducido, y qué está revisado", + "webb.sprak.kolumn.sprak": "Idioma", + "webb.sprak.kolumn.granssnitt": "Interfaz", + "webb.sprak.kolumn.granskat": "Método revisado por un especialista", + "webb.sprak.granskat.ja": "Sí", + "webb.sprak.granskat.nej": "No — texto de procedimiento en inglés", + "webb.sprak.matt": + "La cobertura de la interfaz se mide, no se estima: una prueba hace fallar la compilación si falta alguna clave en algún idioma. El estado de revisión es una afirmación sobre personas, no sobre archivos, y se fija a mano.", + "webb.sprak.kallsprak": + "El texto de procedimiento en inglés es una traducción de la fuente sueca y aún no ha sido leído por un especialista que ejerza el oficio. Se le exige lo mismo que a los otros nueve idiomas, y aquí se hace sobre él la misma declaración en lugar de exceptuarlo en silencio por ser la fuente.", + "webb.sprak.bevis.etikett": "Verificación", + "webb.sprak.bevis.rubrik": "Las frases que detienen un expediente", + "webb.sprak.bevis.ingress": + "Estas son las frases que impiden a un técnico cerrar un expediente. Una negativa que nadie entiende es una negativa sin salida — por eso son las frases correctas para juzgar una traducción.", + "webb.sprak.ej_granskad": "Sin revisar", + "webb.sprak.granskad": "Revisado", + "webb.sprak.granskad.text": + "El texto de procedimiento en {sprak} ha sido leído por un especialista que ejerce el oficio. Los pasos y los puntos de control se muestran en {sprak} en todo momento.", + "webb.sprak.val.etikett": "Operativa", + "webb.sprak.val.rubrik": "Cómo se elige el idioma", + "webb.sprak.val.s1.rubrik": "Preferencia del usuario", + "webb.sprak.val.s1.text": "Lo que la persona ha seleccionado.", + "webb.sprak.val.s2.rubrik": "Configuración de la organización", + "webb.sprak.val.s2.text": "El idioma de documentación del taller.", + "webb.sprak.val.s3.rubrik": "Idioma del navegador", + "webb.sprak.val.s3.text": "El primer idioma que la plataforma reconoce.", + "webb.sprak.val.s4.rubrik": "Inglés", + "webb.sprak.val.s4.text": "El predeterminado, y la fuente.", + "webb.sprak.val.notering": + "La organización va deliberadamente por delante del navegador. Un taller en Alemania con un técnico polaco necesita un solo idioma de documentación — el expediente no debe cambiar de idioma según quién escribiera la línea.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/fr.mjs b/felsokning/services-backup/gemensam/sprak/fr.mjs new file mode 100644 index 0000000..273b585 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/fr.mjs @@ -0,0 +1,328 @@ +// ALVA-SPEC-060 · Français. +// +// Les instructions sont à l’infinitif, sans s’adresser à l’utilisateur : +// « Photographier le compteur », et non « Photographiez le compteur ». +// C’est le registre des consignes techniques, et cela respecte +// ALVA-SPEC-001 §7 — le système signale un état, il ne s’adresse pas à +// l’opérateur. +// +// `granskat: false` : l’interface est traduite, le texte de procédure +// n’est pas relu par un spécialiste. Voir index.mjs. + +export const FR = { + // ---- Degrés de preuve --------------------------------------------------- + "evidens.E1": "Observation", + "evidens.E2": "Photographie", + "evidens.E3": "Vidéo avec son", + "evidens.E4": "Mesure, instrument étalonné", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "étalonnage manquant ou expiré", + "evidens.ej_angivet": "instrument non indiqué", + "evidens.ej_fotograferad": "saisi, non photographié", + + // ---- Le contrôle de clôture --------------------------------------------- + "grind.objekt": "Identification du véhicule ou de l’objet vérifiée", + "grind.historik": "Historique du véhicule vérifié, ou son omission justifiée", + "grind.historik.nekad": "Un contrôle d’historique refusé exige que le motif soit indiqué.", + "grind.historik.saknas": "Aucun contrôle d’historique documenté.", + "grind.matarstallning.ingaende": "Kilométrage à la réception photographié", + "grind.matarstallning.utgaende": "Kilométrage à la restitution photographié", + "grind.matarstallning.saknas": "Aucun kilométrage documenté.", + "grind.matarstallning.ej_foto": + "Le kilométrage est saisi mais non photographié. Photographier le compteur, ou indiquer pourquoi cela n’est pas possible.", + "grind.reproducering": "Vérification du symptôme : reproduit, ou documenté comme non reproductible", + "grind.felorsak": "Analyse de la cause racine documentée", + "grind.atgard": "Action corrective documentée, ou son absence justifiée", + "grind.atgard.saknas": "Ni action réalisée ni motif de son absence n’est documenté.", + "grind.kundbeslut": "Décision du client sur la proposition enregistrée", + "grind.kundbeslut.avbojt": "Travaux réalisés malgré une proposition refusée", + "grind.kundbeslut.avbojt.detalj": + "Le client a refusé la proposition, mais des travaux sont documentés comme réalisés.", + "grind.kvalitetskontroll": "Contrôle qualité effectué — symptôme vérifié comme résolu", + "grind.kontroller": "Points de contrôle de la méthode : preuve, ou dérogation documentée", + "grind.foton": "Photographies présentes pour les points de contrôle qui les exigent", + "grind.slutsats": "Conclusion (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Habilitation haute tension confirmée", + "grind.hogvolt.spanningslos": "Véhicule consigné hors tension selon la procédure du constructeur, absence de tension vérifiée par mesure", + "grind.regelpaket": "La signature du paquet de règles ne correspond pas — clôture bloquée.", + "grind.regelpaket.osignerat": "Un paquet de règles externe est utilisé sans signature — clôture bloquée.", + "grind.evidens": "Niveau de preuve supérieur à E0", + "grind.evidens.saknas": "Aucune preuve d’aucune sorte ne figure dans le journal.", + "grind.foton.detalj": "{kontroller} points de contrôle exigent une photographie, {foton} photographies dans le journal.", + "grind.sparr.ej_uppfyllt": "L’exigence de sécurité n’est pas satisfaite.", + "grind.arendetyp.okant": "Exigence inconnue dans le paquet de règles : {krav}", + "grind.arendetyp.krav": "Exigence pour ce type de dossier : {krav}", + "grind.sakerhet": "Niveau de confiance dans les limites de ce que les preuves soutiennent", + "grind.sakerhet.detalj": "La confiance indiquée ({niva}) dépasse ce que les preuves soutiennent ({tak}). Compléter les preuves, ou abaisser le niveau — une incertitude honnête est une information.", + "grind.eskalering": "Escalades techniques traitées", + "grind.eskalering.detalj": "Une escalade est ouverte ({referens}). Attendre la réponse et la documenter avant de clôturer le dossier.", + + + // ---- La conclusion (ALVA-RULE-200) -------------------------------------- + "slutsats.rubrik": "Conclusion", + "slutsats.konstaterat": "Ce qui est établi", + "slutsats.evidens": "Quelles preuves l’étayent", + "slutsats.avfardat": "Quelles hypothèses ont été écartées, et pourquoi", + "slutsats.osakert": "Ce qui reste incertain", + "slutsats.ickesvar": "Ce n’est pas une réponse. Indiquer ce qui est établi et quelles preuves l’étayent.", + "slutsats.falt.motivering": "Justification", + "slutsats.falt.motivering_ej": "Motif pour lequel la cause n’a pas pu être établie", + "slutsats.falt.uteslutet": "Alternatives écartées", + "slutsats.falt.kvarstaende": "Incertitude restante", + "slutsats.falt.atgardsval": "Choix de l’action", + "slutsats.saknas": "Le champ « {falt} » est absent.", + "slutsats.ickesvar.falt": "{falt} : « {text} » n’est pas un motif. Indiquer ce qui s’applique réellement, et pourquoi.", + "slutsats.for_kort": "Le champ « {falt} » est trop court ({langd} caractères sur un minimum de {minsta}) pour être vérifiable après coup.", + "slutsats.utan_varfor": "{falt} indique le quoi, mais pas le pourquoi. Relier la conclusion aux preuves — qu’est-ce qui, dans celles-ci, la rend fondée ?", + "slutsats.utan_slutsats": "Le dossier ne peut pas être clôturé sans conclusion. Indiquer pourquoi la conclusion découle des preuves.", + "slutsats.hypotes_obemott": "L’hypothèse « {text} » figure dans le journal mais n’est pas traitée. Indiquer pourquoi elle a été écartée, ou pourquoi elle reste ouverte.", + + + // ---- Flux du dossier ---------------------------------------------------- + "arende.nytt": "Nouveau dossier", + "arende.oppna": "Dossiers ouverts", + "arende.avslutade": "Dossiers clôturés", + "arende.avsluta": "Clôturer le dossier", + "arende.avslutat": "Dossier clôturé", + "arende.kan_ej_avslutas": "Le dossier ne peut pas encore être clôturé", + "arende.hinder": "En suspens avant clôture", + "arende.overlamna": "Transmettre", + "arende.ansvarig": "Responsable", + + // ---- Contrôle préalable ------------------------------------------------- + "pre.rubrik": "Contrôle préalable — avant le début des travaux", + "pre.historik.fraga": + "L’historique du véhicule a-t-il été vérifié ? (travaux antérieurs, défauts récurrents, TSB, campagnes)", + "pre.historik.ja": "Oui — vérifié", + "pre.historik.nej": "Non", + "pre.historik.skal": "Motif pour lequel l’historique n’a pas été vérifié (obligatoire)", + "pre.historik.relevant": "Travaux antérieurs pertinents (facultatif — chaîne causale)", + "pre.matarstallning": "Kilométrage", + "pre.fotografera": "Photographier le tableau de bord", + "pre.felbeskrivning": "Description du défaut par le client vérifiée", + "pre.observationer": "Autre chose à la réception ?", + + // ---- Actions ------------------------------------------------------------ + "handling.spara": "Enregistrer", + "handling.avbryt": "Annuler", + "handling.fortsatt": "Continuer", + "handling.tillbaka": "Retour", + "handling.dokumentera": "Documenter", + "handling.fotografera": "Photographier", + "handling.spela_in": "Enregistrer une vidéo", + "handling.undantag": "Documenter une dérogation", + "handling.undantag.skal": "Motif de la dérogation (obligatoire)", + "handling.exportera": "Exporter", + "handling.skriv_ut": "Imprimer", + + // ---- Mesure ------------------------------------------------------------- + "matning.varde": "Valeur", + "matning.enhet": "Unité", + "matning.matdon": "Instrument", + "matning.matdon.valj": "Sélectionner un instrument", + "matning.matdon.okant": "Instrument inconnu. Le déclarer avant d’enregistrer la mesure.", + "matning.kalibrerad_till": "Étalonné jusqu’au", + + // ---- Rapport ------------------------------------------------------------ + "rapport.rubrik": "Rapport de dossier", + "rapport.sammanfattning": "Synthèse", + "rapport.evidens": "Preuves", + "rapport.atgarder": "Actions", + "rapport.harledd": + "Généré à partir du journal du dossier. Les observations et les mesures sont rapportées sans conclusions dépourvues de fondement.", + + // ---- Langue ------------------------------------------------------------- + "sprak.valj": "Langue", + "sprak.granskat": "Relu", + "sprak.ogranskat": "Non relu", + "sprak.tackning": "{procent} % de l’interface", + + "metodik.ogranskad": + "Le texte de procédure n’a pas été relu par un spécialiste technique en {sprak}. Les étapes et les points de contrôle sont affichés en anglais lorsqu’aucune traduction relue n’existe — une traduction non relue d’une consigne de sécurité est pire qu’une consigne en langue étrangère, parce qu’elle n’a pas l’air étrangère.", + "metodik.pa_engelska": "Affiché en anglais — aucune traduction relue en {sprak}", + + // ---- Site public (webb.) ----------------------------------------------- + "webb.nav.oversikt": "Aperçu", + "webb.ansok": "Demander un compte", + "webb.loggain": "Connexion", + "webb.fot.impressum": "Mentions légales", + "webb.fot.dataskydd": "Confidentialité", + "webb.fot.villkor": "Conditions", + "webb.fot.tillganglighet": "Accessibilité", + "webb.fot.sprak": "Langues", + "webb.fot.utgavor": "Notes de version", + + "webb.hero.position": "Plateforme de diagnostic guidé", + "webb.hero.definition": "Des procédures de diagnostic normalisées pour une recherche de pannes répétable et vérifiable.", + + "webb.metod.etikett": "Méthodologie", + "webb.metod.rubrik": "Le modèle ALVA", + "webb.metod.ingress": + "ALVA est une méthode normalisée d’analyse, de localisation, de vérification et d’action systématiques. Chaque décision est traçable, chaque conclusion vérifiable, chaque action reproductible.", + "webb.fas.analysis.syfte": "Recueillir les preuves.", + "webb.fas.analysis.avgransning": "Des faits uniquement. Les hypothèses ne sont pas recueillies dans cette phase.", + "webb.fas.localization.syfte": "Circonscrire le défaut.", + "webb.fas.localization.avgransning": "Restreindre la zone. La cause n’est pas encore établie.", + "webb.fas.verification.syfte": "Confirmer la cause racine.", + "webb.fas.verification.avgransning": "C’est la cause qui est vérifiée — pas le symptôme.", + "webb.fas.action.syfte": "Exécuter l’action corrective.", + "webb.fas.action.avgransning": "Résultat vérifié et documenté. Sinon la phase est incomplète.", + + "webb.drift.etikett": "Fonctionnement", + "webb.drift.rubrik": "Comment ça fonctionne", + "webb.drift.s1.rubrik": "Créer l’organisation", + "webb.drift.s1.text": "L’inscription est soumise puis examinée.", + "webb.drift.s2.rubrik": "Les utilisateurs reçoivent leurs comptes", + "webb.drift.s2.text": "Les rôles sont attribués par l’administrateur de l’organisation.", + "webb.drift.s3.rubrik": "Procédures de diagnostic guidées", + "webb.drift.s3.text": "Chaque dossier suit une procédure définie.", + "webb.drift.s4.rubrik": "Vérification", + "webb.drift.s4.text": "La cause racine est confirmée avant l’action corrective.", + "webb.drift.s5.rubrik": "Documentation", + "webb.drift.s5.text": "Le rapport est généré à partir du journal du dossier.", + "webb.drift.s6.rubrik": "Amélioration continue", + "webb.drift.s6.text": "Les résultats vérifiés affinent les procédures suivantes.", + + "webb.larande.etikett": "Capacité", + "webb.larande.rubrik": "Apprentissage de l’organisation", + "webb.larande.p1": + "La plateforme affine en continu les procédures de diagnostic à partir de l’expérience opérationnelle vérifiée de votre propre organisation.", + "webb.larande.p2": + "Chaque diagnostic achevé nourrit le guidage suivant. Seuls les résultats vérifiés sont utilisés — un dossier clôturé sans cause racine confirmée n’apporte rien, à dessein.", + "webb.larande.p3": + "La base de connaissances appartient à l’organisation. Elle est dérivée de vos dossiers, de vos procédures et de votre documentation, et elle n’est pas agrégée entre clients.", + "webb.larande.block": "Dérivée de", + "webb.larande.k1": "Causes racines vérifiées", + "webb.larande.k2": "Actions correctives confirmées", + "webb.larande.k3": "Catégories de défauts récurrents", + "webb.larande.k4": "Procédures menées à terme", + "webb.larande.k5": "Documentation de l’organisation", + + "webb.rapport.etikett": "Rapports", + "webb.rapport.rubrik": "Rapport trimestriel d’amélioration", + "webb.rapport.ingress": + "Une fois par trimestre, la plateforme répond à six questions sur le travail de l’atelier lui-même. Chaque réponse est dérivée du journal des dossiers — rien n’est estimé, et rien n’est demandé à personne.", + "webb.rapport.f1": "Quels défauts diagnostiquons-nous désormais correctement du premier coup ?", + "webb.rapport.f2": "À quelle fréquence une cause soupçonnée se révèle-t-elle être la vraie ?", + "webb.rapport.f3": "Quels défauts reviennent sans cesse dans le parc ?", + "webb.rapport.f4": "Quelles étapes de procédure sont sautées, et pourquoi ?", + "webb.rapport.f5": "Que sait l’atelier aujourd’hui qu’il ne savait pas au trimestre dernier ?", + "webb.rapport.f6": "Les dossiers sont-ils clôturés, vérifiés et documentés — ou seulement clôturés ?", + + "webb.pris.etikett": "Commercial", + "webb.pris.rubrik": "Licences", + "webb.pris.komponent": "Composant", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licence de plateforme", + "webb.pris.plattform.grund": "Par organisation, annuelle", + "webb.pris.anvandare": "Licences utilisateur", + "webb.pris.anvandare.grund": "Par utilisateur actif, mensuelle", + "webb.pris.moduler": "Modules d’entreprise", + "webb.pris.moduler.grund": "Optionnels, par module", + "webb.pris.betalning": "Aucun paiement en ligne. Aucune souscription d’abonnement. La facturation suit l’examen de la demande.", + "webb.pris.aktivering": "Séquence d’activation", + "webb.pris.a1": "L’organisation soumet son inscription.", + "webb.pris.a2": "La demande est examinée.", + "webb.pris.a3": "La facture est émise.", + "webb.pris.a4": "Le paiement est enregistré.", + "webb.pris.a5": "L’organisation est activée.", + "webb.pris.a6": "Les utilisateurs supplémentaires sont facturés mensuellement.", + + "webb.kallor.etikett": "Infrastructure", + "webb.kallor.rubrik": "Infrastructure de connaissances opérationnelles", + "webb.kallor.p1": + "ALVA utilise les sources de connaissances autorisées propres à l’organisation. L’architecture est neutre vis-à-vis des fournisseurs : chaque source implémente la même interface, et aucun fournisseur n’est présupposé.", + "webb.kallor.p2": + "Une installation bêta fonctionne sur la seule documentation interne. Les fournisseurs externes sont activés plus tard par des connecteurs séparés, sans modification du cœur de la plateforme.", + "webb.kallor.block": "Ordre de résolution des sources", + "webb.kallor.k1": "Procédures internes de l’entreprise", + "webb.kallor.k2": "Documentation OEM", + "webb.kallor.k3": "Bulletins techniques", + "webb.kallor.k4": "Informations de garantie", + "webb.kallor.k5": "Manuels d’atelier", + "webb.kallor.k6": "Diagnostics vérifiés historiques", + "webb.kallor.k7": "Bonnes pratiques de l’organisation", + + "webb.login.etikett": "Accès", + "webb.login.demo": + "Cette connexion n’authentifie rien. Saisissez n’importe quoi dans E-mail et Mot de passe et appuyez sur Se connecter — aucun compte n’est nécessaire, et le portail derrière présente des données d’exemple figées. Reliée à une instance de la plateforme, cette page s’authentifie auprès d’elle, et le portail est fermé sans session valide.", + "webb.login.losenord": "Mot de passe", + "webb.login.ofullstandigt": "Authentification incomplète. E-mail et mot de passe requis.", + "webb.login.misslyckades": "Échec de l’authentification.", + "webb.login.logga_in": "Se connecter", + "webb.login.loggar_in": "Connexion en cours", + "webb.login.glomt": "Mot de passe oublié", + + "webb.ansokan.etikett": "Inscription", + "webb.ansokan.demo": + "Rien n’est envoyé. Soumettre ce formulaire n’enregistre rien, ne notifie personne et ne crée aucune demande — aucun traitement n’existe encore derrière. La description ci-dessous indique comment l’inscription est censée fonctionner, pas ce qui se passe aujourd’hui.", + "webb.ansokan.avsikt": "Fonctionnement prévu : les demandes sont examinées manuellement et aucun paiement n’est perçu à ce stade.", + "webb.falt.foretag": "Entreprise", + "webb.falt.orgnummer": "Numéro d’immatriculation de l’entreprise", + "webb.falt.kontakt": "Personne à contacter", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Téléphone", + "webb.falt.tekniker": "Nombre de techniciens", + "webb.falt.bransch": "Secteur", + "webb.falt.land": "Pays", + "webb.falt.anvandare": "Nombre d’utilisateurs attendu", + "webb.falt.noteringar": "Remarques", + "webb.falt.kravs": "(obligatoire)", + "webb.ansokan.skicka": "Soumettre la demande", + "webb.ansokan.mottagen.etikett": "Demande", + "webb.ansokan.mottagen": "Soumise", + "webb.ansokan.mottagen.demo": + "Rien n’a été transmis et personne n’a été notifié. La référence ci-dessous a été calculée dans votre navigateur à partir de votre saisie et n’existe nulle part ailleurs — elle disparaîtra à la fermeture de cette page.", + "webb.ansokan.status": "Statut", + "webb.ansokan.referens": "Référence", + "webb.ansokan.granskning": + "Les demandes sont examinées manuellement. Une facture est émise à l’approbation. L’organisation est activée une fois le paiement enregistré.", + + "webb.sprak.etikett": "Localisation", + "webb.sprak.rubrik": "Langues", + "webb.sprak.ingress": + "L’anglais est la langue par défaut et la langue source. Neuf traductions suivent. Ce que la plateforme ne fait pas : prétendre qu’une interface traduite signifie une méthode traduite.", + "webb.sprak.princip.etikett": "Principe", + "webb.sprak.princip.rubrik": "Deux sortes de texte", + "webb.sprak.granssnitt.rubrik": "Texte d’interface", + "webb.sprak.granssnitt.beteckning": "Retombe silencieusement", + "webb.sprak.granssnitt.text": + "Étiquettes, boutons, statuts. Fini, rarement modifié. Une chaîne anglaise qui atteint un utilisateur allemand est un agacement, pas un danger — une traduction manquante retombe donc sur l’anglais sans commentaire.", + "webb.sprak.metodik.rubrik": "Texte de procédure", + "webb.sprak.metodik.beteckning": "Ne retombe jamais silencieusement", + "webb.sprak.metodik.text": + "Des instructions pour travailler sur un véhicule. Ici, une traduction non relue est pire qu’une version en langue étrangère — parce que l’anglais a l’air étranger, tandis qu’une mauvaise traduction a l’air d’une instruction. Elle est affichée en anglais et signalée, avec la langue nommée.", + "webb.sprak.invariant": + "La méthode elle-même n’est jamais traduite. Les noms de phase et les mots de statut sont la structure d’ALVA et se lisent à l’identique dans chaque pays, si bien qu’un auditeur peut lire un dossier roumain et un dossier allemand sans savoir dans quelle langue l’atelier travaille.", + "webb.sprak.tackning.etikett": "Couverture", + "webb.sprak.tackning.rubrik": "Ce qui est traduit, et ce qui est relu", + "webb.sprak.kolumn.sprak": "Langue", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Méthode relue par un spécialiste", + "webb.sprak.granskat.ja": "Oui", + "webb.sprak.granskat.nej": "Non — texte de procédure affiché en anglais", + "webb.sprak.matt": + "La couverture de l’interface est mesurée, pas estimée : un test fait échouer la compilation si une clé manque dans une langue. Le statut de relecture est une affirmation sur des personnes, pas sur des fichiers, et il est fixé à la main.", + "webb.sprak.kallsprak": + "Le texte de procédure anglais est une traduction de la source suédoise et n’a pas encore été lu par un spécialiste exerçant le métier. Il est tenu au même niveau d’exigence que les neuf autres langues, et la même déclaration est faite ici à son sujet au lieu de l’excepter discrètement parce qu’il est la source.", + "webb.sprak.bevis.etikett": "Vérification", + "webb.sprak.bevis.rubrik": "Les phrases qui arrêtent un dossier", + "webb.sprak.bevis.ingress": + "Ce sont les phrases qui empêchent un technicien de clôturer un dossier. Un refus que personne ne comprend est un refus sans issue — ce sont donc les bonnes phrases pour juger une traduction.", + "webb.sprak.ej_granskad": "Non relu", + "webb.sprak.granskad": "Relu", + "webb.sprak.granskad.text": + "Le texte de procédure en {sprak} a été lu par un spécialiste exerçant le métier. Les étapes et les points de contrôle sont affichés en {sprak} partout.", + "webb.sprak.val.etikett": "Fonctionnement", + "webb.sprak.val.rubrik": "Comment la langue est choisie", + "webb.sprak.val.s1.rubrik": "Préférence de l’utilisateur", + "webb.sprak.val.s1.text": "Ce que la personne a choisi.", + "webb.sprak.val.s2.rubrik": "Réglage de l’organisation", + "webb.sprak.val.s2.text": "La langue de documentation de l’atelier.", + "webb.sprak.val.s3.rubrik": "Langue du navigateur", + "webb.sprak.val.s3.text": "La première langue que la plateforme reconnaît.", + "webb.sprak.val.s4.rubrik": "Anglais", + "webb.sprak.val.s4.text": "La langue par défaut, et la source.", + "webb.sprak.val.notering": + "L’organisation passe délibérément avant le navigateur. Un atelier en Allemagne avec un technicien polonais a besoin d’une seule langue de documentation — le dossier ne doit pas changer de langue selon qui a écrit la ligne.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/index.mjs b/felsokning/services-backup/gemensam/sprak/index.mjs new file mode 100644 index 0000000..38ba86a --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/index.mjs @@ -0,0 +1,237 @@ +// ALVA-SPEC-060 · Språk. +// +// ---- Två sorters text, och de behandlas olika --------------------------- +// +// GRÄNSSNITTSTEXT är etiketter, knappar, statusar och grindens hinder. +// Den är ändlig, den ändras sällan, och en engelsk sträng som slinker +// igenom till en tysk användare är irriterande men ofarlig. +// +// METODIKINNEHÅLL är instruktioner för arbete på ett fordon: "kontrollera +// bromsvätskans kokpunkt", "säkerställ att fordonet är spänningslöst". +// Där är en oöversatt eller halvöversatt sträng inte irriterande utan +// farlig, och en maskinöversättning som ingen fackman läst är värre än +// engelska — därför att engelska SYNS som ett annat språk, medan en +// felaktig översättning ser ut som en instruktion. +// +// Modulen behandlar dem därför olika. Gränssnittstext faller tillbaka på +// engelska tyst. Metodikinnehåll gör det ALDRIG tyst: det märks som +// oöversatt, med språk och granskningsstatus, så att teknikern vet att +// den läser något som inte är skrivet för dess marknad. +// +// ---- Källspråket är engelska -------------------------------------------- +// +// Inte därför att engelska är bäst, utan därför att ALVA:s struktur redan +// är engelsk och oföränderlig (ALVA-SPEC-001 §2). Ett system där +// strukturen är engelsk och källtexten svensk har två källor att hålla +// synkade, och den ena vinner alltid tyst. +// +// ---- Katalogen är stängd ------------------------------------------------- +// +// Varje nyckel måste finnas i varje språk. Ett test låser det, av samma +// skäl som händelseschemat är stängt: en saknad nyckel som tyst faller +// tillbaka blir aldrig upptäckt, och blir aldrig översatt. +// +// ---- Och en del av den får INTE översättas ------------------------------ +// +// ALVA-SPEC-001 §2 slår fast att fasnamn och statusord är strukturen och +// skrivs likadant i varje land — precis som ett ISO-krav inte blir +// "internationell standard 9001" på svenska. En tysk rapport och en +// rumänsk ska visa samma fyra faser och samma statusord, så att en +// revisor kan läsa båda utan att veta vilket språk verkstaden arbetar på. +// +// Det hade gått att skriva in dem oöversatta i varje katalog och låsa +// dem med ett test. Det är svagare: en översättare som ser "Passed" i +// den polska filen översätter den, och testet blir en tvist i stället +// för en regel. I stället saknas nycklarna helt i översättningarna, och +// `t()` hämtar dem alltid ur engelskan. Då finns det ingenting att +// översätta fel. + +import { EN } from "./en.mjs"; +import { SV } from "./sv.mjs"; +import { DE } from "./de.mjs"; +import { FR } from "./fr.mjs"; +import { ES } from "./es.mjs"; +import { IT } from "./it.mjs"; +import { PL } from "./pl.mjs"; +import { NL } from "./nl.mjs"; +import { PT } from "./pt.mjs"; +import { RO } from "./ro.mjs"; + +/** + * Språken. + * + * Urvalet är EU:s största modersmål plus svenska, som är hemmamarknaden. + * `granskat` säger om en fackman har läst igenom METODIKINNEHÅLLET på + * språket — inte gränssnittet. Se filhuvudet: det är den skillnaden som + * betyder något. + */ +export const SPRAK = [ + { kod: "en", namn: "English", egetNamn: "English", granskat: true }, + { kod: "de", namn: "German", egetNamn: "Deutsch", granskat: false }, + { kod: "fr", namn: "French", egetNamn: "Français", granskat: false }, + { kod: "es", namn: "Spanish", egetNamn: "Español", granskat: false }, + { kod: "it", namn: "Italian", egetNamn: "Italiano", granskat: false }, + { kod: "pl", namn: "Polish", egetNamn: "Polski", granskat: false }, + { kod: "nl", namn: "Dutch", egetNamn: "Nederlands", granskat: false }, + { kod: "pt", namn: "Portuguese", egetNamn: "Português", granskat: false }, + { kod: "ro", namn: "Romanian", egetNamn: "Română", granskat: false }, + { kod: "sv", namn: "Swedish", egetNamn: "Svenska", granskat: true }, +]; + +/** Standardspråket. Även källspråket. */ +export const STANDARD = "en"; + +export const KATALOGER = { en: EN, sv: SV, de: DE, fr: FR, es: ES, it: IT, pl: PL, nl: NL, pt: PT, ro: RO }; + +/** + * Nyckelområden som är ALVA:s struktur och därför aldrig översätts. + * + * `fas.` — de fyra faserna. `status.` — statusorden. Se filhuvudet. + */ +export const INVARIANTA = ["fas.", "status."]; + +/** Om nyckeln tillhör strukturen och alltid ska hämtas ur engelskan. */ +export const arInvariant = (nyckel) => INVARIANTA.some((p) => String(nyckel).startsWith(p)); + +/** + * Slår upp en gränssnittssträng. + * + * Invarianta nycklar hämtas alltid ur engelskan, oavsett språk och + * oavsett vad en katalog råkar innehålla. + * + * Faller i övrigt tillbaka på engelska när nyckeln saknas i språket, och + * på nyckeln själv när den saknas överallt — det sista ska aldrig hända + * och fångas av testet, men en tom sträng i gränssnittet vore värre än + * en synlig nyckel. + * + * `variabler` ersätter {namn} i strängen. Ingen ordning antas: språk + * sätter satsdelar i olika ordning, och en översättning som tvingas följa + * engelskans ordföljd blir fel svenska, tyska och polska på en gång. + */ +export function t(sprak, nyckel, variabler = {}) { + const katalog = arInvariant(nyckel) ? EN : (KATALOGER[sprak] ?? EN); + const rå = katalog[nyckel] ?? EN[nyckel] ?? nyckel; + return String(rå).replace(/\{(\w+)\}/g, (_, n) => (n in variabler ? String(variabler[n]) : `{${n}}`)); +} + +/** Bunden variant, så en vy slipper skicka språket i varje anrop. */ +export const oversattare = (sprak) => (nyckel, variabler) => t(sprak, nyckel, variabler); + +/** + * Vilka nycklar som saknas per språk. + * + * Används av testet — men är exporterad även för driften, som kan visa + * täckningsgraden i stället för att påstå att ett språk är klart. + */ +export function saknade(katalog) { + const nycklar = Object.keys(EN).filter((n) => !arInvariant(n)); + return nycklar.filter((n) => !(n in katalog) || !String(katalog[n]).trim()); +} + +/** + * Invarianta nycklar som en katalog felaktigt definierar. + * + * Tom lista är det normala. En träff betyder att någon har översatt + * strukturen, och det ska stoppa bygget — inte tyst ignoreras av `t()`, + * även om `t()` ignorerar det. + */ +export function otillatna(katalog) { + return Object.keys(katalog).filter(arInvariant); +} + +/** + * Nycklar som finns i katalogen men inte i engelskan. + * + * En sträng utan original. Den kan aldrig visas — `t()` slår upp mot + * EN:s nycklar — så den är antingen ett stavfel eller en kvarleva. + */ +export function overflodiga(katalog) { + return Object.keys(katalog).filter((n) => !arInvariant(n) && !(n in EN)); +} + +/** Andel av gränssnittet som finns på språket. */ +export function tackning(kod) { + const katalog = KATALOGER[kod]; + if (!katalog) return 0; + const totalt = Object.keys(EN).filter((n) => !arInvariant(n)).length; + return totalt === 0 ? 1 : (totalt - saknade(katalog).length) / totalt; +} + +/** + * Väljer språk ur det som finns att gå på. + * + * Ordningen är avsiktlig: användarens eget val väger tyngst, därefter + * organisationens, därefter webbläsarens, sist engelska. Organisationen + * går före webbläsaren därför att en verkstad i Tyskland med en polsk + * tekniker ska kunna ha ett gemensamt dokumentationsspråk — rapporten ska + * inte byta språk beroende på vem som råkade skriva raden. + */ +export function valjSprak({ anvandare, organisation, webblasare } = {}) { + const kanda = new Set(SPRAK.map((s) => s.kod)); + const forsta = [anvandare, organisation, ...(webblasare ?? [])] + .filter(Boolean) + .map((k) => String(k).slice(0, 2).toLowerCase()) + .find((k) => kanda.has(k)); + return forsta ?? STANDARD; +} + +/** + * Besked om metodikinnehåll som inte är granskat på användarens språk. + * + * Returnerar null när allt är i sin ordning. Annars en text som säger + * VAD som inte är granskat och VILKET språk texten faktiskt är på — inte + * en allmän brasklapp, som ingen läser. + */ +export function metodikvarning(sprak) { + const s = SPRAK.find((x) => x.kod === sprak); + if (!s || s.granskat) return null; + return t(sprak, "metodik.ogranskad", { sprak: s.egetNamn }); +} + +/** + * Ärendetyper som skrevs på svenska innan engelska blev källspråket. + * + * Ärendetypen är DATA: den ligger i händelseloggen och slår upp + * regelpaketets extra krav. Ett byte av språk får därför inte göras tyst + * — ett gammalt garantiärende vars typ heter "Garanti" hade annars inte + * matchat "Warranty", fått noll extra krav, och släppts igenom grinden + * med FÄRRE krav än när det öppnades. Ingenting i gränssnittet hade + * visat det. + * + * Tabellen är därför en del av regeln, inte en bekvämlighet, och den tas + * bort först när ingen logg innehåller de gamla värdena. + */ +export const ARENDETYP_ARV = { + "Privat kund": "Private customer", + Företagskund: "Business customer", + Garanti: "Warranty", + Försäkring: "Insurance", + Reklamation: "Complaint", + Begagnatgaranti: "Used-vehicle warranty", + "Intern kvalitetskontroll": "Internal quality check", + "Teknisk utredning": "Technical investigation", +}; + +/** Ärendetypen i sin nuvarande form, oavsett vilket språk den skrevs i. */ +export const arendetypNu = (typ) => ARENDETYP_ARV[typ] ?? typ; + +/** + * Jakande svar på en säkerhetsspärr. + * + * Svaret på en ja/nej-fråga ligger i loggen som text, och spärrfrågorna + * släpper bara igenom ett jakande svar. När knappen bytte från "Ja" till + * "Yes" gick de två isär: klienten skrev "Yes", servern jämförde mot + * "Ja". Utfallet blev fail-closed — högvoltsärenden gick inte att + * avsluta alls — vilket är rätt riktning för ett fel att falla åt, men + * det var en slump och inte en konstruktion. + * + * Det verkliga felet är att en säkerhetsspärr avgörs av en textsträng. + * Rätt lösning är ett booleskt fält i händelsen; tills det finns står + * båda formerna här, och de gäller alla språk samtidigt av samma skäl + * som ordlistorna i ord.mjs gör det: den som svarar ska inte kunna + * missas för att organisationens språkinställning var en annan. + */ +export const JAKANDE = new Set(["Yes", "Ja", "Oui", "Sí", "Si", "Sì", "Tak", "Da", "Sim", "Ja."]); + +/** Är svaret jakande, oavsett vilket språk knappen stod på? */ +export const arJakande = (svar) => JAKANDE.has(String(svar ?? "").trim()); diff --git a/felsokning/services-backup/gemensam/sprak/it.mjs b/felsokning/services-backup/gemensam/sprak/it.mjs new file mode 100644 index 0000000..1427205 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/it.mjs @@ -0,0 +1,328 @@ +// ALVA-SPEC-060 · Italiano. +// +// I pulsanti usano l’imperativo breve («Salva», «Annulla»), come vuole +// la convenzione delle interfacce italiane; le istruzioni all’interno +// delle frasi restano all’infinito («Fotografare il contachilometri»), +// senza rivolgersi all’operatore — ALVA-SPEC-001 §7. +// +// `granskat: false`: l’interfaccia è tradotta, il testo delle procedure +// non è revisionato da uno specialista. Vedi index.mjs. + +export const IT = { + // ---- Gradi di evidenza -------------------------------------------------- + "evidens.E1": "Osservazione", + "evidens.E2": "Fotografia", + "evidens.E3": "Video con audio", + "evidens.E4": "Misurazione, strumento tarato", + "evidens.E5": "Documento", + "evidens.ej_kalibrerad": "taratura mancante o scaduta", + "evidens.ej_angivet": "strumento non indicato", + "evidens.ej_fotograferad": "inserito, non fotografato", + + // ---- Il controllo di chiusura ------------------------------------------- + "grind.objekt": "Identificazione del veicolo o dell’oggetto verificata", + "grind.historik": "Storico del veicolo verificato, o l’omissione motivata", + "grind.historik.nekad": "Un controllo dello storico rifiutato richiede l’indicazione di un motivo.", + "grind.historik.saknas": "Nessun controllo dello storico documentato.", + "grind.matarstallning.ingaende": "Chilometraggio in accettazione fotografato", + "grind.matarstallning.utgaende": "Chilometraggio alla riconsegna fotografato", + "grind.matarstallning.saknas": "Nessun chilometraggio documentato.", + "grind.matarstallning.ej_foto": + "Il chilometraggio è inserito ma non fotografato. Fotografare il contachilometri, oppure indicare perché non è possibile.", + "grind.reproducering": "Verifica del sintomo: riprodotto, oppure documentato come non riproducibile", + "grind.felorsak": "Analisi della causa radice documentata", + "grind.atgard": "Intervento correttivo documentato, o la sua assenza motivata", + "grind.atgard.saknas": "Non è documentato né un intervento eseguito né un motivo della sua assenza.", + "grind.kundbeslut": "Decisione del cliente sulla proposta registrata", + "grind.kundbeslut.avbojt": "Lavoro eseguito nonostante una proposta rifiutata", + "grind.kundbeslut.avbojt.detalj": + "Il cliente ha rifiutato la proposta, ma risulta documentato lavoro eseguito.", + "grind.kvalitetskontroll": "Controllo qualità eseguito — sintomo verificato come risolto", + "grind.kontroller": "Punti di controllo della metodologia: evidenza, oppure deroga documentata", + "grind.foton": "Fotografie presenti per i punti di controllo che le richiedono", + "grind.slutsats": "Conclusione (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Abilitazione alta tensione confermata", + "grind.hogvolt.spanningslos": "Veicolo messo fuori tensione secondo la procedura del costruttore, assenza di tensione verificata mediante misurazione", + "grind.regelpaket": "La firma del pacchetto di regole non corrisponde — chiusura bloccata.", + "grind.regelpaket.osignerat": "È in uso un pacchetto di regole esterno privo di firma — chiusura bloccata.", + "grind.evidens": "Livello di evidenza superiore a E0", + "grind.evidens.saknas": "Nel registro non è presente alcuna evidenza.", + "grind.foton.detalj": "{kontroller} punti di controllo richiedono una fotografia, {foton} fotografie nel registro.", + "grind.sparr.ej_uppfyllt": "Il requisito di sicurezza non è soddisfatto.", + "grind.arendetyp.okant": "Requisito sconosciuto nel pacchetto di regole: {krav}", + "grind.arendetyp.krav": "Requisito per questo tipo di pratica: {krav}", + "grind.sakerhet": "Livello di confidenza entro quanto sostiene l’evidenza", + "grind.sakerhet.detalj": "La confidenza indicata ({niva}) supera quanto sostiene l’evidenza ({tak}). Integrare l’evidenza, oppure abbassare il livello — l’incertezza onesta è informazione.", + "grind.eskalering": "Escalation tecniche risolte", + "grind.eskalering.detalj": "Un’escalation è aperta ({referens}). Attendere la risposta e documentarla prima di chiudere la pratica.", + + + // ---- La conclusione (ALVA-RULE-200) ------------------------------------- + "slutsats.rubrik": "Conclusione", + "slutsats.konstaterat": "Che cosa è stato accertato", + "slutsats.evidens": "Quale evidenza lo sostiene", + "slutsats.avfardat": "Quali ipotesi sono state escluse, e perché", + "slutsats.osakert": "Che cosa resta incerto", + "slutsats.ickesvar": + "Questa non è una risposta. Indicare che cosa è stato accertato e quale evidenza lo sostiene.", + "slutsats.falt.motivering": "Motivazione", + "slutsats.falt.motivering_ej": "Motivo per cui la causa non è stata accertata", + "slutsats.falt.uteslutet": "Alternative escluse", + "slutsats.falt.kvarstaende": "Incertezza residua", + "slutsats.falt.atgardsval": "Scelta dell’intervento", + "slutsats.saknas": "{falt} manca.", + "slutsats.ickesvar.falt": "{falt}: «{text}» non è una motivazione. Indicare che cosa vale realmente, e perché.", + "slutsats.for_kort": "{falt} è troppo breve ({langd} su almeno {minsta} caratteri) per consentirne la verifica in seguito.", + "slutsats.utan_varfor": "{falt} indica che cosa, non perché. Collegare la conclusione all’evidenza — che cosa in essa la sostiene?", + "slutsats.utan_slutsats": "La pratica non può essere chiusa senza una conclusione. Indicare perché la conclusione discende dall’evidenza.", + "slutsats.hypotes_obemott": "L’ipotesi «{text}» è presente nel registro ma non viene trattata. Indicare perché è stata esclusa, o perché resta aperta.", + + + // ---- Flusso della pratica ----------------------------------------------- + "arende.nytt": "Nuova pratica", + "arende.oppna": "Pratiche aperte", + "arende.avslutade": "Pratiche chiuse", + "arende.avsluta": "Chiudi la pratica", + "arende.avslutat": "Pratica chiusa", + "arende.kan_ej_avslutas": "La pratica non può ancora essere chiusa", + "arende.hinder": "Da completare prima della chiusura", + "arende.overlamna": "Trasferisci", + "arende.ansvarig": "Responsabile", + + // ---- Controllo preliminare ---------------------------------------------- + "pre.rubrik": "Controllo preliminare — prima dell’inizio del lavoro", + "pre.historik.fraga": + "Lo storico del veicolo è stato verificato? (lavori precedenti, guasti ricorrenti, TSB, campagne)", + "pre.historik.ja": "Sì — verificato", + "pre.historik.nej": "No", + "pre.historik.skal": "Motivo per cui lo storico non è stato verificato (obbligatorio)", + "pre.historik.relevant": "Lavori precedenti rilevanti (facoltativo — catena causale)", + "pre.matarstallning": "Chilometraggio", + "pre.fotografera": "Fotografare il quadro strumenti", + "pre.felbeskrivning": "Descrizione del guasto fornita dal cliente verificata", + "pre.observationer": "Altro in fase di accettazione?", + + // ---- Azioni ------------------------------------------------------------- + "handling.spara": "Salva", + "handling.avbryt": "Annulla", + "handling.fortsatt": "Continua", + "handling.tillbaka": "Indietro", + "handling.dokumentera": "Documenta", + "handling.fotografera": "Fotografa", + "handling.spela_in": "Registra video", + "handling.undantag": "Documenta una deroga", + "handling.undantag.skal": "Motivo della deroga (obbligatorio)", + "handling.exportera": "Esporta", + "handling.skriv_ut": "Stampa", + + // ---- Misurazione -------------------------------------------------------- + "matning.varde": "Valore", + "matning.enhet": "Unità", + "matning.matdon": "Strumento", + "matning.matdon.valj": "Seleziona strumento", + "matning.matdon.okant": "Strumento sconosciuto. Registrarlo prima di salvare la misurazione.", + "matning.kalibrerad_till": "Tarato fino al", + + // ---- Rapporto ----------------------------------------------------------- + "rapport.rubrik": "Rapporto della pratica", + "rapport.sammanfattning": "Sintesi", + "rapport.evidens": "Evidenze", + "rapport.atgarder": "Interventi", + "rapport.harledd": + "Derivato dal registro della pratica. Le osservazioni e le misurazioni sono riportate senza conclusioni prive di fondamento.", + + // ---- Lingua ------------------------------------------------------------- + "sprak.valj": "Lingua", + "sprak.granskat": "Revisionato", + "sprak.ogranskat": "Non revisionato", + "sprak.tackning": "{procent} % dell’interfaccia", + + "metodik.ogranskad": + "Il testo delle procedure non è stato revisionato da uno specialista tecnico in {sprak}. I passaggi e i punti di controllo sono mostrati in inglese dove non esiste una traduzione revisionata — una traduzione non revisionata di un’istruzione di sicurezza è peggiore di una in lingua straniera, perché non sembra straniera.", + "metodik.pa_engelska": "Mostrato in inglese — nessuna traduzione revisionata in {sprak}", + + // ---- Sito pubblico (webb.) --------------------------------------------- + "webb.nav.oversikt": "Panoramica", + "webb.ansok": "Richiedi un account", + "webb.loggain": "Accedi", + "webb.fot.impressum": "Note legali", + "webb.fot.dataskydd": "Privacy", + "webb.fot.villkor": "Condizioni", + "webb.fot.tillganglighet": "Accessibilità", + "webb.fot.sprak": "Lingue", + "webb.fot.utgavor": "Note di rilascio", + + "webb.hero.position": "Piattaforma di diagnosi guidata", + "webb.hero.definition": "Procedure diagnostiche standardizzate per una ricerca guasti ripetibile e verificabile.", + + "webb.metod.etikett": "Metodologia", + "webb.metod.rubrik": "Il modello ALVA", + "webb.metod.ingress": + "ALVA è un metodo standardizzato di analisi, localizzazione, verifica e azione sistematiche. Ogni decisione è tracciabile, ogni conclusione verificabile, ogni azione riproducibile.", + "webb.fas.analysis.syfte": "Raccogliere le evidenze.", + "webb.fas.analysis.avgransning": "Solo fatti. In questa fase non si raccolgono ipotesi.", + "webb.fas.localization.syfte": "Circoscrivere il guasto.", + "webb.fas.localization.avgransning": "Restringere l’area. La causa non è ancora stabilita.", + "webb.fas.verification.syfte": "Confermare la causa radice.", + "webb.fas.verification.avgransning": "Si verifica la causa — non il sintomo.", + "webb.fas.action.syfte": "Eseguire l’azione correttiva.", + "webb.fas.action.avgransning": "Risultato verificato e documentato. Altrimenti la fase è incompleta.", + + "webb.drift.etikett": "Funzionamento", + "webb.drift.rubrik": "Come funziona", + "webb.drift.s1.rubrik": "Creare l’organizzazione", + "webb.drift.s1.text": "La registrazione viene inviata ed esaminata.", + "webb.drift.s2.rubrik": "Gli utenti ricevono gli account", + "webb.drift.s2.text": "I ruoli sono assegnati dall’amministratore dell’organizzazione.", + "webb.drift.s3.rubrik": "Procedure diagnostiche guidate", + "webb.drift.s3.text": "Ogni pratica segue una procedura definita.", + "webb.drift.s4.rubrik": "Verifica", + "webb.drift.s4.text": "La causa radice viene confermata prima dell’azione correttiva.", + "webb.drift.s5.rubrik": "Documentazione", + "webb.drift.s5.text": "Il rapporto è generato dal registro della pratica.", + "webb.drift.s6.rubrik": "Miglioramento continuo", + "webb.drift.s6.text": "Gli esiti verificati affinano le procedure successive.", + + "webb.larande.etikett": "Capacità", + "webb.larande.rubrik": "Apprendimento dell’organizzazione", + "webb.larande.p1": + "La piattaforma affina di continuo le procedure diagnostiche con l’esperienza operativa verificata della vostra stessa organizzazione.", + "webb.larande.p2": + "Ogni diagnosi completata alimenta la guida successiva. Si usano solo esiti verificati — una pratica chiusa senza causa radice confermata non apporta nulla, per costruzione.", + "webb.larande.p3": + "La base di conoscenza appartiene all’organizzazione. È derivata dalle vostre pratiche, dalle vostre procedure e dalla vostra documentazione, e non viene aggregata tra clienti.", + "webb.larande.block": "Derivata da", + "webb.larande.k1": "Cause radice verificate", + "webb.larande.k2": "Azioni correttive confermate", + "webb.larande.k3": "Categorie di guasti ricorrenti", + "webb.larande.k4": "Procedure portate a termine", + "webb.larande.k5": "Documentazione dell’organizzazione", + + "webb.rapport.etikett": "Rendicontazione", + "webb.rapport.rubrik": "Rapporto trimestrale di miglioramento", + "webb.rapport.ingress": + "Una volta a trimestre la piattaforma risponde a sei domande sul lavoro dell’officina stessa. Ogni risposta è derivata dal registro delle pratiche — nulla è stimato, e a nessuno viene chiesto nulla.", + "webb.rapport.f1": "Quali guasti diagnostichiamo ora correttamente al primo tentativo?", + "webb.rapport.f2": "Quanto spesso una causa sospetta si rivela quella reale?", + "webb.rapport.f3": "Quali guasti continuano a ripresentarsi nella flotta?", + "webb.rapport.f4": "Quali passaggi della procedura vengono saltati, e perché?", + "webb.rapport.f5": "Cosa sa oggi l’officina che non sapeva il trimestre scorso?", + "webb.rapport.f6": "Le pratiche vengono chiuse, verificate e documentate — o soltanto chiuse?", + + "webb.pris.etikett": "Commerciale", + "webb.pris.rubrik": "Licenze", + "webb.pris.komponent": "Componente", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licenza di piattaforma", + "webb.pris.plattform.grund": "Per organizzazione, annuale", + "webb.pris.anvandare": "Licenze utente", + "webb.pris.anvandare.grund": "Per utente attivo, mensile", + "webb.pris.moduler": "Moduli enterprise", + "webb.pris.moduler.grund": "Facoltativi, per modulo", + "webb.pris.betalning": "Nessun pagamento online. Nessuna sottoscrizione di abbonamento. La fatturazione segue l’esame della richiesta.", + "webb.pris.aktivering": "Sequenza di attivazione", + "webb.pris.a1": "L’organizzazione invia la registrazione.", + "webb.pris.a2": "La richiesta viene esaminata.", + "webb.pris.a3": "Viene emessa la fattura.", + "webb.pris.a4": "Il pagamento viene registrato.", + "webb.pris.a5": "L’organizzazione viene attivata.", + "webb.pris.a6": "Gli utenti aggiuntivi sono fatturati mensilmente.", + + "webb.kallor.etikett": "Infrastruttura", + "webb.kallor.rubrik": "Infrastruttura operativa della conoscenza", + "webb.kallor.p1": + "ALVA usa le fonti di conoscenza autorizzate proprie dell’organizzazione. L’architettura è neutrale rispetto al fornitore: ogni fonte implementa la stessa interfaccia, e nessun fornitore è presupposto.", + "webb.kallor.p2": + "Un’installazione beta lavora sulla sola documentazione interna. I fornitori esterni si abilitano in seguito tramite connettori separati, senza modifiche al nucleo della piattaforma.", + "webb.kallor.block": "Ordine di risoluzione delle fonti", + "webb.kallor.k1": "Procedure interne dell’azienda", + "webb.kallor.k2": "Documentazione OEM", + "webb.kallor.k3": "Bollettini tecnici", + "webb.kallor.k4": "Informazioni di garanzia", + "webb.kallor.k5": "Manuali di officina", + "webb.kallor.k6": "Storico delle diagnosi verificate", + "webb.kallor.k7": "Buone pratiche dell’organizzazione", + + "webb.login.etikett": "Accesso", + "webb.login.demo": + "Questo accesso non autentica nulla. Scrivete qualsiasi cosa in E-mail e Password e premete Accedi — non serve alcun account, e il portale dietro presenta dati di esempio fissi. Collegata a un’istanza della piattaforma, questa pagina si autentica presso di essa, e senza una sessione valida il portale è chiuso.", + "webb.login.losenord": "Password", + "webb.login.ofullstandigt": "Autenticazione incompleta. E-mail e password sono richieste.", + "webb.login.misslyckades": "Autenticazione non riuscita.", + "webb.login.logga_in": "Accedi", + "webb.login.loggar_in": "Accesso in corso", + "webb.login.glomt": "Password dimenticata", + + "webb.ansokan.etikett": "Registrazione", + "webb.ansokan.demo": + "Non viene inviato nulla. Inviare questo modulo non salva nulla, non avvisa nessuno e non crea alcuna richiesta — dietro non c’è ancora alcun sistema che la riceva. La descrizione qui sotto dice come la registrazione è pensata per funzionare, non cosa accade oggi.", + "webb.ansokan.avsikt": "Funzionamento previsto: le richieste sono esaminate manualmente e in questa fase non viene riscosso alcun pagamento.", + "webb.falt.foretag": "Azienda", + "webb.falt.orgnummer": "Numero di iscrizione al Registro delle Imprese", + "webb.falt.kontakt": "Referente", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefono", + "webb.falt.tekniker": "Numero di tecnici", + "webb.falt.bransch": "Settore", + "webb.falt.land": "Paese", + "webb.falt.anvandare": "Numero previsto di utenti", + "webb.falt.noteringar": "Note", + "webb.falt.kravs": "(obbligatorio)", + "webb.ansokan.skicka": "Invia la richiesta", + "webb.ansokan.mottagen.etikett": "Richiesta", + "webb.ansokan.mottagen": "Inviata", + "webb.ansokan.mottagen.demo": + "Nulla è stato trasmesso e nessuno è stato avvisato. Il riferimento qui sotto è stato calcolato nel vostro browser da ciò che avete digitato e non esiste altrove — sparirà alla chiusura di questa pagina.", + "webb.ansokan.status": "Stato", + "webb.ansokan.referens": "Riferimento", + "webb.ansokan.granskning": + "Le richieste sono esaminate manualmente. All’approvazione viene emessa una fattura. L’organizzazione viene attivata alla registrazione del pagamento.", + + "webb.sprak.etikett": "Localizzazione", + "webb.sprak.rubrik": "Lingue", + "webb.sprak.ingress": + "L’inglese è la lingua predefinita e la lingua di origine. Seguono nove traduzioni. Ciò che la piattaforma non fa è affermare che un’interfaccia tradotta significhi un metodo tradotto.", + "webb.sprak.princip.etikett": "Principio", + "webb.sprak.princip.rubrik": "Due tipi di testo", + "webb.sprak.granssnitt.rubrik": "Testo d’interfaccia", + "webb.sprak.granssnitt.beteckning": "Ripiega in silenzio", + "webb.sprak.granssnitt.text": + "Etichette, pulsanti, stati. Un insieme finito, che cambia di rado. Una stringa inglese che raggiunge un utente tedesco è un fastidio, non un pericolo — una traduzione mancante ripiega quindi sull’inglese senza commento.", + "webb.sprak.metodik.rubrik": "Testo di procedura", + "webb.sprak.metodik.beteckning": "Non ripiega mai in silenzio", + "webb.sprak.metodik.text": + "Istruzioni per lavorare su un veicolo. Qui una traduzione non revisionata è peggiore di una in lingua straniera — perché l’inglese sembra straniero, mentre una cattiva traduzione sembra un’istruzione. Viene mostrata in inglese e contrassegnata, con la lingua nominata.", + "webb.sprak.invariant": + "Il metodo in sé non viene mai tradotto. I nomi delle fasi e le parole di stato sono la struttura di ALVA e si leggono identici in ogni paese, così un revisore può leggere un fascicolo rumeno e uno tedesco senza sapere in quale lingua lavora l’officina.", + "webb.sprak.tackning.etikett": "Copertura", + "webb.sprak.tackning.rubrik": "Cosa è tradotto, e cosa è revisionato", + "webb.sprak.kolumn.sprak": "Lingua", + "webb.sprak.kolumn.granssnitt": "Interfaccia", + "webb.sprak.kolumn.granskat": "Metodo revisionato da uno specialista", + "webb.sprak.granskat.ja": "Sì", + "webb.sprak.granskat.nej": "No — testo di procedura in inglese", + "webb.sprak.matt": + "La copertura dell’interfaccia è misurata, non stimata: un test fa fallire la build se una chiave manca in una lingua. Lo stato di revisione è un’affermazione su persone, non su file, e si imposta a mano.", + "webb.sprak.kallsprak": + "Il testo di procedura inglese è una traduzione della fonte svedese e non è ancora stato letto da uno specialista che esercita il mestiere. È tenuto allo stesso standard delle altre nove lingue, e qui se ne fa la stessa dichiarazione invece di esentarlo in silenzio perché è la fonte.", + "webb.sprak.bevis.etikett": "Verifica", + "webb.sprak.bevis.rubrik": "Le frasi che fermano una pratica", + "webb.sprak.bevis.ingress": + "Queste sono le frasi che negano a un tecnico la chiusura di una pratica. Un rifiuto che nessuno capisce è un rifiuto senza via d’uscita — sono quindi le frasi giuste per giudicare una traduzione.", + "webb.sprak.ej_granskad": "Non revisionato", + "webb.sprak.granskad": "Revisionato", + "webb.sprak.granskad.text": + "Il testo di procedura in {sprak} è stato letto da uno specialista che esercita il mestiere. Passaggi e punti di controllo sono mostrati in {sprak} ovunque.", + "webb.sprak.val.etikett": "Funzionamento", + "webb.sprak.val.rubrik": "Come viene scelta la lingua", + "webb.sprak.val.s1.rubrik": "Preferenza dell’utente", + "webb.sprak.val.s1.text": "Ciò che la singola persona ha selezionato.", + "webb.sprak.val.s2.rubrik": "Impostazione dell’organizzazione", + "webb.sprak.val.s2.text": "La lingua di documentazione dell’officina.", + "webb.sprak.val.s3.rubrik": "Lingua del browser", + "webb.sprak.val.s3.text": "La prima lingua che la piattaforma riconosce.", + "webb.sprak.val.s4.rubrik": "Inglese", + "webb.sprak.val.s4.text": "La predefinita, e la fonte.", + "webb.sprak.val.notering": + "L’organizzazione precede deliberatamente il browser. Un’officina in Germania con un tecnico polacco ha bisogno di un’unica lingua di documentazione — il fascicolo non deve cambiare lingua a seconda di chi ha scritto la riga.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/nl.mjs b/felsokning/services-backup/gemensam/sprak/nl.mjs new file mode 100644 index 0000000..ce9b89c --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/nl.mjs @@ -0,0 +1,327 @@ +// ALVA-SPEC-060 · Nederlands. +// +// Knoppen staan in de infinitief («Opslaan», «Annuleren»), zoals de +// Nederlandse interfaceconventie voorschrijft. Instructies binnen een +// zin richten zich niet tot de gebruiker — ALVA-SPEC-001 §7: het systeem +// meldt een toestand. +// +// `granskat: false`: de interface is vertaald, de proceduretekst is niet +// door een vakspecialist gecontroleerd. Zie index.mjs. + +export const NL = { + // ---- Bewijsgraden ------------------------------------------------------- + "evidens.E1": "Waarneming", + "evidens.E2": "Foto", + "evidens.E3": "Video met geluid", + "evidens.E4": "Meting, gekalibreerd meetmiddel", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "kalibratie ontbreekt of is verlopen", + "evidens.ej_angivet": "meetmiddel niet opgegeven", + "evidens.ej_fotograferad": "ingevoerd, niet gefotografeerd", + + // ---- De afsluitcontrole ------------------------------------------------- + "grind.objekt": "Identificatie van voertuig of object geverifieerd", + "grind.historik": "Voertuighistorie gecontroleerd, of het achterwege blijven ervan gemotiveerd", + "grind.historik.nekad": "Een geweigerde historiecontrole vereist een opgegeven reden.", + "grind.historik.saknas": "Geen historiecontrole gedocumenteerd.", + "grind.matarstallning.ingaende": "Kilometerstand bij inname gefotografeerd", + "grind.matarstallning.utgaende": "Kilometerstand bij aflevering gefotografeerd", + "grind.matarstallning.saknas": "Geen kilometerstand gedocumenteerd.", + "grind.matarstallning.ej_foto": + "De kilometerstand is ingevoerd maar niet gefotografeerd. Kilometerteller fotograferen, of aangeven waarom dat niet mogelijk is.", + "grind.reproducering": "Symptoomverificatie: gereproduceerd, of gedocumenteerd als niet reproduceerbaar", + "grind.felorsak": "Analyse van de grondoorzaak gedocumenteerd", + "grind.atgard": "Corrigerende maatregel gedocumenteerd, of het uitblijven ervan gemotiveerd", + "grind.atgard.saknas": "Noch een uitgevoerde maatregel noch een reden voor het uitblijven ervan is gedocumenteerd.", + "grind.kundbeslut": "Klantbeslissing over het voorstel vastgelegd", + "grind.kundbeslut.avbojt": "Werk uitgevoerd ondanks een afgewezen voorstel", + "grind.kundbeslut.avbojt.detalj": + "De klant heeft het voorstel afgewezen, maar er is werk gedocumenteerd als uitgevoerd.", + "grind.kvalitetskontroll": "Kwaliteitscontrole uitgevoerd — symptoom aantoonbaar verholpen", + "grind.kontroller": "Controlepunten van de methodiek: bewijs, of een gedocumenteerde uitzondering", + "grind.foton": "Foto's aanwezig bij de controles die dat vereisen", + "grind.slutsats": "Eindconclusie (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Hoogvoltbevoegdheid bevestigd", + "grind.hogvolt.spanningslos": "Voertuig spanningsloos gemaakt volgens de procedure van de fabrikant, spanningsloosheid door meting vastgesteld", + "grind.regelpaket": "Handtekening van het regelpakket komt niet overeen — afsluiten geblokkeerd.", + "grind.regelpaket.osignerat": "Er wordt een extern regelpakket zonder handtekening gebruikt — afsluiten geblokkeerd.", + "grind.evidens": "Bewijsgraad boven E0", + "grind.evidens.saknas": "Er is geen enkel bewijs in het logboek aanwezig.", + "grind.foton.detalj": "{kontroller} controles vereisen een foto, {foton} foto's in het logboek.", + "grind.sparr.ej_uppfyllt": "Aan de veiligheidseis is niet voldaan.", + "grind.arendetyp.okant": "Onbekende eis in het regelpakket: {krav}", + "grind.arendetyp.krav": "Eis voor dit zaaktype: {krav}", + "grind.sakerhet": "Betrouwbaarheidsniveau binnen wat het bewijs draagt", + "grind.sakerhet.detalj": "De opgegeven betrouwbaarheid ({niva}) overstijgt wat het bewijs draagt ({tak}). Het bewijs aanvullen, of het niveau verlagen — eerlijke onzekerheid is informatie.", + "grind.eskalering": "Technische escalaties beantwoord", + "grind.eskalering.detalj": "Er staat een escalatie open ({referens}). Wacht het antwoord af en documenteer het voordat de zaak wordt afgesloten.", + + + // ---- De eindconclusie (ALVA-RULE-200) ----------------------------------- + "slutsats.rubrik": "Eindconclusie", + "slutsats.konstaterat": "Wat is vastgesteld", + "slutsats.evidens": "Welk bewijs dat draagt", + "slutsats.avfardat": "Welke hypothesen zijn verworpen, en waarom", + "slutsats.osakert": "Wat onzeker blijft", + "slutsats.ickesvar": "Dat is geen antwoord. Aangeven wat is vastgesteld en welk bewijs dat draagt.", + "slutsats.falt.motivering": "Onderbouwing", + "slutsats.falt.motivering_ej": "Reden waarom de oorzaak niet kon worden vastgesteld", + "slutsats.falt.uteslutet": "Verworpen alternatieven", + "slutsats.falt.kvarstaende": "Resterende onzekerheid", + "slutsats.falt.atgardsval": "Keuze van de maatregel", + "slutsats.saknas": "{falt} ontbreekt.", + "slutsats.ickesvar.falt": "{falt}: „{text}” is geen onderbouwing. Aangeven wat er werkelijk geldt, en waarom.", + "slutsats.for_kort": "{falt} is te kort ({langd} van minstens {minsta} tekens) om achteraf toetsbaar te zijn.", + "slutsats.utan_varfor": "{falt} geeft het wat aan, niet het waarom. De conclusie met het bewijs verbinden — wat daarin maakt dat dit volgt?", + "slutsats.utan_slutsats": "De zaak kan niet worden afgesloten zonder eindconclusie. Aangeven waarom de conclusie uit het bewijs volgt.", + "slutsats.hypotes_obemott": "De hypothese „{text}” staat in het logboek maar wordt niet behandeld. Aangeven waarom zij is verworpen, of waarom zij open blijft.", + + + // ---- Zaakverloop -------------------------------------------------------- + "arende.nytt": "Nieuwe zaak", + "arende.oppna": "Openstaande zaken", + "arende.avslutade": "Afgesloten zaken", + "arende.avsluta": "Zaak afsluiten", + "arende.avslutat": "Zaak afgesloten", + "arende.kan_ej_avslutas": "De zaak kan nog niet worden afgesloten", + "arende.hinder": "Openstaand vóór afsluiting", + "arende.overlamna": "Overdragen", + "arende.ansvarig": "Verantwoordelijke", + + // ---- Vooronderzoek ------------------------------------------------------ + "pre.rubrik": "Vooronderzoek — voordat het werk begint", + "pre.historik.fraga": + "Is de voertuighistorie gecontroleerd? (eerdere werkzaamheden, terugkerende storingen, TSB's, campagnes)", + "pre.historik.ja": "Ja — gecontroleerd", + "pre.historik.nej": "Nee", + "pre.historik.skal": "Reden waarom de historie niet is gecontroleerd (verplicht)", + "pre.historik.relevant": "Relevante eerdere werkzaamheden (optioneel — oorzakelijke keten)", + "pre.matarstallning": "Kilometerstand", + "pre.fotografera": "Instrumentenpaneel fotograferen", + "pre.felbeskrivning": "Storingsbeschrijving van de klant geverifieerd", + "pre.observationer": "Nog iets bij de inname?", + + // ---- Handelingen -------------------------------------------------------- + "handling.spara": "Opslaan", + "handling.avbryt": "Annuleren", + "handling.fortsatt": "Doorgaan", + "handling.tillbaka": "Terug", + "handling.dokumentera": "Documenteren", + "handling.fotografera": "Fotograferen", + "handling.spela_in": "Video opnemen", + "handling.undantag": "Een uitzondering documenteren", + "handling.undantag.skal": "Reden voor de uitzondering (verplicht)", + "handling.exportera": "Exporteren", + "handling.skriv_ut": "Afdrukken", + + // ---- Meting ------------------------------------------------------------- + "matning.varde": "Waarde", + "matning.enhet": "Eenheid", + "matning.matdon": "Meetmiddel", + "matning.matdon.valj": "Meetmiddel kiezen", + "matning.matdon.okant": "Onbekend meetmiddel. Registreren voordat de meting wordt opgeslagen.", + "matning.kalibrerad_till": "Gekalibreerd tot", + + // ---- Rapport ------------------------------------------------------------ + "rapport.rubrik": "Zaakrapport", + "rapport.sammanfattning": "Samenvatting", + "rapport.evidens": "Bewijs", + "rapport.atgarder": "Maatregelen", + "rapport.harledd": + "Afgeleid uit het zaaklogboek. Waarnemingen en metingen worden weergegeven zonder conclusies waarvoor de onderbouwing ontbreekt.", + + // ---- Taal --------------------------------------------------------------- + "sprak.valj": "Taal", + "sprak.granskat": "Gecontroleerd", + "sprak.ogranskat": "Niet gecontroleerd", + "sprak.tackning": "{procent} % van de interface", + + "metodik.ogranskad": + "De proceduretekst is niet gecontroleerd door een technisch specialist voor {sprak}. Stappen en controlepunten worden in het Engels weergegeven waar geen gecontroleerde vertaling bestaat — een ongecontroleerde vertaling van een veiligheidsinstructie is slechter dan een anderstalige, omdat die er niet anderstalig uitziet.", + "metodik.pa_engelska": "Weergegeven in het Engels — geen gecontroleerde vertaling in {sprak}", + + // ---- Publieke website (webb.) ------------------------------------------ + "webb.nav.oversikt": "Overzicht", + "webb.ansok": "Account aanvragen", + "webb.loggain": "Inloggen", + "webb.fot.impressum": "Colofon", + "webb.fot.dataskydd": "Privacybeleid", + "webb.fot.villkor": "Voorwaarden", + "webb.fot.tillganglighet": "Toegankelijkheid", + "webb.fot.sprak": "Talen", + "webb.fot.utgavor": "Release-informatie", + + "webb.hero.position": "Geleid diagnoseplatform", + "webb.hero.definition": "Gestandaardiseerde diagnoseprocedures voor herhaalbaar en verifieerbaar storingzoeken.", + + "webb.metod.etikett": "Methodiek", + "webb.metod.rubrik": "Het ALVA-model", + "webb.metod.ingress": + "ALVA is een gestandaardiseerde methode voor systematische analyse, lokalisatie, verificatie en actie. Elke beslissing is traceerbaar, elke conclusie verifieerbaar, elke actie reproduceerbaar.", + "webb.fas.analysis.syfte": "Bewijs verzamelen.", + "webb.fas.analysis.avgransning": "Alleen feiten. Hypothesen worden in deze fase niet verzameld.", + "webb.fas.localization.syfte": "De storing insluiten.", + "webb.fas.localization.avgransning": "Het gebied verkleinen. De oorzaak staat nog niet vast.", + "webb.fas.verification.syfte": "De grondoorzaak bevestigen.", + "webb.fas.verification.avgransning": "De oorzaak wordt geverifieerd — niet het symptoom.", + "webb.fas.action.syfte": "De corrigerende maatregel uitvoeren.", + "webb.fas.action.avgransning": "Resultaat geverifieerd en gedocumenteerd. Anders is de fase onvolledig.", + + "webb.drift.etikett": "Werking", + "webb.drift.rubrik": "Zo werkt het", + "webb.drift.s1.rubrik": "Organisatie aanmaken", + "webb.drift.s1.text": "De registratie wordt ingediend en beoordeeld.", + "webb.drift.s2.rubrik": "Gebruikers krijgen accounts", + "webb.drift.s2.text": "Rollen worden toegekend door de beheerder van de organisatie.", + "webb.drift.s3.rubrik": "Geleide diagnoseprocedures", + "webb.drift.s3.text": "Elke zaak volgt een gedefinieerde procedure.", + "webb.drift.s4.rubrik": "Verificatie", + "webb.drift.s4.text": "De grondoorzaak wordt bevestigd vóór de corrigerende maatregel.", + "webb.drift.s5.rubrik": "Documentatie", + "webb.drift.s5.text": "Het dossier wordt gegenereerd uit het zaaklogboek.", + "webb.drift.s6.rubrik": "Continue verbetering", + "webb.drift.s6.text": "Geverifieerde uitkomsten verfijnen volgende procedures.", + + "webb.larande.etikett": "Functionaliteit", + "webb.larande.rubrik": "Lerende organisatie", + "webb.larande.p1": + "Het platform verfijnt diagnoseprocedures voortdurend met geverifieerde praktijkervaring uit de eigen organisatie.", + "webb.larande.p2": + "Elke afgeronde diagnose voedt de volgende begeleiding. Alleen geverifieerde uitkomsten worden gebruikt — een zaak die zonder bevestigde grondoorzaak is afgesloten draagt bewust niets bij.", + "webb.larande.p3": + "De kennisbank is van de organisatie. Ze wordt afgeleid uit uw zaken, uw procedures en uw documentatie, en wordt niet over klanten heen samengevoegd.", + "webb.larande.block": "Afgeleid uit", + "webb.larande.k1": "Geverifieerde grondoorzaken", + "webb.larande.k2": "Bevestigde corrigerende maatregelen", + "webb.larande.k3": "Terugkerende storingscategorieën", + "webb.larande.k4": "Registraties van afgeronde procedures", + "webb.larande.k5": "Documentatie van de organisatie", + + "webb.rapport.etikett": "Rapportage", + "webb.rapport.rubrik": "Verbeterrapport per kwartaal", + "webb.rapport.ingress": + "Eén keer per kwartaal beantwoordt het platform zes vragen over het eigen werk van de werkplaats. Elk antwoord wordt afgeleid uit het zaaklogboek — niets wordt geschat, en niemand wordt iets gevraagd.", + "webb.rapport.f1": "Welke storingen stellen we nu in één keer goed vast?", + "webb.rapport.f2": "Hoe vaak blijkt een vermoede oorzaak de werkelijke te zijn?", + "webb.rapport.f3": "Welke storingen blijven in het wagenpark terugkomen?", + "webb.rapport.f4": "Welke procedurestappen worden overgeslagen, en waarom?", + "webb.rapport.f5": "Wat weet de werkplaats nu dat ze vorig kwartaal niet wist?", + "webb.rapport.f6": "Worden zaken afgerond, geverifieerd en gedocumenteerd — of alleen afgerond?", + + "webb.pris.etikett": "Commercieel", + "webb.pris.rubrik": "Licenties", + "webb.pris.komponent": "Onderdeel", + "webb.pris.grund": "Grondslag", + "webb.pris.plattform": "Platformlicentie", + "webb.pris.plattform.grund": "Per organisatie, jaarlijks", + "webb.pris.anvandare": "Gebruikerslicenties", + "webb.pris.anvandare.grund": "Per actieve gebruiker, maandelijks", + "webb.pris.moduler": "Bedrijfsmodules", + "webb.pris.moduler.grund": "Optioneel, per module", + "webb.pris.betalning": "Geen onlinebetaling. Geen abonnementsregistratie. Facturering volgt op beoordeling van de aanvraag.", + "webb.pris.aktivering": "Activeringsvolgorde", + "webb.pris.a1": "De organisatie dient de registratie in.", + "webb.pris.a2": "De aanvraag wordt beoordeeld.", + "webb.pris.a3": "De factuur wordt verstuurd.", + "webb.pris.a4": "De betaling wordt geregistreerd.", + "webb.pris.a5": "De organisatie wordt geactiveerd.", + "webb.pris.a6": "Extra gebruikers worden maandelijks gefactureerd.", + + "webb.kallor.etikett": "Infrastructuur", + "webb.kallor.rubrik": "Operationele kennisinfrastructuur", + "webb.kallor.p1": + "ALVA gebruikt de eigen geautoriseerde kennisbronnen van de organisatie. De architectuur is leveranciersneutraal: elke bron implementeert dezelfde interface, en geen leverancier wordt verondersteld.", + "webb.kallor.p2": + "Een bèta-installatie werkt uitsluitend met interne documentatie. Externe leveranciers worden later via aparte koppelingen ingeschakeld, zonder wijziging aan de platformkern.", + "webb.kallor.block": "Volgorde van bronraadpleging", + "webb.kallor.k1": "Interne bedrijfsprocedures", + "webb.kallor.k2": "OEM-documentatie", + "webb.kallor.k3": "Technische bulletins", + "webb.kallor.k4": "Garantie-informatie", + "webb.kallor.k5": "Werkplaatshandboeken", + "webb.kallor.k6": "Historische geverifieerde diagnoses", + "webb.kallor.k7": "Best practices van de organisatie", + + "webb.login.etikett": "Toegang", + "webb.login.demo": + "Deze login authenticeert niets. Typ wat dan ook in E-mail en Wachtwoord en druk op Inloggen — er is geen account nodig, en het portaal erachter toont vaste voorbeeldgegevens. Verbonden met een platforminstantie authenticeert deze pagina zich daartegen, en zonder geldige sessie is het portaal gesloten.", + "webb.login.losenord": "Wachtwoord", + "webb.login.ofullstandigt": "Authenticatie onvolledig. E-mail en wachtwoord vereist.", + "webb.login.misslyckades": "Authenticatie mislukt.", + "webb.login.logga_in": "Inloggen", + "webb.login.loggar_in": "Bezig met inloggen", + "webb.login.glomt": "Wachtwoord vergeten", + + "webb.ansokan.etikett": "Registratie", + "webb.ansokan.demo": + "Er wordt niets verzonden. Dit formulier indienen slaat niets op, meldt niemand iets en maakt geen aanvraag aan — er zit nog geen verwerking achter. De beschrijving hieronder zegt hoe de registratie bedoeld is te werken, niet wat er vandaag gebeurt.", + "webb.ansokan.avsikt": "Beoogde werking: aanvragen worden handmatig beoordeeld en in dit stadium wordt geen betaling geïnd.", + "webb.falt.foretag": "Bedrijf", + "webb.falt.orgnummer": "Registratienummer", + "webb.falt.kontakt": "Contactpersoon", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefoon", + "webb.falt.tekniker": "Aantal technici", + "webb.falt.bransch": "Branche", + "webb.falt.land": "Land", + "webb.falt.anvandare": "Verwacht aantal gebruikers", + "webb.falt.noteringar": "Opmerkingen", + "webb.falt.kravs": "(verplicht)", + "webb.ansokan.skicka": "Aanvraag indienen", + "webb.ansokan.mottagen.etikett": "Aanvraag", + "webb.ansokan.mottagen": "Ingediend", + "webb.ansokan.mottagen.demo": + "Er is niets verzonden en niemand is op de hoogte gebracht. De referentie hieronder is in uw browser berekend uit wat u typte en bestaat nergens anders — ze is weg zodra u deze pagina sluit.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Referentie", + "webb.ansokan.granskning": + "Aanvragen worden handmatig beoordeeld. Bij goedkeuring wordt een factuur verstuurd. De organisatie wordt geactiveerd zodra de betaling is geregistreerd.", + + "webb.sprak.etikett": "Lokalisatie", + "webb.sprak.rubrik": "Talen", + "webb.sprak.ingress": + "Engels is de standaard en de brontaal. Negen vertalingen volgen. Wat het platform niet doet, is beweren dat een vertaalde interface een vertaalde methode betekent.", + "webb.sprak.princip.etikett": "Principe", + "webb.sprak.princip.rubrik": "Twee soorten tekst", + "webb.sprak.granssnitt.rubrik": "Interfacetekst", + "webb.sprak.granssnitt.beteckning": "Valt stilzwijgend terug", + "webb.sprak.granssnitt.text": + "Labels, knoppen, statussen. Eindig, zelden gewijzigd. Een Engelse tekst die een Duitse gebruiker bereikt is een ergernis, geen gevaar — een ontbrekende vertaling valt dus zonder commentaar terug op het Engels.", + "webb.sprak.metodik.rubrik": "Proceduretekst", + "webb.sprak.metodik.beteckning": "Valt nooit stilzwijgend terug", + "webb.sprak.metodik.text": + "Instructies voor werk aan een voertuig. Hier is een ongecontroleerde vertaling erger dan een anderstalige — want Engels ziet er anderstalig uit, terwijl een slechte vertaling eruitziet als een instructie. De tekst wordt in het Engels getoond en gemarkeerd, met de taal benoemd.", + "webb.sprak.invariant": + "De methode zelf wordt nooit vertaald. Fasenamen en statuswoorden zijn de structuur van ALVA en luiden in elk land hetzelfde, zodat een auditor een Roemeens en een Duits dossier kan lezen zonder te weten in welke taal de werkplaats werkt.", + "webb.sprak.tackning.etikett": "Dekking", + "webb.sprak.tackning.rubrik": "Wat vertaald is, en wat gecontroleerd is", + "webb.sprak.kolumn.sprak": "Taal", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Methode gecontroleerd door een specialist", + "webb.sprak.granskat.ja": "Ja", + "webb.sprak.granskat.nej": "Nee — proceduretekst in het Engels", + "webb.sprak.matt": + "De dekking van de interface wordt gemeten, niet geschat: een test laat de build falen als een sleutel in een taal ontbreekt. De controlestatus is een uitspraak over mensen, niet over bestanden, en wordt handmatig ingesteld.", + "webb.sprak.kallsprak": + "De Engelse proceduretekst is een vertaling van de Zweedse bron en is nog niet gelezen door een specialist die het vak uitoefent. Hij wordt langs dezelfde meetlat gelegd als de andere negen talen, en hier wordt over hem dezelfde uitspraak gedaan in plaats van hem stilletjes uit te zonderen omdat hij de bron is.", + "webb.sprak.bevis.etikett": "Verificatie", + "webb.sprak.bevis.rubrik": "De zinnen die een zaak stoppen", + "webb.sprak.bevis.ingress": + "Dit zijn de zinnen die een technicus beletten een zaak af te sluiten. Een weigering die niemand begrijpt is een weigering zonder uitweg — dus zijn dit de juiste zinnen om een vertaling op te beoordelen.", + "webb.sprak.ej_granskad": "Niet gecontroleerd", + "webb.sprak.granskad": "Gecontroleerd", + "webb.sprak.granskad.text": + "De proceduretekst in {sprak} is gelezen door een specialist die het vak uitoefent. Stappen en controlepunten worden overal in {sprak} getoond.", + "webb.sprak.val.etikett": "Werking", + "webb.sprak.val.rubrik": "Hoe de taal wordt gekozen", + "webb.sprak.val.s1.rubrik": "Voorkeur van de gebruiker", + "webb.sprak.val.s1.text": "Wat de persoon zelf heeft geselecteerd.", + "webb.sprak.val.s2.rubrik": "Instelling van de organisatie", + "webb.sprak.val.s2.text": "De documentatietaal van de werkplaats.", + "webb.sprak.val.s3.rubrik": "Browsertaal", + "webb.sprak.val.s3.text": "De eerste taal die het platform herkent.", + "webb.sprak.val.s4.rubrik": "Engels", + "webb.sprak.val.s4.text": "De standaard, en de bron.", + "webb.sprak.val.notering": + "De organisatie gaat bewust vóór de browser. Een werkplaats in Duitsland met een Poolse technicus heeft één documentatietaal nodig — het dossier mag niet van taal wisselen afhankelijk van wie de tekst toevallig schreef.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/ord.mjs b/felsokning/services-backup/gemensam/sprak/ord.mjs new file mode 100644 index 0000000..e2e34f3 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/ord.mjs @@ -0,0 +1,175 @@ +// ALVA-SPEC-060 · Ordlistorna bakom ALVA-RULE-200. +// +// ---- Varför den här filen finns ----------------------------------------- +// +// motivering.mjs granskar teknikerns slutsats med två ordlistor: fraser +// som ser ut som svar men inte är det ("klart", "trasig"), och ord som +// visar att en text faktiskt innehåller ett resonemang ("eftersom", +// "beror på"). Båda listorna var enbart svenska. +// +// Det var inte en översättningsbrist utan ett hål i regeln. En tysk +// tekniker som skrev "erledigt" i motiveringsfältet passerade filtret +// för icke-svar, därför att filtret bara kände igen "klart". Grinden +// släppte igenom avslutet och rapporten såg fullständig ut. Regeln som +// är hela produktens värde gällde i praktiken bara på svenska. +// +// ---- Union, inte uppslag per språk -------------------------------------- +// +// Listorna slås ihop över alla språk i stället för att väljas efter +// användarens språkinställning. Två skäl: +// +// Ett icke-svar är ett icke-svar oavsett språk. Jämförelsen görs mot +// HELA det normaliserade fältet, inte mot delsträngar, så "ok" fångas +// likadant vare sig verkstaden står i Kraków eller Köln — och en +// tekniker som skriver på ett annat språk än organisationens +// inställning ska inte kunna passera på den vägen. +// +// Orsaksorden går åt andra hållet: unionen gör kontrollen mer +// tillåtande, aldrig strängare. Ett felaktigt träffat orsaksord +// släpper igenom en text, det blockerar aldrig en riktig. Det är rätt +// riktning för ett fel att luta åt när alternativet är att neka en +// tekniker avslut på ett korrekt skrivet underlag. +// +// ---- Vad som medvetet INTE står i listorna ------------------------------- +// +// "inget", "keine", "none". Att ingenting kvarstår osäkert är ett giltigt +// och ofta korrekt svar (se motivering.mjs). Att göra det till ett +// icke-svar hade tvingat fram utfyllnad, och utfyllnad är sämre än ett +// kort sant svar. +// +// Orsaksord kortare än fyra tecken. De jämförs som delsträngar och skulle +// träffa inuti orelaterade ord på andra språk. +// +// ---- Granskningsstatus --------------------------------------------------- +// +// Svenska och engelska är genomgångna. Övriga språk är en första +// uppsättning och ska läsas av någon som arbetar i verkstad på språket +// innan de räknas som granskade — det är samma gräns som `granskat` i +// SPRAK, och den gäller här därför att listorna är metodik, inte +// gränssnitt. + +/** Fraser som ser ut som svar men inte är det. Jämförs mot hela fältet. */ +export const ICKESVAR_PER_SPRAK = { + sv: ["klart", "klar", "färdig", "fardig", "färdigt", "fardigt", "ok", "okej", "åtgärdat", "atgardat", "åtgärdad", "atgardad", + "fixat", "utfört", "utfort", "lagad", "lagat", "se ovan", "se logg", "enligt ovan", "som vanligt", + "trasig", "trasigt", "defekt", "sliten", "slitet", "utsliten", "behöver bytas", "behover bytas", "byttes", "bytt", "utbytt", + "enligt kund", "kunden sa", "vet ej", "ingen aning", "diverse", "allmänt slitage", "-", "n/a", "na"], + en: ["done", "ok", "okay", "fixed", "repaired", "see above", "see log", "as above", "as usual", + "broken", "defective", "faulty", "worn", "worn out", "needs replacing", "needs replacement", "replaced", + "per customer", "as per customer", "customer said", "don't know", "dont know", "no idea", "unknown", + "not sure", "dunno", "no clue", "various", "general wear"], + de: ["erledigt", "behoben", "repariert", "siehe oben", "siehe protokoll", "wie oben", "wie üblich", "wie ublich", + "kaputt", "verschlissen", "abgenutzt", "muss getauscht werden", "getauscht", "gewechselt", + "defekt", "fertig", "in ordnung", "i.o.", "erneuert", "ersetzt", + "laut kunde", "kunde sagte", "weiß nicht", "weiss nicht", "keine ahnung", "diverses", + "allgemeiner verschleiß", "allgemeiner verschleiss", "k.a."], + fr: ["fait", "terminé", "termine", "réparé", "repare", "voir ci-dessus", "voir journal", "comme ci-dessus", + "comme d'habitude", "cassé", "casse", "défectueux", "defectueux", "usé", "usagé", "usage", + "à remplacer", "a remplacer", "remplacé", "remplace", "selon le client", "le client a dit", + "use", "hors service", "hs", "en panne", "changé", "change", "comme d’habitude", + "je ne sais pas", "aucune idée", "aucune idee", "divers", "usure générale", "usure generale"], + es: ["hecho", "listo", "reparado", "ver arriba", "ver registro", "como arriba", "como siempre", + "roto", "defectuoso", "desgastado", "hay que cambiarlo", "cambiado", "sustituido", + "arreglado", "solucionado", "averiado", "ya está", "ya esta", + "según el cliente", "segun el cliente", "el cliente dijo", "no sé", "no se", "ni idea", + "varios", "desgaste general"], + it: ["fatto", "risolto", "riparato", "vedi sopra", "vedi registro", "come sopra", "come al solito", + "rotto", "difettoso", "usurato", "da sostituire", "sostituito", "cambiato", + "sistemato", "a posto", "guasto", "tutto ok", "boh", "non ne ho idea", + "secondo il cliente", "il cliente ha detto", "non so", "nessuna idea", "vari", "usura generale", "n/d"], + pl: ["zrobione", "gotowe", "naprawione", "patrz wyżej", "patrz wyzej", "patrz dziennik", "jak wyżej", "jak wyzej", + "jak zwykle", "zepsute", "uszkodzone", "zużyte", "zuzyte", "do wymiany", "wymienione", + "według klienta", "wedlug klienta", "klient powiedział", "klient powiedzial", + "jw", "jw.", "naprawiono", "wymieniono", "zrobiono", "usunięto", "usunieto", "bez uwag", + "uszkodzony", "zepsuty", "zużyty", "zuzyty", "wg klienta", + "nie wiem", "nie mam pojęcia", "nie mam pojecia", "różne", "rozne", "ogólne zużycie", "ogolne zuzycie"], + nl: ["klaar", "oké", "oke", "gedaan", "gerepareerd", "verholpen", "opgelost", "in orde", "zie boven", "zie logboek", "zoals boven", + "zoals gewoonlijk", "kapot", "defect", "stuk", "versleten", "moet vervangen worden", "vervangen", + "volgens klant", "klant zei", "weet niet", "geen idee", "diversen", "algemene slijtage", "n.v.t.", "nvt"], + pt: ["feito", "pronto", "reparado", "ver acima", "ver registo", "como acima", "como habitual", + "partido", "avariado", "defeituoso", "desgastado", "a substituir", "substituído", "substituido", + "resolvido", "arranjado", "concluído", "concluido", + "segundo o cliente", "o cliente disse", "não sei", "nao sei", "nenhuma ideia", "não faço ideia", "nao faco ideia", + "diversos", "desgaste geral"], + ro: ["gata", "rezolvat", "reparat", "vezi mai sus", "vezi jurnal", "ca mai sus", "ca de obicei", + "stricat", "stricată", "stricata", "defect", "defectă", "defecta", "uzat", "uzată", "uzata", + "de înlocuit", "de inlocuit", "înlocuit", "inlocuit", "schimbat", + "conform clientului", "clientul a spus", "nu știu", "nu stiu", "habar n-am", "habar nu am", "diverse", + "uzură generală", "uzura generala"], +}; + +/** + * Ord som visar att texten innehåller ett orsakssamband. + * + * Jämförs som delsträngar, därför minst fyra tecken — se filhuvudet. + */ +export const ORSAKSORD_PER_SPRAK = { + sv: ["eftersom", "därför", "darfor", "beror", "orsakas", "orsakade", "medför", "medfor", "leder till", + "ledde till", "vilket", "på grund av", "pga", "resulterar", "resulterade", "innebär", "innebar", + "orsakar", "orsakat", "pa grund av", "till foljd av", "tyder på", "tyder pa", "indikerar", + "följaktligen", "foljaktligen", "till följd av", "förklarar", "forklarar", "stämmer överens", + "stammer overens", "bekräftas av", "bekraftas av", "visar att", "utesluter"], + en: ["because", "since", "due to", "caused", "causes", "results in", "resulted in", "leads to", "led to", + "therefore", "hence", "consequently", "explain", "confirmed by", "confirms", "shows that", "rules out", + "consistent with", "indicat", "points to", "which means"], + de: ["weil", "aufgrund", "verursacht", "führt zu", "fuhrt zu", "führte zu", "fuhrte zu", "folglich", + "daher", "deshalb", "wegen", "zurückzuführen", "zuruckzufuhren", "bedingt durch", "dadurch", "somit", + "erklärt", "erklart", "bestätigt", "bestatigt", "zeigt, dass", + "zeigt dass", "schließt aus", "schliesst aus", "ausgeschlossen", "stimmt überein", "stimmt uberein", "deutet auf"], + fr: ["parce que", "puisque", "en raison de", "à cause de", "a cause de", "provoqué", "provoque", + "entraîne", "entraine", "entraîné", "conduit à", "conduit a", "par conséquent", "par consequent", + "donc", "prouve", "explique", "confirmé par", "confirme par", "montre que", "exclut", "correspond à", "correspond a", + "indique"], + es: ["porque", "ya que", "puesto que", "dado que", "debido a", "a causa de", "se debe a", "causado", + "provoca", "provocó", "provoco", "conduce a", + "por lo tanto", "por consiguiente", "explica", "confirmado por", "muestra que", "descarta", + "coincide con", "indica"], + it: ["perché", "perche", "poiché", "poiche", "dato che", "in quanto", "a causa di", "dovuto a", "causato", + "provoca", "comporta", "porta a", "deriva da", + "quindi", "pertanto", "di conseguenza", "spiega", "confermato da", "mostra che", "esclude", + "coincide con", "compatibile con", "coerente con", "indica"], + pl: ["ponieważ", "poniewaz", "gdyż", "gdyz", "dlatego", "z powodu", "spowodowa", "powoduj", "prowadzi do", + "w związku", "w zwiazku", "zatem", "wynika", "wskutek", "na skutek", "skutkuje", + "świadczy", "swiadczy", "wyjaśni", "wyjasni", "potwierdz", + "pokazuje, że", "pokazuje że", "pokazuje ze", "wyklucza", "zgodne z", "wskazuj"], + nl: ["omdat", "aangezien", "doordat", "vanwege", "waardoor", "als gevolg van", "te wijten aan", + "veroorzaakt", "leidt tot", "leidde tot", "daarom", + "bijgevolg", "verklaart", "bevestigd door", "blijkt uit", "toont aan", "wijst op", "sluit uit", "komt overeen", "duidt op"], + pt: ["porque", "dado que", "uma vez que", "visto que", "devido a", "deve-se a", "causad", "provoc", + "leva a", "resulta", "origina", "portanto", "por conseguinte", + "explica", "confirmado por", "mostra que", "exclui", "coincide com", "indica"], + ro: ["deoarece", "pentru că", "pentru ca", "fiindcă", "fiindca", "întrucât", "intrucat", "din cauza", + "se datorează", "se datoreaza", "cauzat", "provoacă", "provoaca", "duce la", "rezultă", "rezulta", "determinat", + "prin urmare", "așadar", "asadar", "explică", "explica", "confirmat de", "confirmată de", "confirmata de", + "arată că", "arata ca", "exclude", "corespunde cu", "indică", "indica"], +}; + +/** + * Ord som visar att texten hänvisar till dokumenterad evidens. + * + * Samma delsträngsjämförelse och samma unionsresonemang som ovan. Här + * är det extra tydligt varför unionen är rätt: engelskans "photo" + * innehåller inte "foto", så en engelsk tekniker som skrev "photo shows + * corrosion at pin 14" hänvisade till evidens som filtret inte såg. + */ +export const EVIDENSORD_PER_SPRAK = { + sv: ["mät", "matning", "matvarde", "uppmatt", "foto", "bild", "video", "felkod", "avläs", "avlas", "prov", "test", "dokument", "historik", "kontroll"], + en: ["measur", "photo", "image", "picture", "video", "fault code", "trouble code", "dtc", "reading", "voltage", "resistance", "inspect", "test", "document", "history", "check", "scan"], + de: ["mess", "foto", "bild", "video", "fehlercode", "fehlerspeicher", "ausles", "ablesung", "prüf", "pruf", "dokument", "historie", "kontrolle"], + fr: ["mesur", "photo", "image", "vidéo", "video", "code défaut", "code defaut", "relevé", "releve", "essai", "diag", "valise", "document", "historique", "contrôle", "controle"], + es: ["medici", "medida", "foto", "imagen", "vídeo", "video", "código de avería", "codigo de averia", "código de error", "codigo de error", "lectura", "prueba", "comprob", "diagnosis", "escáner", "escaner", "documento", "historial", "control"], + it: ["misur", "foto", "immagine", "video", "codice errore", "codice di errore", "codice guasto", "lettura", "prova", "documento", "storico", "controllo"], + pl: ["pomiar", "zmierzon", "zdjęci", "zdjeci", "foto", "obraz", "wideo", "film", "kod błędu", "kod bledu", "odczyt", "test", "prób", "skan", "dokument", "histori", "kontrol"], + nl: ["meting", "gemeten", "meetwaarde", "foto", "afbeelding", "video", "foutcode", "storingscode", "uitlezing", "uitgelezen", "test", "proef", "document", "historie", "controle"], + pt: ["medi", "foto", "imagem", "vídeo", "video", "código de avaria", "codigo de avaria", "código de erro", "codigo de erro", "leitura", "ensaio", "documento", "histórico", "historico", "controlo"], + ro: ["măsur", "masur", "foto", "poză", "poza", "imagine", "video", "cod de eroare", "cod eroare", "citire", "verific", "test", "document", "istoric", "control"], +}; + +const union = (per) => [...new Set(Object.values(per).flat())]; + +export const ICKESVAR = union(ICKESVAR_PER_SPRAK); +export const ORSAKSORD = union(ORSAKSORD_PER_SPRAK); +export const EVIDENSORD = union(EVIDENSORD_PER_SPRAK); + +/** Kortaste orsaksord som får jämföras som delsträng. Se filhuvudet. */ +export const MINSTA_ORSAKSORD = 4; diff --git a/felsokning/services-backup/gemensam/sprak/pl.mjs b/felsokning/services-backup/gemensam/sprak/pl.mjs new file mode 100644 index 0000000..34067e6 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/pl.mjs @@ -0,0 +1,329 @@ +// ALVA-SPEC-060 · Polski. +// +// ---- Odmiana nazwy języka ------------------------------------------------ +// +// {sprak} wstawia własną nazwę języka — «Polski» — w mianowniku. Po +// polsku «w języku Polski» jest błędem, a odmienić wstawianego wyrazu +// nie sposób bez tablicy przypadków dla dziesięciu języków. Dlatego +// zdania są tak sformułowane, by nazwa stała w nawiasie jako etykieta: +// «w tym języku ({sprak})». To wybór świadomy, nie niedopatrzenie. +// +// `granskat: false`: interfejs jest przetłumaczony, tekst procedur nie +// został sprawdzony przez specjalistę. Zob. index.mjs. + +export const PL = { + // ---- Stopnie dowodu ----------------------------------------------------- + "evidens.E1": "Obserwacja", + "evidens.E2": "Fotografia", + "evidens.E3": "Wideo z dźwiękiem", + "evidens.E4": "Pomiar, przyrząd wzorcowany", + "evidens.E5": "Dokument", + "evidens.ej_kalibrerad": "brak wzorcowania lub wzorcowanie wygasło", + "evidens.ej_angivet": "nie podano przyrządu", + "evidens.ej_fotograferad": "wprowadzono, nie sfotografowano", + + // ---- Kontrola zamknięcia ------------------------------------------------ + "grind.objekt": "Identyfikacja pojazdu lub obiektu zweryfikowana", + "grind.historik": "Historia pojazdu sprawdzona albo brak sprawdzenia uzasadniony", + "grind.historik.nekad": "Odmowa sprawdzenia historii wymaga podania powodu.", + "grind.historik.saknas": "Nie udokumentowano sprawdzenia historii.", + "grind.matarstallning.ingaende": "Stan licznika przy przyjęciu sfotografowany", + "grind.matarstallning.utgaende": "Stan licznika przy wydaniu sfotografowany", + "grind.matarstallning.saknas": "Nie udokumentowano stanu licznika.", + "grind.matarstallning.ej_foto": + "Stan licznika został wprowadzony, ale nie sfotografowany. Sfotografować licznik lub podać, dlaczego nie jest to możliwe.", + "grind.reproducering": "Weryfikacja objawu: odtworzony lub udokumentowany jako nieodtwarzalny", + "grind.felorsak": "Analiza przyczyny źródłowej udokumentowana", + "grind.atgard": "Działanie naprawcze udokumentowane albo jego brak uzasadniony", + "grind.atgard.saknas": "Nie udokumentowano ani wykonanego działania, ani powodu jego braku.", + "grind.kundbeslut": "Decyzja klienta w sprawie propozycji zarejestrowana", + "grind.kundbeslut.avbojt": "Praca wykonana mimo odrzuconej propozycji", + "grind.kundbeslut.avbojt.detalj": "Klient odrzucił propozycję, ale praca jest udokumentowana jako wykonana.", + "grind.kvalitetskontroll": "Kontrola jakości wykonana — potwierdzono ustąpienie objawu", + "grind.kontroller": "Punkty kontrolne metodyki: dowód lub udokumentowane odstępstwo", + "grind.foton": "Fotografie obecne przy punktach kontrolnych, które ich wymagają", + "grind.slutsats": "Wniosek końcowy (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Uprawnienia do prac przy wysokim napięciu potwierdzone", + "grind.hogvolt.spanningslos": "Pojazd pozbawiony napięcia zgodnie z procedurą producenta, brak napięcia potwierdzony pomiarem", + "grind.regelpaket": "Podpis pakietu reguł nie zgadza się — zamknięcie zablokowane.", + "grind.regelpaket.osignerat": "Używany jest zewnętrzny pakiet reguł bez podpisu — zamknięcie zablokowane.", + "grind.evidens": "Poziom dowodu powyżej E0", + "grind.evidens.saknas": "W dzienniku nie ma żadnego dowodu.", + "grind.foton.detalj": "Punktów kontrolnych wymagających fotografii: {kontroller}; fotografii w dzienniku: {foton}.", + "grind.sparr.ej_uppfyllt": "Wymóg bezpieczeństwa nie jest spełniony.", + "grind.arendetyp.okant": "Nieznany wymóg w pakiecie reguł: {krav}", + "grind.arendetyp.krav": "Wymóg dla tego rodzaju sprawy: {krav}", + "grind.sakerhet": "Poziom pewności w granicach tego, co potwierdzają dowody", + "grind.sakerhet.detalj": "Podany poziom pewności ({niva}) przekracza to, co potwierdzają dowody ({tak}). Uzupełnić dowody lub obniżyć poziom — uczciwa niepewność to informacja.", + "grind.eskalering": "Eskalacje techniczne z odpowiedzią", + "grind.eskalering.detalj": "Eskalacja jest otwarta ({referens}). Poczekać na odpowiedź i udokumentować ją przed zamknięciem sprawy.", + + + // ---- Wniosek końcowy (ALVA-RULE-200) ------------------------------------ + "slutsats.rubrik": "Wniosek końcowy", + "slutsats.konstaterat": "Co zostało ustalone", + "slutsats.evidens": "Jakie dowody to potwierdzają", + "slutsats.avfardat": "Które hipotezy odrzucono i dlaczego", + "slutsats.osakert": "Co pozostaje niepewne", + "slutsats.ickesvar": "To nie jest wniosek. Podać, co zostało ustalone i jakie dowody to potwierdzają.", + "slutsats.falt.motivering": "Uzasadnienie", + "slutsats.falt.motivering_ej": "Powód, dla którego nie ustalono przyczyny", + "slutsats.falt.uteslutet": "Odrzucone alternatywy", + "slutsats.falt.kvarstaende": "Pozostała niepewność", + "slutsats.falt.atgardsval": "Wybór działania", + "slutsats.saknas": "Brakuje: {falt}.", + "slutsats.ickesvar.falt": "{falt}: „{text}” nie jest uzasadnieniem. Podać, jaki jest stan faktyczny i dlaczego.", + "slutsats.for_kort": "Pole „{falt}” jest za krótkie ({langd} z co najmniej {minsta} znaków), aby dało się je później zweryfikować.", + "slutsats.utan_varfor": "{falt} podaje co, ale nie dlaczego. Powiązać wniosek z dowodami — co w nich sprawia, że to wynika?", + "slutsats.utan_slutsats": "Sprawy nie można zamknąć bez wniosku końcowego. Podać, dlaczego wniosek wynika z dowodów.", + "slutsats.hypotes_obemott": "Hipoteza „{text}” znajduje się w dzienniku, ale nie została omówiona. Podać, dlaczego ją odrzucono lub dlaczego pozostaje otwarta.", + + + // ---- Przebieg sprawy ---------------------------------------------------- + "arende.nytt": "Nowa sprawa", + "arende.oppna": "Sprawy otwarte", + "arende.avslutade": "Sprawy zamknięte", + "arende.avsluta": "Zamknij sprawę", + "arende.avslutat": "Sprawa zamknięta", + "arende.kan_ej_avslutas": "Sprawy nie można jeszcze zamknąć", + "arende.hinder": "Do wykonania przed zamknięciem", + "arende.overlamna": "Przekaż", + "arende.ansvarig": "Osoba odpowiedzialna", + + // ---- Kontrola wstępna --------------------------------------------------- + "pre.rubrik": "Diagnostyka wstępna — przed rozpoczęciem pracy", + "pre.historik.fraga": + "Czy historia pojazdu została sprawdzona? (wcześniejsze naprawy, powtarzające się usterki, TSB, akcje serwisowe)", + "pre.historik.ja": "Tak — sprawdzona", + "pre.historik.nej": "Nie", + "pre.historik.skal": "Powód niesprawdzenia historii (wymagane)", + "pre.historik.relevant": "Istotne wcześniejsze naprawy (opcjonalnie — łańcuch przyczynowy)", + "pre.matarstallning": "Stan licznika", + "pre.fotografera": "Sfotografuj zestaw wskaźników", + "pre.felbeskrivning": "Opis usterki podany przez klienta zweryfikowany", + "pre.observationer": "Coś jeszcze przy przyjęciu?", + + // ---- Czynności ---------------------------------------------------------- + "handling.spara": "Zapisz", + "handling.avbryt": "Anuluj", + "handling.fortsatt": "Dalej", + "handling.tillbaka": "Wstecz", + "handling.dokumentera": "Udokumentuj", + "handling.fotografera": "Sfotografuj", + "handling.spela_in": "Nagraj wideo", + "handling.undantag": "Udokumentuj odstępstwo", + "handling.undantag.skal": "Powód odstępstwa (wymagane)", + "handling.exportera": "Eksportuj", + "handling.skriv_ut": "Drukuj", + + // ---- Pomiar ------------------------------------------------------------- + "matning.varde": "Wartość", + "matning.enhet": "Jednostka", + "matning.matdon": "Przyrząd", + "matning.matdon.valj": "Wybierz przyrząd", + "matning.matdon.okant": "Nieznany przyrząd. Zarejestrować go przed zapisaniem pomiaru.", + "matning.kalibrerad_till": "Wzorcowanie ważne do", + + // ---- Raport ------------------------------------------------------------- + "rapport.rubrik": "Raport sprawy", + "rapport.sammanfattning": "Podsumowanie", + "rapport.evidens": "Dowody", + "rapport.atgarder": "Działania", + "rapport.harledd": + "Wyprowadzony z dziennika sprawy. Obserwacje i pomiary są przedstawiane bez wniosków pozbawionych podstaw.", + + // ---- Język -------------------------------------------------------------- + "sprak.valj": "Język", + "sprak.granskat": "Sprawdzony", + "sprak.ogranskat": "Niesprawdzony", + "sprak.tackning": "{procent} % interfejsu", + + "metodik.ogranskad": + "Tekst procedur nie został sprawdzony przez specjalistę technicznego w tym języku ({sprak}). Kroki i punkty kontrolne są wyświetlane po angielsku tam, gdzie nie ma sprawdzonego tłumaczenia — niesprawdzone tłumaczenie instrukcji bezpieczeństwa jest gorsze niż instrukcja w obcym języku, ponieważ nie wygląda obco.", + "metodik.pa_engelska": "Wyświetlane po angielsku — brak sprawdzonego tłumaczenia w tym języku ({sprak})", + + // ---- Publiczna witryna (webb.) ----------------------------------------- + "webb.nav.oversikt": "Przegląd", + "webb.ansok": "Wniosek o konto", + "webb.loggain": "Logowanie", + "webb.fot.impressum": "Nota prawna", + "webb.fot.dataskydd": "Prywatność", + "webb.fot.villkor": "Warunki", + "webb.fot.tillganglighet": "Dostępność", + "webb.fot.sprak": "Języki", + "webb.fot.utgavor": "Informacje o wydaniach", + + "webb.hero.position": "Platforma diagnostyki prowadzonej", + "webb.hero.definition": "Znormalizowane procedury diagnostyczne dla powtarzalnego i weryfikowalnego diagnozowania usterek.", + + "webb.metod.etikett": "Metodyka", + "webb.metod.rubrik": "Model ALVA", + "webb.metod.ingress": + "ALVA to znormalizowana metoda systematycznej analizy, lokalizacji, weryfikacji i działania. Każda decyzja jest identyfikowalna, każdy wniosek weryfikowalny, każde działanie powtarzalne.", + "webb.fas.analysis.syfte": "Zebrać dowody.", + "webb.fas.analysis.avgransning": "Wyłącznie fakty. W tej fazie nie zbiera się hipotez.", + "webb.fas.localization.syfte": "Zlokalizować usterkę.", + "webb.fas.localization.avgransning": "Ograniczyć obszar. Przyczyna nie jest jeszcze ustalona.", + "webb.fas.verification.syfte": "Potwierdzić przyczynę źródłową.", + "webb.fas.verification.avgransning": "Weryfikowana jest przyczyna — nie objaw.", + "webb.fas.action.syfte": "Wykonać działanie naprawcze.", + "webb.fas.action.avgransning": "Wynik zweryfikowany i udokumentowany. Inaczej faza jest niezakończona.", + + "webb.drift.etikett": "Sposób działania", + "webb.drift.rubrik": "Jak to działa", + "webb.drift.s1.rubrik": "Utworzenie organizacji", + "webb.drift.s1.text": "Wniosek rejestracyjny zostaje złożony i rozpatrzony.", + "webb.drift.s2.rubrik": "Użytkownicy otrzymują konta", + "webb.drift.s2.text": "Role przydziela administrator organizacji.", + "webb.drift.s3.rubrik": "Prowadzone procedury diagnostyczne", + "webb.drift.s3.text": "Każda sprawa przebiega według zdefiniowanej procedury.", + "webb.drift.s4.rubrik": "Weryfikacja", + "webb.drift.s4.text": "Przyczyna źródłowa jest potwierdzana przed działaniem naprawczym.", + "webb.drift.s5.rubrik": "Dokumentacja", + "webb.drift.s5.text": "Raport powstaje z dziennika sprawy.", + "webb.drift.s6.rubrik": "Ciągłe doskonalenie", + "webb.drift.s6.text": "Zweryfikowane wyniki udoskonalają kolejne procedury.", + + "webb.larande.etikett": "Możliwości", + "webb.larande.rubrik": "Uczenie się organizacji", + "webb.larande.p1": + "Platforma stale udoskonala procedury diagnostyczne na podstawie zweryfikowanego doświadczenia operacyjnego własnej organizacji.", + "webb.larande.p2": + "Każda ukończona diagnoza zasila kolejne wskazówki. Używane są wyłącznie zweryfikowane wyniki — sprawa zamknięta bez potwierdzonej przyczyny źródłowej z założenia nie wnosi niczego.", + "webb.larande.p3": + "Baza wiedzy należy do organizacji. Powstaje z jej własnych spraw, procedur i dokumentacji i nie jest agregowana między klientami.", + "webb.larande.block": "Pochodzi z", + "webb.larande.k1": "Zweryfikowane przyczyny źródłowe", + "webb.larande.k2": "Potwierdzone działania naprawcze", + "webb.larande.k3": "Powtarzające się kategorie usterek", + "webb.larande.k4": "Zapisy ukończonych procedur", + "webb.larande.k5": "Dokumentacja organizacji", + + "webb.rapport.etikett": "Raportowanie", + "webb.rapport.rubrik": "Kwartalny raport doskonalenia", + "webb.rapport.ingress": + "Raz na kwartał platforma odpowiada na sześć pytań o pracę samego warsztatu. Każda odpowiedź pochodzi z dziennika spraw — niczego się nie szacuje i nikogo o nic nie pyta.", + "webb.rapport.f1": "Które usterki diagnozujemy teraz poprawnie za pierwszym razem?", + "webb.rapport.f2": "Jak często podejrzewana przyczyna okazuje się rzeczywistą?", + "webb.rapport.f3": "Które usterki wciąż wracają we flocie?", + "webb.rapport.f4": "Które kroki procedury są pomijane i dlaczego?", + "webb.rapport.f5": "Co warsztat wie teraz, czego nie wiedział w zeszłym kwartale?", + "webb.rapport.f6": "Czy sprawy są zamykane, weryfikowane i dokumentowane — czy tylko zamykane?", + + "webb.pris.etikett": "Warunki handlowe", + "webb.pris.rubrik": "Licencjonowanie", + "webb.pris.komponent": "Składnik", + "webb.pris.grund": "Podstawa", + "webb.pris.plattform": "Licencja platformy", + "webb.pris.plattform.grund": "Na organizację, rocznie", + "webb.pris.anvandare": "Licencje użytkowników", + "webb.pris.anvandare.grund": "Na aktywnego użytkownika, miesięcznie", + "webb.pris.moduler": "Moduły korporacyjne", + "webb.pris.moduler.grund": "Opcjonalne, na moduł", + "webb.pris.betalning": "Bez płatności online. Bez samodzielnego wykupu subskrypcji. Fakturowanie następuje po rozpatrzeniu wniosku.", + "webb.pris.aktivering": "Kolejność aktywacji", + "webb.pris.a1": "Organizacja składa wniosek rejestracyjny.", + "webb.pris.a2": "Wniosek jest rozpatrywany.", + "webb.pris.a3": "Wystawiana jest faktura.", + "webb.pris.a4": "Płatność zostaje zaksięgowana.", + "webb.pris.a5": "Organizacja zostaje aktywowana.", + "webb.pris.a6": "Dodatkowi użytkownicy są rozliczani miesięcznie.", + + "webb.kallor.etikett": "Infrastruktura", + "webb.kallor.rubrik": "Operacyjna infrastruktura wiedzy", + "webb.kallor.p1": + "ALVA korzysta z własnych, autoryzowanych źródeł wiedzy organizacji. Architektura jest neutralna wobec dostawców: każde źródło implementuje ten sam interfejs i żadnego dostawcy nie zakłada się z góry.", + "webb.kallor.p2": + "Instalacja beta pracuje wyłącznie na dokumentacji wewnętrznej. Zewnętrzni dostawcy są włączani później przez osobne konektory, bez zmian w rdzeniu platformy.", + "webb.kallor.block": "Kolejność przeszukiwania źródeł", + "webb.kallor.k1": "Wewnętrzne procedury firmy", + "webb.kallor.k2": "Dokumentacja OEM", + "webb.kallor.k3": "Biuletyny techniczne", + "webb.kallor.k4": "Informacje gwarancyjne", + "webb.kallor.k5": "Podręczniki serwisowe", + "webb.kallor.k6": "Historyczne zweryfikowane diagnozy", + "webb.kallor.k7": "Dobre praktyki organizacji", + + "webb.login.etikett": "Dostęp", + "webb.login.demo": + "To logowanie niczego nie uwierzytelnia. Wpisz cokolwiek w polach E-mail i Hasło i naciśnij Zaloguj się — konto nie jest potrzebne, a portal za nim pokazuje stałe dane przykładowe. Połączona z instancją platformy strona uwierzytelnia się wobec niej, a bez ważnej sesji portal jest zamknięty.", + "webb.login.losenord": "Hasło", + "webb.login.ofullstandigt": "Uwierzytelnianie niekompletne. Wymagane są e-mail i hasło.", + "webb.login.misslyckades": "Uwierzytelnianie nie powiodło się.", + "webb.login.logga_in": "Zaloguj się", + "webb.login.loggar_in": "Logowanie w toku", + "webb.login.glomt": "Nie pamiętam hasła", + + "webb.ansokan.etikett": "Rejestracja", + "webb.ansokan.demo": + "Nic nie jest wysyłane. Wysłanie formularza niczego nie zapisuje, nikogo nie powiadamia i nie tworzy wniosku — nie stoi za nim jeszcze żaden proces przyjmowania zgłoszeń. Opis poniżej mówi, jak rejestracja ma działać, a nie co dzieje się dziś.", + "webb.ansokan.avsikt": "Zamierzone działanie: wnioski są rozpatrywane ręcznie i na tym etapie nie jest pobierana żadna płatność.", + "webb.falt.foretag": "Firma", + "webb.falt.orgnummer": "Numer rejestrowy", + "webb.falt.kontakt": "Osoba kontaktowa", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Liczba techników", + "webb.falt.bransch": "Branża", + "webb.falt.land": "Kraj", + "webb.falt.anvandare": "Przewidywana liczba użytkowników", + "webb.falt.noteringar": "Uwagi", + "webb.falt.kravs": "(wymagane)", + "webb.ansokan.skicka": "Wyślij wniosek", + "webb.ansokan.mottagen.etikett": "Wniosek", + "webb.ansokan.mottagen": "Złożono", + "webb.ansokan.mottagen.demo": + "Nic nie zostało przekazane i nikt nie został powiadomiony. Poniższy numer referencyjny został obliczony w przeglądarce z tego, co wpisano, i nie istnieje nigdzie indziej — zniknie po zamknięciu tej strony.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Numer referencyjny", + "webb.ansokan.granskning": + "Wnioski są rozpatrywane ręcznie. Po zatwierdzeniu wystawiana jest faktura. Organizacja zostaje aktywowana po zaksięgowaniu płatności.", + + "webb.sprak.etikett": "Lokalizacja", + "webb.sprak.rubrik": "Języki", + "webb.sprak.ingress": + "Angielski jest językiem domyślnym i źródłowym. Po nim następuje dziewięć tłumaczeń. Czego platforma nie robi: nie twierdzi, że przetłumaczony interfejs oznacza przetłumaczoną metodę.", + "webb.sprak.princip.etikett": "Zasada", + "webb.sprak.princip.rubrik": "Dwa rodzaje tekstu", + "webb.sprak.granssnitt.rubrik": "Tekst interfejsu", + "webb.sprak.granssnitt.beteckning": "Po cichu wraca do angielskiego", + "webb.sprak.granssnitt.text": + "Etykiety, przyciski, statusy. Zbiór skończony, rzadko zmieniany. Angielski tekst, który trafi do niemieckiego użytkownika, to irytacja, nie zagrożenie — brakujące tłumaczenie jest więc bez komentarza zastępowane angielskim.", + "webb.sprak.metodik.rubrik": "Tekst procedur", + "webb.sprak.metodik.beteckning": "Nigdy nie wraca po cichu", + "webb.sprak.metodik.text": + "Instrukcje pracy przy pojeździe. Tu niesprawdzone tłumaczenie jest gorsze niż tekst w obcym języku — bo angielski wygląda obco, a złe tłumaczenie wygląda jak instrukcja. Taki tekst jest pokazywany po angielsku i oznaczany, z podaną nazwą języka.", + "webb.sprak.invariant": + "Sama metoda nigdy nie jest tłumaczona. Nazwy faz i słowa statusów to struktura ALVA i czyta się je identycznie w każdym kraju, więc audytor może przeczytać rumuńskie i niemieckie akta sprawy, nie wiedząc, w jakim języku pracuje warsztat.", + "webb.sprak.tackning.etikett": "Pokrycie", + "webb.sprak.tackning.rubrik": "Co jest przetłumaczone, a co sprawdzone", + "webb.sprak.kolumn.sprak": "Język", + "webb.sprak.kolumn.granssnitt": "Interfejs", + "webb.sprak.kolumn.granskat": "Metodyka sprawdzona przez specjalistę", + "webb.sprak.granskat.ja": "Tak", + "webb.sprak.granskat.nej": "Nie — tekst procedur po angielsku", + "webb.sprak.matt": + "Pokrycie interfejsu jest mierzone, nie szacowane: test przerywa kompilację, jeśli w którymkolwiek języku brakuje jakiegokolwiek klucza. Status sprawdzenia to stwierdzenie o ludziach, nie o plikach, i ustawia się go ręcznie.", + "webb.sprak.kallsprak": + "Angielski tekst procedur jest tłumaczeniem szwedzkiego źródła i nie został jeszcze przeczytany przez specjalistę pracującego w zawodzie. Obowiązuje go ten sam standard co pozostałe dziewięć języków i tutaj pada o nim to samo stwierdzenie, zamiast po cichu robić wyjątek dlatego, że jest źródłem.", + "webb.sprak.bevis.etikett": "Weryfikacja", + "webb.sprak.bevis.rubrik": "Zdania, które zatrzymują sprawę", + "webb.sprak.bevis.ingress": + "To zdania, które odmawiają technikowi zamknięcia sprawy. Odmowa, której nikt nie rozumie, to odmowa bez wyjścia — dlatego to właściwe zdania do oceny tłumaczenia.", + "webb.sprak.ej_granskad": "Niesprawdzone", + "webb.sprak.granskad": "Sprawdzone", + "webb.sprak.granskad.text": + "Tekst procedur w tym języku ({sprak}) został przeczytany przez specjalistę pracującego w zawodzie. Kroki i punkty kontrolne są w całości pokazywane w tym języku ({sprak}).", + "webb.sprak.val.etikett": "Sposób działania", + "webb.sprak.val.rubrik": "Jak wybierany jest język", + "webb.sprak.val.s1.rubrik": "Wybór użytkownika", + "webb.sprak.val.s1.text": "To, co wybrała dana osoba.", + "webb.sprak.val.s2.rubrik": "Ustawienie organizacji", + "webb.sprak.val.s2.text": "Język dokumentacji warsztatu.", + "webb.sprak.val.s3.rubrik": "Język przeglądarki", + "webb.sprak.val.s3.text": "Pierwszy język, który platforma rozpoznaje.", + "webb.sprak.val.s4.rubrik": "Angielski", + "webb.sprak.val.s4.text": "Domyślny i źródłowy.", + "webb.sprak.val.notering": + "Organizacja celowo stoi wyżej niż przeglądarka. Warsztat w Niemczech z polskim technikiem potrzebuje jednego języka dokumentacji — akta sprawy nie mogą zmieniać języka w zależności od tego, kto akurat dokonał wpisu.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/pt.mjs b/felsokning/services-backup/gemensam/sprak/pt.mjs new file mode 100644 index 0000000..8ef6ad6 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/pt.mjs @@ -0,0 +1,327 @@ +// ALVA-SPEC-060 · Português. +// +// Português europeu (pt-PT), sem o Acordo aplicado às formas em que as +// duas normas divergem de maneira visível para um técnico: «contacto», +// «registo», «conta-quilómetros». Se surgir um mercado brasileiro, é uma +// entrada nova no catálogo — «pt-BR» — e não uma emenda a esta. +// +// `granskat: false`: a interface está traduzida, o texto dos +// procedimentos não foi revisto por um especialista. Ver index.mjs. + +export const PT = { + // ---- Graus de evidência ------------------------------------------------- + "evidens.E1": "Observação", + "evidens.E2": "Fotografia", + "evidens.E3": "Vídeo com som", + "evidens.E4": "Medição, instrumento calibrado", + "evidens.E5": "Documento", + "evidens.ej_kalibrerad": "calibração em falta ou expirada", + "evidens.ej_angivet": "instrumento não indicado", + "evidens.ej_fotograferad": "introduzido, não fotografado", + + // ---- O controlo de encerramento ----------------------------------------- + "grind.objekt": "Identificação do veículo ou do objeto verificada", + "grind.historik": "Histórico do veículo verificado, ou a omissão da verificação justificada", + "grind.historik.nekad": "Uma verificação de histórico recusada exige a indicação de um motivo.", + "grind.historik.saknas": "Nenhuma verificação de histórico documentada.", + "grind.matarstallning.ingaende": "Quilometragem à entrada fotografada", + "grind.matarstallning.utgaende": "Quilometragem à saída fotografada", + "grind.matarstallning.saknas": "Nenhuma quilometragem documentada.", + "grind.matarstallning.ej_foto": + "A quilometragem está introduzida mas não fotografada. Fotografar o conta-quilómetros, ou indicar por que motivo não é possível.", + "grind.reproducering": "Verificação do sintoma: reproduzido, ou documentado como não reproduzível", + "grind.felorsak": "Análise da causa raiz documentada", + "grind.atgard": "Ação corretiva documentada, ou a sua ausência justificada", + "grind.atgard.saknas": "Não está documentada nem uma ação realizada nem um motivo para a sua ausência.", + "grind.kundbeslut": "Decisão do cliente sobre a proposta registada", + "grind.kundbeslut.avbojt": "Trabalho realizado apesar de uma proposta recusada", + "grind.kundbeslut.avbojt.detalj": "O cliente recusou a proposta, mas há trabalho documentado como realizado.", + "grind.kvalitetskontroll": "Controlo de qualidade efetuado — sintoma verificado como resolvido", + "grind.kontroller": "Pontos de controlo da metodologia: evidência, ou uma exceção documentada", + "grind.foton": "Fotografias presentes nos pontos de controlo que as exigem", + "grind.slutsats": "Conclusão (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Habilitação de alta tensão confirmada", + "grind.hogvolt.spanningslos": "Veículo colocado fora de tensão segundo o procedimento do fabricante, ausência de tensão verificada por medição", + "grind.regelpaket": "A assinatura do pacote de regras não corresponde — encerramento bloqueado.", + "grind.regelpaket.osignerat": + "Está a ser utilizado um pacote de regras externo sem assinatura — encerramento bloqueado.", + "grind.evidens": "Nível de evidência superior a E0", + "grind.evidens.saknas": "Não existe qualquer evidência no registo.", + "grind.foton.detalj": "{kontroller} pontos de controlo exigem fotografia, {foton} fotografias no registo.", + "grind.sparr.ej_uppfyllt": "O requisito de segurança não está cumprido.", + "grind.arendetyp.okant": "Requisito desconhecido no pacote de regras: {krav}", + "grind.arendetyp.krav": "Requisito para este tipo de processo: {krav}", + "grind.sakerhet": "Nível de confiança dentro do que a evidência sustenta", + "grind.sakerhet.detalj": "A confiança indicada ({niva}) excede o que a evidência sustenta ({tak}). Completar a evidência, ou baixar o nível — a incerteza honesta é informação.", + "grind.eskalering": "Escalonamentos técnicos respondidos", + "grind.eskalering.detalj": "Há um escalonamento em aberto ({referens}). Aguardar a resposta e documentá-la antes de encerrar o processo.", + + + // ---- A conclusão (ALVA-RULE-200) ---------------------------------------- + "slutsats.rubrik": "Conclusão", + "slutsats.konstaterat": "O que foi estabelecido", + "slutsats.evidens": "Que evidência o sustenta", + "slutsats.avfardat": "Que hipóteses foram afastadas, e porquê", + "slutsats.osakert": "O que permanece incerto", + "slutsats.ickesvar": "Isso não é uma conclusão. Indicar o que foi estabelecido e que evidência o sustenta.", + "slutsats.falt.motivering": "Fundamentação", + "slutsats.falt.motivering_ej": "Motivo pelo qual a causa não pôde ser estabelecida", + "slutsats.falt.uteslutet": "Alternativas afastadas", + "slutsats.falt.kvarstaende": "Incerteza remanescente", + "slutsats.falt.atgardsval": "Escolha da ação", + "slutsats.saknas": "Falta o campo «{falt}».", + "slutsats.ickesvar.falt": "{falt}: «{text}» não é uma fundamentação. Indicar o que se aplica realmente, e porquê.", + "slutsats.for_kort": "O campo «{falt}» é demasiado curto ({langd} de pelo menos {minsta} caracteres) para poder ser verificado posteriormente.", + "slutsats.utan_varfor": "{falt} indica o quê, mas não o porquê. Ligar a conclusão à evidência — o que nela faz com que isto decorra?", + "slutsats.utan_slutsats": "O processo não pode ser encerrado sem uma conclusão. Indicar por que motivo a conclusão decorre da evidência.", + "slutsats.hypotes_obemott": "A hipótese «{text}» consta do registo mas não é tratada. Indicar por que motivo foi afastada, ou por que motivo permanece em aberto.", + + + // ---- Fluxo do processo -------------------------------------------------- + "arende.nytt": "Novo processo", + "arende.oppna": "Processos abertos", + "arende.avslutade": "Processos encerrados", + "arende.avsluta": "Encerrar o processo", + "arende.avslutat": "Processo encerrado", + "arende.kan_ej_avslutas": "O processo ainda não pode ser encerrado", + "arende.hinder": "Pendente antes do encerramento", + "arende.overlamna": "Transferir", + "arende.ansvarig": "Responsável", + + // ---- Verificação prévia ------------------------------------------------- + "pre.rubrik": "Verificação prévia — antes do início do trabalho", + "pre.historik.fraga": + "O histórico do veículo foi verificado? (trabalhos anteriores, avarias recorrentes, TSB, campanhas)", + "pre.historik.ja": "Sim — verificado", + "pre.historik.nej": "Não", + "pre.historik.skal": "Motivo pelo qual o histórico não foi verificado (obrigatório)", + "pre.historik.relevant": "Trabalhos anteriores relevantes (opcional — cadeia causal)", + "pre.matarstallning": "Quilometragem", + "pre.fotografera": "Fotografar o painel de instrumentos", + "pre.felbeskrivning": "Descrição da avaria pelo cliente verificada", + "pre.observationer": "Mais alguma coisa na receção?", + + // ---- Ações -------------------------------------------------------------- + "handling.spara": "Guardar", + "handling.avbryt": "Cancelar", + "handling.fortsatt": "Continuar", + "handling.tillbaka": "Voltar", + "handling.dokumentera": "Documentar", + "handling.fotografera": "Fotografar", + "handling.spela_in": "Gravar vídeo", + "handling.undantag": "Documentar uma exceção", + "handling.undantag.skal": "Motivo da exceção (obrigatório)", + "handling.exportera": "Exportar", + "handling.skriv_ut": "Imprimir", + + // ---- Medição ------------------------------------------------------------ + "matning.varde": "Valor", + "matning.enhet": "Unidade", + "matning.matdon": "Instrumento", + "matning.matdon.valj": "Selecionar instrumento", + "matning.matdon.okant": "Instrumento desconhecido. Registá-lo antes de guardar a medição.", + "matning.kalibrerad_till": "Calibrado até", + + // ---- Relatório ---------------------------------------------------------- + "rapport.rubrik": "Relatório do processo", + "rapport.sammanfattning": "Resumo", + "rapport.evidens": "Evidência", + "rapport.atgarder": "Ações", + "rapport.harledd": + "Derivado do registo do processo. As observações e as medições são apresentadas sem conclusões que careçam de fundamento.", + + // ---- Idioma ------------------------------------------------------------- + "sprak.valj": "Idioma", + "sprak.granskat": "Revisto", + "sprak.ogranskat": "Não revisto", + "sprak.tackning": "{procent} % da interface", + + "metodik.ogranskad": + "O texto dos procedimentos não foi revisto por um especialista técnico em {sprak}. Os passos e os pontos de controlo são apresentados em inglês onde não existe tradução revista — uma tradução não revista de uma instrução de segurança é pior do que uma em língua estrangeira, porque não parece estrangeira.", + "metodik.pa_engelska": "Apresentado em inglês — sem tradução revista em {sprak}", + + // ---- Site público (webb.) ---------------------------------------------- + "webb.nav.oversikt": "Visão geral", + "webb.ansok": "Solicitar conta", + "webb.loggain": "Iniciar sessão", + "webb.fot.impressum": "Informação legal", + "webb.fot.dataskydd": "Privacidade", + "webb.fot.villkor": "Condições", + "webb.fot.tillganglighet": "Acessibilidade", + "webb.fot.sprak": "Idiomas", + "webb.fot.utgavor": "Notas de versão", + + "webb.hero.position": "Plataforma de diagnóstico guiado", + "webb.hero.definition": "Procedimentos de diagnóstico padronizados para uma deteção de avarias repetível e verificável.", + + "webb.metod.etikett": "Metodologia", + "webb.metod.rubrik": "O modelo ALVA", + "webb.metod.ingress": + "O ALVA é um método padronizado de análise, localização, verificação e ação sistemáticas. Cada decisão é rastreável, cada conclusão verificável, cada ação reproduzível.", + "webb.fas.analysis.syfte": "Reunir evidências.", + "webb.fas.analysis.avgransning": "Apenas factos. Nesta fase não se recolhem hipóteses.", + "webb.fas.localization.syfte": "Isolar a avaria.", + "webb.fas.localization.avgransning": "Estreitar a área. A causa ainda não está estabelecida.", + "webb.fas.verification.syfte": "Confirmar a causa raiz.", + "webb.fas.verification.avgransning": "Verifica-se a causa — não o sintoma.", + "webb.fas.action.syfte": "Executar a ação corretiva.", + "webb.fas.action.avgransning": "Resultado verificado e documentado. Caso contrário, a fase está incompleta.", + + "webb.drift.etikett": "Funcionamento", + "webb.drift.rubrik": "Como funciona", + "webb.drift.s1.rubrik": "Criar a organização", + "webb.drift.s1.text": "O registo é submetido e analisado.", + "webb.drift.s2.rubrik": "Os utilizadores recebem contas", + "webb.drift.s2.text": "As funções são atribuídas pelo administrador da organização.", + "webb.drift.s3.rubrik": "Procedimentos de diagnóstico guiados", + "webb.drift.s3.text": "Cada processo segue um procedimento definido.", + "webb.drift.s4.rubrik": "Verificação", + "webb.drift.s4.text": "A causa raiz é confirmada antes da ação corretiva.", + "webb.drift.s5.rubrik": "Documentação", + "webb.drift.s5.text": "O relatório é gerado a partir do registo do processo.", + "webb.drift.s6.rubrik": "Melhoria contínua", + "webb.drift.s6.text": "Resultados verificados refinam os procedimentos seguintes.", + + "webb.larande.etikett": "Capacidade", + "webb.larande.rubrik": "Aprendizagem da organização", + "webb.larande.p1": + "A plataforma refina continuamente os procedimentos de diagnóstico com a experiência operacional verificada da sua própria organização.", + "webb.larande.p2": + "Cada diagnóstico concluído alimenta a orientação seguinte. Só se usam resultados verificados — um processo encerrado sem causa raiz confirmada não contribui com nada, por conceção.", + "webb.larande.p3": + "A base de conhecimento pertence à organização. É derivada dos seus processos, dos seus procedimentos e da sua documentação, e não é agregada entre clientes.", + "webb.larande.block": "Derivada de", + "webb.larande.k1": "Causas raiz verificadas", + "webb.larande.k2": "Ações corretivas confirmadas", + "webb.larande.k3": "Categorias de avarias recorrentes", + "webb.larande.k4": "Registos de conclusão de procedimentos", + "webb.larande.k5": "Documentação da organização", + + "webb.rapport.etikett": "Relatórios", + "webb.rapport.rubrik": "Relatório trimestral de melhoria", + "webb.rapport.ingress": + "Uma vez por trimestre, a plataforma responde a seis perguntas sobre o próprio trabalho da oficina. Cada resposta é derivada do registo de processos — nada é estimado e nada é perguntado a ninguém.", + "webb.rapport.f1": "Que avarias diagnosticamos agora corretamente à primeira?", + "webb.rapport.f2": "Com que frequência uma causa suspeita se revela a verdadeira?", + "webb.rapport.f3": "Que avarias continuam a voltar na frota?", + "webb.rapport.f4": "Que passos do procedimento são saltados, e porquê?", + "webb.rapport.f5": "O que sabe a oficina agora que não sabia no trimestre passado?", + "webb.rapport.f6": "Os processos são encerrados, verificados e documentados — ou apenas encerrados?", + + "webb.pris.etikett": "Comercial", + "webb.pris.rubrik": "Licenciamento", + "webb.pris.komponent": "Componente", + "webb.pris.grund": "Base", + "webb.pris.plattform": "Licença de plataforma", + "webb.pris.plattform.grund": "Por organização, anual", + "webb.pris.anvandare": "Licenças de utilizador", + "webb.pris.anvandare.grund": "Por utilizador ativo, mensal", + "webb.pris.moduler": "Módulos empresariais", + "webb.pris.moduler.grund": "Opcionais, por módulo", + "webb.pris.betalning": "Sem pagamento online. Sem adesão a subscrição. A faturação segue-se à análise do pedido.", + "webb.pris.aktivering": "Sequência de ativação", + "webb.pris.a1": "A organização submete o registo.", + "webb.pris.a2": "O pedido é analisado.", + "webb.pris.a3": "A fatura é emitida.", + "webb.pris.a4": "O pagamento é registado.", + "webb.pris.a5": "A organização é ativada.", + "webb.pris.a6": "Os utilizadores adicionais são faturados mensalmente.", + + "webb.kallor.etikett": "Infraestrutura", + "webb.kallor.rubrik": "Infraestrutura operacional de conhecimento", + "webb.kallor.p1": + "O ALVA usa as fontes de conhecimento autorizadas da própria organização. A arquitetura é neutra quanto ao fornecedor: cada fonte implementa a mesma interface e nenhum fornecedor é pressuposto.", + "webb.kallor.p2": + "Uma instalação beta trabalha apenas com documentação interna. Fornecedores externos são ativados mais tarde através de conectores separados, sem alteração do núcleo da plataforma.", + "webb.kallor.block": "Ordem de resolução das fontes", + "webb.kallor.k1": "Procedimentos internos da empresa", + "webb.kallor.k2": "Documentação OEM", + "webb.kallor.k3": "Boletins técnicos", + "webb.kallor.k4": "Informação de garantia", + "webb.kallor.k5": "Manuais de oficina", + "webb.kallor.k6": "Diagnósticos históricos verificados", + "webb.kallor.k7": "Boas práticas da organização", + + "webb.login.etikett": "Acesso", + "webb.login.demo": + "Este início de sessão não autentica nada. Escreva qualquer coisa em E-mail e Palavra-passe e prima Entrar — não é preciso conta, e o portal por trás apresenta dados de exemplo fixos. Ligada a uma instância da plataforma, esta página autentica-se junto dela, e sem sessão válida o portal está fechado.", + "webb.login.losenord": "Palavra-passe", + "webb.login.ofullstandigt": "Autenticação incompleta. E-mail e palavra-passe são obrigatórios.", + "webb.login.misslyckades": "A autenticação falhou.", + "webb.login.logga_in": "Entrar", + "webb.login.loggar_in": "A entrar", + "webb.login.glomt": "Recuperar palavra-passe", + + "webb.ansokan.etikett": "Registo", + "webb.ansokan.demo": + "Nada é enviado. Submeter este formulário não guarda nada, não notifica ninguém e não cria nenhum pedido — ainda não há receção por trás. A descrição abaixo diz como o registo deve funcionar, não o que acontece hoje.", + "webb.ansokan.avsikt": "Funcionamento previsto: os pedidos são analisados manualmente e nesta fase não é cobrado qualquer pagamento.", + "webb.falt.foretag": "Empresa", + "webb.falt.orgnummer": "Número de registo comercial", + "webb.falt.kontakt": "Pessoa de contacto", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefone", + "webb.falt.tekniker": "Número de técnicos", + "webb.falt.bransch": "Setor", + "webb.falt.land": "País", + "webb.falt.anvandare": "Número previsto de utilizadores", + "webb.falt.noteringar": "Notas", + "webb.falt.kravs": "(obrigatório)", + "webb.ansokan.skicka": "Submeter o pedido", + "webb.ansokan.mottagen.etikett": "Pedido", + "webb.ansokan.mottagen": "Submetido", + "webb.ansokan.mottagen.demo": + "Nada foi transmitido e ninguém foi notificado. A referência abaixo foi calculada no seu navegador a partir do que escreveu e não existe em mais lado nenhum — desaparecerá quando fechar esta página.", + "webb.ansokan.status": "Estado", + "webb.ansokan.referens": "Referência", + "webb.ansokan.granskning": + "Os pedidos são analisados manualmente. A fatura é emitida após a aprovação. A organização é ativada quando o pagamento é registado.", + + "webb.sprak.etikett": "Localização", + "webb.sprak.rubrik": "Idiomas", + "webb.sprak.ingress": + "O inglês é o idioma predefinido e o idioma de origem. Seguem-se nove traduções. O que a plataforma não faz é afirmar que uma interface traduzida significa um método traduzido.", + "webb.sprak.princip.etikett": "Princípio", + "webb.sprak.princip.rubrik": "Dois tipos de texto", + "webb.sprak.granssnitt.rubrik": "Texto de interface", + "webb.sprak.granssnitt.beteckning": "Reverte em silêncio", + "webb.sprak.granssnitt.text": + "Etiquetas, botões, estados. Finito, raramente alterado. Uma cadeia de caracteres em inglês que chega a um utilizador alemão é uma irritação, não um perigo — por isso uma tradução em falta reverte para o inglês sem comentário.", + "webb.sprak.metodik.rubrik": "Texto de procedimento", + "webb.sprak.metodik.beteckning": "Nunca reverte em silêncio", + "webb.sprak.metodik.text": + "Instruções para trabalhar num veículo. Aqui uma tradução não revista é pior do que uma em língua estrangeira — porque o inglês parece estrangeiro, enquanto uma má tradução parece uma instrução. É mostrada em inglês e assinalada, com o idioma nomeado.", + "webb.sprak.invariant": + "O método em si nunca é traduzido. Os nomes das fases e as palavras de estado são a estrutura do ALVA e leem-se de forma idêntica em todos os países, para que um auditor possa ler um processo romeno e um alemão sem saber em que idioma a oficina trabalha.", + "webb.sprak.tackning.etikett": "Cobertura", + "webb.sprak.tackning.rubrik": "O que está traduzido, e o que está revisto", + "webb.sprak.kolumn.sprak": "Idioma", + "webb.sprak.kolumn.granssnitt": "Interface", + "webb.sprak.kolumn.granskat": "Método revisto por um especialista", + "webb.sprak.granskat.ja": "Sim", + "webb.sprak.granskat.nej": "Não — texto de procedimento em inglês", + "webb.sprak.matt": + "A cobertura da interface é medida, não estimada: um teste faz falhar a compilação se faltar alguma chave em algum idioma. O estado de revisão é uma afirmação sobre pessoas, não sobre ficheiros, e é definido à mão.", + "webb.sprak.kallsprak": + "O texto de procedimento em inglês é uma tradução da fonte sueca e ainda não foi lido por um especialista que exerce o ofício. É sujeito ao mesmo padrão dos outros nove idiomas, e aqui faz-se sobre ele a mesma declaração em vez de o excetuar em silêncio por ser a fonte.", + "webb.sprak.bevis.etikett": "Verificação", + "webb.sprak.bevis.rubrik": "As frases que param um processo", + "webb.sprak.bevis.ingress": + "Estas são as frases que recusam a um técnico o encerramento de um processo. Uma recusa que ninguém entende é uma recusa sem saída — por isso são as frases certas para julgar uma tradução.", + "webb.sprak.ej_granskad": "Não revisto", + "webb.sprak.granskad": "Revisto", + "webb.sprak.granskad.text": + "O texto de procedimento em {sprak} foi lido por um especialista que exerce o ofício. Os passos e os pontos de controlo são mostrados em {sprak} em toda a plataforma.", + "webb.sprak.val.etikett": "Funcionamento", + "webb.sprak.val.rubrik": "Como o idioma é escolhido", + "webb.sprak.val.s1.rubrik": "Preferência do utilizador", + "webb.sprak.val.s1.text": "O que a pessoa selecionou.", + "webb.sprak.val.s2.rubrik": "Definição da organização", + "webb.sprak.val.s2.text": "O idioma de documentação da oficina.", + "webb.sprak.val.s3.rubrik": "Idioma do navegador", + "webb.sprak.val.s3.text": "O primeiro idioma que a plataforma reconhece.", + "webb.sprak.val.s4.rubrik": "Inglês", + "webb.sprak.val.s4.text": "O predefinido, e a origem.", + "webb.sprak.val.notering": + "A organização vem deliberadamente antes do navegador. Uma oficina na Alemanha com um técnico polaco precisa de um único idioma de documentação — o processo não pode mudar de idioma conforme quem escreveu a linha.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/ro.mjs b/felsokning/services-backup/gemensam/sprak/ro.mjs new file mode 100644 index 0000000..ed51685 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/ro.mjs @@ -0,0 +1,333 @@ +// ALVA-SPEC-060 · Română. +// +// Imperativul persoanei a II-a singular este folosit consecvent, atât +// pe butoane («Salvează») cât și în instrucțiuni («Fotografiază +// kilometrajul»). Amestecul dintre singular și plural este cea mai +// frecventă inconsecvență din interfețele traduse în română, iar aici +// este evitat deliberat. +// +// Diacriticele sunt cele corecte — ș și ț cu virgulă (U+0219, U+021B), +// nu cu sedilă. Un raport tipărit cu sedile arată ca un document +// netradus. +// +// `granskat: false`: interfața este tradusă, textul procedurilor nu este +// verificat de un specialist. A se vedea index.mjs. + +export const RO = { + // ---- Grade de dovadă ---------------------------------------------------- + "evidens.E1": "Observație", + "evidens.E2": "Fotografie", + "evidens.E3": "Înregistrare video cu sunet", + "evidens.E4": "Măsurătoare, instrument etalonat", + "evidens.E5": "Document", + "evidens.ej_kalibrerad": "etalonare lipsă sau expirată", + "evidens.ej_angivet": "instrument neindicat", + "evidens.ej_fotograferad": "introdus, nefotografiat", + + // ---- Controlul de închidere --------------------------------------------- + "grind.objekt": "Identificarea vehiculului sau a obiectului verificată", + "grind.historik": "Istoricul vehiculului verificat, sau omiterea verificării justificată", + "grind.historik.nekad": "O verificare refuzată a istoricului necesită un motiv declarat.", + "grind.historik.saknas": "Nicio verificare a istoricului documentată.", + "grind.matarstallning.ingaende": "Kilometrajul la primire fotografiat", + "grind.matarstallning.utgaende": "Kilometrajul la predare fotografiat", + "grind.matarstallning.saknas": "Niciun kilometraj documentat.", + "grind.matarstallning.ej_foto": + "Kilometrajul este introdus, dar nu este fotografiat. Fotografiază odometrul sau indică de ce acest lucru nu este posibil.", + "grind.reproducering": "Verificarea simptomului: reprodus sau documentat ca nereproductibil", + "grind.felorsak": "Analiza cauzei rădăcină documentată", + "grind.atgard": "Acțiune corectivă documentată, sau absența ei justificată", + "grind.atgard.saknas": "Nu este documentată nici o acțiune efectuată, nici un motiv pentru absența acesteia.", + "grind.kundbeslut": "Decizia clientului cu privire la propunere, înregistrată", + "grind.kundbeslut.avbojt": "Lucrare efectuată în pofida unei propuneri refuzate", + "grind.kundbeslut.avbojt.detalj": + "Clientul a refuzat propunerea, dar există lucrări documentate ca fiind efectuate.", + "grind.kvalitetskontroll": "Control de calitate efectuat — simptom verificat ca rezolvat", + "grind.kontroller": "Punctele de control ale metodologiei: dovadă sau o excepție documentată", + "grind.foton": "Fotografii prezente pentru controalele care le impun", + "grind.slutsats": "Concluzie finală (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Autorizarea pentru înaltă tensiune confirmată", + "grind.hogvolt.spanningslos": "Vehicul scos de sub tensiune conform procedurii producătorului, absența tensiunii verificată prin măsurare", + "grind.regelpaket": "Semnătura pachetului de reguli nu corespunde — închiderea este blocată.", + "grind.regelpaket.osignerat": + "Se utilizează un pachet de reguli extern fără semnătură — închiderea este blocată.", + "grind.evidens": "Nivel de dovadă peste E0", + "grind.evidens.saknas": "În jurnal nu există nicio dovadă.", + "grind.foton.detalj": "Controale care necesită fotografie: {kontroller}; fotografii în jurnal: {foton}.", + "grind.sparr.ej_uppfyllt": "Cerința de siguranță nu este îndeplinită.", + "grind.arendetyp.okant": "Cerință necunoscută în pachetul de reguli: {krav}", + "grind.arendetyp.krav": "Cerință pentru acest tip de caz: {krav}", + "grind.sakerhet": "Nivel de încredere în limitele a ceea ce susțin dovezile", + "grind.sakerhet.detalj": "Încrederea indicată ({niva}) depășește ceea ce susțin dovezile ({tak}). Completează dovezile sau coboară nivelul — incertitudinea onestă este informație.", + "grind.eskalering": "Escaladări tehnice cu răspuns", + "grind.eskalering.detalj": "O escaladare este deschisă ({referens}). Așteaptă răspunsul și documentează-l înainte de închiderea cazului.", + + + // ---- Concluzia finală (ALVA-RULE-200) ----------------------------------- + "slutsats.rubrik": "Concluzie finală", + "slutsats.konstaterat": "Ce a fost stabilit", + "slutsats.evidens": "Ce dovezi susțin acest lucru", + "slutsats.avfardat": "Ce ipoteze au fost înlăturate și de ce", + "slutsats.osakert": "Ce rămâne incert", + "slutsats.ickesvar": "Acesta nu este un răspuns. Indică ce a fost stabilit și ce dovezi susțin acest lucru.", + "slutsats.falt.motivering": "Justificare", + "slutsats.falt.motivering_ej": "Motivul pentru care cauza nu a putut fi stabilită", + "slutsats.falt.uteslutet": "Alternative înlăturate", + "slutsats.falt.kvarstaende": "Incertitudine rămasă", + "slutsats.falt.atgardsval": "Alegerea acțiunii", + "slutsats.saknas": "Lipsește: {falt}.", + "slutsats.ickesvar.falt": "{falt}: „{text}” nu este o justificare. Indică ce se aplică în realitate și de ce.", + "slutsats.for_kort": "Câmpul {falt} este prea scurt pentru a putea fi verificat ulterior (caractere: {langd}, minim: {minsta}).", + "slutsats.utan_varfor": "{falt} spune ce, dar nu de ce. Leagă concluzia de dovezi — ce anume din ele face ca aceasta să decurgă?", + "slutsats.utan_slutsats": "Cazul nu poate fi închis fără o concluzie finală. Indică de ce concluzia decurge din dovezi.", + "slutsats.hypotes_obemott": "Ipoteza „{text}” se află în jurnal, dar nu este tratată. Indică de ce a fost înlăturată sau de ce rămâne deschisă.", + + + // ---- Parcursul cazului -------------------------------------------------- + "arende.nytt": "Caz nou", + "arende.oppna": "Cazuri deschise", + "arende.avslutade": "Cazuri închise", + "arende.avsluta": "Închide cazul", + "arende.avslutat": "Caz închis", + "arende.kan_ej_avslutas": "Cazul nu poate fi încă închis", + "arende.hinder": "Rămâne de rezolvat înainte de închidere", + "arende.overlamna": "Predă", + "arende.ansvarig": "Responsabil", + + // ---- Verificare prealabilă ---------------------------------------------- + "pre.rubrik": "Verificare prealabilă — înainte de începerea lucrării", + "pre.historik.fraga": + "A fost verificat istoricul vehiculului? (lucrări anterioare, defecte recurente, TSB, campanii)", + "pre.historik.ja": "Da — verificat", + "pre.historik.nej": "Nu", + "pre.historik.skal": "Motivul pentru care istoricul nu a fost verificat (obligatoriu)", + "pre.historik.relevant": "Lucrări anterioare relevante (opțional — lanț cauzal)", + "pre.matarstallning": "Kilometraj", + "pre.fotografera": "Fotografiază panoul de instrumente", + "pre.felbeskrivning": "Descrierea defectului de către client verificată", + "pre.observationer": "Altceva la primire?", + + // ---- Acțiuni ------------------------------------------------------------ + "handling.spara": "Salvează", + "handling.avbryt": "Anulează", + "handling.fortsatt": "Continuă", + "handling.tillbaka": "Înapoi", + "handling.dokumentera": "Documentează", + "handling.fotografera": "Fotografiază", + "handling.spela_in": "Înregistrează video", + "handling.undantag": "Documentează o excepție", + "handling.undantag.skal": "Motivul excepției (obligatoriu)", + "handling.exportera": "Exportă", + "handling.skriv_ut": "Tipărește", + + // ---- Măsurare ----------------------------------------------------------- + "matning.varde": "Valoare", + "matning.enhet": "Unitate", + "matning.matdon": "Instrument", + "matning.matdon.valj": "Selectează instrumentul", + "matning.matdon.okant": "Instrument necunoscut. Înregistrează-l înainte de salvarea măsurătorii.", + "matning.kalibrerad_till": "Etalonat până la", + + // ---- Raport ------------------------------------------------------------- + "rapport.rubrik": "Raportul cazului", + "rapport.sammanfattning": "Rezumat", + "rapport.evidens": "Dovezi", + "rapport.atgarder": "Acțiuni", + "rapport.harledd": + "Derivat din jurnalul cazului. Observațiile și măsurătorile sunt prezentate fără concluzii lipsite de temei.", + + // ---- Limbă -------------------------------------------------------------- + "sprak.valj": "Limbă", + "sprak.granskat": "Revizuit", + "sprak.ogranskat": "Nerevizuit", + "sprak.tackning": "{procent} % din interfață", + + "metodik.ogranskad": + "Textul procedurilor nu a fost verificat de un specialist tehnic în limba {sprak}. Pașii și punctele de control sunt afișate în engleză acolo unde nu există o traducere verificată — o traducere neverificată a unei instrucțiuni de siguranță este mai gravă decât una într-o limbă străină, pentru că nu arată ca fiind străină.", + "metodik.pa_engelska": "Afișat în engleză — nicio traducere verificată în limba {sprak}", + + // ---- Site-ul public (webb.) -------------------------------------------- + "webb.nav.oversikt": "Prezentare generală", + "webb.ansok": "Solicitați un cont", + "webb.loggain": "Autentificare", + "webb.fot.impressum": "Informații legale", + "webb.fot.dataskydd": "Confidențialitate", + "webb.fot.villkor": "Condiții", + "webb.fot.tillganglighet": "Accesibilitate", + "webb.fot.sprak": "Limbi", + "webb.fot.utgavor": "Note de versiune", + + "webb.hero.position": "Platformă de diagnosticare ghidată", + "webb.hero.definition": "Proceduri de diagnosticare standardizate pentru o depanare repetabilă și verificabilă.", + + "webb.metod.etikett": "Metodologie", + "webb.metod.rubrik": "Modelul ALVA", + "webb.metod.ingress": + "ALVA este o metodă standardizată de analiză, localizare, verificare și acțiune sistematice. Fiecare decizie este trasabilă, fiecare concluzie verificabilă, fiecare acțiune reproductibilă.", + "webb.fas.analysis.syfte": "Strânge dovezile.", + "webb.fas.analysis.avgransning": "Doar fapte. În această fază nu se adună ipoteze.", + "webb.fas.localization.syfte": "Izolează defectul.", + "webb.fas.localization.avgransning": "Restrânge zona. Cauza nu este încă stabilită.", + "webb.fas.verification.syfte": "Confirmă cauza rădăcină.", + "webb.fas.verification.avgransning": "Se verifică cauza — nu simptomul.", + "webb.fas.action.syfte": "Execută acțiunea corectivă.", + "webb.fas.action.avgransning": "Rezultat verificat și documentat. Altfel faza este incompletă.", + + "webb.drift.etikett": "Funcționare", + "webb.drift.rubrik": "Cum funcționează", + "webb.drift.s1.rubrik": "Crearea organizației", + "webb.drift.s1.text": "Înregistrarea este depusă și examinată.", + "webb.drift.s2.rubrik": "Utilizatorii primesc conturi", + "webb.drift.s2.text": "Rolurile sunt atribuite de administratorul organizației.", + "webb.drift.s3.rubrik": "Proceduri de diagnosticare ghidate", + "webb.drift.s3.text": "Fiecare caz urmează o procedură definită.", + "webb.drift.s4.rubrik": "Verificare", + "webb.drift.s4.text": "Cauza rădăcină este confirmată înaintea acțiunii corective.", + "webb.drift.s5.rubrik": "Documentare", + "webb.drift.s5.text": "Dosarul este generat din jurnalul cazului.", + "webb.drift.s6.rubrik": "Îmbunătățire continuă", + "webb.drift.s6.text": "Rezultatele verificate rafinează procedurile următoare.", + + "webb.larande.etikett": "Capacitate", + "webb.larande.rubrik": "Învățarea organizației", + "webb.larande.p1": + "Platforma rafinează continuu procedurile de diagnosticare cu experiența operațională verificată a propriei organizații.", + "webb.larande.p2": + "Fiecare diagnostic încheiat alimentează ghidarea ulterioară. Se folosesc doar rezultate verificate — un caz închis fără cauză rădăcină confirmată nu contribuie cu nimic, în mod intenționat.", + "webb.larande.p3": + "Baza de cunoștințe aparține organizației. Este derivată din cazurile, procedurile și documentația dumneavoastră și nu este agregată între clienți.", + "webb.larande.block": "Derivată din", + "webb.larande.k1": "Cauze rădăcină verificate", + "webb.larande.k2": "Acțiuni corective confirmate", + "webb.larande.k3": "Categorii de defecte recurente", + "webb.larande.k4": "Evidențe de finalizare a procedurilor", + "webb.larande.k5": "Documentația organizației", + + "webb.rapport.etikett": "Raportare", + "webb.rapport.rubrik": "Raport trimestrial de îmbunătățire", + "webb.rapport.ingress": + "O dată pe trimestru, platforma răspunde la șase întrebări despre munca atelierului însuși. Fiecare răspuns este derivat din jurnalul cazurilor — nimic nu se estimează și nimeni nu este întrebat nimic.", + "webb.rapport.f1": "Ce defecte diagnosticăm acum corect din prima?", + "webb.rapport.f2": "Cât de des o cauză bănuită se dovedește a fi cea reală?", + "webb.rapport.f3": "Ce defecte revin mereu în flotă?", + "webb.rapport.f4": "Ce pași de procedură sunt omiși, și de ce?", + "webb.rapport.f5": "Ce știe atelierul acum și nu știa trimestrul trecut?", + "webb.rapport.f6": "Cazurile sunt încheiate, verificate și documentate — sau doar încheiate?", + + "webb.pris.etikett": "Comercial", + "webb.pris.rubrik": "Licențiere", + "webb.pris.komponent": "Componentă", + "webb.pris.grund": "Bază", + "webb.pris.plattform": "Licență de platformă", + "webb.pris.plattform.grund": "Per organizație, anual", + "webb.pris.anvandare": "Licențe de utilizator", + "webb.pris.anvandare.grund": "Per utilizator activ, lunar", + "webb.pris.moduler": "Module pentru întreprinderi", + "webb.pris.moduler.grund": "Opționale, per modul", + "webb.pris.betalning": "Fără plată online. Fără înscriere la abonament. Facturarea urmează examinării cererii.", + "webb.pris.aktivering": "Secvența de activare", + "webb.pris.a1": "Organizația depune înregistrarea.", + "webb.pris.a2": "Cererea este examinată.", + "webb.pris.a3": "Se emite factura.", + "webb.pris.a4": "Plata este înregistrată.", + "webb.pris.a5": "Organizația este activată.", + "webb.pris.a6": "Utilizatorii suplimentari sunt facturați lunar.", + + "webb.kallor.etikett": "Infrastructură", + "webb.kallor.rubrik": "Infrastructură operațională de cunoștințe", + "webb.kallor.p1": + "ALVA folosește sursele de cunoștințe autorizate ale organizației. Arhitectura este neutră față de furnizor: fiecare sursă implementează aceeași interfață și niciun furnizor nu este presupus.", + "webb.kallor.p2": + "O instalare beta lucrează doar cu documentația internă. Furnizorii externi sunt activați mai târziu prin conectori separați, fără modificarea nucleului platformei.", + "webb.kallor.block": "Ordinea de rezolvare a surselor", + "webb.kallor.k1": "Proceduri interne ale firmei", + "webb.kallor.k2": "Documentație OEM", + "webb.kallor.k3": "Buletine tehnice", + "webb.kallor.k4": "Informații de garanție", + "webb.kallor.k5": "Manuale de atelier", + "webb.kallor.k6": "Diagnostice istorice verificate", + "webb.kallor.k7": "Bune practici ale organizației", + + "webb.login.etikett": "Acces", + "webb.login.demo": + "Această autentificare nu autentifică nimic. Scrieți orice în E-mail și Parolă și apăsați Conectare — nu este nevoie de cont, iar portalul din spate prezintă date de exemplu fixe. Conectată la o instanță a platformei, această pagină se autentifică față de ea, iar fără o sesiune validă portalul este închis.", + "webb.login.losenord": "Parolă", + "webb.login.ofullstandigt": "Autentificare incompletă. E-mailul și parola sunt obligatorii.", + "webb.login.misslyckades": "Autentificarea a eșuat.", + "webb.login.logga_in": "Conectare", + "webb.login.loggar_in": "Se conectează", + "webb.login.glomt": "Parolă uitată", + + "webb.ansokan.etikett": "Înregistrare", + "webb.ansokan.demo": + "Nu se trimite nimic. Trimiterea formularului nu salvează nimic, nu anunță pe nimeni și nu creează nicio cerere — în spate nu există încă niciun proces de preluare. Descrierea de mai jos arată cum ar urma să funcționeze înregistrarea, nu ce se întâmplă astăzi.", + "webb.ansokan.avsikt": "Funcționare prevăzută: cererile sunt examinate manual și în această etapă nu se percepe nicio plată.", + "webb.falt.foretag": "Firmă", + "webb.falt.orgnummer": "Număr de înregistrare", + "webb.falt.kontakt": "Persoană de contact", + "webb.falt.epost": "E-mail", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Număr de tehnicieni", + "webb.falt.bransch": "Domeniu", + "webb.falt.land": "Țară", + "webb.falt.anvandare": "Număr estimat de utilizatori", + "webb.falt.noteringar": "Observații", + "webb.falt.kravs": "(obligatoriu)", + "webb.ansokan.skicka": "Trimiteți cererea", + "webb.ansokan.mottagen.etikett": "Cerere", + "webb.ansokan.mottagen": "Depusă", + "webb.ansokan.mottagen.demo": + "Nimic nu a fost transmis și nimeni nu a fost anunțat. Referința de mai jos a fost calculată în browserul dumneavoastră din ce ați scris și nu există nicăieri altundeva — va dispărea la închiderea acestei pagini.", + "webb.ansokan.status": "Stare", + "webb.ansokan.referens": "Referință", + "webb.ansokan.granskning": + "Cererile sunt examinate manual. La aprobare se emite o factură. Organizația este activată după înregistrarea plății.", + + "webb.sprak.etikett": "Localizare", + "webb.sprak.rubrik": "Limbi", + "webb.sprak.ingress": + "Engleza este limba implicită și limba sursă. Urmează nouă traduceri. Ce nu face platforma: nu pretinde că o interfață tradusă înseamnă o metodă tradusă.", + "webb.sprak.princip.etikett": "Principiu", + "webb.sprak.princip.rubrik": "Două feluri de text", + "webb.sprak.granssnitt.rubrik": "Text de interfață", + "webb.sprak.granssnitt.beteckning": "Revine în tăcere", + "webb.sprak.granssnitt.text": + "Etichete, butoane, stări. Finit, rar schimbat. Un șir englezesc care ajunge la un utilizator german este o iritare, nu un pericol — o traducere lipsă revine deci la engleză fără comentariu.", + "webb.sprak.metodik.rubrik": "Text de procedură", + "webb.sprak.metodik.beteckning": "Nu revine niciodată în tăcere", + "webb.sprak.metodik.text": + "Instrucțiuni pentru lucrul la un vehicul. Aici o traducere neverificată este mai rea decât una într-o limbă străină — pentru că engleza pare străină, în timp ce o traducere proastă pare o instrucțiune. Este afișat în engleză și marcat, cu indicarea limbii.", + "webb.sprak.invariant": + "Metoda în sine nu se traduce niciodată. Numele fazelor și cuvintele de stare sunt structura ALVA și se citesc identic în fiecare țară, astfel încât un auditor poate citi un dosar românesc și unul german fără să știe în ce limbă lucrează atelierul.", + "webb.sprak.tackning.etikett": "Acoperire", + "webb.sprak.tackning.rubrik": "Ce este tradus, și ce este verificat", + "webb.sprak.kolumn.sprak": "Limbă", + "webb.sprak.kolumn.granssnitt": "Interfață", + "webb.sprak.kolumn.granskat": "Metodă revizuită de un specialist", + "webb.sprak.granskat.ja": "Da", + "webb.sprak.granskat.nej": "Nu — textul de procedură afișat în engleză", + "webb.sprak.matt": + "Acoperirea interfeței se măsoară, nu se estimează: un test oprește compilarea dacă vreo cheie lipsește din vreo limbă. Starea de verificare este o afirmație despre oameni, nu despre fișiere, și se stabilește manual.", + "webb.sprak.kallsprak": + "Textul de procedură în engleză este o traducere a sursei suedeze și nu a fost încă citit de un specialist care practică meseria. I se aplică același standard ca celorlalte nouă limbi, iar aici se face despre el aceeași declarație în loc să fie exceptat în tăcere pentru că este sursa.", + "webb.sprak.bevis.etikett": "Verificare", + "webb.sprak.bevis.rubrik": "Frazele care opresc un caz", + "webb.sprak.bevis.ingress": + "Acestea sunt frazele care refuză unui tehnician închiderea unui caz. Un refuz pe care nimeni nu îl înțelege este un refuz fără ieșire — deci sunt frazele potrivite pentru a judeca o traducere.", + "webb.sprak.ej_granskad": "Nerevizuit", + "webb.sprak.granskad": "Revizuit", + "webb.sprak.granskad.text": + "Textul de procedură în limba {sprak} a fost revizuit de un specialist care practică meseria. Pașii și punctele de control sunt afișate peste tot în limba {sprak}.", + "webb.sprak.val.etikett": "Funcționare", + "webb.sprak.val.rubrik": "Cum se alege limba", + "webb.sprak.val.s1.rubrik": "Preferința utilizatorului", + "webb.sprak.val.s1.text": "Ce a selectat persoana.", + "webb.sprak.val.s2.rubrik": "Setarea organizației", + "webb.sprak.val.s2.text": "Limba de documentare a atelierului.", + "webb.sprak.val.s3.rubrik": "Limba browserului", + "webb.sprak.val.s3.text": "Prima limbă pe care platforma o recunoaște.", + "webb.sprak.val.s4.rubrik": "Engleza", + "webb.sprak.val.s4.text": "Limba implicită — și limba sursă.", + "webb.sprak.val.notering": + "Organizația trece intenționat înaintea browserului. Un atelier din Germania cu un tehnician polonez are nevoie de o singură limbă de documentare — dosarul nu trebuie să schimbe limba în funcție de cine a scris rândul.", +}; diff --git a/felsokning/services-backup/gemensam/sprak/sv.mjs b/felsokning/services-backup/gemensam/sprak/sv.mjs new file mode 100644 index 0000000..5630139 --- /dev/null +++ b/felsokning/services-backup/gemensam/sprak/sv.mjs @@ -0,0 +1,325 @@ +// ALVA-SPEC-060 · Svenska. +// +// Hemmamarknadens språk och det enda utöver engelska där metodik- +// innehållet är läst av någon som kan yrket. Därför `granskat: true` i +// SPRAK — inte för att svenskan är bättre skriven än tyskan här, utan +// för att den är kontrollerad av en fackman och tyskan inte är det. +// +// Fas- och statusord saknas medvetet. De är ALVA:s struktur och skrivs +// likadant på varje språk (ALVA-SPEC-001 §2). Se index.mjs. + +export const SV = { + // ---- Evidensgrader ------------------------------------------------------ + "evidens.E1": "Observation", + "evidens.E2": "Fotografi", + "evidens.E3": "Video med ljud", + "evidens.E4": "Mätning, kalibrerat mätdon", + "evidens.E5": "Dokument", + "evidens.ej_kalibrerad": "kalibrering saknas eller har gått ut", + "evidens.ej_angivet": "mätdon ej angivet", + "evidens.ej_fotograferad": "inmatad, ej fotograferad", + + // ---- Kvalitetsgrinden --------------------------------------------------- + "grind.objekt": "Fordonets eller objektets identitet verifierad", + "grind.historik": "Fordonets historik kontrollerad, eller den uteblivna kontrollen motiverad", + "grind.historik.nekad": "En nekad historikkontroll kräver ett angivet skäl.", + "grind.historik.saknas": "Ingen historikkontroll dokumenterad.", + "grind.matarstallning.ingaende": "Ingående mätarställning fotograferad", + "grind.matarstallning.utgaende": "Utgående mätarställning fotograferad", + "grind.matarstallning.saknas": "Ingen mätarställning dokumenterad.", + "grind.matarstallning.ej_foto": + "Mätarställningen är inmatad men inte fotograferad. Fotografera mätaren, eller ange varför det inte är möjligt.", + "grind.reproducering": "Symtomverifiering: reproducerat, eller dokumenterat som ej reproducerbart", + "grind.felorsak": "Grundorsaksanalys dokumenterad", + "grind.atgard": "Åtgärd dokumenterad, eller den uteblivna åtgärden motiverad", + "grind.atgard.saknas": "Varken utförd åtgärd eller skäl till att den uteblivit är dokumenterat.", + "grind.kundbeslut": "Kundens beslut om förslaget registrerat", + "grind.kundbeslut.avbojt": "Arbete utfört trots avböjt förslag", + "grind.kundbeslut.avbojt.detalj": "Kunden avböjde förslaget, men arbete är dokumenterat som utfört.", + "grind.kvalitetskontroll": "Kvalitetskontroll utförd — symtomet verifierat avhjälpt", + "grind.kontroller": "Metodikens kontrollpunkter: evidens, eller dokumenterat undantag", + "grind.foton": "Foton finns för de kontrollpunkter som kräver det", + "grind.slutsats": "Slutsats (ALVA-RULE-200)", + "grind.hogvolt.behorighet": "Högvoltsbehörighet bekräftad", + "grind.hogvolt.spanningslos": "Fordonet spänningslöst enligt tillverkarens procedur, spänningsfrihet verifierad genom mätning", + "grind.regelpaket": "Regelpaketets signatur stämmer inte — avslut spärrat.", + "grind.regelpaket.osignerat": "Ett externt regelpaket används utan signatur — avslut spärrat.", + "grind.evidens": "Evidensnivå över E0", + "grind.evidens.saknas": "Ingen evidens av något slag finns i loggen.", + "grind.foton.detalj": "{kontroller} kontrollpunkter kräver foto, {foton} foton i loggen.", + "grind.sparr.ej_uppfyllt": "Säkerhetskravet är inte uppfyllt.", + "grind.arendetyp.okant": "Okänt krav i regelpaketet: {krav}", + "grind.arendetyp.krav": "Krav för ärendetypen: {krav}", + "grind.sakerhet": "Konfidensnivån inom vad underlaget bär", + "grind.sakerhet.detalj": "Angiven konfidens ({niva}) överstiger vad underlaget bär ({tak}). Komplettera underlaget, eller sänk nivån — ärlig osäkerhet är information.", + "grind.eskalering": "Tekniska eskaleringar besvarade", + "grind.eskalering.detalj": "En eskalering är öppen ({referens}). Invänta svaret och dokumentera det innan ärendet avslutas.", + + + // ---- Slutsatsen (ALVA-RULE-200) ----------------------------------------- + "slutsats.rubrik": "Slutsats", + "slutsats.konstaterat": "Vad som är konstaterat", + "slutsats.evidens": "Vilken evidens som bär det", + "slutsats.avfardat": "Vilka hypoteser som avfärdats, och varför", + "slutsats.osakert": "Vad som fortfarande är osäkert", + "slutsats.ickesvar": "Det är inte ett svar. Ange vad som är konstaterat och vilken evidens som bär det.", + "slutsats.falt.motivering": "Motivering", + "slutsats.falt.motivering_ej": "Skäl till att orsaken inte fastställts", + "slutsats.falt.uteslutet": "Uteslutna alternativ", + "slutsats.falt.kvarstaende": "Kvarstående osäkerhet", + "slutsats.falt.atgardsval": "Val av åtgärd", + "slutsats.saknas": "{falt} saknas.", + "slutsats.ickesvar.falt": "{falt}: ”{text}” är inget skäl. Beskriv vad som faktiskt gäller och varför.", + "slutsats.for_kort": "{falt} är för kort ({langd} av minst {minsta} tecken) för att gå att granska i efterhand.", + "slutsats.utan_varfor": "{falt} anger vad, men inte varför. Knyt slutsatsen till evidensen — vad i den gör att det här följer?", + "slutsats.utan_slutsats": "Ärendet kan inte avslutas utan en slutsats. Motivera varför slutsatsen följer av underlaget.", + "slutsats.hypotes_obemott": "Hypotesen ”{text}” finns i loggen men bemöts inte. Ange varför den avfärdades, eller varför den kvarstår.", + + + // ---- Ärendeflödet ------------------------------------------------------- + "arende.nytt": "Nytt ärende", + "arende.oppna": "Öppna ärenden", + "arende.avslutade": "Avslutade ärenden", + "arende.avsluta": "Avsluta ärendet", + "arende.avslutat": "Ärendet avslutat", + "arende.kan_ej_avslutas": "Ärendet kan ännu inte avslutas", + "arende.hinder": "Kvarstår före avslut", + "arende.overlamna": "Lämna över", + "arende.ansvarig": "Ansvarig", + + // ---- Förkontroll -------------------------------------------------------- + "pre.rubrik": "Förkontroll — innan arbetet börjar", + "pre.historik.fraga": "Är fordonets historik kontrollerad? (tidigare arbeten, återkommande fel, TSB:er, kampanjer)", + "pre.historik.ja": "Ja — kontrollerad", + "pre.historik.nej": "Nej", + "pre.historik.skal": "Skäl till att historiken inte kontrollerats (obligatoriskt)", + "pre.historik.relevant": "Relevanta tidigare arbeten (valfritt — orsakskedja)", + "pre.matarstallning": "Mätarställning", + "pre.fotografera": "Fotografera instrumentpanelen", + "pre.felbeskrivning": "Kundens felbeskrivning verifierad", + "pre.observationer": "Något annat vid mottagandet?", + + // ---- Handlingar --------------------------------------------------------- + "handling.spara": "Spara", + "handling.avbryt": "Avbryt", + "handling.fortsatt": "Fortsätt", + "handling.tillbaka": "Tillbaka", + "handling.dokumentera": "Dokumentera", + "handling.fotografera": "Fotografera", + "handling.spela_in": "Spela in video", + "handling.undantag": "Dokumentera ett undantag", + "handling.undantag.skal": "Skäl till undantaget (obligatoriskt)", + "handling.exportera": "Exportera", + "handling.skriv_ut": "Skriv ut", + + // ---- Mätning ------------------------------------------------------------ + "matning.varde": "Värde", + "matning.enhet": "Enhet", + "matning.matdon": "Mätdon", + "matning.matdon.valj": "Välj mätdon", + "matning.matdon.okant": "Okänt mätdon. Registrera det innan mätningen sparas.", + "matning.kalibrerad_till": "Kalibrerad t.o.m.", + + // ---- Rapport ------------------------------------------------------------ + "rapport.rubrik": "Ärenderapport", + "rapport.sammanfattning": "Sammanfattning", + "rapport.evidens": "Evidens", + "rapport.atgarder": "Åtgärder", + "rapport.harledd": + "Härledd ur ärendeloggen. Observationer och mätningar redovisas utan slutsatser som saknar stöd.", + + // ---- Språk -------------------------------------------------------------- + "sprak.valj": "Språk", + "sprak.granskat": "Granskat", + "sprak.ogranskat": "Ej granskat", + "sprak.tackning": "{procent} % av gränssnittet", + + "metodik.ogranskad": + "Metodiktexten är inte granskad av en fackman på {sprak}. Steg och kontrollpunkter visas på engelska där ingen granskad översättning finns — en ogranskad översättning av en säkerhetsinstruktion är sämre än en på främmande språk, därför att den inte ser främmande ut.", + "metodik.pa_engelska": "Visas på engelska — ingen granskad översättning på {sprak}", + + // ---- Publika webbplatsen (webb.) --------------------------------------- + "webb.nav.oversikt": "Översikt", + "webb.ansok": "Ansök om konto", + "webb.loggain": "Logga in", + "webb.fot.impressum": "Impressum", + "webb.fot.dataskydd": "Dataskydd", + "webb.fot.villkor": "Villkor", + "webb.fot.tillganglighet": "Tillgänglighet", + "webb.fot.sprak": "Språk", + "webb.fot.utgavor": "Versionsinformation", + + "webb.hero.position": "Guidad diagnostikplattform", + "webb.hero.definition": "Standardiserade diagnosprocedurer för repeterbar och verifierbar felsökning.", + + "webb.metod.etikett": "Metodik", + "webb.metod.rubrik": "ALVA-modellen", + "webb.metod.ingress": + "ALVA är en standardiserad metod för systematisk analys, lokalisering, verifiering och åtgärd. Varje beslut är spårbart, varje slutsats verifierbar, varje åtgärd reproducerbar.", + "webb.fas.analysis.syfte": "Samla evidens.", + "webb.fas.analysis.avgransning": "Enbart fakta. Hypoteser samlas inte i denna fas.", + "webb.fas.localization.syfte": "Ringa in felet.", + "webb.fas.localization.avgransning": "Avgränsa området. Orsaken är ännu inte fastställd.", + "webb.fas.verification.syfte": "Bekräfta grundorsaken.", + "webb.fas.verification.avgransning": "Orsaken verifieras — inte symtomet.", + "webb.fas.action.syfte": "Utför åtgärden.", + "webb.fas.action.avgransning": "Resultatet verifierat och dokumenterat. Annars är fasen inte avslutad.", + + "webb.drift.etikett": "Drift", + "webb.drift.rubrik": "Så fungerar det", + "webb.drift.s1.rubrik": "Skapa organisation", + "webb.drift.s1.text": "Registreringen skickas in och granskas.", + "webb.drift.s2.rubrik": "Användarna får konton", + "webb.drift.s2.text": "Roller tilldelas av organisationens administratör.", + "webb.drift.s3.rubrik": "Guidade diagnosprocedurer", + "webb.drift.s3.text": "Varje ärende följer en definierad procedur.", + "webb.drift.s4.rubrik": "Verifiering", + "webb.drift.s4.text": "Grundorsaken bekräftas före åtgärd.", + "webb.drift.s5.rubrik": "Dokumentation", + "webb.drift.s5.text": "Rapporten genereras ur ärendeloggen.", + "webb.drift.s6.rubrik": "Ständig förbättring", + "webb.drift.s6.text": "Verifierade utfall förfinar kommande procedurer.", + + "webb.larande.etikett": "Förmåga", + "webb.larande.rubrik": "Organisationens lärande", + "webb.larande.p1": + "Plattformen förfinar löpande diagnosprocedurerna med verifierad drifterfarenhet från den egna organisationen.", + "webb.larande.p2": + "Varje slutförd diagnos bidrar till kommande vägledning. Endast verifierade utfall används — ett ärende som stängts utan bekräftad grundorsak bidrar med ingenting, avsiktligt.", + "webb.larande.p3": + "Kunskapsbasen tillhör organisationen. Den härleds ur era ärenden, era procedurer och er dokumentation, och den aggregeras inte över kunder.", + "webb.larande.block": "Härledd ur", + "webb.larande.k1": "Verifierade grundorsaker", + "webb.larande.k2": "Bekräftade åtgärder", + "webb.larande.k3": "Återkommande felkategorier", + "webb.larande.k4": "Genomförda procedurer", + "webb.larande.k5": "Organisationens dokumentation", + + "webb.rapport.etikett": "Rapportering", + "webb.rapport.rubrik": "Kvartalsvis förbättringsrapport", + "webb.rapport.ingress": + "En gång i kvartalet besvarar plattformen sex frågor om verkstadens eget arbete. Varje svar härleds ur ärendeloggen — ingenting uppskattas, och ingen tillfrågas.", + "webb.rapport.f1": "Vilka fel diagnostiserar vi nu rätt på första försöket?", + "webb.rapport.f2": "Hur ofta visar sig en misstänkt orsak vara den verkliga?", + "webb.rapport.f3": "Vilka fel återkommer i flottan?", + "webb.rapport.f4": "Vilka procedursteg hoppas över, och varför?", + "webb.rapport.f5": "Vad vet verkstaden nu som den inte visste förra kvartalet?", + "webb.rapport.f6": "Avslutas, verifieras och dokumenteras ärendena — eller avslutas de bara?", + + "webb.pris.etikett": "Kommersiellt", + "webb.pris.rubrik": "Licensiering", + "webb.pris.komponent": "Komponent", + "webb.pris.grund": "Grund", + "webb.pris.plattform": "Plattformslicens", + "webb.pris.plattform.grund": "Per organisation, årsvis", + "webb.pris.anvandare": "Användarlicenser", + "webb.pris.anvandare.grund": "Per aktiv användare, månadsvis", + "webb.pris.moduler": "Företagsmoduler", + "webb.pris.moduler.grund": "Valfria, per modul", + "webb.pris.betalning": "Ingen onlinebetalning. Ingen prenumerationsregistrering. Fakturering följer på granskning av ansökan.", + "webb.pris.aktivering": "Aktiveringssekvens", + "webb.pris.a1": "Organisationen skickar in registreringen.", + "webb.pris.a2": "Ansökan granskas.", + "webb.pris.a3": "Faktura ställs ut.", + "webb.pris.a4": "Betalningen registreras.", + "webb.pris.a5": "Organisationen aktiveras.", + "webb.pris.a6": "Ytterligare användare faktureras månadsvis.", + + "webb.kallor.etikett": "Infrastruktur", + "webb.kallor.rubrik": "Operativ kunskapsinfrastruktur", + "webb.kallor.p1": + "ALVA använder organisationens egna auktoriserade kunskapskällor. Arkitekturen är leverantörsneutral: varje källa implementerar samma gränssnitt, och ingen leverantör förutsätts.", + "webb.kallor.p2": + "En betainstallation arbetar enbart mot intern dokumentation. Externa leverantörer aktiveras senare via separata anslutningar, utan ändring i plattformskärnan.", + "webb.kallor.block": "Källornas prioritetsordning", + "webb.kallor.k1": "Interna företagsprocedurer", + "webb.kallor.k2": "OEM-dokumentation", + "webb.kallor.k3": "Tekniska bulletiner", + "webb.kallor.k4": "Garantiinformation", + "webb.kallor.k5": "Verkstadshandböcker", + "webb.kallor.k6": "Historiska verifierade diagnoser", + "webb.kallor.k7": "Organisationens bästa praxis", + + "webb.login.etikett": "Åtkomst", + "webb.login.demo": + "Den här inloggningen autentiserar ingenting. Skriv vad som helst i E-post och Lösenord och tryck Logga in — inget konto behövs, och portalen bakom visar fasta exempeldata. Ansluten till en plattformsinstans autentiserar sidan mot den, och portalen är stängd utan giltig session.", + "webb.login.losenord": "Lösenord", + "webb.login.ofullstandigt": "Autentisering ofullständig. E-post och lösenord krävs.", + "webb.login.misslyckades": "Autentisering misslyckades.", + "webb.login.logga_in": "Logga in", + "webb.login.loggar_in": "Loggar in", + "webb.login.glomt": "Glömt lösenord", + + "webb.ansokan.etikett": "Registrering", + "webb.ansokan.demo": + "Ingenting skickas. Att skicka formuläret lagrar ingenting, meddelar ingen och skapar ingen ansökan — det finns ännu inget mottagande system bakom det. Beskrivningen nedan anger hur registreringen är avsedd att fungera, inte vad som sker i dag.", + "webb.ansokan.avsikt": "Avsedd drift: ansökningar granskas manuellt och ingen betalning tas i detta skede.", + "webb.falt.foretag": "Företag", + "webb.falt.orgnummer": "Organisationsnummer", + "webb.falt.kontakt": "Kontaktperson", + "webb.falt.epost": "E-post", + "webb.falt.telefon": "Telefon", + "webb.falt.tekniker": "Antal tekniker", + "webb.falt.bransch": "Bransch", + "webb.falt.land": "Land", + "webb.falt.anvandare": "Förväntat antal användare", + "webb.falt.noteringar": "Noteringar", + "webb.falt.kravs": "(obligatoriskt)", + "webb.ansokan.skicka": "Skicka ansökan", + "webb.ansokan.mottagen.etikett": "Ansökan", + "webb.ansokan.mottagen": "Inskickad", + "webb.ansokan.mottagen.demo": + "Ingenting skickades och ingen underrättades. Referensen nedan beräknades i er webbläsare ur det ni skrev och finns ingen annanstans — den är borta när ni stänger sidan.", + "webb.ansokan.status": "Status", + "webb.ansokan.referens": "Referens", + "webb.ansokan.granskning": + "Ansökningar granskas manuellt. Faktura ställs ut vid godkännande. Organisationen aktiveras när betalningen registrerats.", + + "webb.sprak.etikett": "Lokalisering", + "webb.sprak.rubrik": "Språk", + "webb.sprak.ingress": + "Engelska är standard och källspråk. Nio översättningar följer. Vad plattformen inte gör är att påstå att ett översatt gränssnitt betyder en översatt metod.", + "webb.sprak.princip.etikett": "Princip", + "webb.sprak.princip.rubrik": "Två sorters text", + "webb.sprak.granssnitt.rubrik": "Gränssnittstext", + "webb.sprak.granssnitt.beteckning": "Faller tillbaka tyst", + "webb.sprak.granssnitt.text": + "Etiketter, knappar, statusar. Ändlig, sällan ändrad. En engelsk sträng som når en tysk användare är en irritation, inte en fara — så en saknad översättning faller tillbaka på engelska utan kommentar.", + "webb.sprak.metodik.rubrik": "Metodiktext", + "webb.sprak.metodik.beteckning": "Faller aldrig tillbaka tyst", + "webb.sprak.metodik.text": + "Instruktioner för arbete på ett fordon. Här är en ogranskad översättning sämre än en på främmande språk — därför att engelska ser främmande ut, medan en dålig översättning ser ut som en instruktion. Den visas på engelska och markeras, med språket namngivet.", + "webb.sprak.invariant": + "Metoden själv översätts aldrig. Fasnamn och statusord är ALVA:s struktur och läses likadant i varje land, så att en revisor kan läsa en rumänsk och en tysk ärendejournal utan att veta vilket språk verkstaden arbetar på.", + "webb.sprak.tackning.etikett": "Täckning", + "webb.sprak.tackning.rubrik": "Vad som är översatt, och vad som är granskat", + "webb.sprak.kolumn.sprak": "Språk", + "webb.sprak.kolumn.granssnitt": "Gränssnitt", + "webb.sprak.kolumn.granskat": "Metodik granskad av fackman", + "webb.sprak.granskat.ja": "Ja", + "webb.sprak.granskat.nej": "Nej — metodiktext visas på engelska", + "webb.sprak.matt": + "Gränssnittstäckningen mäts, inte uppskattas: ett test fäller bygget om någon nyckel saknas i något språk. Granskningsstatus är ett påstående om människor, inte om filer, och sätts för hand.", + "webb.sprak.kallsprak": + "Den engelska metodiktexten är en översättning av den svenska källan och har ännu inte lästs av en fackman i yrket. Den hålls till samma standard som de andra nio språken, och samma besked lämnas om den här i stället för att tyst undantas för att den är källan.", + "webb.sprak.bevis.etikett": "Verifiering", + "webb.sprak.bevis.rubrik": "Strängarna som stoppar ett ärende", + "webb.sprak.bevis.ingress": + "Detta är meningarna som nekar en tekniker att avsluta ett ärende. Ett avslag ingen förstår är ett avslag utan väg igenom — så det är rätt strängar att bedöma en översättning på.", + "webb.sprak.ej_granskad": "Ej granskad", + "webb.sprak.granskad": "Granskad", + "webb.sprak.granskad.text": + "Metodiktext på {sprak} har lästs av en fackman i yrket. Steg och kontrollpunkter visas på {sprak} genomgående.", + "webb.sprak.val.etikett": "Drift", + "webb.sprak.val.rubrik": "Hur språket väljs", + "webb.sprak.val.s1.rubrik": "Användarens val", + "webb.sprak.val.s1.text": "Vad den enskilda har valt.", + "webb.sprak.val.s2.rubrik": "Organisationens inställning", + "webb.sprak.val.s2.text": "Verkstadens dokumentationsspråk.", + "webb.sprak.val.s3.rubrik": "Webbläsarens språk", + "webb.sprak.val.s3.text": "Det första språk plattformen känner igen.", + "webb.sprak.val.s4.rubrik": "Engelska", + "webb.sprak.val.s4.text": "Standarden, och källan.", + "webb.sprak.val.notering": + "Organisationen går före webbläsaren avsiktligt. En verkstad i Tyskland med en polsk tekniker behöver ett gemensamt dokumentationsspråk — journalen får inte byta språk beroende på vem som råkade skriva raden.", +}; diff --git a/felsokning/services-backup/gemensam/statistik.mjs b/felsokning/services-backup/gemensam/statistik.mjs new file mode 100644 index 0000000..ac31139 --- /dev/null +++ b/felsokning/services-backup/gemensam/statistik.mjs @@ -0,0 +1,208 @@ +// ALVA-REP-0100 · Operational metrics. +// +// Vilka siffror som visas avgör vad organisationen optimerar. Det är +// därför det här är ett produktbeslut och inte ett rapportjobb. +// +// Uteslutna med avsikt: +// +// Ärenden per tekniker Belönar snabbhet över noggrannhet. Den som +// hoppar över kontroller vinner. +// Genomsnittlig ledtid Samma sak, en gång till. Ett svårt fel +// SKA ta längre tid. +// Inloggningar, aktivitet Mäter användning, inte nytta. +// +// Medtagna, och varför var och en är handlingsbar: +// +// VERIFIERINGSGRAD Andel avslut med fastställd orsak. Sjunker den +// gissar organisationen mer. Det här är det enda +// måttet ett försäkringsbolag egentligen bryr sig +// om. +// REPRODUKTIONSGRAD Andel symptom som återskapats. Låg siffra +// förutsäger återkommande fordon — man kan inte +// åtgärda det man inte sett. +// OMARBETNING Samma fordon tillbaka med samma orsakskategori. +// Det dyraste felet i en verkstad, och det enda +// ingen mäter. +// UNDANTAGSFREKVENS Vilka kontroller hoppas över, per steg. Ett +// steg med hög frekvens är antingen fel skrivet +// eller kräver utrustning som saknas — bägge är +// åtgärdbara, ingetdera syns i dag. +// EVIDENSPROFIL Fördelning E0–E6. Visar om underlaget vilar på +// mätningar eller på observationer. +// FASFÖRDELNING Var tiden går i ALVA-termer. +// +// Alla funktioner är rena över (ärenden). Ingen databasåtkomst, ingen +// klocka — samma underlag ger samma siffra, vilket är förutsättningen +// för att en kvartalsrapport ska gå att granska i efterhand. + +import { fasFor } from "./faser.mjs"; + +const handelserI = (arende) => (arende.handelser ?? []).map((p) => p.handelse ?? p); +const av = (handelser, typ) => handelser.filter((h) => h?.typ === typ); +const arAvslutat = (arende) => handelserI(arende).some((h) => h?.typ === "arende_avslutat"); + +/** Andel med en decimal, eller null när underlaget saknas. */ +function andel(traffar, av_) { + return av_ === 0 ? null : Math.round((traffar / av_) * 1000) / 10; +} + +/** + * Verifieringsgrad — andel avslutade ärenden där orsaken fastställdes. + * + * Att den inte är 100 % är friskt. En organisation som aldrig skriver + * "orsaken kunde inte fastställas" ljuger antingen, eller får bara enkla + * fel. Talet är intressant över tid, inte som absolut nivå. + */ +export function verifieringsgrad(arenden) { + const avslutade = arenden.filter(arAvslutat); + const fastställda = avslutade.filter((a) => { + const s = av(handelserI(a), "slutsats").at(-1); + return s && s.orsakFastställd !== false; + }); + return { antal: avslutade.length, fastställda: fastställda.length, andel: andel(fastställda.length, avslutade.length) }; +} + +/** Reproduktionsgrad — andel ärenden där symptomet faktiskt återskapades. */ +export function reproduktionsgrad(arenden) { + const medSvar = arenden.filter((a) => av(handelserI(a), "reproducering").length > 0); + const fördelning = { ja: 0, delvis: 0, nej: 0 }; + for (const a of medSvar) { + const r = av(handelserI(a), "reproducering").at(-1); + if (r?.status in fördelning) fördelning[r.status] += 1; + } + return { antal: medSvar.length, ...fördelning, andel: andel(fördelning.ja, medSvar.length) }; +} + +/** + * Omarbetning — samma fordon tillbaka med samma orsakskategori. + * + * Det dyraste felet i en verkstad och det enda ingen mäter, eftersom det + * kräver att man kopplar ihop två ärenden som ser fristående ut. Här är + * kopplingen gratis: loggen bär fordonets identifierare och orsakens + * kategori. + * + * @param inomDagar fönster; 90 dagar är en rimlig utgångspunkt men + * varje verkstad har sin egen uppfattning. + */ +export function omarbetning(arenden, inomDagar = 90) { + const perFordon = new Map(); + for (const a of arenden) { + if (!arAvslutat(a)) continue; + const ident = av(handelserI(a), "objekt_identifierat").at(-1)?.objekt?.identifierare; + if (!ident) continue; + const orsak = av(handelserI(a), "felorsak").at(-1); + const avslut = (a.handelser ?? []).find((p) => (p.handelse ?? p)?.typ === "arende_avslutat")?.tidpunkt; + if (!orsak || !avslut) continue; + if (!perFordon.has(ident)) perFordon.set(ident, []); + perFordon.get(ident).push({ tid: Date.parse(avslut), kategorier: orsak.orsaker ?? [], arende: a.id }); + } + + const fall = []; + const fönster = inomDagar * 86400000; + for (const [ident, poster] of perFordon) { + poster.sort((a, b) => a.tid - b.tid); + for (let i = 1; i < poster.length; i += 1) { + const föregående = poster[i - 1]; + const nu = poster[i]; + const delad = nu.kategorier.filter((k) => föregående.kategorier.includes(k)); + if (delad.length > 0 && nu.tid - föregående.tid <= fönster) { + fall.push({ identifierare: ident, kategorier: delad, dagar: Math.round((nu.tid - föregående.tid) / 86400000) }); + } + } + } + const avslutade = arenden.filter(arAvslutat).length; + return { antal: fall.length, andel: andel(fall.length, avslutade), fall: fall.slice(0, 20) }; +} + +/** + * Undantagsfrekvens per metodiksteg. + * + * Ett steg som ofta hoppas över är antingen felskrivet eller kräver + * utrustning verkstaden inte har. Bägge går att åtgärda — men bara om + * man vet vilket steg det gäller, vilket ingen gör i dag. + */ +export function undantagsfrekvens(arenden) { + const per = new Map(); + for (const a of arenden) { + for (const h of av(handelserI(a), "kontroll_utford")) { + const nyckel = `${h.stegId}/${h.kontrollId}`; + if (!per.has(nyckel)) per.set(nyckel, { steg: h.stegId, kontroll: h.kontrollId, text: h.text, utforda: 0, undantag: 0 }); + const rad = per.get(nyckel); + rad.utforda += 1; + if ((h.undantag ?? "").trim()) rad.undantag += 1; + } + } + return [...per.values()] + .map((r) => ({ ...r, fas: fasFor(r.steg), andel: andel(r.undantag, r.utforda) })) + .filter((r) => r.undantag > 0) + .sort((a, b) => b.andel - a.andel || b.undantag - a.undantag); +} + +/** Evidensprofil — fördelning av starkaste evidenstyp per ärende. */ +export function evidensprofil(arenden) { + const nivå = { E0: 0, E1: 0, E2: 0, E3: 0, E4: 0, E5: 0, E6: 0 }; + for (const a of arenden) { + const h = handelserI(a); + const har = { + E5: av(h, "arbetsorder_skannad").length > 0, + // Ett mätvärde räknas som E4 bara med spårbart mätdon; utan det är + // det teknikerns observation av en siffra (QUALITY-AUDIT M-1). + E4: av(h, "matvarde").some((m) => m.matdonId), + E3: av(h, "video").length > 0, + E2: av(h, "foto").length > 0, + E1: av(h, "observation").length + av(h, "kontroll_utford").length > 0, + }; + const källor = ["E2", "E3", "E4", "E5"].filter((n) => har[n]).length; + const högsta = källor >= 2 ? "E6" : (["E5", "E4", "E3", "E2", "E1"].find((n) => har[n]) ?? "E0"); + nivå[högsta] += 1; + } + return nivå; +} + +/** Fasfördelning — var tiden går, i ALVA-termer. */ +export function fasfordelning(arenden) { + const per = { analysis: 0, localization: 0, verification: 0, action: 0 }; + for (const a of arenden) { + for (const h of handelserI(a)) { + if (h?.stegId) per[fasFor(h.stegId)] += 1; + } + } + const summa = Object.values(per).reduce((s, v) => s + v, 0); + return Object.fromEntries( + Object.entries(per).map(([fas, antal]) => [fas, { antal, andel: andel(antal, summa) }]), + ); +} + +/** Orsakskategorier över flottan. */ +export function orsakskategorier(arenden) { + const per = new Map(); + for (const a of arenden) { + for (const h of av(handelserI(a), "felorsak")) { + for (const k of h.orsaker ?? []) per.set(k, (per.get(k) ?? 0) + 1); + } + } + return [...per.entries()] + .map(([kategori, antal]) => ({ kategori, antal })) + .sort((a, b) => b.antal - a.antal); +} + +/** + * Hela underlaget för portalens analysvy och kvartalsrapporten. + * + * En enda funktion, så att skärmen och rapporten aldrig kan visa olika + * siffror för samma period — det är den vanligaste orsaken till att + * ingen litar på rapporten. + */ +export function oversikt(arenden, { omarbetningsfonster = 90 } = {}) { + return { + version: "ALVA-REP-0100", + antal: { totalt: arenden.length, avslutade: arenden.filter(arAvslutat).length }, + verifiering: verifieringsgrad(arenden), + reproduktion: reproduktionsgrad(arenden), + omarbetning: omarbetning(arenden, omarbetningsfonster), + undantag: undantagsfrekvens(arenden).slice(0, 10), + evidens: evidensprofil(arenden), + faser: fasfordelning(arenden), + orsaker: orsakskategorier(arenden).slice(0, 12), + }; +} diff --git a/felsokning/services-backup/gemensam/support.mjs b/felsokning/services-backup/gemensam/support.mjs new file mode 100644 index 0000000..36fcd31 --- /dev/null +++ b/felsokning/services-backup/gemensam/support.mjs @@ -0,0 +1,108 @@ +// ALVA-PROC-0050 · Felanmälan och supportärenden. +// +// ---- Varför anmälan sitter i ärendet ------------------------------------ +// +// En felanmälan som skrivs någon annanstans än där felet uppstod tappar +// det som gör den användbar. Teknikern minns inte ärendenumret, vet inte +// vilken metodik som kördes, och kan inte plattformsversionen — så +// anmälan blir "det funkar inte" och supporten får börja med att fråga +// tillbaka. Två dagar går innan någon vet vad som hände. +// +// Sammanhanget HÄRLEDS därför i stället för att skrivas in: ärende, +// metodik, plattformsversion, spår-id och vilket steg som var öppet. Det +// är samma hållning som mot sammanfattningen och fakturan — det som går +// att härleda ska inte skrivas av. +// +// ---- Status är en projektion -------------------------------------------- +// +// Anmälan är oföränderlig. Det som händer med den — svar, statusbyten — +// är egna poster, och statusen härleds ur dem. Samma skäl som för +// fakturan: en anmälan vars historia kan skrivas om är inte ett underlag +// när någon senare frågar hur länge felet var känt. + +/** ALVA-SUP-0001 */ +export const supportbeteckning = (nummer) => `ALVA-SUP-${String(nummer).padStart(4, "0")}`; + +/** + * Vad anmälan gäller. + * + * Skilt åt därför att de har olika brådska och olika mottagare. En + * felanmälan om systemet mitt i ett ärende blockerar arbete just nu; + * ett förbättringsförslag gör det inte, och att blanda dem gör att + * bägge behandlas som det senare. + */ +export const TYPER = [ + { id: "felanmalan", rubrik: "Fault report", text: "The platform did something wrong, or stopped working." }, + { id: "fraga", rubrik: "Question", text: "The methodology or the rules are unclear in this case." }, + { id: "forbattring", rubrik: "Improvement", text: "Something works, but could work better." }, +]; + +/** + * Tillstånden. Ordningen är enkelriktad — utom att en stängd anmälan kan + * öppnas igen, vilket händer när felet visar sig inte vara borta. + */ +export const STATUSAR = ["mottagen", "under_arbete", "atgardad", "stangd"]; + +/** + * Statusen härleds ur inläggen, den lagras inte. + * + * Senaste statusposten gäller. Saknas statusposter är anmälan mottagen — + * det är vad den är i det ögonblick den skapas, och ingenting behöver + * skrivas för att det ska bli sant. + */ +export function supportstatus(inlagg = []) { + const senaste = [...inlagg].filter((i) => i?.typ === "status" && STATUSAR.includes(i.status)).pop(); + return senaste?.status ?? "mottagen"; +} + +/** + * Granskar en anmälan innan den tas emot. + * + * Kraven är låga med flit. En tröskel som gör det jobbigt att anmäla ger + * färre anmälningar, inte färre fel — och de fel som inte anmäls är de + * som lever längst. Men en rubrik som "fel" och en tom beskrivning gör + * anmälan obrukbar för den som ska svara, så det finns ett golv. + */ +export function granskaAnmalan({ typ, rubrik, beskrivning } = {}) { + if (!TYPER.some((t) => t.id === typ)) return "Ange vad anmälan gäller."; + if (typeof rubrik !== "string" || rubrik.trim().length < 5) { + return "Rubriken behöver säga vad det gäller — minst fem tecken."; + } + if (rubrik.trim().length > 200) return "Rubriken är för lång (max 200 tecken)."; + if (typeof beskrivning !== "string" || beskrivning.trim().length < 20) { + return "Beskriv vad som hände och vad du väntade dig — minst tjugo tecken. Den som ska svara ser inte din skärm."; + } + if (beskrivning.length > 20_000) return "Beskrivningen är för lång (max 20 000 tecken)."; + return null; +} + +/** + * Sammanhanget, härlett ur det som redan finns. + * + * Fälten är medvetet få och tekniska. Ingenting identifierande om + * fordonet eller kunden följer med: en supportanmälan är inte ett skäl + * att flytta personuppgifter till ett annat system, och den som svarar + * behöver dem inte för att läsa ett spår. + */ +export function sammanhang({ arendeId, metodikId, steg, plattformsversion, sparId, klient } = {}) { + const ut = {}; + if (arendeId) ut.arende = String(arendeId); + if (metodikId) ut.metodik = String(metodikId); + if (steg) ut.steg = String(steg); + if (plattformsversion) ut.plattform = String(plattformsversion); + if (sparId) ut.spar = String(sparId); + if (klient) ut.klient = String(klient).slice(0, 200); + return ut; +} + +/** Fälten som ALDRIG får följa med en anmälan. Låst av test. */ +export const FORBJUDET_I_SAMMANHANG = [ + "identifierare", + "regnr", + "vin", + "agare", + "kund", + "personnummer", + "telefon", + "epost", +]; diff --git a/felsokning/services-backup/gemensam/version.mjs b/felsokning/services-backup/gemensam/version.mjs new file mode 100644 index 0000000..94f8bdd --- /dev/null +++ b/felsokning/services-backup/gemensam/version.mjs @@ -0,0 +1,201 @@ +// ALVA-SPEC-002 · Utgåvor. +// +// ---- Varför versionen är en egen modul --------------------------------- +// +// Den stod som en sträng i gränssnittskoden och räknades aldrig upp. +// Systemet påstod "ALVA 1.0" efter att kvalitetsgrinden flyttats till +// servern, schemat stängts, faktureringen byggts, portalen stängts och +// tre revisioner härdats. Versionen sa alltså ingenting om vad man fick. +// +// Historiken nedan är HÄRLEDD UR VAD SOM FAKTISKT LEVERERATS, inte +// skriven för att se ut som en produkt med lång historia. Varje post +// pekar på den commit där arbetet finns. En utgåva som inte går att peka +// på hör inte hemma i listan — en påhittad ändringslogg är samma sorts +// obefogade säkerhet som produkten finns för att undvika. +// +// ---- Vad som gör en utgåva stor ----------------------------------------- +// +// Andra siffran räcker för ny funktion. Första siffran höjs bara när +// GARANTIERNA ändras — vad systemet lovar om ett underlag. Det har hänt +// två gånger: när fakturan blev ett härlett dokument i systemet, och när +// TÜV-härdningen ändrade vad loggen garanterar. + +export const UTGAVOR = [ + { + version: "3.6", + datum: "2026-08-06", + commit: "4dea6d2", + rubrik: "Betalarspåret: beviskraven följer den som betalar", + andringar: [ + "FGS-1.0 (garantistandarden) inarbetad: betalaren är en händelse i loggen — spår, namn, claim-/skadereferens och godkännande — och grinden kräver den för de ärendetyper där någon annan än kunden betalar. Tre nya ärendetyper: vagnskadegaranti, förlängd garanti och leasing/fleet, med egna regelpaketskrav.", + "Eskaleringsspärren (DISS-mönstret): en öppnad teknisk förfrågan till tillverkare eller garantigivare utan dokumenterat svar spärrar avslutet, matchad per referens. Bulletinernas \"invänta svar\" är nu en grindregel, inte ett råd.", + "Ett verkligt fynd på vägen: serverns ärendetypskrav var en tyst no-op — servern matade in den distribuerade paketformen i en funktion som bara läste den slimmade, så garanti- och försäkringskraven fanns bara som råd i klienten. C-2-mönstret, återuppstånden genom ett format. Grinden läser nu båda formerna, med regressionstest.", + "Claim- och skadenummer kan komma ur den registrerade betalaren, inte bara ur skannad arbetsorder. Goodwill och externa garantigivare kräver dokumenterat godkännande före avslut.", + "Reservdelshändelsen: artikelnummer, serienummer, batch och markering att den demonterade delen sparas i väntan på garantibeslut. Betalare, eskalering och reservdel är interna i delningsfiltret — kunden ser åtgärden, inte förhandlingen.", + "Kartläggningen av hela FGS-1.0 mot plattformen bokförd i ALVA-DOC-0011, med ärlig gräns: ODIS/DISS/SAGA2 är tillverkarsystem — ALVA bär bevispaketet och eskaleringsspåret, den ersätter dem inte.", + ], + }, + { + version: "3.5", + datum: "2026-08-06", + commit: "8072ea6", + rubrik: "Fackgranskning av metodikinnehållet — säkerheten först, på riktigt", + andringar: [ + "Sex domängranskningar av metodikbiblioteket. Tio kritiska fynd åtgärdade: högvoltens skyddsutrustning kom SIST i säkerhetssekvensen och står nu först, spänningsfrihetsmätningen kräver kategoriserat instrument med prova–mät–prova, frånskiljaren låses mot återinkoppling, och metodiken säger nu vad som är förbjudet utan behörighet.", + "Klimatmetodiken fick sin saknade behörighetsgrind (f-gascertifikat) — samma mönster som högvolt. ADAS-provkörningen instruerade bokstavligen att provocera nödbromsingrepp i trafik; nu tillverkarens testmetod.", + "Ett klistringsfel gjorde att engelska styrningsklagomål ('pulls', 'steering') matchade högvoltsmetodiken — styrningens nyckelord låg inklistrade i högvoltens lista. Borttaget.", + "Säkerhet före ingrepp överallt: öppna aldrig varmt kylsystem, trycksänk bränslesystemet och rör aldrig högtryckssidan, deaktivera tändning och insprutning före kompressionsprov, avgasutsug vid mätning med motorn igång, ingen provkörning vid bränsle- eller bromsvätskeläckage, prova aldrig SRS-kretsar med mätare.", + "23 nya kontroller där granskningen fann diagnostiska hål (bromsvätskenivå, brutna fjädrar, drivaxlar, emissionsmätning, HVIL, vattenprov), mätvärden med referensvärden och villkor i stället för nakna uppmaningar, och felkodsavläsning i det generiska skyddsnätet.", + "Grindtexten för spänningslöshet kräver nu uttryckligen verifiering genom mätning — på alla tio språken. Protokoll för mänsklig fackgranskning per marknad: ALVA-DOC-0010. Granskat-flaggorna är oförändrade — detta är en teknisk granskning, inte marknadens.", + ], + }, + { + version: "3.4", + datum: "2026-08-06", + commit: "c1c56e6", + rubrik: "Kedjesvep, registret gäller alla vägar, och ett fynd svepet gjorde själv", + andringar: [ + "Kedjesvepet: driftens nattliga fråga \"är allt fortfarande sant?\". Varje kedja och försegling i varje organisation verifieras — på begäran av arbetsledaren, nattligen av driften, med felkod vid brott så larmet är gratis.", + "Svepet hittade en riktig bugg första gången det kördes: leverantörshändelser bröt sina egna kedjelänkar därför att den kanoniska formen behandlade undefined som null medan databasen släpper nyckeln. Kanonisk form är nu exakt den form som överlever rundresan.", + "Kalibreringsfaktumet fryses vid mottagandet: taket \"hög\" kräver ett mätdon vars kalibrering gällde när mätningen togs — inte bara ett mätdon.", + "Mätdonsregistret gäller även leverantörsprotokoll. Okänt instrument nedgraderas öppet i stället för att tros på.", + "Säkerhetshuvuden på varje API-svar, ärlig 413 för för stora kroppar, och startvarning vid oförseglad drift.", + ], + }, + { + version: "3.3", + datum: "2026-08-06", + commit: "9378089", + rubrik: "Härdning: kedjad logg, förseglat avslut, härledd säkerhetsnivå", + andringar: [ + "Varje händelse bär en hash av sitt innehåll och föregående händelses hash, beräknad av servern. Den som ändrar en rad i efterhand — även med full databasbehörighet — bryter varje efterföljande länk, och brottet pekar ut sig självt.", + "Avslut förseglas: kedjans rot och en HMAC med en nyckel som aldrig finns i databasen. En omräknad kedja kan inte förseglas om.", + "Verifieringen säger vad den bevisar och inte: innehållet är oförändrat sedan mottagandet — ingenting om tiden före, ingenting om sanningshalten.", + "Signaturen vid avslut skrivs ur verifierad token i stället för att tas emot som text. Fältet intygar nu exakt vad det kan intyga.", + "Säkerhetsnivån i felorsaksanalysen är ett tak härlett ur underlaget: hög kräver reproducerat symptom och spårbart mätvärde. Teknikern kan sänka — ärlig osäkerhet är information — men aldrig höja.", + ], + }, + { + version: "3.2", + datum: "2026-08-06", + commit: "af27275", + rubrik: "Engelska som standard, tio EU-språk, och en regel som gällde på ett", + andringar: [ + "Engelska är standard- och källspråk. Nio översättningar: tyska, franska, spanska, italienska, polska, nederländska, portugisiska, rumänska och svenska.", + "Gränssnittstext faller tillbaka på engelska tyst. Metodikinnehåll gör det aldrig tyst — en ogranskad översättning av en säkerhetsinstruktion är sämre än en engelsk, därför att engelska SYNS som ett annat språk.", + "Metodens struktur översätts inte. Fasnamn och statusord står likadant i varje land, så att en revisor kan läsa en rumänsk och en tysk rapport utan att veta vilket språk verkstaden arbetar på.", + "Kvalitetsgrindens hinder följer organisationens språk. En spärr på fel språk är en spärr utan väg förbi.", + "ALVA-RULE-200 gällde bara på svenska: en tekniker som skrev \"erledigt\" eller \"done\" i motiveringsfältet passerade filtret för icke-svar. Rättat på tio språk — det var ett hål i regeln, inte en översättningsbrist.", + ], + }, + { + version: "3.1", + datum: "2026-08-06", + commit: "a94c228", + rubrik: "Regelverk, försäkringsunderlag och support i ärendet", + andringar: [ + "Garantiregimer för EU och USA med källa och kontrolldatum per post — och skiljelinjen mellan lagstadgat och avtalat, som förväxlas åt bägge håll.", + "Försäkringsvillkor som underlag för ett samtal. Modulen avgör ingenting och kan inte avgöra något.", + "Felanmälan i varje ärende, med härlett sammanhang. Ett regnr kan inte smugglas med.", + "Mätarställningen ska vara fotograferad, inte bara inskriven — kravet fanns bara i gränssnittet och var därmed en vana, inte en spärr.", + ], + }, + { + version: "3.0", + datum: "2026-08-06", + commit: "b147694", + rubrik: "TÜV-revision och härdning av integritetsgarantierna", + stor: true, + andringar: [ + "Händelsenyckeln skopad till ärendet: en organisation kan inte längre hindra en annan från att skriva historik.", + "Mätkedjan slås upp i mätdonsregistret — E4 var tidigare ett påstående klienten gjorde om sig själv.", + "Personnycklarna kuverteras under en huvudnyckel utanför databasen.", + "Gallringen verkställs av ett eget jobb i stället för att bara vara ett datum ingen läste.", + "Åtkomstloggen och raderingsregistret är append-only.", + ], + }, + { + version: "2.1", + datum: "2026-08-06", + commit: "3b83fc9", + rubrik: "Portalen stängd på riktigt", + andringar: [ + "Inloggningen autentiserar mot plattformen och portalen är oåtkomlig utan giltig session.", + "Fakturavyn läser organisationens egna fakturor; ett misslyckat anrop visar felet, inte exemplet.", + ], + }, + { + version: "2.0", + datum: "2026-08-06", + commit: "b457543", + rubrik: "Fakturering i systemet", + stor: true, + andringar: [ + "Fakturan härleds ur organisationens tillstånd i stället för att skrivas in.", + "Utfärdad faktura är oföränderlig; betalning är en händelse och statusen en projektion.", + "Nummerserie utan luckor. Ingen betalleverantör är inblandad.", + ], + }, + { + version: "1.3", + datum: "2026-08-05", + commit: "6676262", + rubrik: "Egen typografi och läsbarhet på telefon", + andringar: [ + "Inbäddad typografi enligt ALVA-SPEC-001 — inga externa hämtningar.", + "Ritningsstämpel på rapporten, räta hörn, tätare spärrning.", + ], + }, + { + version: "1.2", + datum: "2026-08-05", + commit: "86d553b", + rubrik: "Sviten körs faktiskt", + andringar: [ + "ALVA:s tester lades in i CI. De hade passerat bara när någon körde dem för hand — ett test som ingen kör är dokumentation, inte en spärr.", + "Genomgången blev ett incheckat test.", + ], + }, + { + version: "1.1", + datum: "2026-08-05", + commit: "31727a4", + rubrik: "Leverantörsrevision 2 och härdning", + andringar: [ + "Händelseschemat stängt: okända fält avvisas i stället för att lagras.", + "Kvalitetsregeln finns på ett ställe — klienten frågar grinden i stället för att ha en egen åsikt.", + ], + }, + { + version: "1.0", + datum: "2026-08-04", + commit: "590fab3", + rubrik: "ALVA-metoden", + andringar: [ + "Analys · Lokalisering · Verifiering · Åtgärd som metod, inte som assistent.", + "Kvalitetsgrinden serverauktoritativ. Avslutssats enligt ALVA-RULE-200.", + ], + }, +]; + +/** Nuvarande utgåva. Enda stället siffran finns. */ +export const NUVARANDE = UTGAVOR[0]; + +/** Så versionen skrivs i gränssnittet och i ritningsstämpeln. */ +export const PLATTFORMSVERSION = `ALVA ${NUVARANDE.version}`; + +/** + * Versionen som gällde när ett ärende stängdes. + * + * Ritningsstämpeln ska visa vad som gällde VID AVSLUTET, inte vad som + * gäller den dag rapporten läses — det står i stämpelns egen beskrivning, + * och gällde ändå inte för versionen, som lästes ur den aktuella + * konstanten. Ett ärende stängt under 1.0 visade 3.1. + * + * Avslutshändelsen bär numera versionen. Äldre ärenden saknar den, och då + * sägs det uttryckligen i stället för att dagens siffra lånas ut. + */ +export function versionVidAvslut(avslutshandelse) { + const v = avslutshandelse?.plattformsversion; + return typeof v === "string" && v.trim() ? v : "Ej registrerad (stängt före ALVA 3.1)"; +}