@marinoscar/kvox-eval 0.0.0-bootstrap → 0.1.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/ask/index.d.ts +6 -0
- package/dist/ask/index.js +28 -0
- package/dist/ask/question-schema.d.ts +121 -0
- package/dist/ask/question-schema.js +167 -0
- package/dist/ask/report.d.ts +88 -0
- package/dist/ask/report.js +170 -0
- package/dist/ask/scorer.d.ts +104 -0
- package/dist/ask/scorer.js +164 -0
- package/dist/ask-run/ask-command.d.ts +62 -0
- package/dist/ask-run/ask-command.js +254 -0
- package/dist/ask-run/host/app.d.ts +65 -0
- package/dist/ask-run/host/app.js +146 -0
- package/dist/ask-run/host/db.d.ts +38 -0
- package/dist/ask-run/host/db.js +126 -0
- package/dist/ask-run/host/ports.d.ts +35 -0
- package/dist/ask-run/host/ports.js +113 -0
- package/dist/ask-run/index.d.ts +13 -0
- package/dist/ask-run/index.js +35 -0
- package/dist/ask-run/replay.d.ts +128 -0
- package/dist/ask-run/replay.js +212 -0
- package/dist/ask-run/run-ask-eval.d.ts +125 -0
- package/dist/ask-run/run-ask-eval.js +193 -0
- package/dist/ask-run/seed-golden-graph.d.ts +64 -0
- package/dist/ask-run/seed-golden-graph.js +495 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/kg-command.d.ts +66 -0
- package/dist/cli/kg-command.js +238 -0
- package/dist/cli/main.d.ts +18 -0
- package/dist/cli/main.js +69 -0
- package/dist/fixtures/domains.d.ts +16 -0
- package/dist/fixtures/domains.js +33 -0
- package/dist/fixtures/fixture-schema.d.ts +323 -0
- package/dist/fixtures/fixture-schema.js +199 -0
- package/dist/fixtures/index.d.ts +16 -0
- package/dist/fixtures/index.js +40 -0
- package/dist/fixtures/load.d.ts +73 -0
- package/dist/fixtures/load.js +137 -0
- package/dist/fixtures/questions.d.ts +15 -0
- package/dist/fixtures/questions.js +26 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +36 -0
- package/dist/kg/gold-runner.d.ts +13 -0
- package/dist/kg/gold-runner.js +57 -0
- package/dist/kg/index.d.ts +17 -0
- package/dist/kg/index.js +65 -0
- package/dist/kg/internals.d.ts +7 -0
- package/dist/kg/internals.js +17 -0
- package/dist/kg/prediction-schema.d.ts +245 -0
- package/dist/kg/prediction-schema.js +102 -0
- package/dist/kg/predictions.d.ts +11 -0
- package/dist/kg/predictions.js +49 -0
- package/dist/kg/report.d.ts +84 -0
- package/dist/kg/report.js +125 -0
- package/dist/kg/run-kg-eval.d.ts +77 -0
- package/dist/kg/run-kg-eval.js +137 -0
- package/dist/kg/runner.d.ts +72 -0
- package/dist/kg/runner.js +51 -0
- package/dist/kg/runners/extract-resolve-runner.d.ts +40 -0
- package/dist/kg/runners/extract-resolve-runner.js +199 -0
- package/dist/kg/runners/extract-runner.d.ts +65 -0
- package/dist/kg/runners/extract-runner.js +243 -0
- package/dist/kg/scorer.d.ts +226 -0
- package/dist/kg/scorer.js +463 -0
- package/dist/kg/targets.d.ts +17 -0
- package/dist/kg/targets.js +16 -0
- package/dist/kg/text.d.ts +45 -0
- package/dist/kg/text.js +100 -0
- package/dist/live/adapters.d.ts +16 -0
- package/dist/live/adapters.js +58 -0
- package/dist/live/env.d.ts +39 -0
- package/dist/live/env.js +56 -0
- package/dist/live/index.d.ts +6 -0
- package/dist/live/index.js +16 -0
- package/dist/live/live-ai.d.ts +104 -0
- package/dist/live/live-ai.js +157 -0
- package/dist/parity/constants.d.ts +79 -0
- package/dist/parity/constants.js +104 -0
- package/dist/parity/index.d.ts +20 -0
- package/dist/parity/index.js +38 -0
- package/dist/parity/internals.d.ts +8 -0
- package/dist/parity/internals.js +16 -0
- package/dist/parity/scenario.d.ts +45 -0
- package/dist/parity/scenario.js +245 -0
- package/dist/parity/script.d.ts +44 -0
- package/dist/parity/script.js +136 -0
- package/dist/parity/snapshot.d.ts +43 -0
- package/dist/parity/snapshot.js +71 -0
- package/dist/real-data/index.d.ts +2 -0
- package/dist/real-data/index.js +12 -0
- package/dist/real-data/real-data.d.ts +76 -0
- package/dist/real-data/real-data.js +156 -0
- package/fixtures/kg-golden/README.md +136 -0
- package/fixtures/kg-golden/ask-questions.json +458 -0
- package/fixtures/kg-golden/meetings/m01-northwind-pilot-kickoff.json +792 -0
- package/fixtures/kg-golden/meetings/m02-halden-migration-planning.json +814 -0
- package/fixtures/kg-golden/meetings/m03-larkspur-weekly-sync.json +601 -0
- package/fixtures/kg-golden/meetings/m04-corvid-intake-discovery.json +690 -0
- package/fixtures/kg-golden/meetings/m05-hosting-vendor-selection.json +631 -0
- package/fixtures/kg-golden/meetings/m06-northwind-floor-data-review.json +649 -0
- package/fixtures/kg-golden/meetings/m07-gullwing-intro-notes.json +428 -0
- package/fixtures/kg-golden/meetings/m08-halden-migration-checkpoint.json +672 -0
- package/fixtures/kg-golden/meetings/m09-northwind-platform-org-update.json +711 -0
- package/fixtures/kg-golden/meetings/m10-corvid-prototype-review.json +623 -0
- package/fixtures/kg-golden/meetings/m11-solvane-grid-dashboard-scoping.json +689 -0
- package/fixtures/kg-golden/meetings/m12-northwind-pilot-replan.json +681 -0
- package/fixtures/kg-golden/meetings/m13-halden-cutover-go-no-go.json +721 -0
- package/fixtures/kg-golden/meetings/m14-gullwing-discovery-readout.json +719 -0
- package/fixtures/kg-golden/meetings/m15-halden-handover-ravi-leaving.json +716 -0
- package/fixtures/kg-golden/meetings/m16-varden-claims-portal-intro.json +704 -0
- package/fixtures/kg-golden/meetings/m17-corvid-usability-debrief.json +572 -0
- package/fixtures/kg-golden/meetings/m18-hosting-decision-revisited.json +655 -0
- package/fixtures/kg-golden/meetings/m19-solvane-integration-kickoff.json +726 -0
- package/fixtures/kg-golden/meetings/m20-northwind-go-live-retro.json +673 -0
- package/fixtures/kg-golden/meetings/m21-quarterly-account-review.json +449 -0
- package/fixtures/kg-golden/meetings/m22-halden-intro-call-2020.json +558 -0
- package/fixtures/kg-golden/meetings/m23-gullwing-model-review.json +702 -0
- package/fixtures/kg-golden/meetings/m24-varden-discovery-midpoint.json +643 -0
- package/fixtures/kg-golden/meetings/m25-corvid-rollout-planning.json +642 -0
- package/fixtures/kg-golden/meetings/m26-larkspur-engineering-prep.json +695 -0
- package/fixtures/kg-golden/meetings/m27-solvane-phase-one-demo.json +649 -0
- package/fixtures/kg-golden/meetings/m28-northwind-building-5-scoping.json +715 -0
- package/fixtures/kg-golden/meetings/m29-halden-southern-cutover-review.json +752 -0
- package/fixtures/kg-golden/meetings/m30-varden-portal-build-kickoff.json +697 -0
- package/fixtures/kg-golden/meetings/m31-gullwing-rollout-renewal.json +681 -0
- package/fixtures/kg-golden/meetings/m32-brambleway-weekly-sync.json +472 -0
- package/fixtures/kg-golden/meetings/m33-nina-oliver-one-on-one.json +342 -0
- package/fixtures/kg-golden/meetings/m34-coffee-with-dana.json +249 -0
- package/fixtures/kg-golden/meetings/m35-brambleway-june-sync.json +553 -0
- package/fixtures/kg-golden/meetings/m36-copenhagen-conference-planning.json +296 -0
- package/host/generated/client.d.ts +1 -0
- package/host/generated/client.js +5 -0
- package/host/generated/default.d.ts +1 -0
- package/host/generated/default.js +5 -0
- package/host/generated/edge.d.ts +1 -0
- package/host/generated/edge.js +1658 -0
- package/host/generated/index-browser.js +1684 -0
- package/host/generated/index.d.ts +192335 -0
- package/host/generated/index.js +1658 -0
- package/host/generated/package.json +139 -0
- package/host/generated/query_compiler_fast_bg.js +2 -0
- package/host/generated/query_compiler_fast_bg.wasm +0 -0
- package/host/generated/query_compiler_fast_bg.wasm-base64.js +2 -0
- package/host/generated/runtime/client.d.ts +3433 -0
- package/host/generated/runtime/client.js +110 -0
- package/host/generated/runtime/index-browser.d.ts +90 -0
- package/host/generated/runtime/index-browser.js +6 -0
- package/host/generated/runtime/wasm-compiler-edge.js +100 -0
- package/host/generated/schema.prisma +6312 -0
- package/host/generated/wasm-edge-light-loader.mjs +5 -0
- package/host/generated/wasm-worker-loader.mjs +5 -0
- package/host/prisma/fragments/kvox.ask.prisma +126 -0
- package/host/prisma/fragments/kvox.graph.prisma +1073 -0
- package/host/prisma/fragments/kvox.notes.prisma +787 -0
- package/host/prisma/fragments/kvox.search.prisma +211 -0
- package/host/prisma/fragments/kvox.transcripts.prisma +790 -0
- package/host/prisma/kvox.lock +101 -0
- package/host/prisma/migrations/20240101000000_initial/migration.sql +297 -0
- package/host/prisma/migrations/20240101000001_add_personal_access_tokens/migration.sql +31 -0
- package/host/prisma/migrations/20240101000002_add_credentials/migration.sql +20 -0
- package/host/prisma/migrations/20240101000003_add_notification_deliveries/migration.sql +27 -0
- package/host/prisma/migrations/20240101000004_drop_stale_uuid_defaults/migration.sql +35 -0
- package/host/prisma/migrations/20240101000005_add_notifications/migration.sql +22 -0
- package/host/prisma/migrations/20240101000006_add_push_subscriptions/migration.sql +25 -0
- package/host/prisma/migrations/20240101000007_add_jobs/migration.sql +110 -0
- package/host/prisma/migrations/20240101000008_add_worker_nodes/migration.sql +90 -0
- package/host/prisma/migrations/20240101000009_add_database_backup_runs/migration.sql +104 -0
- package/host/prisma/migrations/20240101000010_add_notification_broadcasts/migration.sql +57 -0
- package/host/prisma/migrations/20240101000011_add_backup_run_job_link/migration.sql +87 -0
- package/host/prisma/migrations/20240101000012_add_job_node_secrets/migration.sql +61 -0
- package/host/prisma/migrations/20240101000013_add_backup_run_pg_dump_version/migration.sql +26 -0
- package/host/prisma/migrations/20240101000014_add_job_claim_token/migration.sql +92 -0
- package/host/prisma/migrations/20240101000015_add_ai_platform/migration.sql +108 -0
- package/host/prisma/migrations/20240101000016_revoke_viewer_ai_use/migration.sql +32 -0
- package/host/prisma/migrations/20240101000017_add_user_credentials/migration.sql +29 -0
- package/host/prisma/migrations/20240101000018_add_device_session_credential_link/migration.sql +52 -0
- package/host/prisma/migrations/20240101000019_add_worker_node_vitals/migration.sql +3 -0
- package/host/prisma/migrations/20240101000020_add_job_trace_context/migration.sql +6 -0
- package/host/prisma/migrations/20240101000021_add_retention_created_at_indexes/migration.sql +13 -0
- package/host/prisma/migrations/20240101000022_add_organizations/migration.sql +142 -0
- package/host/prisma/migrations/20240101000023_split_system_org_roles/migration.sql +131 -0
- package/host/prisma/migrations/20240101000024_org_scoped_rls/migration.sql +215 -0
- package/host/prisma/migrations/20240101000025_add_groups/migration.sql +165 -0
- package/host/prisma/migrations/20240101000026_add_grants/migration.sql +117 -0
- package/host/prisma/migrations/20240101000027_add_org_settings/migration.sql +49 -0
- package/host/prisma/migrations/20240101000028_add_org_credentials/migration.sql +54 -0
- package/host/prisma/migrations/20240101000029_add_jobs_org_id/migration.sql +18 -0
- package/host/prisma/migrations/20240101000030_add_jobs_org_id_status_index/migration.sql +6 -0
- package/host/prisma/migrations/20240101000031_add_broadcast_target_org/migration.sql +21 -0
- package/host/prisma/migrations/20240101000032_add_android_app/migration.sql +60 -0
- package/host/prisma/migrations/20240101000033_open_job_reason/migration.sql +16 -0
- package/host/prisma/migrations/20240101000034_ai_usage_feature/migration.sql +37 -0
- package/host/prisma/migrations/20240101000035_add_app_metadata/migration.sql +49 -0
- package/host/prisma/migrations/20240101000036_open_sharing_vocabularies/migration.sql +97 -0
- package/host/prisma/migrations/20260914130000_kvox_add_transcripts/migration.sql +322 -0
- package/host/prisma/migrations/20260914140000_kvox_add_notes/migration.sql +311 -0
- package/host/prisma/migrations/20260915120000_kvox_add_search_vectors/migration.sql +150 -0
- package/host/prisma/migrations/20260915130000_kvox_add_search_embeddings/migration.sql +362 -0
- package/host/prisma/migrations/20260915210000_kvox_add_note_title_source/migration.sql +30 -0
- package/host/prisma/migrations/20260923120000_kvox_add_note_generation_context_snapshot/migration.sql +24 -0
- package/host/prisma/migrations/20260923130000_kvox_add_user_hidden_note_templates/migration.sql +35 -0
- package/host/prisma/migrations/20260923140000_kvox_add_transcript_speaker_identities/migration.sql +6 -0
- package/host/prisma/migrations/20260923150000_kvox_transcript_name_checks/migration.sql +95 -0
- package/host/prisma/migrations/20260923160000_kvox_add_note_body_format/migration.sql +27 -0
- package/host/prisma/migrations/20260923170000_kvox_add_note_version_body_format/migration.sql +23 -0
- package/host/prisma/migrations/20260926012548_kvox_add_knowledge_graph/migration.sql +680 -0
- package/host/prisma/migrations/20260926030000_kvox_fix_kg_relations_valid_precision_chk/migration.sql +29 -0
- package/host/prisma/migrations/20260926030100_kvox_add_transcript_recorded_at/migration.sql +9 -0
- package/host/prisma/migrations/20260926030200_kvox_kg_evidence_invariant/migration.sql +60 -0
- package/host/prisma/migrations/20260926040000_kvox_add_kg_graph_layouts/migration.sql +30 -0
- package/host/prisma/migrations/20260926050000_kvox_add_ask_conversations/migration.sql +99 -0
- package/host/prisma/migrations/20260926231512_kvox_add_kg_exports/migration.sql +52 -0
- package/host/prisma/migrations/20261016120000_kvox_transcripts_org_id/migration.sql +49 -0
- package/host/prisma/platform.lock +319 -0
- package/host/prisma/schema/app.kvox.ask.prisma +110 -0
- package/host/prisma/schema/app.kvox.graph.prisma +1030 -0
- package/host/prisma/schema/app.kvox.notes.prisma +746 -0
- package/host/prisma/schema/app.kvox.search.prisma +208 -0
- package/host/prisma/schema/app.kvox.transcripts.prisma +726 -0
- package/host/prisma/schema/platform.ai.prisma +419 -0
- package/host/prisma/schema/platform.android-app.prisma +46 -0
- package/host/prisma/schema/platform.base.prisma +15 -0
- package/host/prisma/schema/platform.credentials.prisma +156 -0
- package/host/prisma/schema/platform.db-backup.prisma +359 -0
- package/host/prisma/schema/platform.identity.prisma +638 -0
- package/host/prisma/schema/platform.jobs.prisma +824 -0
- package/host/prisma/schema/platform.notifications.prisma +641 -0
- package/host/prisma/schema/platform.settings.prisma +53 -0
- package/host/prisma/schema/platform.sharing.prisma +208 -0
- package/host/prisma/schema/platform.storage.prisma +118 -0
- package/package.json +112 -1
- package/parity/requests.ask.json +2681 -0
- package/parity/requests.m01.json +1554 -0
- package/parity/requests.m02.json +1554 -0
- package/parity/requests.m03.json +1554 -0
- package/parity/requests.m04.json +1554 -0
- package/parity/requests.m05.json +1554 -0
- package/parity/requests.m06.json +1554 -0
- package/parity/requests.m07.json +1554 -0
- package/parity/requests.m08.json +1554 -0
- package/parity/requests.m09.json +1554 -0
- package/parity/requests.m10.json +1554 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Notes (issue #48, epic #45)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- Five tables: `notes`, `note_templates`, `note_generations`, `note_versions`,
|
|
5
|
+
-- `note_exports`. See the block comment above the `Note` model in
|
|
6
|
+
-- prisma/schema.prisma for the full reasoning behind every `onDelete` choice
|
|
7
|
+
-- below (in particular why `owner_id` cascades while the three source
|
|
8
|
+
-- pointers restrict, why `template_id` is `SetNull` rather than `Restrict`,
|
|
9
|
+
-- why `note_generations.job_id` mirrors `DatabaseBackupRun.jobId`/
|
|
10
|
+
-- `TranscriptExport.jobId`, and why `note_exports`' content-addressed lookup
|
|
11
|
+
-- is a plain non-unique index rather than the `@@unique` docs/specs/notes.md
|
|
12
|
+
-- §4.6's prose names), and docs/specs/notes.md §1, §4, §6, §7 for the
|
|
13
|
+
-- product reasoning this file intentionally does not restate at length.
|
|
14
|
+
--
|
|
15
|
+
-- NO HAND-WRITTEN, PRISMA-INEXPRESSIBLE CONSTRAINTS IN THIS MIGRATION, unlike
|
|
16
|
+
-- `20260914130000_add_transcripts` (which needed one, a partial unique index
|
|
17
|
+
-- for `transcript_speakers.label`). Every unique constraint below
|
|
18
|
+
-- (`note_templates` on `(owner_id, name)`, `note_generations.job_id`,
|
|
19
|
+
-- `note_versions` on `(note_id, version)` and `(note_id, client_batch_id)`,
|
|
20
|
+
-- `note_exports.job_id`) is a plain, fully Prisma-expressible unique index,
|
|
21
|
+
-- and the content-addressed export lookup is a plain non-unique index —
|
|
22
|
+
-- there is nothing here Prisma's own DSL cannot say.
|
|
23
|
+
-- =============================================================================
|
|
24
|
+
|
|
25
|
+
-- CreateEnum
|
|
26
|
+
CREATE TYPE "NoteStatus" AS ENUM ('draft', 'generating', 'ready', 'failed', 'deleting');
|
|
27
|
+
|
|
28
|
+
-- CreateEnum
|
|
29
|
+
CREATE TYPE "NoteSourceType" AS ENUM ('transcript', 'note', 'document');
|
|
30
|
+
|
|
31
|
+
-- CreateEnum
|
|
32
|
+
CREATE TYPE "NoteGenerationKind" AS ENUM ('create', 'regenerate', 'preview');
|
|
33
|
+
|
|
34
|
+
-- CreateEnum
|
|
35
|
+
CREATE TYPE "NoteGenerationStatus" AS ENUM ('pending', 'streaming', 'succeeded', 'failed');
|
|
36
|
+
|
|
37
|
+
-- CreateEnum
|
|
38
|
+
CREATE TYPE "NoteGenerationErrorClass" AS ENUM ('auth', 'refusal', 'rate_limit', 'other');
|
|
39
|
+
|
|
40
|
+
-- CreateEnum
|
|
41
|
+
CREATE TYPE "NoteVersionKind" AS ENUM ('ai_generated', 'edit', 'restore');
|
|
42
|
+
|
|
43
|
+
-- CreateEnum
|
|
44
|
+
CREATE TYPE "NoteExportStatus" AS ENUM ('pending', 'ready', 'failed');
|
|
45
|
+
|
|
46
|
+
-- CreateTable
|
|
47
|
+
CREATE TABLE "notes" (
|
|
48
|
+
"id" UUID NOT NULL,
|
|
49
|
+
"owner_id" UUID NOT NULL,
|
|
50
|
+
"title" TEXT NOT NULL,
|
|
51
|
+
"body" TEXT NOT NULL DEFAULT '',
|
|
52
|
+
"status" "NoteStatus" NOT NULL DEFAULT 'draft',
|
|
53
|
+
"current_version" INTEGER NOT NULL DEFAULT 0,
|
|
54
|
+
"provider" TEXT,
|
|
55
|
+
"model" TEXT,
|
|
56
|
+
"current_generation_id" UUID,
|
|
57
|
+
"source_type" "NoteSourceType" NOT NULL,
|
|
58
|
+
"source_transcript_id" UUID,
|
|
59
|
+
"source_note_id" UUID,
|
|
60
|
+
"source_object_id" UUID,
|
|
61
|
+
"template_id" UUID,
|
|
62
|
+
"context_text" TEXT,
|
|
63
|
+
"failure_reason" TEXT,
|
|
64
|
+
"deleted_at" TIMESTAMPTZ,
|
|
65
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
66
|
+
"updated_at" TIMESTAMPTZ NOT NULL,
|
|
67
|
+
|
|
68
|
+
CONSTRAINT "notes_pkey" PRIMARY KEY ("id")
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
-- CreateTable
|
|
72
|
+
CREATE TABLE "note_templates" (
|
|
73
|
+
"id" UUID NOT NULL,
|
|
74
|
+
"owner_id" UUID,
|
|
75
|
+
"name" TEXT NOT NULL,
|
|
76
|
+
"description" TEXT NOT NULL,
|
|
77
|
+
"instructions" TEXT NOT NULL,
|
|
78
|
+
"output_format" TEXT NOT NULL,
|
|
79
|
+
"structure" JSONB NOT NULL,
|
|
80
|
+
"tone" TEXT,
|
|
81
|
+
"length" TEXT,
|
|
82
|
+
"model" TEXT,
|
|
83
|
+
"is_archived" BOOLEAN NOT NULL DEFAULT false,
|
|
84
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
85
|
+
"updated_at" TIMESTAMPTZ NOT NULL,
|
|
86
|
+
|
|
87
|
+
CONSTRAINT "note_templates_pkey" PRIMARY KEY ("id")
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
-- CreateTable
|
|
91
|
+
CREATE TABLE "note_generations" (
|
|
92
|
+
"id" UUID NOT NULL,
|
|
93
|
+
"note_id" UUID,
|
|
94
|
+
"kind" "NoteGenerationKind" NOT NULL,
|
|
95
|
+
"status" "NoteGenerationStatus" NOT NULL DEFAULT 'pending',
|
|
96
|
+
"error_class" "NoteGenerationErrorClass",
|
|
97
|
+
"error_detail" TEXT,
|
|
98
|
+
"template_id" UUID,
|
|
99
|
+
"template_name_snapshot" TEXT NOT NULL,
|
|
100
|
+
"context_text" TEXT,
|
|
101
|
+
"source_type" "NoteSourceType" NOT NULL,
|
|
102
|
+
"source_transcript_id" UUID,
|
|
103
|
+
"source_note_id" UUID,
|
|
104
|
+
"source_object_id" UUID,
|
|
105
|
+
"provider_id" TEXT NOT NULL,
|
|
106
|
+
"model" TEXT NOT NULL,
|
|
107
|
+
"content" TEXT NOT NULL DEFAULT '',
|
|
108
|
+
"last_event_id" INTEGER NOT NULL DEFAULT 0,
|
|
109
|
+
"prompt_tokens" INTEGER,
|
|
110
|
+
"completion_tokens" INTEGER,
|
|
111
|
+
"job_id" UUID,
|
|
112
|
+
"expires_at" TIMESTAMPTZ,
|
|
113
|
+
"started_at" TIMESTAMPTZ,
|
|
114
|
+
"completed_at" TIMESTAMPTZ,
|
|
115
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
116
|
+
|
|
117
|
+
CONSTRAINT "note_generations_pkey" PRIMARY KEY ("id")
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
-- CreateTable
|
|
121
|
+
CREATE TABLE "note_versions" (
|
|
122
|
+
"id" UUID NOT NULL,
|
|
123
|
+
"note_id" UUID NOT NULL,
|
|
124
|
+
"version" INTEGER NOT NULL,
|
|
125
|
+
"kind" "NoteVersionKind" NOT NULL,
|
|
126
|
+
"body" TEXT NOT NULL,
|
|
127
|
+
"summary" TEXT,
|
|
128
|
+
"author_id" UUID,
|
|
129
|
+
"generation_id" UUID,
|
|
130
|
+
"restored_from_version" INTEGER,
|
|
131
|
+
"client_batch_id" TEXT,
|
|
132
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
133
|
+
|
|
134
|
+
CONSTRAINT "note_versions_pkey" PRIMARY KEY ("id")
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
-- CreateTable
|
|
138
|
+
CREATE TABLE "note_exports" (
|
|
139
|
+
"id" UUID NOT NULL,
|
|
140
|
+
"note_id" UUID NOT NULL,
|
|
141
|
+
"version" INTEGER NOT NULL,
|
|
142
|
+
"format" TEXT NOT NULL,
|
|
143
|
+
"options" JSONB NOT NULL,
|
|
144
|
+
"options_hash" TEXT NOT NULL,
|
|
145
|
+
"status" "NoteExportStatus" NOT NULL DEFAULT 'pending',
|
|
146
|
+
"object_id" UUID,
|
|
147
|
+
"job_id" UUID,
|
|
148
|
+
"requested_by_id" UUID NOT NULL,
|
|
149
|
+
"error" TEXT,
|
|
150
|
+
"expires_at" TIMESTAMPTZ NOT NULL,
|
|
151
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
152
|
+
|
|
153
|
+
CONSTRAINT "note_exports_pkey" PRIMARY KEY ("id")
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
-- CreateIndex
|
|
157
|
+
-- "This owner's notes, newest first" — the notes library list view.
|
|
158
|
+
CREATE INDEX "notes_owner_id_updated_at_idx" ON "notes"("owner_id", "updated_at" DESC);
|
|
159
|
+
|
|
160
|
+
-- CreateIndex
|
|
161
|
+
-- "Every note in state X" — notes.housekeeping's scans (#53) and any future
|
|
162
|
+
-- admin-adjacent health check.
|
|
163
|
+
CREATE INDEX "notes_status_idx" ON "notes"("status");
|
|
164
|
+
|
|
165
|
+
-- CreateIndex
|
|
166
|
+
-- A user may not have two templates sharing a name (issue #48). PostgreSQL's
|
|
167
|
+
-- standard NULLS-DISTINCT behaviour means this is a no-op among built-ins —
|
|
168
|
+
-- every built-in's owner_id is permanently NULL, and NULL is never equal to
|
|
169
|
+
-- NULL for uniqueness purposes, so any number of built-in rows may coexist
|
|
170
|
+
-- regardless of name. See the block comment above the Note model in
|
|
171
|
+
-- schema.prisma for the full reasoning.
|
|
172
|
+
CREATE UNIQUE INDEX "note_templates_owner_id_name_key" ON "note_templates"("owner_id", "name");
|
|
173
|
+
|
|
174
|
+
-- CreateIndex
|
|
175
|
+
-- One note_generations row per producing job — mirrors
|
|
176
|
+
-- note_exports_job_id_key/transcript_exports_job_id_key exactly: a second
|
|
177
|
+
-- generation claiming the SAME job is unrepresentable.
|
|
178
|
+
CREATE UNIQUE INDEX "note_generations_job_id_key" ON "note_generations"("job_id");
|
|
179
|
+
|
|
180
|
+
-- CreateIndex
|
|
181
|
+
-- The version sequence itself.
|
|
182
|
+
CREATE UNIQUE INDEX "note_versions_note_id_version_key" ON "note_versions"("note_id", "version");
|
|
183
|
+
|
|
184
|
+
-- CreateIndex
|
|
185
|
+
-- The idempotent-retry key for a batch save (#53). A plain UNIQUE index,
|
|
186
|
+
-- deliberately not hand-written: PostgreSQL's standard NULLS-DISTINCT
|
|
187
|
+
-- behaviour already gives exactly the wanted semantics for this nullable
|
|
188
|
+
-- column (any number of null-client_batch_id versions, e.g. AI-generated or
|
|
189
|
+
-- restore, may coexist; at most one per note for any given non-null value).
|
|
190
|
+
CREATE UNIQUE INDEX "note_versions_note_id_client_batch_id_key" ON "note_versions"("note_id", "client_batch_id");
|
|
191
|
+
|
|
192
|
+
-- CreateIndex
|
|
193
|
+
-- One note_exports row per rendering job — mirrors
|
|
194
|
+
-- transcript_exports_job_id_key exactly: a second render for the SAME job is
|
|
195
|
+
-- unrepresentable.
|
|
196
|
+
CREATE UNIQUE INDEX "note_exports_job_id_key" ON "note_exports"("job_id");
|
|
197
|
+
|
|
198
|
+
-- CreateIndex
|
|
199
|
+
-- The reuse-by-content-hash lookup (docs/specs/notes.md §8.4). Deliberately
|
|
200
|
+
-- NOT unique — mirrors transcript_exports_lookup_idx's real shape rather
|
|
201
|
+
-- than spec §4.6's own shorthand "unique" wording: an expired or failed row
|
|
202
|
+
-- may briefly coexist with a fresh one sharing the same key before
|
|
203
|
+
-- housekeeping removes the stale one. Named explicitly
|
|
204
|
+
-- (`map: "note_exports_lookup_idx"` in schema.prisma) because the default
|
|
205
|
+
-- four-column name is one byte over PostgreSQL's 63-byte identifier limit.
|
|
206
|
+
CREATE INDEX "note_exports_lookup_idx" ON "note_exports"("note_id", "version", "format", "options_hash");
|
|
207
|
+
|
|
208
|
+
-- AddForeignKey
|
|
209
|
+
-- Cascade: a note's only reason to exist is that exactly one user owns it,
|
|
210
|
+
-- and this application's access model has no path to it once its owner is
|
|
211
|
+
-- gone (no notes:read_any, docs/specs/notes.md §6.3). See the block comment
|
|
212
|
+
-- above the Note model for the full reasoning.
|
|
213
|
+
ALTER TABLE "notes" ADD CONSTRAINT "notes_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
214
|
+
|
|
215
|
+
-- AddForeignKey
|
|
216
|
+
-- SetNull: losing the pointer loses only "which generation is live right
|
|
217
|
+
-- now," never the generation row itself or any note_versions row it already
|
|
218
|
+
-- produced.
|
|
219
|
+
ALTER TABLE "notes" ADD CONSTRAINT "notes_current_generation_id_fkey" FOREIGN KEY ("current_generation_id") REFERENCES "note_generations"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
220
|
+
|
|
221
|
+
-- AddForeignKey
|
|
222
|
+
-- Restrict, with a REAL user-visible consequence unlike the belt-and-
|
|
223
|
+
-- suspenders Restrict on storage_objects below: a transcript that has
|
|
224
|
+
-- produced a note cannot be deleted while that note still exists.
|
|
225
|
+
-- DELETE /api/transcripts/:id gains a pre-check that 409s naming the
|
|
226
|
+
-- blocking note id(s) before this constraint is ever reached (see the block
|
|
227
|
+
-- comment above the Note model — that pre-check is backend work landing
|
|
228
|
+
-- alongside this migration, not part of this migration itself).
|
|
229
|
+
ALTER TABLE "notes" ADD CONSTRAINT "notes_source_transcript_id_fkey" FOREIGN KEY ("source_transcript_id") REFERENCES "transcripts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
230
|
+
|
|
231
|
+
-- AddForeignKey
|
|
232
|
+
-- Restrict, self-referencing: a note cited as another note's source cannot
|
|
233
|
+
-- be deleted while that derivative note still exists. #53's own
|
|
234
|
+
-- notes.service.ts delete guard is the note-on-note counterpart to the
|
|
235
|
+
-- transcript pre-check above.
|
|
236
|
+
ALTER TABLE "notes" ADD CONSTRAINT "notes_source_note_id_fkey" FOREIGN KEY ("source_note_id") REFERENCES "notes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
237
|
+
|
|
238
|
+
-- AddForeignKey
|
|
239
|
+
-- Restrict, belt-and-suspenders: the uploaded document is
|
|
240
|
+
-- managed_by: 'notes', so the generic storage DELETE already 409s on it
|
|
241
|
+
-- before this constraint is ever tested.
|
|
242
|
+
ALTER TABLE "notes" ADD CONSTRAINT "notes_source_object_id_fkey" FOREIGN KEY ("source_object_id") REFERENCES "storage_objects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
243
|
+
|
|
244
|
+
-- AddForeignKey
|
|
245
|
+
-- SetNull, not Restrict: a template is a reusable recipe, not evidence.
|
|
246
|
+
-- note_generations.template_name_snapshot keeps "which recipe produced this"
|
|
247
|
+
-- as a plain string forever, independent of whether the template row itself
|
|
248
|
+
-- survives.
|
|
249
|
+
ALTER TABLE "notes" ADD CONSTRAINT "notes_template_id_fkey" FOREIGN KEY ("template_id") REFERENCES "note_templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
250
|
+
|
|
251
|
+
-- AddForeignKey
|
|
252
|
+
-- Cascade: a user's own custom template has no meaning once its owner is
|
|
253
|
+
-- gone. Never fires for a built-in row, whose owner_id is permanently NULL.
|
|
254
|
+
ALTER TABLE "note_templates" ADD CONSTRAINT "note_templates_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
255
|
+
|
|
256
|
+
-- AddForeignKey
|
|
257
|
+
-- Cascade: a note_generations row belongs entirely to the note that owns
|
|
258
|
+
-- it (NULL note_id for a preview, which has none).
|
|
259
|
+
ALTER TABLE "note_generations" ADD CONSTRAINT "note_generations_note_id_fkey" FOREIGN KEY ("note_id") REFERENCES "notes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
260
|
+
|
|
261
|
+
-- AddForeignKey
|
|
262
|
+
-- SetNull, matching notes.template_id above.
|
|
263
|
+
ALTER TABLE "note_generations" ADD CONSTRAINT "note_generations_template_id_fkey" FOREIGN KEY ("template_id") REFERENCES "note_templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
264
|
+
|
|
265
|
+
-- AddForeignKey
|
|
266
|
+
-- SetNull, mirroring note_exports_job_id_fkey/transcript_exports_job_id_fkey
|
|
267
|
+
-- exactly: job.history.purge deletes jobs rows on a retention schedule
|
|
268
|
+
-- independent of this row's own lifetime, and losing the LINK must never
|
|
269
|
+
-- mean losing the GENERATION (its buffered content is what a
|
|
270
|
+
-- note_versions.ai_generated row was stamped from).
|
|
271
|
+
ALTER TABLE "note_generations" ADD CONSTRAINT "note_generations_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "jobs"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
272
|
+
|
|
273
|
+
-- AddForeignKey
|
|
274
|
+
ALTER TABLE "note_versions" ADD CONSTRAINT "note_versions_note_id_fkey" FOREIGN KEY ("note_id") REFERENCES "notes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
275
|
+
|
|
276
|
+
-- AddForeignKey
|
|
277
|
+
-- SetNull: a version is permanent history and must survive its human
|
|
278
|
+
-- author's own account being deleted. NULL already means "the AI" for a
|
|
279
|
+
-- different reason (note.generate never sets an author at all) — this FK
|
|
280
|
+
-- action only ever fires for a human-authored version whose author was
|
|
281
|
+
-- later deleted.
|
|
282
|
+
ALTER TABLE "note_versions" ADD CONSTRAINT "note_versions_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
283
|
+
|
|
284
|
+
-- AddForeignKey
|
|
285
|
+
-- SetNull: the version's body already carries everything durable; losing
|
|
286
|
+
-- the pointer back to the generation that produced it loses only "which
|
|
287
|
+
-- streaming run wrote this," never the content itself.
|
|
288
|
+
ALTER TABLE "note_versions" ADD CONSTRAINT "note_versions_generation_id_fkey" FOREIGN KEY ("generation_id") REFERENCES "note_generations"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
289
|
+
|
|
290
|
+
-- AddForeignKey
|
|
291
|
+
ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_note_id_fkey" FOREIGN KEY ("note_id") REFERENCES "notes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
292
|
+
|
|
293
|
+
-- AddForeignKey
|
|
294
|
+
-- Restrict, matching every storage_objects FK on the transcripts feature:
|
|
295
|
+
-- only notes.housekeeping's expiry sweep may remove the rendered file, and
|
|
296
|
+
-- only after deleting this row first.
|
|
297
|
+
ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "storage_objects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
298
|
+
|
|
299
|
+
-- AddForeignKey
|
|
300
|
+
-- SetNull, mirroring transcript_exports_job_id_fkey exactly: job.history
|
|
301
|
+
-- .purge deletes jobs rows on a retention schedule independent of this
|
|
302
|
+
-- row's own 7-day export expiry, and losing the LINK must never mean losing
|
|
303
|
+
-- the EXPORT.
|
|
304
|
+
ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "jobs"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
305
|
+
|
|
306
|
+
-- AddForeignKey
|
|
307
|
+
-- Cascade: unlike every SetNull audit column elsewhere in this schema, an
|
|
308
|
+
-- export is a disposable, byte-for-byte reproducible artifact with a 7-day
|
|
309
|
+
-- expiry already built in — nothing is lost by deleting a pending/ready
|
|
310
|
+
-- export row alongside the account that requested it.
|
|
311
|
+
ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_requested_by_id_fkey" FOREIGN KEY ("requested_by_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Full-text search: generated tsvector columns + GIN indexes (issue #174,
|
|
3
|
+
-- epic #164)
|
|
4
|
+
-- =============================================================================
|
|
5
|
+
--
|
|
6
|
+
-- ⚠ THIS MIGRATION TAKES `ACCESS EXCLUSIVE` ON THREE TABLES AND REWRITES
|
|
7
|
+
-- THEM: `transcript_segments`, `notes`, `transcripts`. `ADD COLUMN ...
|
|
8
|
+
-- GENERATED ALWAYS AS (...) STORED` is not a metadata-only change — Postgres
|
|
9
|
+
-- computes the expression for every existing row and rewrites the whole
|
|
10
|
+
-- table to store it, holding `ACCESS EXCLUSIVE` on that table for the
|
|
11
|
+
-- duration (no reads, no writes, nothing, from any other session). On a
|
|
12
|
+
-- large corpus this is MINUTES of a hard lock per table. An operator running
|
|
13
|
+
-- this against production must plan a maintenance window (or accept the
|
|
14
|
+
-- outage) exactly the way a large `ALTER TABLE` anywhere else in this
|
|
15
|
+
-- codebase would require — this is not a `CREATE INDEX CONCURRENTLY`
|
|
16
|
+
-- migration and cannot be made into one, because a generated column's
|
|
17
|
+
-- initial computation is inherently a table rewrite, not an index build.
|
|
18
|
+
--
|
|
19
|
+
-- AND THAT REWRITE IS THE BACKFILL. There is no job, no API key, no
|
|
20
|
+
-- on-demand action, no cron sweep that populates these columns after the
|
|
21
|
+
-- fact. The moment this migration commits, `search_vector`/
|
|
22
|
+
-- `title_search_vector` exist and are already correct for every row already
|
|
23
|
+
-- in the table, because Postgres computed them as part of the same rewrite
|
|
24
|
+
-- that added the column. Every transcript segment, every note, every
|
|
25
|
+
-- transcript title already in this database is searchable the instant this
|
|
26
|
+
-- lands, with zero application code ever running. That is the entire reason
|
|
27
|
+
-- full-text search is layer 1 of epic #164: it needed no ingestion pipeline
|
|
28
|
+
-- of its own, only this one migration.
|
|
29
|
+
--
|
|
30
|
+
-- GENERATED COLUMNS, NOT TRIGGERS — and the reason is RESTORE, not tidiness.
|
|
31
|
+
-- This repository ships a real database restore
|
|
32
|
+
-- (docs/specs/database-restore.md), and `pg_restore` loads data with
|
|
33
|
+
-- `session_replication_role = replica` in effect for the load session (the
|
|
34
|
+
-- same mode `pg_restore --disable-triggers` documents). A regular
|
|
35
|
+
-- `AFTER INSERT/UPDATE` trigger DOES NOT FIRE under that mode — so a
|
|
36
|
+
-- trigger-based implementation would restore a database that LOOKS whole
|
|
37
|
+
-- (every row present, every column present) but whose search index is
|
|
38
|
+
-- silently empty, with nothing anywhere — no error, no warning, no failed
|
|
39
|
+
-- job — telling anyone it happened. A `GENERATED ... STORED` column is
|
|
40
|
+
-- computed by the storage layer itself on every tuple, independent of
|
|
41
|
+
-- triggers and independent of `session_replication_role`, and cannot be
|
|
42
|
+
-- switched off by a restore path that has no idea this column exists.
|
|
43
|
+
--
|
|
44
|
+
-- THE TWO-ARGUMENT `to_tsvector(regconfig, text)` FORM IS LOAD-BEARING.
|
|
45
|
+
-- `to_tsvector(text)` — the one-argument form — is STABLE, not IMMUTABLE:
|
|
46
|
+
-- it reads `default_text_search_config` from session state to decide which
|
|
47
|
+
-- configuration to use, and a generated column's expression is REQUIRED by
|
|
48
|
+
-- Postgres to be IMMUTABLE (a value that must be recomputable identically
|
|
49
|
+
-- from the row alone, forever, independent of session/GUC state). Postgres
|
|
50
|
+
-- enforces this at `ALTER TABLE` time — `ADD COLUMN ... GENERATED ALWAYS AS
|
|
51
|
+
-- (to_tsvector(coalesce(text, ''))) STORED` is REJECTED outright with
|
|
52
|
+
-- "generation expression is not immutable". The two-argument form used
|
|
53
|
+
-- below, with `'english'::regconfig` as an explicit literal, has no such
|
|
54
|
+
-- session dependency and IS immutable — it is the ONLY form Postgres will
|
|
55
|
+
-- accept here. Do not "simplify" this to the one-argument form: it does not
|
|
56
|
+
-- merely behave differently, it does not apply at all, and the failure mode
|
|
57
|
+
-- is this whole migration erroring out rather than something subtly wrong.
|
|
58
|
+
-- A successful `prisma migrate` against a real Postgres (as this issue's
|
|
59
|
+
-- verification step requires) is the actual proof the expression is
|
|
60
|
+
-- immutable — there is no static check that substitutes for it.
|
|
61
|
+
--
|
|
62
|
+
-- THE `'english'` CONFIGURATION IS A DELIBERATE, STATED v1 LIMITATION, not
|
|
63
|
+
-- an oversight. `transcripts.language` exists and is nullable, so a
|
|
64
|
+
-- per-row configuration is technically expressible (Postgres allows a
|
|
65
|
+
-- generated expression to reference sibling columns) — but it would need a
|
|
66
|
+
-- `regconfig` cast built from arbitrary, unvalidated row data (an invalid
|
|
67
|
+
-- or unrecognized language string then fails the whole insert/update, not
|
|
68
|
+
-- just the search feature), and a GIN index built against a per-row-varying
|
|
69
|
+
-- configuration is effectively unusable for a query issued before the
|
|
70
|
+
-- caller knows which language a given row is in. One corpus-wide
|
|
71
|
+
-- configuration, stated plainly here, is the v1 choice; a later issue can
|
|
72
|
+
-- revisit it once there is a real multi-language corpus to justify the
|
|
73
|
+
-- complexity. The cost of this choice: English-language stemming
|
|
74
|
+
-- ("discussed" matching "discuss") is WRONG for non-English content —
|
|
75
|
+
-- `to_tsvector('english', ...)` will still tokenize non-English text, so
|
|
76
|
+
-- exact-token matching keeps working, but stemming, stop-word removal and
|
|
77
|
+
-- ranking are all tuned for English regardless of what a row actually
|
|
78
|
+
-- contains.
|
|
79
|
+
--
|
|
80
|
+
-- WHY `notes.search_vector` IS WEIGHTED A/B AND `transcript_segments
|
|
81
|
+
-- .search_vector` IS NOT: a note has a `title` the user themselves wrote
|
|
82
|
+
-- and a `body` the AI generated (or the user has since edited) — a hit in
|
|
83
|
+
-- the title is real, human-authored evidence the note is about that term,
|
|
84
|
+
-- and `setweight(..., 'A')` on the title plus `setweight(..., 'B')` on the
|
|
85
|
+
-- body lets `ts_rank`/`ts_rank_cd` reflect that when a search matches both
|
|
86
|
+
-- lists. A `transcript_segments` row is one line of spoken text with no
|
|
87
|
+
-- title of its own to weight against — there is nothing to distinguish, so
|
|
88
|
+
-- its vector is a single unweighted `to_tsvector` over `text`.
|
|
89
|
+
--
|
|
90
|
+
-- WHY `transcripts.title_search_vector` EXISTS AS ITS OWN COLUMN rather
|
|
91
|
+
-- than being folded into (or replaced by) the segment vector: a
|
|
92
|
+
-- transcript's title lives on the PARENT row, one string, while its
|
|
93
|
+
-- segments are a separate child table with a many-to-one relationship back
|
|
94
|
+
-- to it. A title match has to be able to surface a transcript that has NO
|
|
95
|
+
-- matching segment at all (a recording titled "Q3 Pricing Review" whose
|
|
96
|
+
-- spoken content never says the word "pricing") — which is only possible
|
|
97
|
+
-- if the title has its own indexed vector to match against, independent of
|
|
98
|
+
-- whatever its segments do or do not contain.
|
|
99
|
+
--
|
|
100
|
+
-- INTENTIONAL SCHEMA DRIFT, named the same way `jobs_active_dedup_uniq_idx`
|
|
101
|
+
-- and `database_backup_runs_active_uniq_idx` are named elsewhere in this
|
|
102
|
+
-- repository: every generation expression below, and all three GIN
|
|
103
|
+
-- indexes, are HAND-WRITTEN in this file only. `schema.prisma` has no DSL
|
|
104
|
+
-- for a generated column's expression and no DSL for an index's access
|
|
105
|
+
-- method (`USING gin`) — the Prisma model below can only declare the
|
|
106
|
+
-- columns exist (as `Unsupported("tsvector")?`) and point a reader here.
|
|
107
|
+
-- `prisma migrate dev`/`diff` will never regenerate this file's contents
|
|
108
|
+
-- and must never be asked to "reconcile" it away. This is the fourth
|
|
109
|
+
-- occurrence of this exact pattern in this schema (`jobs`,
|
|
110
|
+
-- `database_backup_runs`, `transcript_speakers` before it) — see each of
|
|
111
|
+
-- those tables' own migration headers for the precedent.
|
|
112
|
+
-- =============================================================================
|
|
113
|
+
|
|
114
|
+
-- AlterTable: transcript_segments — unweighted, one line of speech with no
|
|
115
|
+
-- title of its own. See the header above for why the two-argument
|
|
116
|
+
-- `to_tsvector` form is REQUIRED (the one-argument form is rejected as
|
|
117
|
+
-- non-immutable).
|
|
118
|
+
ALTER TABLE "transcript_segments"
|
|
119
|
+
ADD COLUMN "search_vector" tsvector
|
|
120
|
+
GENERATED ALWAYS AS (to_tsvector('english'::regconfig, coalesce("text", ''))) STORED;
|
|
121
|
+
|
|
122
|
+
-- CreateIndex
|
|
123
|
+
CREATE INDEX "transcript_segments_search_vector_idx"
|
|
124
|
+
ON "transcript_segments" USING GIN ("search_vector");
|
|
125
|
+
|
|
126
|
+
-- AlterTable: notes — weighted A (title, human-authored) / B (body,
|
|
127
|
+
-- AI-generated or user-edited). See the header above for the ranking
|
|
128
|
+
-- argument this weighting exists to support.
|
|
129
|
+
ALTER TABLE "notes"
|
|
130
|
+
ADD COLUMN "search_vector" tsvector
|
|
131
|
+
GENERATED ALWAYS AS (
|
|
132
|
+
setweight(to_tsvector('english'::regconfig, coalesce("title", '')), 'A') ||
|
|
133
|
+
setweight(to_tsvector('english'::regconfig, coalesce("body", '')), 'B')
|
|
134
|
+
) STORED;
|
|
135
|
+
|
|
136
|
+
-- CreateIndex
|
|
137
|
+
CREATE INDEX "notes_search_vector_idx"
|
|
138
|
+
ON "notes" USING GIN ("search_vector");
|
|
139
|
+
|
|
140
|
+
-- AlterTable: transcripts — the title lives on the parent row and must be
|
|
141
|
+
-- searchable even when no child segment matches at all. See the header
|
|
142
|
+
-- above for why this is a separate column rather than folded into the
|
|
143
|
+
-- segment vector.
|
|
144
|
+
ALTER TABLE "transcripts"
|
|
145
|
+
ADD COLUMN "title_search_vector" tsvector
|
|
146
|
+
GENERATED ALWAYS AS (to_tsvector('english'::regconfig, coalesce("title", ''))) STORED;
|
|
147
|
+
|
|
148
|
+
-- CreateIndex
|
|
149
|
+
CREATE INDEX "transcripts_title_search_vector_idx"
|
|
150
|
+
ON "transcripts" USING GIN ("title_search_vector");
|