import { sql } from "drizzle-orm"; import { index, integer, numeric, sqliteTable, text, uniqueIndex, } from "drizzle-orm/sqlite-core"; import type { TaskStageContext } from "@/lib/types"; type FilingMetrics = { revenue: number | null; netIncome: number | null; totalAssets: number | null; cash: number | null; debt: number | null; }; type TaxonomyAssetType = | "instance" | "schema" | "presentation" | "label" | "calculation" | "definition" | "pdf" | "other"; type TaxonomyParseStatus = "ready" | "partial" | "failed"; type TaxonomyMetricValidationStatus = | "not_run" | "matched" | "mismatch" | "error"; type CoverageStatus = "backlog" | "active" | "watch" | "archive"; type CoveragePriority = "low" | "medium" | "high"; type ResearchJournalEntryType = "note" | "filing_note" | "status_change"; type ResearchArtifactKind = | "filing" | "ai_report" | "note" | "upload" | "memo_snapshot" | "status_change"; type ResearchArtifactSource = "system" | "user"; type ResearchVisibilityScope = "private" | "organization"; type ResearchMemoRating = "strong_buy" | "buy" | "hold" | "sell"; type ResearchMemoConviction = "low" | "medium" | "high"; type ResearchMemoSection = | "thesis" | "variant_view" | "catalysts" | "risks" | "disconfirming_evidence" | "next_actions"; 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" | "cash_flow_statement" | "ratios" | "segments_kpis" | "adjusted" | "custom_metrics"; type FilingAnalysis = { provider?: string; model?: string; text?: string; legacyInsights?: string; extraction?: { summary: string; keyPoints: string[]; redFlags: string[]; followUpQuestions: string[]; portfolioSignals: string[]; segmentSpecificData: string[]; geographicRevenueBreakdown: string[]; companySpecificData: string[]; secApiCrossChecks: string[]; confidence: number; }; extractionMeta?: { provider: string; model: string; source: "primary_document" | "metadata_fallback"; generatedAt: string; }; }; type FinancialStatementKind = | "income" | "balance" | "cash_flow" | "disclosure" | "equity" | "comprehensive_income"; type FilingStatementPeriod = { id: string; filingId: number; accessionNumber: string; filingDate: string; periodStart: string | null; periodEnd: string | null; filingType: "10-K" | "10-Q"; periodLabel: string; }; type StatementValuesByPeriod = Record; type FilingFaithfulStatementSnapshotRow = { key: string; label: string; concept: string | null; order: number; depth: number; isSubtotal: boolean; values: StatementValuesByPeriod; }; type StandardizedStatementSnapshotRow = { key: string; label: string; concept: string; category: string; sourceConcepts: string[]; values: StatementValuesByPeriod; }; type DimensionStatementSnapshotRow = { rowKey: string; concept: string | null; periodId: string; axis: string; member: string; value: number | null; unit: string | null; }; type FilingStatementBundle = { periods: FilingStatementPeriod[]; statements: Record< FinancialStatementKind, FilingFaithfulStatementSnapshotRow[] >; }; type StandardizedStatementBundle = { periods: FilingStatementPeriod[]; statements: Record< FinancialStatementKind, StandardizedStatementSnapshotRow[] >; }; type DimensionStatementBundle = { statements: Record; }; type TaxonomyDimensionMember = { axis: string; member: string; }; type TaxonomyStatementSnapshotRow = { key: string; label: string; conceptKey: string; qname: string; namespaceUri: string; localName: string; isExtension: boolean; statement: FinancialStatementKind; roleUri: string | null; order: number; depth: number; parentKey: string | null; values: Record; units: Record; hasDimensions: boolean; sourceFactIds: number[]; }; type TaxonomyStatementBundle = { periods: FilingStatementPeriod[]; statements: Record; }; type TaxonomySurfaceSnapshotRow = { key: string; label: string; category: string; templateSection?: string; order: number; unit: "currency" | "count" | "shares" | "percent" | "ratio"; values: Record; sourceConcepts: string[]; sourceRowKeys: string[]; sourceFactIds: number[]; formulaKey: string | null; hasDimensions: boolean; resolvedSourceRowKeys: Record; statement?: "income" | "balance" | "cash_flow"; detailCount?: number; }; type TaxonomyDetailSnapshotRow = { key: string; parentSurfaceKey: string; label: string; conceptKey: string; qname: string; namespaceUri: string; localName: string; unit: string | null; values: Record; sourceFactIds: number[]; isExtension: boolean; dimensionsSummary: string[]; residualFlag: boolean; }; type TaxonomyDetailStatementMap = Record; type StructuredKpiSnapshotRow = { key: string; label: string; category: string; unit: "currency" | "count" | "shares" | "percent" | "ratio"; order: number; segment: string | null; axis: string | null; member: string | null; values: Record; sourceConcepts: string[]; sourceFactIds: number[]; provenanceType: "taxonomy" | "structured_note"; hasDimensions: boolean; }; type ComputationSpecSnapshot = | { type: "ratio"; numerator: string; denominator: string } | { type: "yoy_growth"; source: string } | { type: "cagr"; source: string; years: number } | { type: "per_share"; source: string; shares_key: string } | { type: "simple"; formula: string }; type ComputedDefinitionSnapshotRow = { key: string; label: string; category: string; order: number; unit: "currency" | "count" | "shares" | "percent" | "ratio"; computation: ComputationSpecSnapshot; supported_cadences?: FinancialCadence[]; requires_external_data?: string[]; }; type TaxonomyNormalizationSummary = { surfaceRowCount: number; detailRowCount: number; kpiRowCount: number; unmappedRowCount: number; materialUnmappedRowCount: number; warnings: string[]; }; type TaxonomyMetricValidationCheck = { metricKey: keyof FilingMetrics; taxonomyValue: number | null; llmValue: number | null; absoluteDiff: number | null; relativeDiff: number | null; status: TaxonomyMetricValidationStatus; evidencePages: number[]; pdfUrl: string | null; provider: string | null; model: string | null; error: string | null; }; type TaxonomyMetricValidationResult = { status: TaxonomyMetricValidationStatus; checks: TaxonomyMetricValidationCheck[]; validatedAt: string | null; }; const authDateColumn = { mode: "timestamp_ms", } as const; export const user = sqliteTable( "user", { id: text("id").primaryKey().notNull(), name: text("name").notNull(), email: text("email").notNull(), emailVerified: integer("emailVerified", { mode: "boolean" }) .notNull() .default(false), image: text("image"), createdAt: integer("createdAt", authDateColumn).notNull(), updatedAt: integer("updatedAt", authDateColumn).notNull(), role: text("role"), banned: integer("banned", { mode: "boolean" }).default(false), banReason: text("banReason"), banExpires: integer("banExpires", authDateColumn), }, (table) => ({ userEmailUnique: uniqueIndex("user_email_uidx").on(table.email), }), ); export const organization = sqliteTable( "organization", { id: text("id").primaryKey().notNull(), name: text("name").notNull(), slug: text("slug").notNull(), logo: text("logo"), createdAt: integer("createdAt", authDateColumn).notNull(), metadata: text("metadata"), }, (table) => ({ organizationSlugUnique: uniqueIndex("organization_slug_uidx").on( table.slug, ), }), ); export const session = sqliteTable( "session", { id: text("id").primaryKey().notNull(), expiresAt: integer("expiresAt", authDateColumn).notNull(), token: text("token").notNull(), createdAt: integer("createdAt", authDateColumn).notNull(), updatedAt: integer("updatedAt", authDateColumn).notNull(), ipAddress: text("ipAddress"), userAgent: text("userAgent"), userId: text("userId") .notNull() .references(() => user.id, { onDelete: "cascade" }), impersonatedBy: text("impersonatedBy"), activeOrganizationId: text("activeOrganizationId"), }, (table) => ({ sessionTokenUnique: uniqueIndex("session_token_uidx").on(table.token), sessionUserIdIndex: index("session_userId_idx").on(table.userId), }), ); export const account = sqliteTable( "account", { id: text("id").primaryKey().notNull(), accountId: text("accountId").notNull(), providerId: text("providerId").notNull(), userId: text("userId") .notNull() .references(() => user.id, { onDelete: "cascade" }), accessToken: text("accessToken"), refreshToken: text("refreshToken"), idToken: text("idToken"), accessTokenExpiresAt: integer("accessTokenExpiresAt", authDateColumn), refreshTokenExpiresAt: integer("refreshTokenExpiresAt", authDateColumn), scope: text("scope"), password: text("password"), createdAt: integer("createdAt", authDateColumn).notNull(), updatedAt: integer("updatedAt", authDateColumn).notNull(), }, (table) => ({ accountUserIdIndex: index("account_userId_idx").on(table.userId), }), ); export const verification = sqliteTable( "verification", { id: text("id").primaryKey().notNull(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: integer("expiresAt", authDateColumn).notNull(), createdAt: integer("createdAt", authDateColumn).notNull(), updatedAt: integer("updatedAt", authDateColumn).notNull(), }, (table) => ({ verificationIdentifierIndex: index("verification_identifier_idx").on( table.identifier, ), }), ); export const member = sqliteTable( "member", { id: text("id").primaryKey().notNull(), organizationId: text("organizationId") .notNull() .references(() => organization.id, { onDelete: "cascade" }), userId: text("userId") .notNull() .references(() => user.id, { onDelete: "cascade" }), role: text("role").notNull().default("member"), createdAt: integer("createdAt", authDateColumn).notNull(), }, (table) => ({ memberOrganizationIdIndex: index("member_organizationId_idx").on( table.organizationId, ), memberUserIdIndex: index("member_userId_idx").on(table.userId), }), ); export const invitation = sqliteTable( "invitation", { id: text("id").primaryKey().notNull(), organizationId: text("organizationId") .notNull() .references(() => organization.id, { onDelete: "cascade" }), email: text("email").notNull(), role: text("role"), status: text("status").notNull().default("pending"), expiresAt: integer("expiresAt", authDateColumn).notNull(), createdAt: integer("createdAt", authDateColumn).notNull(), inviterId: text("inviterId") .notNull() .references(() => user.id, { onDelete: "cascade" }), }, (table) => ({ invitationOrganizationIdIndex: index("invitation_organizationId_idx").on( table.organizationId, ), invitationEmailIndex: index("invitation_email_idx").on(table.email), }), ); export const watchlistItem = sqliteTable( "watchlist_item", { id: integer("id").primaryKey({ autoIncrement: true }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), ticker: text("ticker").notNull(), company_name: text("company_name").notNull(), sector: text("sector"), category: text("category"), tags: text("tags", { mode: "json" }).$type(), status: text("status").$type().notNull().default("backlog"), priority: text("priority") .$type() .notNull() .default("medium"), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), last_reviewed_at: text("last_reviewed_at"), }, (table) => ({ watchlistUserTickerUnique: uniqueIndex("watchlist_user_ticker_uidx").on( table.user_id, table.ticker, ), watchlistUserCreatedIndex: index("watchlist_user_created_idx").on( table.user_id, table.created_at, ), watchlistUserUpdatedIndex: index("watchlist_user_updated_idx").on( table.user_id, table.updated_at, ), }), ); export const holding = sqliteTable( "holding", { id: integer("id").primaryKey({ autoIncrement: true }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), ticker: text("ticker").notNull(), company_name: text("company_name"), shares: numeric("shares").notNull(), avg_cost: numeric("avg_cost").notNull(), current_price: numeric("current_price"), market_value: numeric("market_value").notNull(), gain_loss: numeric("gain_loss").notNull(), gain_loss_pct: numeric("gain_loss_pct").notNull(), last_price_at: text("last_price_at"), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ holdingUserTickerUnique: uniqueIndex("holding_user_ticker_uidx").on( table.user_id, table.ticker, ), holdingUserIndex: index("holding_user_idx").on(table.user_id), }), ); export const filing = sqliteTable( "filing", { id: integer("id").primaryKey({ autoIncrement: true }), ticker: text("ticker").notNull(), filing_type: text("filing_type").$type<"10-K" | "10-Q" | "8-K">().notNull(), filing_date: text("filing_date").notNull(), accession_number: text("accession_number").notNull(), cik: text("cik").notNull(), company_name: text("company_name").notNull(), filing_url: text("filing_url"), submission_url: text("submission_url"), primary_document: text("primary_document"), metrics: text("metrics", { mode: "json" }).$type(), analysis: text("analysis", { mode: "json" }).$type(), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ filingAccessionUnique: uniqueIndex("filing_accession_uidx").on( table.accession_number, ), filingTickerDateIndex: index("filing_ticker_date_idx").on( table.ticker, table.filing_date, ), filingDateIndex: index("filing_date_idx").on(table.filing_date), }), ); export const filingStatementSnapshot = sqliteTable( "filing_statement_snapshot", { id: integer("id").primaryKey({ autoIncrement: true }), filing_id: integer("filing_id") .notNull() .references(() => filing.id, { onDelete: "cascade" }), ticker: text("ticker").notNull(), filing_date: text("filing_date").notNull(), filing_type: text("filing_type").$type<"10-K" | "10-Q">().notNull(), period_end: text("period_end"), statement_bundle: text("statement_bundle", { mode: "json", }).$type(), standardized_bundle: text("standardized_bundle", { mode: "json", }).$type(), dimension_bundle: text("dimension_bundle", { mode: "json", }).$type(), parse_status: text("parse_status") .$type<"ready" | "partial" | "failed">() .notNull(), parse_error: text("parse_error"), source: text("source") .$type<"sec_filing_summary" | "xbrl_instance" | "companyfacts_fallback">() .notNull(), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ filingStatementFilingUnique: uniqueIndex("filing_stmt_filing_uidx").on( table.filing_id, ), filingStatementTickerDateIndex: index("filing_stmt_ticker_date_idx").on( table.ticker, table.filing_date, ), filingStatementDateIndex: index("filing_stmt_date_idx").on( table.filing_date, ), filingStatementStatusIndex: index("filing_stmt_status_idx").on( table.parse_status, ), }), ); export const filingTaxonomySnapshot = sqliteTable( "filing_taxonomy_snapshot", { id: integer("id").primaryKey({ autoIncrement: true }), filing_id: integer("filing_id") .notNull() .references(() => filing.id, { onDelete: "cascade" }), ticker: text("ticker").notNull(), filing_date: text("filing_date").notNull(), filing_type: text("filing_type").$type<"10-K" | "10-Q">().notNull(), parse_status: text("parse_status").$type().notNull(), parse_error: text("parse_error"), source: text("source") .$type< "xbrl_instance" | "xbrl_instance_with_linkbase" | "legacy_html_fallback" >() .notNull(), parser_engine: text("parser_engine").notNull().default("fiscal-xbrl"), parser_version: text("parser_version").notNull().default("unknown"), taxonomy_regime: text("taxonomy_regime") .$type<"us-gaap" | "ifrs-full" | "unknown">() .notNull() .default("unknown"), fiscal_pack: text("fiscal_pack"), periods: text("periods", { mode: "json" }).$type(), faithful_rows: text("faithful_rows", { mode: "json" }).$type< TaxonomyStatementBundle["statements"] | null >(), statement_rows: text("statement_rows", { mode: "json" }).$type< TaxonomyStatementBundle["statements"] | null >(), surface_rows: text("surface_rows", { mode: "json" }).$type | null>(), detail_rows: text("detail_rows", { mode: "json" }).$type | null>(), kpi_rows: text("kpi_rows", { mode: "json" }).$type< StructuredKpiSnapshotRow[] | null >(), computed_definitions: text("computed_definitions", { mode: "json" }).$type< ComputedDefinitionSnapshotRow[] | null >(), derived_metrics: text("derived_metrics", { mode: "json", }).$type(), validation_result: text("validation_result", { mode: "json", }).$type(), normalization_summary: text("normalization_summary", { mode: "json", }).$type(), facts_count: integer("facts_count").notNull().default(0), concepts_count: integer("concepts_count").notNull().default(0), dimensions_count: integer("dimensions_count").notNull().default(0), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ filingTaxonomySnapshotFilingUnique: uniqueIndex( "filing_taxonomy_snapshot_filing_uidx", ).on(table.filing_id), filingTaxonomySnapshotTickerDateIndex: index( "filing_taxonomy_snapshot_ticker_date_idx", ).on(table.ticker, table.filing_date), filingTaxonomySnapshotStatusIndex: index( "filing_taxonomy_snapshot_status_idx", ).on(table.parse_status), }), ); export const filingTaxonomyContext = sqliteTable( "filing_taxonomy_context", { id: integer("id").primaryKey({ autoIncrement: true }), snapshot_id: integer("snapshot_id") .notNull() .references(() => filingTaxonomySnapshot.id, { onDelete: "cascade" }), context_id: text("context_id").notNull(), entity_identifier: text("entity_identifier"), entity_scheme: text("entity_scheme"), period_start: text("period_start"), period_end: text("period_end"), period_instant: text("period_instant"), segment_json: text("segment_json", { mode: "json" }).$type | null>(), scenario_json: text("scenario_json", { mode: "json" }).$type | null>(), created_at: text("created_at").notNull(), }, (table) => ({ filingTaxonomyContextSnapshotIndex: index( "filing_taxonomy_context_snapshot_idx", ).on(table.snapshot_id), filingTaxonomyContextUnique: uniqueIndex("filing_taxonomy_context_uidx").on( table.snapshot_id, table.context_id, ), }), ); export const filingTaxonomyAsset = sqliteTable( "filing_taxonomy_asset", { id: integer("id").primaryKey({ autoIncrement: true }), snapshot_id: integer("snapshot_id") .notNull() .references(() => filingTaxonomySnapshot.id, { onDelete: "cascade" }), asset_type: text("asset_type").$type().notNull(), name: text("name").notNull(), url: text("url").notNull(), size_bytes: integer("size_bytes"), score: numeric("score"), is_selected: integer("is_selected", { mode: "boolean" }) .notNull() .default(false), created_at: text("created_at").notNull(), }, (table) => ({ filingTaxonomyAssetSnapshotIndex: index( "filing_taxonomy_asset_snapshot_idx", ).on(table.snapshot_id), filingTaxonomyAssetTypeIndex: index("filing_taxonomy_asset_type_idx").on( table.snapshot_id, table.asset_type, ), }), ); export const filingTaxonomyConcept = sqliteTable( "filing_taxonomy_concept", { id: integer("id").primaryKey({ autoIncrement: true }), snapshot_id: integer("snapshot_id") .notNull() .references(() => filingTaxonomySnapshot.id, { onDelete: "cascade" }), concept_key: text("concept_key").notNull(), qname: text("qname").notNull(), namespace_uri: text("namespace_uri").notNull(), local_name: text("local_name").notNull(), label: text("label"), is_extension: integer("is_extension", { mode: "boolean" }) .notNull() .default(false), balance: text("balance"), period_type: text("period_type"), data_type: text("data_type"), statement_kind: text("statement_kind").$type(), role_uri: text("role_uri"), authoritative_concept_key: text("authoritative_concept_key"), mapping_method: text("mapping_method"), surface_key: text("surface_key"), detail_parent_surface_key: text("detail_parent_surface_key"), kpi_key: text("kpi_key"), residual_flag: integer("residual_flag", { mode: "boolean" }) .notNull() .default(false), presentation_order: numeric("presentation_order"), presentation_depth: integer("presentation_depth"), parent_concept_key: text("parent_concept_key"), is_abstract: integer("is_abstract", { mode: "boolean" }) .notNull() .default(false), created_at: text("created_at").notNull(), }, (table) => ({ filingTaxonomyConceptSnapshotIndex: index( "filing_taxonomy_concept_snapshot_idx", ).on(table.snapshot_id), filingTaxonomyConceptStatementIndex: index( "filing_taxonomy_concept_statement_idx", ).on(table.snapshot_id, table.statement_kind), filingTaxonomyConceptUnique: uniqueIndex("filing_taxonomy_concept_uidx").on( table.snapshot_id, table.concept_key, table.role_uri, table.presentation_order, ), }), ); export const filingTaxonomyFact = sqliteTable( "filing_taxonomy_fact", { id: integer("id").primaryKey({ autoIncrement: true }), snapshot_id: integer("snapshot_id") .notNull() .references(() => filingTaxonomySnapshot.id, { onDelete: "cascade" }), concept_key: text("concept_key").notNull(), qname: text("qname").notNull(), namespace_uri: text("namespace_uri").notNull(), local_name: text("local_name").notNull(), data_type: text("data_type"), statement_kind: text("statement_kind").$type(), role_uri: text("role_uri"), authoritative_concept_key: text("authoritative_concept_key"), mapping_method: text("mapping_method"), surface_key: text("surface_key"), detail_parent_surface_key: text("detail_parent_surface_key"), kpi_key: text("kpi_key"), residual_flag: integer("residual_flag", { mode: "boolean" }) .notNull() .default(false), context_id: text("context_id").notNull(), unit: text("unit"), decimals: text("decimals"), precision: text("precision"), nil: integer("nil", { mode: "boolean" }).notNull().default(false), value_num: numeric("value_num").notNull(), period_start: text("period_start"), period_end: text("period_end"), period_instant: text("period_instant"), dimensions: text("dimensions", { mode: "json" }) .$type() .notNull(), is_dimensionless: integer("is_dimensionless", { mode: "boolean" }) .notNull() .default(true), source_file: text("source_file"), created_at: text("created_at").notNull(), }, (table) => ({ filingTaxonomyFactSnapshotIndex: index( "filing_taxonomy_fact_snapshot_idx", ).on(table.snapshot_id), filingTaxonomyFactConceptIndex: index( "filing_taxonomy_fact_concept_idx", ).on(table.snapshot_id, table.concept_key), filingTaxonomyFactPeriodIndex: index("filing_taxonomy_fact_period_idx").on( table.snapshot_id, table.period_end, table.period_instant, ), filingTaxonomyFactStatementIndex: index( "filing_taxonomy_fact_statement_idx", ).on(table.snapshot_id, table.statement_kind), }), ); export const filingTaxonomyMetricValidation = sqliteTable( "filing_taxonomy_metric_validation", { id: integer("id").primaryKey({ autoIncrement: true }), snapshot_id: integer("snapshot_id") .notNull() .references(() => filingTaxonomySnapshot.id, { onDelete: "cascade" }), metric_key: text("metric_key").$type().notNull(), taxonomy_value: numeric("taxonomy_value"), llm_value: numeric("llm_value"), absolute_diff: numeric("absolute_diff"), relative_diff: numeric("relative_diff"), status: text("status").$type().notNull(), evidence_pages: text("evidence_pages", { mode: "json" }) .$type() .notNull(), pdf_url: text("pdf_url"), provider: text("provider"), model: text("model"), error: text("error"), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ filingTaxonomyMetricValidationSnapshotIndex: index( "filing_taxonomy_metric_validation_snapshot_idx", ).on(table.snapshot_id), filingTaxonomyMetricValidationStatusIndex: index( "filing_taxonomy_metric_validation_status_idx", ).on(table.snapshot_id, table.status), filingTaxonomyMetricValidationUnique: uniqueIndex( "filing_taxonomy_metric_validation_uidx", ).on(table.snapshot_id, table.metric_key), }), ); export const companyFinancialBundle = sqliteTable( "company_financial_bundle", { id: integer("id").primaryKey({ autoIncrement: true }), ticker: text("ticker").notNull(), surface_kind: text("surface_kind").$type().notNull(), cadence: text("cadence").$type().notNull(), bundle_version: integer("bundle_version").notNull(), source_snapshot_ids: text("source_snapshot_ids", { mode: "json" }) .$type() .notNull(), source_signature: text("source_signature").notNull(), payload: text("payload", { mode: "json" }) .$type>() .notNull(), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ companyFinancialBundleUnique: uniqueIndex( "company_financial_bundle_uidx", ).on(table.ticker, table.surface_kind, table.cadence), companyFinancialBundleTickerIndex: index( "company_financial_bundle_ticker_idx", ).on(table.ticker, table.updated_at), }), ); export const companyOverviewCache = sqliteTable( "company_overview_cache", { id: integer("id").primaryKey({ autoIncrement: true }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), ticker: text("ticker").notNull(), cache_version: integer("cache_version").notNull(), source_signature: text("source_signature").notNull(), payload: text("payload", { mode: "json" }) .$type>() .notNull(), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ companyOverviewCacheUnique: uniqueIndex("company_overview_cache_uidx").on( table.user_id, table.ticker, ), companyOverviewCacheLookupIndex: index( "company_overview_cache_lookup_idx", ).on(table.user_id, table.ticker, table.updated_at), }), ); export const filingLink = sqliteTable( "filing_link", { id: integer("id").primaryKey({ autoIncrement: true }), filing_id: integer("filing_id") .notNull() .references(() => filing.id, { onDelete: "cascade" }), link_type: text("link_type").notNull(), url: text("url").notNull(), source: text("source").notNull().default("sec"), created_at: text("created_at").notNull(), }, (table) => ({ filingLinkUnique: uniqueIndex("filing_link_unique_uidx").on( table.filing_id, table.url, ), filingLinkFilingIndex: index("filing_link_filing_idx").on(table.filing_id), }), ); 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" | "index_search" >() .notNull(), status: text("status") .$type<"queued" | "running" | "completed" | "failed">() .notNull(), stage: text("stage").notNull(), stage_detail: text("stage_detail"), stage_context: text("stage_context", { mode: "json", }).$type(), resource_key: text("resource_key"), notification_read_at: text("notification_read_at"), notification_silenced_at: text("notification_silenced_at"), priority: integer("priority").notNull(), payload: text("payload", { mode: "json" }) .$type>() .notNull(), result: text("result", { mode: "json" }).$type | null>(), error: text("error"), attempts: integer("attempts").notNull(), max_attempts: integer("max_attempts").notNull(), workflow_run_id: text("workflow_run_id"), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), finished_at: text("finished_at"), }, (table) => ({ taskUserCreatedIndex: index("task_user_created_idx").on( table.user_id, table.created_at, ), taskUserUpdatedIndex: index("task_user_updated_idx").on( table.user_id, table.updated_at, ), taskStatusIndex: index("task_status_idx").on(table.status), taskUserResourceStatusIndex: index("task_user_resource_status_idx").on( table.user_id, table.task_type, table.resource_key, table.status, table.created_at, ), taskWorkflowRunUnique: uniqueIndex("task_workflow_run_uidx").on( table.workflow_run_id, ), }), ); // Note: Partial unique index for active resource-scoped task deduplication is created via // migration 0013_task_active_resource_unique.sql. SQLite does not support partial indexes // in drizzle schema DSL, so the index is managed separately: // CREATE UNIQUE INDEX task_active_resource_uidx ON task_run (user_id, task_type, resource_key) // WHERE resource_key IS NOT NULL AND status IN ('queued', 'running'); export const taskStageEvent = sqliteTable( "task_stage_event", { id: integer("id").primaryKey({ autoIncrement: true }), task_id: text("task_id") .notNull() .references(() => taskRun.id, { onDelete: "cascade" }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), stage: text("stage").notNull(), stage_detail: text("stage_detail"), stage_context: text("stage_context", { mode: "json", }).$type(), status: text("status") .$type<"queued" | "running" | "completed" | "failed">() .notNull(), created_at: text("created_at").notNull(), }, (table) => ({ taskStageEventTaskCreatedIndex: index( "task_stage_event_task_created_idx", ).on(table.task_id, table.created_at), taskStageEventUserCreatedIndex: index( "task_stage_event_user_created_idx", ).on(table.user_id, table.created_at), }), ); export const portfolioInsight = sqliteTable( "portfolio_insight", { id: integer("id").primaryKey({ autoIncrement: true }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), provider: text("provider").notNull(), model: text("model").notNull(), content: text("content").notNull(), created_at: text("created_at").notNull(), }, (table) => ({ insightUserCreatedIndex: index("insight_user_created_idx").on( table.user_id, table.created_at, ), }), ); export const researchJournalEntry = sqliteTable( "research_journal_entry", { id: integer("id").primaryKey({ autoIncrement: true }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), ticker: text("ticker").notNull(), accession_number: text("accession_number"), entry_type: text("entry_type").$type().notNull(), title: text("title"), body_markdown: text("body_markdown").notNull(), metadata: text("metadata", { mode: "json" }).$type | null>(), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ researchJournalTickerIndex: index("research_journal_ticker_idx").on( table.user_id, table.ticker, table.created_at, ), 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() .notNull(), source_ref: text("source_ref").notNull(), scope: text("scope").$type().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 | null>(), index_status: text("index_status").$type().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 researchArtifact = sqliteTable( "research_artifact", { id: integer("id").primaryKey({ autoIncrement: true }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), organization_id: text("organization_id").references(() => organization.id, { onDelete: "set null", }), ticker: text("ticker").notNull(), accession_number: text("accession_number"), kind: text("kind").$type().notNull(), source: text("source") .$type() .notNull() .default("user"), subtype: text("subtype"), title: text("title"), summary: text("summary"), body_markdown: text("body_markdown"), search_text: text("search_text"), visibility_scope: text("visibility_scope") .$type() .notNull() .default("private"), tags: text("tags", { mode: "json" }).$type(), metadata: text("metadata", { mode: "json" }).$type | null>(), file_name: text("file_name"), mime_type: text("mime_type"), file_size_bytes: integer("file_size_bytes"), storage_path: text("storage_path"), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ researchArtifactTickerIndex: index("research_artifact_ticker_idx").on( table.user_id, table.ticker, table.updated_at, ), researchArtifactKindIndex: index("research_artifact_kind_idx").on( table.user_id, table.kind, table.updated_at, ), researchArtifactAccessionIndex: index("research_artifact_accession_idx").on( table.user_id, table.accession_number, ), researchArtifactSourceIndex: index("research_artifact_source_idx").on( table.user_id, table.source, table.updated_at, ), }), ); export const researchMemo = sqliteTable( "research_memo", { id: integer("id").primaryKey({ autoIncrement: true }), user_id: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), organization_id: text("organization_id").references(() => organization.id, { onDelete: "set null", }), ticker: text("ticker").notNull(), rating: text("rating").$type(), conviction: text("conviction").$type(), time_horizon_months: integer("time_horizon_months"), packet_title: text("packet_title"), packet_subtitle: text("packet_subtitle"), thesis_markdown: text("thesis_markdown").notNull().default(""), variant_view_markdown: text("variant_view_markdown").notNull().default(""), catalysts_markdown: text("catalysts_markdown").notNull().default(""), risks_markdown: text("risks_markdown").notNull().default(""), disconfirming_evidence_markdown: text("disconfirming_evidence_markdown") .notNull() .default(""), next_actions_markdown: text("next_actions_markdown").notNull().default(""), created_at: text("created_at").notNull(), updated_at: text("updated_at").notNull(), }, (table) => ({ researchMemoTickerUnique: uniqueIndex("research_memo_ticker_uidx").on( table.user_id, table.ticker, ), researchMemoUpdatedIndex: index("research_memo_updated_idx").on( table.user_id, table.updated_at, ), }), ); export const researchMemoEvidence = sqliteTable( "research_memo_evidence", { id: integer("id").primaryKey({ autoIncrement: true }), memo_id: integer("memo_id") .notNull() .references(() => researchMemo.id, { onDelete: "cascade" }), artifact_id: integer("artifact_id") .notNull() .references(() => researchArtifact.id, { onDelete: "cascade" }), section: text("section").$type().notNull(), annotation: text("annotation"), sort_order: integer("sort_order").notNull().default(0), created_at: text("created_at").notNull(), }, (table) => ({ researchMemoEvidenceMemoIndex: index("research_memo_evidence_memo_idx").on( table.memo_id, table.section, table.sort_order, ), researchMemoEvidenceArtifactIndex: index( "research_memo_evidence_artifact_idx", ).on(table.artifact_id), researchMemoEvidenceUnique: uniqueIndex( "research_memo_evidence_unique_uidx", ).on(table.memo_id, table.artifact_id, table.section), }), ); export const authSchema = { user, session, account, verification, organization, member, invitation, }; export const appSchema = { watchlistItem, holding, filing, filingStatementSnapshot, filingTaxonomySnapshot, filingTaxonomyAsset, filingTaxonomyConcept, filingTaxonomyFact, filingTaxonomyMetricValidation, companyFinancialBundle, companyOverviewCache, filingLink, taskRun, taskStageEvent, portfolioInsight, researchJournalEntry, searchDocument, searchChunk, researchArtifact, researchMemo, researchMemoEvidence, }; export const schema = { ...authSchema, ...appSchema, };