Add search and RAG workspace flows

This commit is contained in:
2026-03-07 20:34:00 -05:00
parent db01f207a5
commit e20aba998b
35 changed files with 3417 additions and 372 deletions

View File

@@ -1,3 +1,4 @@
import { sql } from 'drizzle-orm';
import {
index,
integer,
@@ -31,6 +32,9 @@ type CoverageStatus = 'backlog' | 'active' | 'watch' | 'archive';
type CoveragePriority = 'low' | 'medium' | 'high';
type ResearchJournalEntryType = 'note' | 'filing_note' | 'status_change';
type FinancialCadence = 'annual' | 'quarterly' | 'ltm';
type SearchDocumentScope = 'global' | 'user';
type SearchDocumentSourceKind = 'filing_document' | 'filing_brief' | 'research_note';
type SearchIndexStatus = 'pending' | 'indexed' | 'failed';
type FinancialSurfaceKind =
| 'income_statement'
| 'balance_sheet'
@@ -500,7 +504,7 @@ export const filingLink = sqliteTable('filing_link', {
export const taskRun = sqliteTable('task_run', {
id: text('id').primaryKey().notNull(),
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
task_type: text('task_type').$type<'sync_filings' | 'refresh_prices' | 'analyze_filing' | 'portfolio_insights'>().notNull(),
task_type: text('task_type').$type<'sync_filings' | 'refresh_prices' | 'analyze_filing' | 'portfolio_insights' | 'index_search'>().notNull(),
status: text('status').$type<'queued' | 'running' | 'completed' | 'failed'>().notNull(),
stage: text('stage').notNull(),
stage_detail: text('stage_detail'),
@@ -570,6 +574,55 @@ export const researchJournalEntry = sqliteTable('research_journal_entry', {
researchJournalAccessionIndex: index('research_journal_accession_idx').on(table.user_id, table.accession_number)
}));
export const searchDocument = sqliteTable('search_document', {
id: integer('id').primaryKey({ autoIncrement: true }),
source_kind: text('source_kind').$type<SearchDocumentSourceKind>().notNull(),
source_ref: text('source_ref').notNull(),
scope: text('scope').$type<SearchDocumentScope>().notNull(),
user_id: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
ticker: text('ticker'),
accession_number: text('accession_number'),
title: text('title'),
content_text: text('content_text').notNull(),
content_hash: text('content_hash').notNull(),
metadata: text('metadata', { mode: 'json' }).$type<Record<string, unknown> | null>(),
index_status: text('index_status').$type<SearchIndexStatus>().notNull(),
indexed_at: text('indexed_at'),
last_error: text('last_error'),
created_at: text('created_at').notNull(),
updated_at: text('updated_at').notNull()
}, (table) => ({
searchDocumentSourceUnique: uniqueIndex('search_document_source_uidx').on(
table.scope,
sql`ifnull(${table.user_id}, '')`,
table.source_kind,
table.source_ref
),
searchDocumentScopeIndex: index('search_document_scope_idx').on(
table.scope,
table.source_kind,
table.ticker,
table.updated_at
),
searchDocumentAccessionIndex: index('search_document_accession_idx').on(table.accession_number, table.source_kind)
}));
export const searchChunk = sqliteTable('search_chunk', {
id: integer('id').primaryKey({ autoIncrement: true }),
document_id: integer('document_id').notNull().references(() => searchDocument.id, { onDelete: 'cascade' }),
chunk_index: integer('chunk_index').notNull(),
chunk_text: text('chunk_text').notNull(),
char_count: integer('char_count').notNull(),
start_offset: integer('start_offset').notNull(),
end_offset: integer('end_offset').notNull(),
heading_path: text('heading_path'),
citation_label: text('citation_label').notNull(),
created_at: text('created_at').notNull()
}, (table) => ({
searchChunkUnique: uniqueIndex('search_chunk_document_chunk_uidx').on(table.document_id, table.chunk_index),
searchChunkDocumentIndex: index('search_chunk_document_idx').on(table.document_id)
}));
export const authSchema = {
user,
session,
@@ -595,7 +648,9 @@ export const appSchema = {
taskRun,
taskStageEvent,
portfolioInsight,
researchJournalEntry
researchJournalEntry,
searchDocument,
searchChunk
};
export const schema = {