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>
This commit is contained in:
60
server/migrations/versions/0014_qualification_scan.py
Normal file
60
server/migrations/versions/0014_qualification_scan.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""资质库上传识别(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;
|
||||
""")
|
||||
Reference in New Issue
Block a user