import { pgTable, text, integer, real, index, uniqueIndex, jsonb, boolean } from 'drizzle-orm/pg-core'; import { createdAt, ts, jsonObject } from './_common.js'; /** * Scanner sessions (§114): what the user submitted, what the model guessed, which candidates we * showed and what the user picked. The pick is the feedback loop for identification quality. */ export const scannerSessions = pgTable( 'scanner_sessions', { id: text('id').primaryKey(), userId: text('user_id'), anonId: text('anon_id'), ipHash: text('ip_hash'), mode: text('mode').notNull(), // photo | url | text inputUrl: text('input_url'), inputText: text('input_text'), imageCount: integer('image_count').notNull().default(0), /** stored thumbnails (data URLs, ≤ 3 × ~40 KB) for review; never the originals */ thumbnails: jsonb('thumbnails').$type().notNull().default([]), guess: jsonObject>('guess'), guessConfidence: real('guess_confidence'), candidates: jsonb('candidates').$type>().notNull().default([]), chosenAssetId: text('chosen_asset_id'), /** matched listing (url mode) */ listing: jsonObject>('listing'), model: text('model'), usdEst: real('usd_est').notNull().default(0), durationMs: integer('duration_ms'), error: text('error'), createdAt: createdAt(), chosenAt: ts('chosen_at'), }, (t) => [index('scanner_sessions_created_idx').on(t.createdAt), index('scanner_sessions_ip_idx').on(t.ipHash, t.createdAt), index('scanner_sessions_user_idx').on(t.userId)], ); /** AI Research conversations (§128). One session per anonymous cookie/user thread. */ export const researchSessions = pgTable( 'research_sessions', { id: text('id').primaryKey(), userId: text('user_id'), anonId: text('anon_id'), title: text('title'), model: text('model'), messageCount: integer('message_count').notNull().default(0), usdEst: real('usd_est').notNull().default(0), archived: boolean('archived').notNull().default(false), createdAt: createdAt(), updatedAt: ts('updated_at').notNull().defaultNow(), }, (t) => [index('research_sessions_anon_idx').on(t.anonId, t.updatedAt), index('research_sessions_user_idx').on(t.userId, t.updatedAt)], ); export const researchMessages = pgTable( 'research_messages', { id: text('id').primaryKey(), sessionId: text('session_id').notNull(), role: text('role').notNull(), // user | assistant content: text('content').notNull(), /** transparent tool trace: [{name, input, ok, ms, rows}] */ toolCalls: jsonb('tool_calls').$type>().notNull().default([]), usage: jsonObject>('usage'), usdEst: real('usd_est').notNull().default(0), model: text('model'), createdAt: createdAt(), }, (t) => [index('research_messages_session_idx').on(t.sessionId, t.createdAt)], ); /** Per-key daily quota for anonymous AI features (scanner/research) keyed by ip hash or user. */ export const aiQuotas = pgTable( 'ai_quotas', { key: text('key').notNull(), feature: text('feature').notNull(), date: text('date').notNull(), count: integer('count').notNull().default(0), }, (t) => [uniqueIndex('ai_quotas_uq').on(t.key, t.feature, t.date)], );