L16 Lotse-Chat für Monteure: Datenmodell, Provider mit Tool-Schleife, Vorschläge und Bestätigen über bestehende Services
Chatverlauf (LotseConversation/-Message) und Aktionskarten (LotseActionProposal) als Mandantentabellen mit RLS; Schalter lotseChatEnabled. Tool-Use-Schleife mit Runden- und Tokengrenze, Datenminimierung per Platzhalter, Auftragszuordnung im Sichtbarkeits-Scope, Bestätigen/Verwerfen/Alle bestätigen mit Hash, Ablauf und Idempotenz, Transkriptions-API. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
-- L16 Lotse-Chat für Monteure: Chatverlauf, Aktionsvorschläge, Schalter je Mandant
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LotseMessageRole" AS ENUM ('user', 'assistant', 'tool');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LotseProposalStatus" AS ENUM ('proposed', 'confirmed', 'discarded', 'expired', 'failed');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "tenant_settings" ADD COLUMN "lotse_chat_enabled" BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "lotse_conversations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"work_order_id" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"last_message_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "lotse_conversations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "lotse_messages" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"conversation_id" TEXT NOT NULL,
|
||||
"role" "LotseMessageRole" NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"content" JSONB,
|
||||
"ai_generation_id" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "lotse_messages_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "lotse_action_proposals" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"conversation_id" TEXT NOT NULL,
|
||||
"message_id" TEXT,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"work_order_id" TEXT,
|
||||
"kind" TEXT NOT NULL,
|
||||
"payload" JSONB NOT NULL,
|
||||
"payload_hash" TEXT NOT NULL,
|
||||
"status" "LotseProposalStatus" NOT NULL DEFAULT 'proposed',
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"result" JSONB,
|
||||
"error_code" TEXT,
|
||||
"confirmed_at" TIMESTAMP(3),
|
||||
"confirmed_by_id" TEXT,
|
||||
"decided_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "lotse_action_proposals_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "lotse_conversations_tenant_id_user_id_last_message_at_idx" ON "lotse_conversations"("tenant_id", "user_id", "last_message_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "lotse_conversations_tenant_id_last_message_at_idx" ON "lotse_conversations"("tenant_id", "last_message_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "lotse_messages_tenant_id_conversation_id_created_at_idx" ON "lotse_messages"("tenant_id", "conversation_id", "created_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "lotse_action_proposals_tenant_id_conversation_id_created_at_idx" ON "lotse_action_proposals"("tenant_id", "conversation_id", "created_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "lotse_action_proposals_tenant_id_user_id_status_idx" ON "lotse_action_proposals"("tenant_id", "user_id", "status");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "lotse_messages" ADD CONSTRAINT "lotse_messages_conversation_id_fkey" FOREIGN KEY ("conversation_id") REFERENCES "lotse_conversations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "lotse_action_proposals" ADD CONSTRAINT "lotse_action_proposals_conversation_id_fkey" FOREIGN KEY ("conversation_id") REFERENCES "lotse_conversations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- L16: Mandantentrennung (RLS) für die neuen Tabellen
|
||||
SELECT enable_tenant_rls('lotse_conversations');
|
||||
SELECT enable_tenant_rls('lotse_messages');
|
||||
SELECT enable_tenant_rls('lotse_action_proposals');
|
||||
@@ -79,6 +79,8 @@ model TenantSettings {
|
||||
lotseAddressForm String? @map("lotse_address_form")
|
||||
// L10b (Spec §31): monthly AI token budget (input + output) per tenant; null = env AI_MONTHLY_TOKEN_LIMIT, 0 = unlimited
|
||||
aiMonthlyTokenLimit Int? @map("ai_monthly_token_limit")
|
||||
// L16 Lotse-Chat für Monteure: switch per tenant (effective only while the Lotse module is on)
|
||||
lotseChatEnabled Boolean @default(true) @map("lotse_chat_enabled")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@ -1433,3 +1435,85 @@ model BillingRecord {
|
||||
@@index([tenantId, workOrderId])
|
||||
@@map("billing_records")
|
||||
}
|
||||
|
||||
// ---------- L16 Lotse-Chat für Monteure ----------
|
||||
|
||||
enum LotseMessageRole {
|
||||
user
|
||||
assistant
|
||||
tool
|
||||
}
|
||||
|
||||
enum LotseProposalStatus {
|
||||
proposed
|
||||
confirmed
|
||||
discarded
|
||||
expired
|
||||
failed
|
||||
}
|
||||
|
||||
/// Chat of ONE user with the Lotse (only visible to that user). Optional work order context
|
||||
/// (chat opened from the order detail). Deleted by the Lotse retention (services/lotse/retention.ts).
|
||||
model LotseConversation {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
userId String @map("user_id")
|
||||
workOrderId String? @map("work_order_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
lastMessageAt DateTime @default(now()) @map("last_message_at")
|
||||
|
||||
messages LotseMessage[]
|
||||
proposals LotseActionProposal[]
|
||||
|
||||
@@index([tenantId, userId, lastMessageAt])
|
||||
@@index([tenantId, lastMessageAt])
|
||||
@@map("lotse_conversations")
|
||||
}
|
||||
|
||||
/// One chat message. `text` = what the user sees; `content` = structured parts (chips, proposal ids,
|
||||
/// links, placeholder text sent to the model).
|
||||
model LotseMessage {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
conversationId String @map("conversation_id")
|
||||
role LotseMessageRole
|
||||
text String
|
||||
content Json?
|
||||
aiGenerationId String? @map("ai_generation_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
conversation LotseConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([tenantId, conversationId, createdAt])
|
||||
@@map("lotse_messages")
|
||||
}
|
||||
|
||||
/// Action card proposed by the Lotse. Nothing is executed before the owner confirms it
|
||||
/// (services/lotse/chat/confirm.ts runs the existing services with the owner's context).
|
||||
model LotseActionProposal {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
conversationId String @map("conversation_id")
|
||||
messageId String? @map("message_id")
|
||||
userId String @map("user_id")
|
||||
workOrderId String? @map("work_order_id")
|
||||
kind String
|
||||
payload Json
|
||||
payloadHash String @map("payload_hash")
|
||||
status LotseProposalStatus @default(proposed)
|
||||
expiresAt DateTime @map("expires_at")
|
||||
result Json?
|
||||
errorCode String? @map("error_code")
|
||||
confirmedAt DateTime? @map("confirmed_at")
|
||||
confirmedById String? @map("confirmed_by_id")
|
||||
decidedAt DateTime? @map("decided_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
conversation LotseConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([tenantId, conversationId, createdAt])
|
||||
@@index([tenantId, userId, status])
|
||||
@@map("lotse_action_proposals")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user