Files
craftvia/src/lib/lotse/content.ts
T
msolarczekandClaude Opus 5 ff5c57f276 L9 Lotse – KI-Assistent: Transkription, Berichtsentwurf, Vollständigkeitsprüfung, Freigabeprinzip (Services)
- OpenAI-kompatible Transkription + Processor transcription (done/failed/disabled, AiGeneration, Notiz aus Sprachnotiz)
- Claude-Lotse (strukturierte Ausgabe, Refusal/Fallback), Datenminimierung, Vorschläge in content.lotse
- Vollständigkeitsprüfung (Regeln + KI-Hinweise mit Deep-Link), Einstellungen, KI-Protokoll
- Freigabeprinzip: Submit eines Lotse-Entwurfs nur mit Prüfbestätigung (serverseitig)
- Migration lotse_address_form (TenantSettings)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:19:38 +02:00

51 lines
2.2 KiB
TypeScript

import { z } from "zod";
/**
* Lotse block of the report snapshot (client-safe, lane L9). Stored as optional `content.lotse` of
* `ReportContent` (src/lib/reports/content.ts), so it is frozen together with the report on approval.
*
* Suggestions are kept SEPARATE from `content.texts`: nothing the model wrote reaches the report text
* until a person accepts it (Spec §15.3/§15.4, Brandbook §12.4). `reviewedAt` is set on submit when
* the submitter confirmed "Ich habe den Vorschlag geprüft" (checked server-side in submitReport).
*/
/** Report text fields the Lotse drafts (ReportDraftOutput); `problems` stays technician-only. */
export const LOTSE_TEXT_FIELDS = ["workPerformed", "deviations", "additionalWork", "openItems", "nextSteps", "hints"] as const;
export type LotseTextField = (typeof LOTSE_TEXT_FIELDS)[number];
export const LOTSE_SUGGESTION_STATES = ["pending", "accepted", "discarded"] as const;
export type LotseSuggestionState = (typeof LOTSE_SUGGESTION_STATES)[number];
export const LOTSE_TEXT_MAX = 10_000;
export const LOTSE_MISSING_MAX = 20;
const isoDateTime = z.string().datetime({ offset: true });
export const lotseSuggestionSchema = z.object({
field: z.enum(LOTSE_TEXT_FIELDS),
text: z.string().max(LOTSE_TEXT_MAX),
state: z.enum(LOTSE_SUGGESTION_STATES),
decidedAt: isoDateTime.nullable(),
});
export type LotseSuggestion = z.infer<typeof lotseSuggestionSchema>;
export const lotseBlockSchema = z.object({
generationId: z.string(),
model: z.string(),
draftedAt: isoDateTime,
draftedById: z.string().nullable(),
suggestions: z.array(lotseSuggestionSchema),
/** plain-language hints of what the model could not find in the data */
missingInformation: z.array(z.string().max(500)).max(LOTSE_MISSING_MAX),
reviewedAt: isoDateTime.nullable(),
reviewedById: z.string().nullable(),
});
export type LotseBlock = z.infer<typeof lotseBlockSchema>;
export const LOTSE_ADDRESS_FORMS = ["sie", "du"] as const;
export type LotseAddressForm = (typeof LOTSE_ADDRESS_FORMS)[number];
export function pendingSuggestions(block: LotseBlock | null | undefined): LotseSuggestion[] {
return block ? block.suggestions.filter((s) => s.state === "pending") : [];
}