From bcf4c69c92808dc43544ec53a95bb09458d1918a Mon Sep 17 00:00:00 2001 From: francy51 Date: Mon, 2 Mar 2026 09:33:44 -0500 Subject: [PATCH] feat(financials-v2): add statement snapshot schema and shared types --- drizzle/0001_glossy_statement_snapshots.sql | 22 ++++++ drizzle/meta/_journal.json | 9 ++- lib/server/db/schema.ts | 80 +++++++++++++++++++++ lib/types.ts | 73 +++++++++++++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 drizzle/0001_glossy_statement_snapshots.sql diff --git a/drizzle/0001_glossy_statement_snapshots.sql b/drizzle/0001_glossy_statement_snapshots.sql new file mode 100644 index 0000000..d7a008e --- /dev/null +++ b/drizzle/0001_glossy_statement_snapshots.sql @@ -0,0 +1,22 @@ +CREATE TABLE `filing_statement_snapshot` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `filing_id` integer NOT NULL, + `ticker` text NOT NULL, + `filing_date` text NOT NULL, + `filing_type` text NOT NULL, + `period_end` text, + `statement_bundle` text, + `standardized_bundle` text, + `dimension_bundle` text, + `parse_status` text NOT NULL, + `parse_error` text, + `source` text NOT NULL, + `created_at` text NOT NULL, + `updated_at` text NOT NULL, + FOREIGN KEY (`filing_id`) REFERENCES `filing`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `filing_stmt_filing_uidx` ON `filing_statement_snapshot` (`filing_id`);--> statement-breakpoint +CREATE INDEX `filing_stmt_ticker_date_idx` ON `filing_statement_snapshot` (`ticker`,`filing_date`);--> statement-breakpoint +CREATE INDEX `filing_stmt_date_idx` ON `filing_statement_snapshot` (`filing_date`);--> statement-breakpoint +CREATE INDEX `filing_stmt_status_idx` ON `filing_statement_snapshot` (`parse_status`); diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 82c091a..c35c118 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1772137733427, "tag": "0000_cold_silver_centurion", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1772417400000, + "tag": "0001_glossy_statement_snapshots", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/lib/server/db/schema.ts b/lib/server/db/schema.ts index d450b63..7e89a9c 100644 --- a/lib/server/db/schema.ts +++ b/lib/server/db/schema.ts @@ -40,6 +40,63 @@ type FilingAnalysis = { }; }; +type FinancialStatementKind = 'income' | 'balance' | 'cash_flow' | 'equity' | 'comprehensive_income'; + +type FilingStatementPeriod = { + id: string; + filingId: number; + accessionNumber: string; + filingDate: string; + 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; +}; + +type StandardizedStatementBundle = { + periods: FilingStatementPeriod[]; + statements: Record; +}; + +type DimensionStatementBundle = { + statements: Record; +}; + const authDateColumn = { mode: 'timestamp_ms' } as const; @@ -192,6 +249,28 @@ export const filing = sqliteTable('filing', { 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 filingLink = sqliteTable('filing_link', { id: integer('id').primaryKey({ autoIncrement: true }), filing_id: integer('filing_id').notNull().references(() => filing.id, { onDelete: 'cascade' }), @@ -250,6 +329,7 @@ export const appSchema = { watchlistItem, holding, filing, + filingStatementSnapshot, filingLink, taskRun, portfolioInsight diff --git a/lib/types.ts b/lib/types.ts index d8ebb91..ec14677 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -127,6 +127,79 @@ export type CompanyFinancialPoint = { debt: number | null; }; +export type FinancialStatementMode = 'standardized' | 'filing_faithful'; +export type FinancialStatementKind = 'income' | 'balance' | 'cash_flow' | 'equity' | 'comprehensive_income'; +export type FinancialHistoryWindow = '10y' | 'all'; + +export type FinancialStatementPeriod = { + id: string; + filingId: number; + accessionNumber: string; + filingDate: string; + periodEnd: string | null; + filingType: Extract; + periodLabel: string; +}; + +export type StandardizedStatementRow = { + key: string; + label: string; + concept: string; + category: string; + sourceConcepts: string[]; + values: Record; + hasDimensions: boolean; +}; + +export type FilingFaithfulStatementRow = { + key: string; + label: string; + concept: string | null; + order: number; + depth: number; + isSubtotal: boolean; + values: Record; + hasDimensions: boolean; +}; + +export type DimensionBreakdownRow = { + rowKey: string; + concept: string | null; + periodId: string; + axis: string; + member: string; + value: number | null; + unit: string | null; +}; + +export type CompanyFinancialStatementsResponse = { + company: { + ticker: string; + companyName: string; + cik: string | null; + }; + mode: FinancialStatementMode; + statement: FinancialStatementKind; + window: FinancialHistoryWindow; + periods: FinancialStatementPeriod[]; + rows: StandardizedStatementRow[] | FilingFaithfulStatementRow[]; + nextCursor: string | null; + coverage: { + filings: number; + rows: number; + dimensions: number; + }; + dataSourceStatus: { + enabled: boolean; + hydratedFilings: number; + partialFilings: number; + failedFilings: number; + pendingFilings: number; + queuedSync: boolean; + }; + dimensionBreakdown: Record | null; +}; + export type CompanyAiReport = { accessionNumber: string; filingDate: string;