managarten/apps/api/src/modules/research/schema.ts
Till JS a91a6076cc refactor: rename planta → plants, clean up codebase
- Rename planta module to plants everywhere (routes, modules, API,
  branding, i18n, docker, docs, shared packages)
- Fix package name collisions: @mana/credits-service, @mana/subscriptions-service
  (unblocks turbo)
- Extract layout composables: use-ai-tier-items, use-sync-status-items,
  RouteTierGate (layout 1345→1015 lines)
- Create shared DB pool for apps/api (lib/db.ts), migrate 5 modules
- Add automations module queries.ts with useAllAutomations/useEnabledAutomations
- Remove debug console.log statements from production code
- Rename storage display name: Ablage → Speicher

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:59:44 +02:00

69 lines
2.7 KiB
TypeScript

/**
* Research module — DB schema (Drizzle / pgSchema 'research')
*
* Server-side store for deep-research runs orchestrated by apps/api.
* Lives in mana_platform under its own pgSchema.
*
* - research_results: one row per research run, holds plan + final synthesis
* - sources: one row per web source consumed by a run
*
* The local-first questions module references research_results.id from
* LocalAnswer.researchResultId; sources are fetched on-demand via the API
* and never mirrored into IndexedDB (they're public web content).
*/
import { drizzle } from 'drizzle-orm/postgres-js';
import { pgSchema, uuid, text, timestamp, integer, jsonb } from 'drizzle-orm/pg-core';
import { getConnection } from '../../lib/db';
export const researchSchema = pgSchema('research');
/**
* One row per research run. Created in `planning` state immediately on
* /start, then updated as the orchestrator advances through phases.
*/
export const researchResults = researchSchema.table('research_results', {
id: uuid('id').defaultRandom().primaryKey(),
userId: text('user_id').notNull(),
questionId: text('question_id').notNull(), // mirrors local LocalQuestion.id (UUID)
depth: text('depth').notNull(), // 'quick' | 'standard' | 'deep'
status: text('status').notNull(), // 'planning' | 'searching' | 'extracting' | 'synthesizing' | 'done' | 'error'
subQueries: jsonb('sub_queries').$type<string[]>(),
summary: text('summary'),
keyPoints: jsonb('key_points').$type<string[]>(),
followUpQuestions: jsonb('follow_up_questions').$type<string[]>(),
errorMessage: text('error_message'),
startedAt: timestamp('started_at', { withTimezone: true }).defaultNow().notNull(),
finishedAt: timestamp('finished_at', { withTimezone: true }),
});
/**
* Sources consumed during a research run. Rank reflects ordering in the
* synthesis prompt so citation [n] in the summary maps to sources[n-1].
*/
export const sources = researchSchema.table('sources', {
id: uuid('id').defaultRandom().primaryKey(),
researchResultId: uuid('research_result_id')
.notNull()
.references(() => researchResults.id, { onDelete: 'cascade' }),
url: text('url').notNull(),
title: text('title'),
snippet: text('snippet'),
extractedContent: text('extracted_content'),
category: text('category'),
rank: integer('rank').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
});
export const db = drizzle(getConnection(), { schema: { researchResults, sources } });
export type ResearchResult = typeof researchResults.$inferSelect;
export type Source = typeof sources.$inferSelect;
export type ResearchDepth = 'quick' | 'standard' | 'deep';
export type ResearchStatus =
| 'planning'
| 'searching'
| 'extracting'
| 'synthesizing'
| 'done'
| 'error';