-- ───────────────────────────────────────────────────────────────────────── -- ADDITIVE MIGRATION — adds analytics tables + indices on hot filter columns. -- Nothing in this file modifies or drops existing data. Safe to `migrate -- deploy` in production. Idempotent: every CREATE uses IF NOT EXISTS. -- ───────────────────────────────────────────────────────────────────────── -- ── Indices on existing tables (speed up isActive/category filters) ────── CREATE INDEX IF NOT EXISTS "GlobalNode_isActive_idx" ON "GlobalNode" ("isActive"); CREATE INDEX IF NOT EXISTS "GlobalNode_nodeType_idx" ON "GlobalNode" ("nodeType"); CREATE INDEX IF NOT EXISTS "GlobalNode_nodeType_isActive_idx" ON "GlobalNode" ("nodeType", "isActive"); CREATE INDEX IF NOT EXISTS "Application_isActive_idx" ON "Application" ("isActive"); CREATE INDEX IF NOT EXISTS "Application_category_idx" ON "Application" ("category"); CREATE INDEX IF NOT EXISTS "NewsArticle_isActive_idx" ON "NewsArticle" ("isActive"); CREATE INDEX IF NOT EXISTS "NewsArticle_isActive_publishedAt_idx" ON "NewsArticle" ("isActive", "publishedAt" DESC); CREATE INDEX IF NOT EXISTS "SparePart_isActive_idx" ON "SparePart" ("isActive"); -- ── FluxAI telemetry ────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS "AiConversation" ( "id" TEXT NOT NULL, "sessionId" TEXT NOT NULL, "visitorIp" TEXT, "userAgent" TEXT, "locale" TEXT, "pageUrl" TEXT, "industryLabel" TEXT, "funnelStage" TEXT NOT NULL DEFAULT 'DISCOVERY', "outcome" TEXT NOT NULL DEFAULT 'OPEN', "messageCount" INTEGER NOT NULL DEFAULT 0, "toolCallCount" INTEGER NOT NULL DEFAULT 0, "estimatedSavingsPercent" DOUBLE PRECISION, "productionVolume" TEXT, "signalId" TEXT, "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "lastMessageAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "closedAt" TIMESTAMP(3), CONSTRAINT "AiConversation_pkey" PRIMARY KEY ("id") ); CREATE UNIQUE INDEX IF NOT EXISTS "AiConversation_sessionId_key" ON "AiConversation" ("sessionId"); CREATE INDEX IF NOT EXISTS "AiConversation_funnelStage_idx" ON "AiConversation" ("funnelStage"); CREATE INDEX IF NOT EXISTS "AiConversation_outcome_idx" ON "AiConversation" ("outcome"); CREATE INDEX IF NOT EXISTS "AiConversation_startedAt_idx" ON "AiConversation" ("startedAt" DESC); CREATE INDEX IF NOT EXISTS "AiConversation_industryLabel_idx" ON "AiConversation" ("industryLabel"); CREATE TABLE IF NOT EXISTS "AiEvent" ( "id" TEXT NOT NULL, "conversationId" TEXT NOT NULL, "type" TEXT NOT NULL, "payloadJson" TEXT NOT NULL, "toolName" TEXT, "latencyMs" INTEGER, "tokensIn" INTEGER, "tokensOut" INTEGER, "cachedTokens" INTEGER, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "AiEvent_pkey" PRIMARY KEY ("id") ); CREATE INDEX IF NOT EXISTS "AiEvent_conversationId_createdAt_idx" ON "AiEvent" ("conversationId", "createdAt"); CREATE INDEX IF NOT EXISTS "AiEvent_type_idx" ON "AiEvent" ("type"); CREATE INDEX IF NOT EXISTS "AiEvent_toolName_idx" ON "AiEvent" ("toolName"); -- ── Foreign keys (added separately so missing references don't break load) ── DO $$ BEGIN ALTER TABLE "AiConversation" ADD CONSTRAINT "AiConversation_signalId_fkey" FOREIGN KEY ("signalId") REFERENCES "OperationsSignal"("id") ON DELETE SET NULL ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN ALTER TABLE "AiEvent" ADD CONSTRAINT "AiEvent_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "AiConversation"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;