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:
@@ -1705,7 +1705,7 @@ paths:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/QualificationInput' }
|
||||
schema: { $ref: '#/components/schemas/QualificationSave' }
|
||||
responses:
|
||||
'201':
|
||||
description: OK
|
||||
@@ -1725,7 +1725,7 @@ paths:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/QualificationInput' }
|
||||
schema: { $ref: '#/components/schemas/QualificationSave' }
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
@@ -1764,6 +1764,87 @@ paths:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Qualification' }
|
||||
|
||||
/qualifications/{qualification_id}/files/{file_id}/previews:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/QualificationId'
|
||||
- name: file_id
|
||||
in: path
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
get:
|
||||
tags: [qualifications]
|
||||
operationId: getQualificationFilePreviews
|
||||
summary: 附件的预览图(图片一张,PDF 每页一张,最多 10 页),详情和大图浏览用
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [file_id, filename, pages]
|
||||
properties:
|
||||
file_id: { $ref: '#/components/schemas/Id' }
|
||||
filename: { type: string }
|
||||
pages:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [page, width, height, url]
|
||||
properties:
|
||||
page: { type: integer }
|
||||
width: { type: integer }
|
||||
height: { type: integer }
|
||||
url: { type: string, description: 预览图(JPEG)的短时签名地址 }
|
||||
|
||||
/qualification-scans:
|
||||
post:
|
||||
tags: [qualifications]
|
||||
operationId: createQualificationScan
|
||||
summary: 上传证书扫描件识别(后台识别,轮询结果;不扣额度,每个企业每天限 100 次)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [upload_id]
|
||||
properties:
|
||||
upload_id: { $ref: '#/components/schemas/Id' }
|
||||
responses:
|
||||
'201':
|
||||
description: 已排队
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/QualificationScan' }
|
||||
'429': { $ref: '#/components/responses/RateLimited' }
|
||||
'503':
|
||||
description: '`ocr_unavailable`:扫描件识别服务没有开或连不上,可以先手动填写'
|
||||
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|
||||
|
||||
/qualification-scans/{scan_id}:
|
||||
parameters:
|
||||
- name: scan_id
|
||||
in: path
|
||||
required: true
|
||||
schema: { $ref: '#/components/schemas/Id' }
|
||||
get:
|
||||
tags: [qualifications]
|
||||
operationId: getQualificationScan
|
||||
summary: 识别结果
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/QualificationScan' }
|
||||
delete:
|
||||
tags: [qualifications]
|
||||
operationId: deleteQualificationScan
|
||||
summary: 丢弃识别结果(连同原件)
|
||||
responses:
|
||||
'204': { description: 已删除 }
|
||||
|
||||
/qualifications/{qualification_id}/files/{file_id}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/QualificationId'
|
||||
@@ -2584,7 +2665,7 @@ components:
|
||||
Citation:
|
||||
type: object
|
||||
description: 出处,不可变(data-model §5.1)
|
||||
required: [id, document_id, document_label, page_no, sentence_keys, quote, quads, normalized_sha256, created_by_kind]
|
||||
required: [id, document_id, document_label, page_no, sentence_keys, quote, quads, normalized_sha256, created_by_kind, ocr]
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
document_id: { $ref: '#/components/schemas/Id' }
|
||||
@@ -2601,6 +2682,7 @@ components:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Quad' }
|
||||
created_by_kind: { $ref: '#/components/schemas/Origin' }
|
||||
ocr: { type: boolean, description: 原句是扫描页或插图上 OCR 识别出来的,可能有错字(界面标「识别文字」) }
|
||||
JobError:
|
||||
type: object
|
||||
description: 给用户看的失败原因;不含文档内容
|
||||
@@ -3200,15 +3282,120 @@ components:
|
||||
level: { type: [string, 'null'], maxLength: 40 }
|
||||
cert_no: { type: [string, 'null'], maxLength: 80, description: 界面上必填(查标时用它核对附件),接口可空,以后批量导入时不一定读得全 }
|
||||
issuer: { type: [string, 'null'], maxLength: 120 }
|
||||
holder: { type: [string, 'null'], maxLength: 120, description: 证书主体(企业名、著作权人、持证人) }
|
||||
valid_from: { type: [string, 'null'], format: date }
|
||||
valid_to: { type: [string, 'null'], format: date, description: 空表示长期有效;要晚于 valid_from }
|
||||
QualificationSave:
|
||||
description: 新增、修改资质。从识别结果保存时带上 scan:记下识别值,原件挂成附件(同一个原件不重复挂)
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/QualificationInput'
|
||||
- type: object
|
||||
properties:
|
||||
scan:
|
||||
type: [object, 'null']
|
||||
required: [scan_id, index]
|
||||
properties:
|
||||
scan_id: { $ref: '#/components/schemas/Id' }
|
||||
index: { type: integer, minimum: 0, description: 识别结果里的第几张证书(从 0 数) }
|
||||
QualificationScan:
|
||||
type: object
|
||||
description: 一次证书识别(vision-plan 第 2 步)。没保存的 24 小时后删除
|
||||
required: [id, filename, status, error, pages, certificates, created_at]
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
filename: { type: string }
|
||||
status: { type: string, enum: [queued, running, succeeded, failed] }
|
||||
error:
|
||||
oneOf:
|
||||
- type: 'null'
|
||||
- $ref: '#/components/schemas/ScanError'
|
||||
pages:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [page, width, height, rotation, url]
|
||||
properties:
|
||||
page: { type: integer }
|
||||
width: { type: integer, description: 识别用的页面图宽(像素),高亮框按它换算成比例 }
|
||||
height: { type: integer }
|
||||
rotation: { type: integer, description: OCR 转正用的角度(0 / 90 / 180 / 270) }
|
||||
url: { type: string, description: 页面图的短时签名地址 }
|
||||
certificates:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/ScanCertificate' }
|
||||
created_at: { $ref: '#/components/schemas/Timestamp' }
|
||||
ScanError:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code: { type: string, examples: [ocr_unavailable, model_unavailable] }
|
||||
message: { type: string }
|
||||
ScanCertificate:
|
||||
type: object
|
||||
required: [page, category, kind, name, cert_no_field, fields, masked, suggest, notes, existing]
|
||||
properties:
|
||||
page: { type: integer }
|
||||
category:
|
||||
type: string
|
||||
enum: [enterprise_qualification, system_cert, software_copyright, product, personnel, business_license, other]
|
||||
kind: { $ref: '#/components/schemas/QualificationKind' }
|
||||
name: { type: string, description: 建议的资质名称:证书标题,后面括注软件名或专业 }
|
||||
cert_no_field: { type: string, enum: [cert_no, reg_no], description: 证书编号取哪个字段(软著取登记号) }
|
||||
fields:
|
||||
type: object
|
||||
description: 字段名 → 识别结果。name、holder、level、specialty、software、cert_no、reg_no、issuer、issued_on、valid_from、valid_to、validity_text
|
||||
additionalProperties: { $ref: '#/components/schemas/ScanField' }
|
||||
masked:
|
||||
type: array
|
||||
description: 证书上有、但被打码或看不清的字段,要手动填写
|
||||
items: { type: string }
|
||||
suggest:
|
||||
type: object
|
||||
description: 推算的建议值,用户点了才填。valid_to:证书只写了有效期长度(如「五年」)时按发证日期推算
|
||||
properties:
|
||||
valid_to: { type: string, format: date }
|
||||
notes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [code]
|
||||
properties:
|
||||
code: { type: string, enum: [personnel, product, business_license, holder_mismatch] }
|
||||
holder: { type: string }
|
||||
tenant: { type: string }
|
||||
existing:
|
||||
description: 资质库里证书编号相同的一条(换证时建议更新它)
|
||||
oneOf:
|
||||
- type: 'null'
|
||||
- type: object
|
||||
required: [id, name]
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
name: { type: string }
|
||||
ScanField:
|
||||
type: object
|
||||
required: [value, check, boxes]
|
||||
properties:
|
||||
value: { type: string, description: 证书上的原样文字 }
|
||||
check:
|
||||
type: string
|
||||
enum: [ok, date_ok, low_conf, mismatch, bad_line, bad_format]
|
||||
description: ok / date_ok 才自动填;low_conf(马赛克、模糊)、mismatch(识别结果和 OCR 对不上)等只作建议
|
||||
day: { type: string, format: date, description: 日期字段换算成的日期 }
|
||||
boxes:
|
||||
type: array
|
||||
description: 所引 OCR 行在页面图上的位置(像素)[x0, y0, x1, y1]
|
||||
items:
|
||||
type: array
|
||||
items: { type: number }
|
||||
Qualification:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/QualificationInput'
|
||||
- type: object
|
||||
required: [id, files, late_for, created_at, updated_at]
|
||||
required: [id, files, recognized, late_for, created_at, updated_at]
|
||||
properties:
|
||||
id: { $ref: '#/components/schemas/Id' }
|
||||
recognized: { type: boolean, description: 是从识别结果保存的(识别值留痕) }
|
||||
files:
|
||||
type: array
|
||||
items:
|
||||
|
||||
Reference in New Issue
Block a user