"""查标第 1 步(m3-plan §5):每条清单项的检查结论(C9),投标分册类型(C8)。 - check_item_result:每次检查对每条参与检查的清单项留一行结论,「已响应」也留(红线 1、2)。 问题只为没响应好的条目生成;复查时按 cache_key 复用上一次的结论(C2)。 - source_document.volume_kind:分册类型只用来排序检索候选和填写「找过哪些分册」,不限制检索范围。 Revision ID: 0005 """ from __future__ import annotations from alembic import op revision = "0005" down_revision = "0004" def upgrade() -> None: op.get_bind().exec_driver_sql(""" ALTER TABLE source_document ADD COLUMN volume_kind text CHECK (volume_kind IN ('whole', 'qualification', 'commercial', 'technical', 'price', 'other')); CREATE TABLE check_item_result ( tenant_id uuid NOT NULL, check_run_id uuid NOT NULL, checklist_item_id uuid NOT NULL, verdict text NOT NULL CHECK (verdict IN ('responded', 'not_found', 'negative_deviation', 'suspected_unmet', 'no_value', 'undetermined', 'scanned')), layer text NOT NULL CHECK (layer IN ('deviation_table', 'rule_compare', 'model', 'retrieval')), evidence_status text NOT NULL CHECK (evidence_status IN ('verified', 'unverified', 'none')), reason_code text, -- 证据没通过、漏答、模型错误等(与读标原因码同一套写法) bid_citation_ids uuid[] NOT NULL DEFAULT '{}', -- 出处不可变,随项目级联删除 detail jsonb NOT NULL DEFAULT '{}', -- 要求值 / 响应值、模型指的页码、找过哪些分册 group_key text, -- 重复条目分组(C5) cache_key bytea, -- 清单项内容 + 分册版本 + 提示词版本(C2) reused_from_run_id uuid, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (tenant_id, check_run_id, checklist_item_id), -- 没有校验通过的证据,不能判为「已响应」(架构 §3.6) CHECK (verdict <> 'responded' OR evidence_status = 'verified'), 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, reused_from_run_id) REFERENCES check_run (tenant_id, id) ON DELETE SET NULL (reused_from_run_id) ); CREATE INDEX ON check_item_result (tenant_id, cache_key) WHERE cache_key IS NOT NULL; ALTER TABLE check_item_result ENABLE ROW LEVEL SECURITY; ALTER TABLE check_item_result FORCE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON check_item_result USING (tenant_id = current_tenant()) WITH CHECK (tenant_id = current_tenant()); """) def downgrade() -> None: op.get_bind().exec_driver_sql("DROP TABLE check_item_result; ALTER TABLE source_document DROP COLUMN volume_kind;")