-- 6. check:检查批次、问题、人工核对(FR-M3-02~08) -- 来源:docs/architecture/schema-draft.sql(2026-11-03 验证过的草案) CREATE TABLE check_run ( id uuid PRIMARY KEY, tenant_id uuid NOT NULL, project_id uuid NOT NULL, run_no int NOT NULL, -- 项目内第几次检查 checklist_version_id uuid NOT NULL, checklist_confirmed boolean NOT NULL, -- false 时报告和界面提示「清单未确认」(G7) rule_set_version text NOT NULL, legal_regime text NOT NULL CHECK (legal_regime IN ('gov_procurement', 'tendering', 'unknown')), lot_ids uuid[] NOT NULL DEFAULT '{}', -- 本次检查的包 previous_run_id uuid, -- 状态沿用的来源 status text NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled')), stage text CHECK (stage IN ('prepare_bid', 'match', 'rules', 'verify', 'carry_over', 'summarize')), checkpoints jsonb NOT NULL DEFAULT '{}', progress int NOT NULL DEFAULT 0 CHECK (progress BETWEEN 0 AND 100), cancel_requested_at timestamptz, error_class text CHECK (error_class IN ('infra', 'input', 'content', 'cancelled')), error_code text, error_detail text, billing text NOT NULL CHECK (billing IN ('charged', 'waived_recheck', 'free_plan')), quota_hold_id uuid, prompt_versions jsonb, coverage jsonb, -- 查了什么、没查什么(报告首页) counts jsonb, -- 各级别问题数,结束时写入 queue_job_id bigint, created_by uuid NOT NULL REFERENCES user_account (id), created_at timestamptz NOT NULL DEFAULT now(), started_at timestamptz, finished_at timestamptz, updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, id), UNIQUE (tenant_id, project_id, run_no), CHECK (billing <> 'charged' OR quota_hold_id IS NOT NULL), FOREIGN KEY (tenant_id, project_id) REFERENCES project (tenant_id, id) ON DELETE CASCADE, FOREIGN KEY (tenant_id, checklist_version_id) REFERENCES checklist_version (tenant_id, id) ON DELETE CASCADE, FOREIGN KEY (tenant_id, previous_run_id) REFERENCES check_run (tenant_id, id) ON DELETE SET NULL (previous_run_id), FOREIGN KEY (tenant_id, quota_hold_id) REFERENCES quota_entry (tenant_id, id) ); CREATE UNIQUE INDEX ON check_run (tenant_id, project_id) WHERE status IN ('queued', 'running'); ALTER TABLE project ADD FOREIGN KEY (tenant_id, latest_check_run_id) REFERENCES check_run (tenant_id, id) ON DELETE SET NULL (latest_check_run_id); CREATE TABLE check_run_document ( -- 本次检查用了哪些投标文件版本 tenant_id uuid NOT NULL, check_run_id uuid NOT NULL, document_id uuid NOT NULL, PRIMARY KEY (tenant_id, check_run_id, document_id), FOREIGN KEY (tenant_id, check_run_id) REFERENCES check_run (tenant_id, id) ON DELETE CASCADE, FOREIGN KEY (tenant_id, document_id) REFERENCES source_document (tenant_id, id) ON DELETE CASCADE ); CREATE TABLE finding ( id uuid PRIMARY KEY, tenant_id uuid NOT NULL, project_id uuid NOT NULL, check_run_id uuid NOT NULL, checklist_item_id uuid, -- AI 判断的问题必填;规则问题有对应条目时填(DM6) rule_code text, -- 规则问题的规则编号,如 PRICE.CAPITAL_MISMATCH match_key text NOT NULL, -- item: 或 rule:[:<目标>];复查沿用按它 + type 匹配 type text NOT NULL CHECK (type IN ('unresponded', 'negative_deviation', 'suspected_unmet', 'formality', 'qualification', 'price_form', 'manual_review')), severity text NOT NULL CHECK (severity IN ('fatal', 'important', 'hint')), title text NOT NULL, explanation text NOT NULL, detector text NOT NULL CHECK (detector IN ('ai', 'rule')), detail jsonb NOT NULL DEFAULT '{}', -- 按类型:参数的要求值 / 响应值、资质预警种类、读到的报价金额 search_scope jsonb, -- 「未找到」时找过哪些分册、哪些章节 checks_passed text[] NOT NULL DEFAULT '{}', presence text NOT NULL CHECK (presence IN ('new', 'carried', 'resolved')), -- 新增 / 沿用 / 本次已解决 carried_from_id uuid, status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed_ok', 'modified', 'ignored')), resolution_note text, -- 忽略原因;致命问题「确认无误」的说明(U4) resolved_by uuid REFERENCES user_account (id), resolved_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, id), UNIQUE (tenant_id, check_run_id, match_key, type), CHECK (checklist_item_id IS NOT NULL OR rule_code IS NOT NULL), CHECK (detector <> 'ai' OR checklist_item_id IS NOT NULL), CHECK (status <> 'ignored' OR resolution_note IS NOT NULL), CHECK (NOT (severity = 'fatal' AND status = 'confirmed_ok') OR resolution_note IS NOT NULL), CHECK (presence <> 'carried' OR carried_from_id IS NOT NULL), FOREIGN KEY (tenant_id, project_id) REFERENCES project (tenant_id, id) ON DELETE CASCADE, FOREIGN KEY (tenant_id, check_run_id) REFERENCES check_run (tenant_id, id) ON DELETE CASCADE, FOREIGN KEY (tenant_id, checklist_item_id) REFERENCES checklist_item (tenant_id, id) ON DELETE CASCADE, FOREIGN KEY (tenant_id, carried_from_id) REFERENCES finding (tenant_id, id) ON DELETE CASCADE ); CREATE INDEX ON finding (tenant_id, check_run_id, severity, status); CREATE TABLE finding_citation ( tenant_id uuid NOT NULL, finding_id uuid NOT NULL, citation_id uuid NOT NULL, side text NOT NULL CHECK (side IN ('tender', 'bid')), position int NOT NULL DEFAULT 0, PRIMARY KEY (tenant_id, finding_id, citation_id), FOREIGN KEY (tenant_id, finding_id) REFERENCES finding (tenant_id, id) ON DELETE CASCADE, FOREIGN KEY (tenant_id, citation_id) REFERENCES citation (tenant_id, id) ON DELETE CASCADE ); -- 需人工核对的勾选:按项目存,复查沿用(P2) CREATE TABLE manual_check ( tenant_id uuid NOT NULL, project_id uuid NOT NULL, check_key text NOT NULL, -- item:<清单项编号> 或 rule:<规则编号> checked boolean NOT NULL, note text, checked_by uuid NOT NULL REFERENCES user_account (id), checked_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (tenant_id, project_id, check_key), FOREIGN KEY (tenant_id, project_id) REFERENCES project (tenant_id, id) ON DELETE CASCADE );