feat(i18n): server-genererad prosa -> nyckel+params (min dag, ombokning, scan-diff)

- Min-dag-noten: noteKey (estimateNote/computedNote) bredvid svensk note; klient t(noteKey).
- Ombokningsskäl (planning.ts): rescheduleReasonKey + params bredvid rescheduleReasonSv;
  klient t(key, {title}). Workerns två andra ombokningstexter lämnade på svensk fallback.
- Scan-diff: reasonCode-enum (6 koder) bredvid reasonSv; klient t(scanDiff.reason.<code>).
- 8 nya nycklar i alla 12 språk. Ingen beslutslogik rörd; alla Sv-fält kvar som fallback.
- Kvar som egna spår: recept-säkerhetsmeddelanden + substitutions-data (översättningstabell).
- typecheck grönt (20/20), vakt grön, inventory-engine 53/53 tester.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 20:36:11 +00:00
parent 18388bb31c
commit 316ba3cb62
19 changed files with 174 additions and 22 deletions
+22 -1
View File
@@ -21,6 +21,19 @@ export interface NewScanObservation {
export type ScanDiffRowKind = "new_item" | "moved" | "quantity_changed" | "vanished" | "unchanged";
/**
* Stabil, översättbar kod för varje distinkt motivering. Klienten översätter
* via i18n-nyckeln `scanDiff.reason.<code>`; `reason` (svenska) är fallback.
* En kod per unikt meddelande finare än `kind` (t.ex. flyttad vs flyttad+mängd).
*/
export type ScanDiffReasonCode =
| "moved_and_qty"
| "moved"
| "qty_changed"
| "unchanged"
| "new_item"
| "vanished";
export interface ScanDiffRow {
kind: ScanDiffRowKind;
previousItemId?: string;
@@ -32,8 +45,10 @@ export interface ScanDiffRow {
newLocationId?: string;
/** Konfidens för slutsatsen (01). */
confidence: number;
/** Mänsklig läsbar motivering. */
/** Mänsklig läsbar motivering (svenska) fallback när klienten saknar översättning. */
reason: string;
/** Stabil kod för klientöversättning (`scanDiff.reason.<code>`). */
reasonCode: ScanDiffReasonCode;
}
export interface ScanDiffResult {
@@ -104,6 +119,7 @@ export function diffScans(
newLocationId: obs.storageLocationId,
confidence: round2(bestMatch.score * obs.observationConfidence),
reason: "Flyttad och ändrad mängd",
reasonCode: "moved_and_qty",
});
} else if (locationChanged) {
rows.push({
@@ -117,6 +133,7 @@ export function diffScans(
newLocationId: obs.storageLocationId,
confidence: round2(bestMatch.score * obs.observationConfidence),
reason: "Flyttad till annan plats",
reasonCode: "moved",
});
} else if (quantityChanged) {
rows.push({
@@ -130,6 +147,7 @@ export function diffScans(
newLocationId: obs.storageLocationId,
confidence: round2(bestMatch.score * obs.observationConfidence),
reason: "Ändrad mängd",
reasonCode: "qty_changed",
});
} else {
rows.push({
@@ -143,6 +161,7 @@ export function diffScans(
newLocationId: obs.storageLocationId,
confidence: round2(bestMatch.score * obs.observationConfidence),
reason: "Oförändrad",
reasonCode: "unchanged",
});
}
}
@@ -160,6 +179,7 @@ export function diffScans(
newLocationId: obs.storageLocationId,
confidence: round2(obs.observationConfidence),
reason: "Ny vara på bilden",
reasonCode: "new_item",
});
}
@@ -176,6 +196,7 @@ export function diffScans(
previousLocationId: item.storageLocationId,
confidence: 0.6,
reason: "Saknas på nya bilden — bekräfta att den är slut",
reasonCode: "vanished",
});
pendingVanishedIds.push(item.id);
}