"""资质库上传识别(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; """)