Files
bid-doctor/server/migrations/versions/0005_check_item_result.py
YANG JIANKUAN e499de7486 feat: 查标第 1 步——上传投标分册 → 检查 → 逐条处理 → 复查沿用
后端
- check 模块:检查预览、发起、进度、取消、记录;六个阶段(准备投标文件、响应匹配、规则、证据校验、
  状态沿用、汇总);问题列表、处理、撤销、批量处理(只限提示级)。
- 每条清单项的检查结论 check_item_result(C9,迁移 0005):「已响应」也带投标原文出处;
  没有校验通过的证据不能判已响应(C3)。级别从清单项算,不让模型定;说明不写整改建议。
- 响应匹配先只有第三层:内存 BM25 检索候选句 + check.match v1 提示词,检索不到不调模型,
  漏答记 missing_answer,模型临时错误只影响那一批。
- 计费:免费版不记流水;专业版 24 小时内复查免扣(记一行 0);expected_charge 不一致时拒绝不扣点;
  失败、取消退回;投标文件读不出文字按输入问题失败、不扣点。
- 投标分册:类型(volume_kind)和版本;电子投标平台加密格式在上传时拦下。
- 解析流水线前几个阶段拆到 document/pipeline/normalize.py,解析和检查共用。
- ops 模块:反馈接口;致命问题确认无误的说明自动进误报反馈(U4)。
- SK1:Valkey 客户端短超时、SSE 连接池上限。SK2:CI 加 Gotenberg、直接跑 init-roles.sh。

前端
- 上传投标文件并检查页、查标页(进度、覆盖范围、按级别筛选、逐条处理和行内说明、撤销、原文抽屉、
  已处理、本次已解决);项目头抽成公共组件,加「查标」标签页;工作台「下一步」覆盖查标阶段。

契约、文档:openapi.yaml 加 item-results、volume_kind、coverage 字段;data-model、api-design §10、
架构 §8 / §13(SK14)、AGENTS.md 同步。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:48:10 +08:00

59 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""查标第 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;")