Files
bid-doctor/server/migrations/versions/0014_qualification_scan.py
YANG JIANKUAN 3344f09563 feat(ocr): 扫描件与图片识别——OCR 进文字层、资质库上传识别、大图浏览
计划见 docs/architecture/vision-plan.md(VS1–VS13,docs/decisions.md)。

- 验证:OCR(RapidOCR + PP-OCRv6 small,CPU)和 DeepSeek Flash 看图在国动投标文件
  和 7 张证书上初测(tech-validation §7,代码 spikes/s5-ocr/)
- OCR 服务 services/ocr(镜像、Compose 服务):整页转正(含 180°)、红章附近按红色通道重读
- 扫描页和插图识别后并入文字层 v6,句子标 source=ocr;出处标「OCR」(迁移 0013);
  依据识别文字的问题降级,规则不查扫描附件;国动端到端见 check-eval §16
- 资质库上传识别:添加资质对话框最上面传证书照片,识别后填表,只预填和 OCR 文字
  对得上的字段;识别值留痕、照片挂成附件(迁移 0014,提示词 vision.cert_fields v1)
- 资质库改为查看详情(只读键值 + 附件缩略图),附件用大图浏览控件看,不开新标签页
  (设计规范 §7.23);PDF 附件按页出预览图

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:50:13 +08:00

61 lines
2.3 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.

"""资质库上传识别(vision-plan 第 2 步):上传证书扫描件 → OCR + 视觉模型识别 → 用户核对后保存成资质。
- qualification_scan:一次识别。原件按企业级密钥加密存对象存储;result 是识别结果(每张证书的字段、引用的 OCR 行、
校验结论)。没保存的识别 24 小时后连同原件删除(worker 定时任务);
- qualification.holder:证书主体(企业名、著作权人、持证人);
- qualification.ai_values:保存时的识别值快照(识别值和用户最终值都留痕,同清单项的 ai_values);
- ai_call.job_type 加 qualification_scan。
Revision ID: 0014
"""
from __future__ import annotations
from alembic import op
revision = "0014"
down_revision = "0013"
def upgrade() -> None:
op.get_bind().exec_driver_sql("""
ALTER TABLE qualification ADD COLUMN holder text;
ALTER TABLE qualification ADD COLUMN ai_values jsonb;
CREATE TABLE qualification_scan (
id uuid PRIMARY KEY,
tenant_id uuid NOT NULL REFERENCES tenant (id),
filename text NOT NULL,
object_key text NOT NULL,
sha256 bytea NOT NULL,
size_bytes bigint NOT NULL,
media_type text NOT NULL,
status text NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'running', 'succeeded', 'failed')),
error_code text,
result jsonb,
created_by uuid NOT NULL REFERENCES user_account (id),
created_at timestamptz NOT NULL DEFAULT now(),
finished_at timestamptz,
UNIQUE (tenant_id, id)
);
CREATE INDEX ON qualification_scan (tenant_id, created_at);
ALTER TABLE qualification_scan ENABLE ROW LEVEL SECURITY;
ALTER TABLE qualification_scan FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON qualification_scan
USING (tenant_id = current_tenant()) WITH CHECK (tenant_id = current_tenant());
ALTER TABLE ai_call DROP CONSTRAINT ai_call_job_type_check;
ALTER TABLE ai_call ADD CONSTRAINT ai_call_job_type_check
CHECK (job_type IN ('parse_job', 'check_run', 'qualification_scan'));
""")
def downgrade() -> None:
op.get_bind().exec_driver_sql("""
-- ai_call 只追加、不能删行:job_type 的约束保持三种取值
DROP TABLE qualification_scan;
ALTER TABLE qualification DROP COLUMN ai_values;
ALTER TABLE qualification DROP COLUMN holder;
""")