generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL_PANEL") } model User { id String @id name String email String @unique emailVerified Boolean @default(false) image String? twoFactorEnabled Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sessions Session[] accounts Account[] twoFactors TwoFactor[] } model Session { id String @id userId String token String @unique expiresAt DateTime ipAddress String? userAgent String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model Account { id String @id userId String accountId String providerId String accessToken String? refreshToken String? accessTokenExpiresAt DateTime? refreshTokenExpiresAt DateTime? scope String? idToken String? password String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model Verification { id String @id identifier String value String expiresAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TwoFactor { id String @id userId String secret String backupCodes String user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model Project { id String @id @default(cuid()) key String @unique name String description String? /// planned | wired | active status String @default("planned") active Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Event { id String @id @default(cuid()) streamId String @unique projectKey String eventType String version Int @default(1) payload Json occurredAt DateTime receivedAt DateTime @default(now()) @@index([projectKey, occurredAt]) @@index([eventType]) } model AuditLog { id String @id @default(cuid()) actorUserId String? projectKey String? endpoint String method String requestHash String? responseStatus Int? durationMs Int? sourceIp String? userAgent String? createdAt DateTime @default(now()) @@index([createdAt]) @@index([actorUserId]) @@index([projectKey]) } // ---------- Phase 6a: Behavioral Insight Pipeline ---------- model SessionMeta { id String @id projectKey String userIdHash String? posthogDistinctId String? isAuthenticated Boolean @default(false) subscriptionTier String? groupKey String? startedAt DateTime durationMs Int pageviewCount Int @default(0) clickCount Int @default(0) errorCount Int @default(0) rageClickCount Int @default(0) deadClickCount Int @default(0) network5xxCount Int @default(0) network4xxCount Int @default(0) startUrl String? promotionReasons String[] tags String[] severity String? score Int? customEventCount Int @default(0) rawMetadataUrl String? status String @default("pending_signal") fingerprint String? processedAt DateTime? createdAt DateTime @default(now()) compressed CompressedSession? @@index([status, createdAt]) @@index([fingerprint]) @@index([projectKey, status]) @@index([userIdHash, startedAt]) @@map("sessions_meta") } model CompressedSession { sessionId String @id fingerprint String semanticTimelineMinioKey String tokenCountInput Int tokenCountEstOutput Int compressionRatio Float? sanitizationMatchCount Int @default(0) sanitizationBreakdown Json? isBundle Boolean @default(false) bundleSessionIds String[] readyForAnalysisAt DateTime @default(now()) createdAt DateTime @default(now()) session SessionMeta @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@index([fingerprint]) @@map("compressed_sessions") } model IngestionWatermark { projectKey String @id lastPolledAt DateTime posthogCursor String? updatedAt DateTime @updatedAt @@map("ingestion_watermarks") } // PostHog identify() person properties cache (24h TTL). model PosthogPersonCache { distinctId String @id properties Json groups Json? refreshedAt DateTime ttlAt DateTime @@index([ttlAt]) @@map("posthog_person_cache") } // PostHog group analytics (e.g. company) cache (24h TTL). model PosthogGroupCache { groupType String groupKey String properties Json refreshedAt DateTime ttlAt DateTime @@id([groupType, groupKey]) @@index([ttlAt]) @@map("posthog_group_cache") } // Per-session custom events fetched from PostHog (joined by $session_id). // Stored so compression can interleave them with rrweb timeline. model SessionCustomEvent { id BigInt @id @default(autoincrement()) sessionId String eventName String timestamp DateTime properties Json @@index([sessionId, timestamp]) @@map("session_custom_events") } // ---------- Phase 6b: LLM Analysis ---------- model PromptTemplate { id String @id @default(cuid()) tag String version Int name String systemPrompt String @db.Text userPromptTemplate String @db.Text outputSchemaJson Json modelTier String // "flash" | "pro" maxOutputTokens Int @default(1500) temperature Float @default(0.3) active Boolean @default(true) performanceStats Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([tag, version]) @@map("prompt_templates") } model Insight { id String @id @default(cuid()) projectKey String type String severity String status String @default("new") fingerprint String title String body Json relatedSessionIds String[] occurrenceCount Int @default(1) uniqueUserCount Int @default(1) firstSeenAt DateTime lastSeenAt DateTime confidence Float? priorityScore Int @default(0) sourcePromptTag String sourcePromptVersion Int sourceModel String sourceCostUsd Float githubIssueUrl String? githubIssueId BigInt? githubIssueNumber Int? githubIssueState String? shippedAt DateTime? validationStartedAt DateTime? validationPeriodDays Int @default(14) regressionDetected Boolean @default(false) validatedAt DateTime? founderNotes String? @db.Text founderSeverityOverride String? founderPriority Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([projectKey, fingerprint]) @@index([status, projectKey]) @@index([severity, status]) @@index([lastSeenAt(sort: Desc)]) @@index([priorityScore(sort: Desc)]) @@map("insights") } model CostLedger { id String @id @default(cuid()) insightId String? sessionId String? projectKey String promptTag String? promptVersion Int? provider String // "deepseek" | "anthropic" | "openrouter" model String tier String // "flash" | "pro" tokensInputCacheMiss Int tokensInputCacheHit Int tokensOutput Int costInputCacheMissUsd Float costInputCacheHitUsd Float costOutputUsd Float costTotalUsd Float cacheHitRatio Float callDurationMs Int? wasFallback Boolean @default(false) wasRetry Boolean @default(false) errorCode String? createdAt DateTime @default(now()) @@index([createdAt]) @@index([projectKey, createdAt]) @@index([insightId]) @@map("cost_ledger") } model BudgetSetting { id String @id @default(cuid()) projectKey String? // null = global settingKey String settingValue Json updatedAt DateTime @updatedAt @@unique([projectKey, settingKey]) @@map("budget_settings") } // ---------- Phase 6e: Eval framework ---------- model EvalSet { id String @id @default(cuid()) promptTag String name String description String? cases Json // [{ id, timeline, expected: {...}, rubric: {severity, hypothesis, ...} }] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt runs EvalRun[] @@index([promptTag]) @@map("eval_sets") } model EvalRun { id String @id @default(cuid()) evalSetId String promptTag String promptVersion Int model String totalCases Int passedSchema Int // JSON parse + schema validate succeeded passedSeverity Int // severity matched expected passedRubric Int // overall rubric pass (subjective fields) totalCostUsd Float totalDurationMs Int avgInputTokens Int avgOutputTokens Int results Json // per-case: { caseId, ok, errors[], output } createdAt DateTime @default(now()) evalSet EvalSet @relation(fields: [evalSetId], references: [id], onDelete: Cascade) @@index([evalSetId, createdAt]) @@index([promptTag, promptVersion]) @@map("eval_runs") }