-- ================================================================ -- 补丁迁移:覆盖 db push 创建的 6 张表 + 3 列 -- 所有语句使用 IF NOT EXISTS 保证幂等(多次执行不报错) -- 日期:2026-02-27 -- ================================================================ -- ============ iit_schema: 4 张新表 + 1 列 + 1 索引 ============ -- 1. IIT 字段元数据表(REDCap 字段定义缓存) CREATE TABLE IF NOT EXISTS "iit_schema"."field_metadata" ( "id" TEXT NOT NULL, "project_id" TEXT NOT NULL, "field_name" TEXT NOT NULL, "field_label" TEXT NOT NULL, "field_type" TEXT NOT NULL, "form_name" TEXT NOT NULL, "section_header" TEXT, "validation" TEXT, "validation_min" TEXT, "validation_max" TEXT, "choices" TEXT, "required" BOOLEAN NOT NULL DEFAULT false, "branching" TEXT, "alias" TEXT, "rule_source" TEXT, "synced_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "field_metadata_pkey" PRIMARY KEY ("id") ); CREATE UNIQUE INDEX IF NOT EXISTS "unique_iit_field_metadata" ON "iit_schema"."field_metadata"("project_id", "field_name"); CREATE INDEX IF NOT EXISTS "idx_iit_field_metadata_project" ON "iit_schema"."field_metadata"("project_id"); CREATE INDEX IF NOT EXISTS "idx_iit_field_metadata_form" ON "iit_schema"."field_metadata"("project_id", "form_name"); -- 2. IIT 质控日志表 CREATE TABLE IF NOT EXISTS "iit_schema"."qc_logs" ( "id" TEXT NOT NULL, "project_id" TEXT NOT NULL, "record_id" TEXT NOT NULL, "event_id" TEXT, "qc_type" TEXT NOT NULL, "form_name" TEXT, "status" TEXT NOT NULL, "issues" JSONB NOT NULL DEFAULT '[]', "rules_evaluated" INTEGER NOT NULL DEFAULT 0, "rules_skipped" INTEGER NOT NULL DEFAULT 0, "rules_passed" INTEGER NOT NULL DEFAULT 0, "rules_failed" INTEGER NOT NULL DEFAULT 0, "rule_version" TEXT NOT NULL, "inclusion_passed" BOOLEAN, "exclusion_passed" BOOLEAN, "triggered_by" TEXT NOT NULL, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "qc_logs_pkey" PRIMARY KEY ("id") ); CREATE INDEX IF NOT EXISTS "idx_iit_qc_log_record_time" ON "iit_schema"."qc_logs"("project_id", "record_id", "created_at"); CREATE INDEX IF NOT EXISTS "idx_iit_qc_log_status_time" ON "iit_schema"."qc_logs"("project_id", "status", "created_at"); CREATE INDEX IF NOT EXISTS "idx_iit_qc_log_type_time" ON "iit_schema"."qc_logs"("project_id", "qc_type", "created_at"); -- 3. IIT 受试者记录汇总表 CREATE TABLE IF NOT EXISTS "iit_schema"."record_summary" ( "id" TEXT NOT NULL, "project_id" TEXT NOT NULL, "record_id" TEXT NOT NULL, "enrolled_at" TIMESTAMP(3), "enrolled_by" TEXT, "last_updated_at" TIMESTAMP(3) NOT NULL, "last_updated_by" TEXT, "last_form_name" TEXT, "form_status" JSONB NOT NULL DEFAULT '{}', "total_forms" INTEGER NOT NULL DEFAULT 0, "completed_forms" INTEGER NOT NULL DEFAULT 0, "completion_rate" DOUBLE PRECISION NOT NULL DEFAULT 0, "latest_qc_status" TEXT, "latest_qc_at" TIMESTAMP(3), "update_count" INTEGER NOT NULL DEFAULT 0, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP(3) NOT NULL, CONSTRAINT "record_summary_pkey" PRIMARY KEY ("id") ); CREATE UNIQUE INDEX IF NOT EXISTS "unique_iit_record_summary" ON "iit_schema"."record_summary"("project_id", "record_id"); CREATE INDEX IF NOT EXISTS "idx_iit_record_summary_enrolled" ON "iit_schema"."record_summary"("project_id", "enrolled_at"); CREATE INDEX IF NOT EXISTS "idx_iit_record_summary_qc_status" ON "iit_schema"."record_summary"("project_id", "latest_qc_status"); CREATE INDEX IF NOT EXISTS "idx_iit_record_summary_completion" ON "iit_schema"."record_summary"("project_id", "completion_rate"); -- 4. IIT 项目级质控统计汇总表 CREATE TABLE IF NOT EXISTS "iit_schema"."qc_project_stats" ( "id" TEXT NOT NULL, "project_id" TEXT NOT NULL, "total_records" INTEGER NOT NULL DEFAULT 0, "passed_records" INTEGER NOT NULL DEFAULT 0, "failed_records" INTEGER NOT NULL DEFAULT 0, "warning_records" INTEGER NOT NULL DEFAULT 0, "inclusion_met" INTEGER NOT NULL DEFAULT 0, "exclusion_met" INTEGER NOT NULL DEFAULT 0, "avg_completion_rate" DOUBLE PRECISION NOT NULL DEFAULT 0, "updated_at" TIMESTAMP(3) NOT NULL, CONSTRAINT "qc_project_stats_pkey" PRIMARY KEY ("id") ); CREATE UNIQUE INDEX IF NOT EXISTS "qc_project_stats_project_id_key" ON "iit_schema"."qc_project_stats"("project_id"); -- 5. IIT projects 表新增 knowledge_base_id 列 ALTER TABLE "iit_schema"."projects" ADD COLUMN IF NOT EXISTS "knowledge_base_id" TEXT; CREATE INDEX IF NOT EXISTS "idx_iit_project_kb" ON "iit_schema"."projects"("knowledge_base_id"); -- ============ ssa_schema: 1 列 + 2 张新表 ============ -- 6a. SSA Session 新增 data_profile 列(Phase 2A) ALTER TABLE "ssa_schema"."ssa_sessions" ADD COLUMN IF NOT EXISTS "data_profile" JSONB; -- 6. SSA 工作流表 CREATE TABLE IF NOT EXISTS "ssa_schema"."ssa_workflows" ( "id" TEXT NOT NULL DEFAULT gen_random_uuid()::text, "session_id" TEXT NOT NULL, "message_id" TEXT, "status" VARCHAR NOT NULL DEFAULT 'pending', "total_steps" INTEGER NOT NULL, "completed_steps" INTEGER NOT NULL DEFAULT 0, "workflow_plan" JSONB NOT NULL, "reasoning" TEXT, "created_at" TIMESTAMP(3) NOT NULL DEFAULT now(), "started_at" TIMESTAMP(3), "completed_at" TIMESTAMP(3), CONSTRAINT "ssa_workflows_pkey" PRIMARY KEY ("id") ); CREATE INDEX IF NOT EXISTS "idx_ssa_workflow_session" ON "ssa_schema"."ssa_workflows"("session_id"); CREATE INDEX IF NOT EXISTS "idx_ssa_workflow_status" ON "ssa_schema"."ssa_workflows"("status"); -- 外键:ssa_workflows.session_id → ssa_sessions.id DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'ssa_workflows_session_id_fkey' ) THEN ALTER TABLE "ssa_schema"."ssa_workflows" ADD CONSTRAINT "ssa_workflows_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "ssa_schema"."ssa_sessions"("id") ON DELETE CASCADE ON UPDATE CASCADE; END IF; END $$; -- 7. SSA 工作流步骤表 CREATE TABLE IF NOT EXISTS "ssa_schema"."ssa_workflow_steps" ( "id" TEXT NOT NULL DEFAULT gen_random_uuid()::text, "workflow_id" TEXT NOT NULL, "step_order" INTEGER NOT NULL, "tool_code" VARCHAR NOT NULL, "tool_name" VARCHAR NOT NULL, "status" VARCHAR NOT NULL DEFAULT 'pending', "input_params" JSONB, "guardrail_checks" JSONB, "output_result" JSONB, "error_info" JSONB, "execution_ms" INTEGER, "started_at" TIMESTAMP(3), "completed_at" TIMESTAMP(3), CONSTRAINT "ssa_workflow_steps_pkey" PRIMARY KEY ("id") ); CREATE INDEX IF NOT EXISTS "idx_ssa_workflow_step_workflow" ON "ssa_schema"."ssa_workflow_steps"("workflow_id"); CREATE INDEX IF NOT EXISTS "idx_ssa_workflow_step_status" ON "ssa_schema"."ssa_workflow_steps"("status"); -- 外键:ssa_workflow_steps.workflow_id → ssa_workflows.id DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'ssa_workflow_steps_workflow_id_fkey' ) THEN ALTER TABLE "ssa_schema"."ssa_workflow_steps" ADD CONSTRAINT "ssa_workflow_steps_workflow_id_fkey" FOREIGN KEY ("workflow_id") REFERENCES "ssa_schema"."ssa_workflows"("id") ON DELETE CASCADE ON UPDATE CASCADE; END IF; END $$; -- ============ rvw_schema: review_tasks 缺失列 ============ -- 8. review_tasks 后续 prisma db push 新增的 8 个列 ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "selected_agents" TEXT[] DEFAULT ARRAY['editorial','methodology']; ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "editorial_score" DOUBLE PRECISION; ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "methodology_score" DOUBLE PRECISION; ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "methodology_status" TEXT; ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "pico_extract" JSONB; ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "context_data" JSONB; ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "is_archived" BOOLEAN DEFAULT false; ALTER TABLE "rvw_schema"."review_tasks" ADD COLUMN IF NOT EXISTS "archived_at" TIMESTAMP(3); CREATE INDEX IF NOT EXISTS "review_tasks_is_archived_idx" ON "rvw_schema"."review_tasks"("is_archived");