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:
YANG JIANKUAN
2026-11-05 17:50:13 +08:00
parent 0b783ababa
commit 3344f09563
63 changed files with 4973 additions and 345 deletions

View File

@@ -862,9 +862,11 @@ S1–S4 风险最高,建议先做;S8(桌面外壳 PoC)等桌面版立项
| SK12 | 参考 BidEngine 的五项改进(2026-11-03) | 见 `ref-bid-engine-analysis.md` §5。**原因码**:没通过校验的结果记具体原因(`extract.FAILURES`,存在清单项 `ai_values.failures`),解析结果按原因计数(`verify_failures`),提示词规则和原因码的对照表在 `prompts/README.md`。**分块**:相邻分块重叠约 800 字;模型输出被截断(`finish_reason=length`)或不是合法 JSON 时,把分块对半重跑,最多 3 层,拆到底仍不完整的记 `output_incomplete`(以前当成「这一块没有结果」,审计发现 deepseek-flash 有输出正好停在 8192 token、整块条目悄悄丢掉的情况)。**项目基本信息**:提示词 v2,12 个字段每个都要表态 found / ambiguous / not_found,跨分块合并后分 found / absent(文件本身没写)/ needs_review(抽取有问题),记在解析结果 `info_fields`。**占位符**:「____元」「××」「( )」「待填」不算取值。**提示词**:substantive v4,qualification / scoring / schedule / tech_param v3,每条禁止事项对应一个原因码。三份样本的待确认:国动 4 → 2,S02 13 → 3,S06 57 → 55(S06 有 4 次对半重跑,截断的分块都救回来了) | 已实现 |
| SK13 | S01–S10 标注集评测后的修正(2026-11-03) | 见 `extract-eval.md` §7。规则:合并时待确认条目不再「借道」正式条目(`pending_categories`),表后说明、章识别、★ 节标题、输出接近上限按截断处理、格式范本和整表概括句不收;提示词 substantive v5、qualification v4、schedule v4、tech_param v4;WPS 旧版 DOC 直接修正字体表字符集;表格按单元格切句(切句 s3-v4)。口径 Q1–Q4 见 decisions.md。报告分数有过拟合,只按人工标注算未全部达标,泛化未测,见 `extract-status.md` | 已实现 |
| SK14 | 查标第 1 步(2026-11-04) | 见 `m3-plan.md`「第 1 步落地记录」。解析流水线的前几个阶段(格式核对、转换、文字层、扫描件判断)拆到 `document/pipeline/normalize.py`,解析任务和检查批次共用;投标文件每个版本只解析一次,扫描页不报错,交给检查按「请人工核对」处理(C10)。新增 `check`、`ops`(反馈)模块,worker 任务 `check_project`。Valkey 客户端设 2 秒超时、SSE 连接池上限 500(SK1);CI 加 Gotenberg、直接跑 `init-roles.sh`(SK2) | 已实现 |
| SK15 | OCR 进文字层(2026-11-05,vision-plan 第 1 步) | **OCR 服务** `services/ocr`(uv workspace 成员、镜像 `deploy/docker/ocr.Dockerfile`、Compose 服务 `ocr`):RapidOCR + PP-OCRv6 small,CPU;整页转正(90° / 270° 试识别、180° 用方向分类模型判);找红章,章附近按红色通道重读;返回行坐标、转正后坐标、红章位置。**worker**(`document/pipeline/ocr.py`):`BD_OCR_PROVIDER=http` 时,扫描页整页、文字页里占页面 5% 以上的图片区域渲染成 200 dpi 图片发给服务;OCR 行换算回 PDF 坐标,和文字层重叠的去掉,章上沿圆弧排的字去掉;切句沿用文字层规则,插图里的字各自成句;句子带 `source = "ocr"` 和置信度,接在原有句子后面编号(文字层 v6,原有编号不变)。OCR 服务不可用按基础设施错误退回额度(`ocr_unavailable`)。**查标**:识别文字参与响应匹配;依据识别文字的问题降一级、报价数字降到提示(VS4);规则检查不看扫描附件和插图上的识别文字(整份扫描件除外);扫描件兜底的说法改为「识别了文字但没认出这份材料」。**出处**:`citation.ocr`(迁移 0013),界面和报告在原句前标「OCR」(设计规范 §7.3,报告模板 v5)。国动真实投标文件 183 页识别约 65 秒,见 `check-eval.md` §16 | 已实现 |
| SK16 | 资质库上传识别(2026-11-05,vision-plan 第 2 步) | 「添加资质」对话框最上面传证书照片,识别后填表(单一入口,VS12;一张图一张证书,VS11)。接口 `POST/GET/DELETE /qualification-scans`,页面图走签名地址;worker 任务 `recognize_qualification`(`qualification/recognize.py`):OCR 服务 → DeepSeek Flash 看图(`vision.cert_fields` v1,`ai.run_json` 加 `images` 入参、`ai_call.job_type` 加 `qualification_scan`)→ 字段引用 OCR 行逐个校验(一致、日期一致、识别不确定、不一致、格式不对),只有一致的预填。识别值存 `qualification.ai_values`,照片挂成附件;新增 `qualification.holder`(迁移 0014)。附件支持 WEBP。不扣额度,每企业每天 100 次 | 已实现 |
SK11 修正前后(同一模型 deepseek-chat,即 deepseek-flash 不思考模式)的待确认数:国动 46 → 4,S02 17 → 13,S06 174 → 57;同一句被拆成多条的情况没有了。剩下的待确认多数是表格两栏或竖排文字串行(要按单元格切句才能解决)。实质性条款任务上比较了 flash 不思考、flash 思考(deepseek-reasoner)、deepseek-v4-pro:抽到的条款大体一致、互有多少,后两者慢 15~45 倍、输出 token 多 7~12 倍,没有看出明显更准;选型等标注集评测出结果再定。
用国动便民小程序(71 页)、S02、S06 三份文件实测(真实模型 DeepSeek):单份 33~87 秒,清单 216~1348 条,正式清单里的条目都带页码和原句。发现并已修复:标注规则说明的碎片被当成条款、页码混进正文、跨页拆成两条、「做无效响应处理」等写法漏认、上级标题下列出的无效情形被合成一条或漏掉、「无效质疑」被误当成投标无效、「是否接受联合体」取错证据。
还没做、留了 TODO 的:转换镜像的字体包(S1)、OCR(S5)、分包对应到清单项、模型路由的候选列表、降级和限流、查标全部。S01–S10 标注集评测和修复见 `extract-eval.md`。
还没做、留了 TODO 的:转换镜像的字体包(S1)、分包对应到清单项、模型路由的候选列表、降级和限流、查标全部。S01–S10 标注集评测和修复见 `extract-eval.md`。

View File

@@ -368,7 +368,18 @@
**局限**:这 6 处都是看着一份真实文件改的。第 4 处的「条件性要求」只认标题里的这几种主体;第 3 处的阈值是按国动这一份文件的扫描页定的,别的文件扫描页的版式(整页图片、多张小图)还没见过。
## 16. 下一步
## 16. 国动真实投标文件开 OCR(2026-11-05,vision-plan 第 1 步)
新开发账号、新项目,重新读标(48 秒,292 条、待确认 2 条),资质库空,投标文件开 OCR 查标。代码见架构 §13 SK15,规则集 r5,提示词 `check.match` v2。看过 §14、§15 的结果,只作参考。
- **识别**:82 页扫描页、20 页文字页里的 25 张插图都识别了文字(OCR 约 65 秒,检查共 94 秒);覆盖范围写明「识别出来的文字参与检查,依据识别文字的问题降一级」。
- **新找到的证据**:7 条要求的投标侧出处落在识别文字上:营业执照(第 16 页)、部署架构图、数据流向图、功能架构图(第 62、63、67 页)、测试流程图(第 87 页)、演示截图(第 175 页)。以前这些地方没有文字。
- **第一次跑(规则也看识别文字)**:36 个问题,全是提示级,其中 21 个是规则在附件上的噪音:劳动合同模板没勾的期限条款(「可能有没填的地方」11 个)、历史合同的「另行协商」条款(「可能附加了条件」5 个)、历史合同的价款(「另一个价格」1 个)等。降级起了作用(没有致命、重要),但这些附件是别人的文件,规则本来就不该查。**改为**:规则检查不看扫描附件和插图上的识别文字;整份扫描的投标文件(文字页不到三成)照查。覆盖范围写明。
- **改后重跑**:16 个问题,全是提示级,规则噪音没有了。和 §15 修后的 11 个比,多出的主要是重新读标后清单不同、模型波动,没有逐条复核。
- **OCR 解决不了、要靠第 3 步页面分类的**:第二十二条的证明材料、纳税和社保证明,材料确实在扫描页上(第 16 页营业执照、第 22–23 页完税证明),识别文字也有,但响应匹配没把它们对上,仍是「请人工核对」或「未给出具体值」;软件自主研发、团队专业能力、国产化适配三条仍说「识别了文字但没认出这份材料」。
- **和 OCR 无关的**:「提供逻辑架构图、部署架构图和主要数据流向图……」「分别说明公众服务端和后台管理端的页面结构……」仍判「未找到」,图和页面原型都在(第 61–65 页),属于 §14 第 5 点「方案应明确……」要整章去对的问题。
## 17. 下一步
1. 分项报价表和采购需求清单逐项对照(P13)**暂缓**:在开发集 7 份招标文件上试读「名称 + 数量」的采购清单表,S02、S08(服务类)没有这种表;S04 把「是否进口」一栏读成了名称;S10 采购包 2 的表跨页、第二页没有表头,漏了大半;S01 9 行里能和投标文件的分项对上名称的只有 7 行。P13 是致命级,按这个抽取质量在真实文件上会大量误报「缺项」,评测集里也没有注入过,先不上,覆盖范围里照写「还没有上线」。散文写法的单价上限同样暂缓。
2. 第 3 步冻结前:确认各项改动的单元测试都有开发集以外的反例;冻结后跑留出集(S03、S06、S09,风格 B + C)和国动真实投标文件,各一次。

View File

@@ -71,11 +71,11 @@
| identity | `tenant` `user_account` `login_identity` `membership` `user_session` | 人、登录方式、成员身份三层(AR11) |
| billing | `plan` `subscription` `activation_request` `quota_account` `quota_entry` `quota_alert` | 套餐带版本;开通申请注销后保留 |
| project | `project` `project_member` `lot` `project_field` | 项目字段的 AI 值和人工值分开存 |
| document | `source_document` `document_page` `parse_job` `citation` | 出处表归 document 模块,各模块共用 |
| document | `source_document` `document_page` `parse_job` `citation` | 出处表归 document 模块,各模块共用;`citation.ocr`(2026-11-05,迁移 0013)标原句是不是 OCR 识别出来的 |
| checklist | `checklist` `checklist_item` `checklist_item_citation` `checklist_version` `checklist_version_item` `marking_rule` | |
| check | `check_run` `check_run_document` `check_item_result` `finding` `finding_citation` `manual_check` | `check_item_result` 2026-11-04 新增(C9) |
| report | `report` | |
| qualification | `qualification` `qualification_file` | |
| qualification | `qualification` `qualification_file` `qualification_scan` | 2026-11-05(迁移 0014):`qualification.holder`(证书主体)、`qualification.ai_values`(从识别结果保存时的识别值);`qualification_scan` 是一次证书识别,没保存的 24 小时后删除 |
| ai | `ai_call` | |
| audit | `audit_event` | |
| ops | `ops_admin` `ops_session` `feedback` | 反馈由用户写,在运营后台处理 |

View File

@@ -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:

View File

@@ -1,4 +1,4 @@
# 技术验证 S1–S4 结论(2026-11-03)
# 技术验证结论(S1–S4 2026-11-03;S5、S6 初测 2026-11-05)
> 依据:架构文档 `architecture.md` §10。代码在 `spikes/`(一次性代码,复现步骤见 `spikes/README.md`),由样本派生的产物都在各子目录的 `out/` 里,不进 git。
> 环境:本机 Docker 29.8.2(Apple Silicon,**arm64**),Gotenberg 8.39.0;样本是 `samples/tenders/` 的 S01–S10。
@@ -12,208 +12,75 @@
| S2 | **可行**:在规范化 PDF 上识别「斜体且下划线」,和 DOCX 原文件逐字对得上 | 4 份文件按字符 100% 一致;片段级召回 0.993~1.0 | 不需要 Docling 来保格式 |
| S3 | **正文可行,表格要改进**:原句高亮的几何位置全部正确;切句在表格里会把相邻单元格拼在一起 | 几何回读 50/50、300/300;文本保真:正文 97.5%,表格内 78% | 切句要识别表格单元格和页眉页脚 |
| S4 | **可行**:两道证据校验 + 第三道「依据校验」后,抽取结果可用;★ 类条款建议用规则兜底保召回 | ★ 召回 93.5%(扣除银标准噪音后约 96.6%),斜体下划线 99.8%;采纳条目 95.2% 属实;每份文件约 0.15 元(高峰价) | §3.6 新增依据校验和规则兜底 |
| S5 | **可行(初测)**:RapidOCR(PP-OCRv6 small,CPU)识别字准、坐标准、够快;整页横放能自动转正;红章压字要局部按红色通道重读 | 国动投标文件 102 页(扫描 + 图表)62 秒;合成扫描件字符差 0.1%~1.1%;坐标回画全部对上 | §3.5 OCR 阶段可以照计划做,见 §7 |
| S5 视觉 | **可行(初测)**:DeepSeek Flash 收图片,页面分类、证书 / 合同字段、图表概述都能用;字段引用 OCR 行做校验能拦住两边读错的情况,但拦不住语义错和手写 | 102 页 23 秒,约 0.47 元(每页约 0.005 元);86 个字段 70 个和 OCR 行一致,16 个不一致里 10 个是跨行 | vision-plan §2 的交叉校验要支持多行、加字段格式校验 |
| S6 | **部分**:印章有无模型判断大体对,浅色骑缝章会漏;手写签名能认出 | 102 页里判「有章」44、「看不清」6;抽查 2 页漏 1 个边缘骑缝章 | 签章位检查要加红色像素初筛;准确率要等有标注的样本 |
## 1. S1 Word 转 PDF 的保真度
## 7. S5 / S6 扫描件识别初测(2026-11-05)
### 1.1 环境核实
> 计划见 `vision-plan.md`。代码在 `spikes/s5-ocr/`(独立虚拟环境,Python 3.12),产物在 `out/`,不进 git。
> 样本:国动真实投标文件(`samples/tenders/GD/响应文件-最新.pdf`,183 页)。本次没有扫描版招标文件样本(用户:招标文件通常是文字稿),证书扫描件用户随后提供。
- **Gotenberg 镜像**:`gotenberg/gotenberg:8`(8.39.0)有 **arm64 版本**,镜像约 2.5GB,基础系统是 Debian 13。
- **LibreOffice 版本**:26.8.1,比参考项目踩过坑的 7.3 / 24.2 都新。
- **自带中文字体**:只有 Noto Sans / Serif CJK。宋体、仿宋、黑体全部回落到西文的 Noto Sans,衬线和黑体都分不清。
- **样本用到的字体**:宋体(约 4.8 万处)、仿宋、黑体、楷体、方正系列、Times New Roman、Arial。都是商业字体,不能随镜像分发。
- **OLE 嵌入对象**:10 份样本里都没有,所以「内嵌 OLE 的 docx 会让旧版 LibreOffice 崩溃」这一条无法用本批样本验证,留到收集到这类样本时再测。
- **耗时与稳定性**:10 份文件 × 6 种配置,全部转换成功,没有崩溃;单份 0.4~2.0 秒。
### 7.1 OCR(RapidOCR 3.10,PP-OCRv6 small 检测 / 识别模型,onnxruntime CPU,Apple Silicon)
### 1.2 真值
- **挑页**:沿用文字层 v5 的扫描页规则,整页识别 80 页;图片占比 ≥ 0.15 的文字页只识别图片区域(跳过小于页面 5% 的小图),22 页。和文字层已有文字重叠的 OCR 行去掉。
- **速度**:200 dpi 渲染 + 识别,102 页 61 秒(约 0.6 秒 / 页,横放的报表页 1.5~3 秒)。按这个速度,300 页全扫描约 3 分钟,满足 10 分钟目标。
- **转正**:第 18、19 页的资产负债表是横放扫描,0° 识别出大量竖长框,试 90° / 270° 取「字数 × 平均置信度」最高者,判为 270°;框换算回原页坐标后画回去全部对上。
- **字准(合成扫描件)**:文字页渲染成图片,分三档退化(干净;灰度 + 倾斜 1° + 模糊 + 噪点 + JPEG 60;降采样 + 倾斜 2° + 更模糊 + JPEG 35),以文字层为真值:
| 来源 | 覆盖 | 说明 |
|---|---|---|
| 旧版 DOC 里 WPS 保存时记录的页数 | S07(79 页)、S09(73 页) | OLE SummaryInformation |
| DOCX 的 `docProps/app.xml` 页数 | **没有** | 8 份 DOCX 都是 ONLYOFFICE 7.2 生成的(苏采云平台导出),不写页数 |
| DOCX 目录里缓存的页码(PAGEREF) | S03、S04、S06 | 生成文档时排版写入;比较「各章标题之间隔了几页」,消除页码起始值的影响 |
| 文件 | 页 | 字数 | 字符差(不看顺序) | 按顺序的错误率 |
|---|---|---|---|---|
| 国动招标文件 | 5–24 | 12,806 | 0.31% / 0.41% / 0.32% | 1.2% / 1.4% / 1.3% |
| 国动投标文件 | 35–60 | 15,436 | 0.15% / 0.09% / 1.09% | 约 31%(表格多,阅读顺序和文字层不同,不是认错字) |
**发现**:平台导出的 DOCX 没有「Word 里显示的页数」可以对照。用户在 Word 或 WPS 里看到的分页,取决于他们自己装的软件和字体。所以「出处页码以规范化 PDF 为准」这条设计是必需的,不是权宜之计。
退化对字准影响很小。真实扫描件上没有真值,用视觉模型的交叉校验和人工抽查代替(§7.2)。
- **红章压字**:资产负债表「核算单位」被红章盖住,原图只认出「南京」「信息技术有限公司」两段。
- 按红色像素阈值抹掉印章:章下面的黑字也一起抹掉,更差。
- **只取红色通道当灰度图**(红章变浅、黑字不变):认出「核算单位:南京会信息技术有限公司」(错一字),肉眼可读出「南京长卿信息技术有限公司」。
- 但整页都用红色通道时,23 页有变化,有的变好(p21、p142、p162),有的变差(p16、p163)。**结论:只对红章附近的区域用红色通道重读,其余保留原图结果。**
- **图表**:技术方案里的 22 页架构图、流程图、界面截图,图里的文字全部读出(例如第 61 页系统逻辑架构图 26 行,四层的每个模块名都在)。这些文字以前不在文字层里,查标「出处没能核对」里架构图那几条(`check-eval.md` §14)就是因为这个。
### 1.3 严重问题:WPS 旧版 DOC 转出来正文是空白
### 7.2 视觉模型(DeepSeek Flash,`deepseek-chat` 不思考,`json_object`)
- **现象**:S07 默认转换后,正文渲染成空白,抽出的文字全是「第第第第」;S07 只有 61 页,真值是 79 页。
- **原因**:WPS 在字体表里把「宋体」的字符集标成了 `02`(SYMBOL,符号字符集)。LibreOffice 据此把正文当成符号,换成了 OpenSymbol 字体;转成 DOCX 后,这些字也变成了 `<w:sym>` 符号字符,不是普通文本。
- **修复**:先转 DOCX,把中文字体名被标成 SYMBOL 的字符集改成 GB2312(`86`),再转 PDF。修复后 S07 正文正常,**80 页**。
- **影响**:
- 转换前必须加这一步预处理。
- 用 DOCX 做交叉校验时,读文本要同时读 `<w:t>` 和 `<w:sym>`。
- 只要规范化 PDF 里出现大段 OpenSymbol 字体、或抽出的文字大量重复同一个字,就判定为「转换异常」:不扣点,提示用户。
- **做法**:每页图片(长边 1600 像素,JPEG 85)+ 带编号的 OCR 行一起发;输出页面类型、是否续页、标题、字段(必须引用 OCR 行号)、印章 / 签名有无、图表概述。字段值归一化后在所引行里找;只有数字一致算「近似」;对不上记「不一致」。
- **速度和成本**:102 页并发 6,23 秒;输入 199,863 token、输出 9,324 token,按 §4.3 的高峰价约 0.47 元,每页约 0.005 元。
- **页面分类**:劳动合同 35、业务合同 27、系统界面截图 9、信用查询截图 8、架构图 8、财务报表 4、流程图 4、纳税证明 2、其他图表 2、营业执照 1、授权书 1、其他 1。按「不是续页」的标记数,3 份业务合同(第 104、119、123 页起)、5 份劳动合同(第 135、142、149、156、163 页起,每份 7 页)的起始页都对,和页面下方的文字说明一致;没有逐页人工核对。第 21 页「登记通知书」归到「其他」,合理。
- **字段**:86 个,70 个和所引 OCR 行一致。16 个不一致:
### 1.4 字体配置对比(页数)
| 原因 | 个数 | 处理 |
|---|---|---|
| 值跨两行(住所、所属期间、合同期限、长合同名) | 10 | 允许引用多行,按拼接后的文字核对 |
| OCR 被红章或下划线切碎(「签订日期:20251」) | 4 | 红章附近用红色通道重读 |
| OCR 读错、模型读对(手写「2025」OCR 读成 2015) | 1 | 交叉校验按设计拦下,进待确认 |
| 模型读错(「长卿」读成「长顺」,还引错了行) | 1 | 交叉校验按设计拦下 |
| 配置 | 说明 |
|---|---|
| A-default | Gotenberg 默认,原文件直接转 |
| B-alias | 加 fontconfig 规则:宋体 / 仿宋 / 楷体 / 方正系列 → Noto Serif CJK SC,黑体 / 雅黑 → Noto Sans CJK SC |
| A-fixed / B-fixed | 同上,但先做 §1.3 的字符集修正 |
| **C10-fixed** | 字符集修正 + 用 fontTools 克隆 Noto CJK,**字形不变,只把行高改成 1.0em**(和宋体一致,上 0.86 / 下 0.14),改名 BD Song / BD Hei,替换规则同 B |
| C13-fixed | 同 C10,行高 1.3em |
**校验拦不住的**:
- 语义错:第 145 页「用人单位 = 甲方」「合同期限 = 1 种工时制度」,值确实在所引行里。要按字段类型加格式校验(单位名要像单位名,期限要有日期)。
- 手写:第 169 页乙方手写「2025 年 11 月 22 日」,OCR 和模型都读成「1 月」,两边一致但都错。手写区域即使两边一致也按低可信处理,不出致命级。
- 半截值:第 18 页「单位名称」取了 OCR 认出的半截「信息技术有限公司」,校验通过。被章挡住的值要标「可能不完整」。
- **印章 / 签名(S6 初测)**:判「有章」44 页、「看不清」6 页;手写签名 9 页。抽查:第 106 页右边缘的浅色骑缝章判成「无」(漏),第 131 页只露一截的骑缝章判「看不清」(合理)。签章检查只看指定签章位,骑缝章影响不大,但要加红色像素初筛补漏。没有标注,准确率没算。
- **图表概述**:架构图、流程图的概述准确(第 61 页把四层和每层的模块全部列对),适合在覆盖范围或出处卡片里作「图片说明(AI 生成,非原文)」。
| 样本 | 真值 | A-default | B-alias | A-fixed | B-fixed | **C10-fixed** | C13-fixed |
|---|---|---|---|---|---|---|---|
| S07(WPS) | 79 | 61(正文空白) | 61(正文空白) | 80 | 80 | **79** | 80 |
| S09(WPS) | 73 | 89 | 90 | 91 | 92 | **75** | 87 |
| S01 | — | 93 | 95 | 93 | 95 | 81 | 90 |
| S03 | — | 145 | 145 | 145 | 145 | 104 | 131 |
| S06 | — | 198 | 199 | 198 | 199 | 166 | 190 |
### 7.3 证书识别(资质库上传识别,vision-plan 第 2 步)
目录页码真值(标题之间的页数跨度误差,括号里是最大单点偏差):
> 样本:用户提供的 7 张网上公开证书图片(不进 git):人员证书 4 张(软考、职业技能等级 ×2、培训证书,其中 2 张姓名 / 编号打了马赛克)、软著 2 张(1 张低清、红章压日期)、一张照片里挂着两张软件产品证书(反光)。每张再转 90°、180°、270° 各做一份,共 28 次。标准答案由 Claude 对着图录(`spikes/s5-ocr/out/certs/gold.json`,不进 git,用户未复核)。代码 `spikes/s5-ocr/certs.py`。
| 样本 | 目录项 | A-fixed | C10-fixed | C13-fixed |
|---|---|---|---|---|
| S03 | 7 | +36.7%(29 页) | **-1.3%(1 页)** | +24.1%(19 页) |
| S04 | 8 | +32.3%(20 页) | **0.0%(1 页)** | +22.6%(14 页) |
| S06 | 6 | +17.3%(26 页) | **-2.2%(11 页)**¹ | +11.5%(26 页) |
- **流程**:图片按 EXIF 转正 → OCR 服务(转正、红章重读)→ 转正后的图 + 带编号的 OCR 行交给 DeepSeek Flash,按证书逐张输出类别和字段(每个字段引用 OCR 行)→ 校验。
- **转正**:90° / 270° 原来就能认;**180° 原来认不出**(逐行识别时 RapidOCR 自己翻正,字认得出,但整页图是倒的,模型把「评估机构」当成了「发证机构」、日期读错)。加了整页倒置检测:挑最多 30 个长行交给 RapidOCR 的方向分类模型,过半判 180° 就整页翻过来重识别。之后 16 种「图 × 角度」全部转对,倒置检测每页多 0.2~0.4 秒。
- **打码**:马赛克轻的地方 OCR 会「读」出字(培训证书的姓名、编号),模型照抄。两层处理:提示词要求被打码的字段不输出、写进 masked;服务端看所引 OCR 行的置信度,低于 0.9 标「识别不确定」,不自动填。透过马赛克读出的行置信度 0.69、0.88,正常行在 0.95 以上。
- **结果**(提示词改了两轮之后):200 个字段对 200,漏 0;打码字段 16 次全部没给值或标了识别不确定;一图两证两张都拆对。字段校验 192 个一致、8 个识别不确定(都是打码字段)、9 个不一致(值是对的,OCR 那行认错或值由两行拼成,会以「识别结果不一致,请核对」给用户)。
- **速度和成本**:每张 OCR 约 2 秒(含转正),模型约 1.5 秒;每张输入约 1,400 token、输出约 220 token,不到 0.005 元。
- **局限**:提示词是对着这 7 张改的(第一轮 50 个字段对 45、打码字段漏了 2 次),满分是调出来的,说明不了泛化。下一批证书不改提示词直接跑,才是正式成绩。样本里没有企业资质证书(CS、ITSS、ISO)和营业执照。
¹ S06 只匹配上 4 个目录项,最大偏差里有标题匹配不准的成分。
### 7.4 这份文件上能多查出什么
**结论**:
- 分页偏差的主因是**行高**,不是衬线还是黑体:Noto CJK 的行高约 1.45em,宋体是 1.0em,同样的「单倍行距」在 Noto 下每行高出 45%。
- 克隆字体的行高改成 1.0em 后,5 个有真值的数据点都落在 ±3% 以内,单点偏差大多在 1 页内。
- **字体许可**:Noto CJK 是 OFL 1.1,允许修改后再分发;改名后的字体仍按 OFL 分发,许可证文件要随镜像带上。
- **仍然存在的风险**:
- 这 5 个数据点都以宋体为主。仿宋、楷体为主的文件(S01、S02、S08)没有真值,偏差未知。
- 克隆字体只调了行高,西文字宽和宋体的半角宽度不同。表格密集、中英混排多的文件可能仍有偏差。
只是看结果做的推演,产品还没实现:
## 2. S2 格式识别(斜体 / 下划线 / 加粗)
- **主体名称不一致(D14)**:营业执照(第 16 页)是「南京长卿科技有限公司」,统一社会信用代码 91320114MA27H0P5X5;利润表、资产负债表、现金流量表(第 17、18、20 页)的单位名称,以及第三份业务合同(第 123–124 页)的乙方,都是「南京长卿信息技术有限公司」;资产负债表上的纳税人识别号和营业执照一致。多半是曾用名,但投标文件里没看到更名证明,值得提示用户核对。
- **证明材料**:第二十二条要的营业执照、财务报表、纳税、信用记录都能按页面类型定位到具体页,不用再出「请人工核对」。
- **评分项**:扫描页里只有营业执照、财务报表、纳税证明、信用报告、3 份业务合同、5 份劳动合同,没有软著、人员证书、国产化证明,「软件自主研发」「团队专业能力」「国产化适配」三条可以直接说「投标文件里没有找到这类材料」,不再说「可能在扫描件里」。
- **业绩**:3 份业务合同的金额(4 万、8 万、17 万)、甲乙方、签订日期都读得出,D15 的时间窗口核对有了数据。
### 2.1 方法
### 7.5 对计划的修改
- **银标准**:DOCX 原文件里「斜体且下划线」的文字。解析 XML 时完整处理样式继承:文档默认 → 段落样式链 → 字符样式链 → 直接格式。注意 python-docx 的 `paragraphs` 不包含表格里的段落,直接用它会漏掉大半。
- **PDF 侧**:
- 斜体:字体名含 Italic,或字形矩阵有倾斜(中文没有斜体字形,LibreOffice 合成斜体时倾斜量是 0.35);
- 下划线:字符底边附近有一条横线覆盖该字;
- 加粗:字体名含 Bold。
- 下划线的判定窗口要包含「字符框内侧」:克隆字体的下行部分变小后,下划线会落进字符框里。
### 2.2 结果(规范化 PDF = C10-fixed)
| 样本 | DOCX 斜体下划线字数 | PDF 识别字数 | 按字符一致 | 片段召回 | 片段精确率 |
|---|---|---|---|---|---|
| S06 | 14209 | 14209 | **100%** | 0.993 | 0.871² |
| S08 | 1088 | 1088 | **100%** | 1.000 | 1.000 |
| S09 | 70 | 70 | **100%** | 1.000 | 1.000 |
| S03 | 98 | 98 | **100%** | 1.000 | 1.000 |
² S06 的片段精确率偏低,是因为 PDF 按阅读顺序把相邻表格单元格的片段连在了一起,按片段匹配就对不上;按字符计数两边完全一致。默认字体配置(A-fixed)下结果相同,说明格式识别不依赖字体选择。
### 2.3 Docling 对比(S06)
| 输入 | 耗时 | 保留斜体下划线 | 页码和坐标 | 表格识别 |
|---|---|---|---|---|
| DOCX 原文件 | 2.1 秒 | **100%**(14209 字) | **没有** | 31 个 |
| 规范化 PDF | 96.2 秒(CPU) | **0** | 有 | 73 个 |
- `docling-serve-cpu` 有 arm64 镜像,约 7.2GB,本机拉取约 5 分钟。版本:docling-serve 1.38.2、docling 2.142.1。
- **结论**:Docling 做不到「格式 + 坐标」同时具备;我们自己从规范化 PDF 提取,两样都有,而且 4 份文件合计只要约 6 秒。Docling **不作为主解析器**。它的 PDF 表格识别可以作为候选,用来解决 S3 的表格切句问题,要再评估。
## 3. S3 原句级坐标与高亮
### 3.1 方法
- 从规范化 PDF 取逐字坐标,按行聚类;同一行里字间空隙大于 1.5 个字号时断开,得到「行片段」。
- 在 。!?; 处断句;段落末行或表格单元格结束时也断句。句子不跨页。
- 编号格式:`<文档>:p<页>:s<页内序号>`。规范化 PDF 不再重新生成,所以编号稳定。
- 每句生成 quads:按行合并成矩形。
- 校验分两种:
- **几何回读**:用 quads 裁剪原页重新取字,和句子比对;
- **文本保真**:句子能在 DOCX 原文里找到,不算 Word 自动编号和页码域。
### 3.2 结果
| 指标 | 50 句(种子 20261103) | 300 句(种子 7) |
|---|---|---|
| 10 份文件总句数 | 24020 | 24020 |
| 跨行句子占比 | 23 / 50 | 118 / 300 |
| **几何回读正确** | **50 / 50** | **300 / 300** |
| 文本保真 | 49 / 50(98%) | 269 / 300(89.7%) |
300 句按位置分类的文本保真率:
| 位置 | 文本保真 | 失败原因 |
|---|---|---|
| 正文 | **196 / 201 = 97.5%** | 个别行内拼接 |
| 表格内 | 71 / 91 = 78.0% | 相邻单元格(序号列、分值列)的文字被拼进同一句 |
| 页眉页脚带 | 2 / 8 = 25.0% | 页眉文字混进了正文句 |
- 高亮图在 `spikes/s3-citation/out/highlight-*.png`(8 张),样式是设计规范的蓝铅笔浅底加蓝铅笔描边。人工看过两张:跨 3 行的句子在句号处结束,同一行后面的下一句没有被带进来。
- **要改进的两点**:
1. 切句前先识别表格单元格,用 PDF 的表格线,或者参考 Docling 的表格结构,按单元格切;
2. 识别每页重复出现的页眉页脚带,单独处理,不并入正文句。
- 自动编号(「60.」「24.」)由 Word 渲染时生成,规范化 PDF 里有、DOCX 文字里没有。出处高亮以 PDF 为准,所以不影响高亮;只是做 DOCX 交叉校验时要容忍这一点。
## 4. S4 大模型抽取实质性条款
### 4.1 方法
- **输入**:S3 的句子,带格式标记(`<iu>` 斜体且下划线、`<u>` 下划线、`<i>` 斜体)。每个分块约 6000 字,10 份文件共 150 块。每个分块前附上从全文摘出的「标注规则说明」句。
- **银标准**(实质性条款句):
- ★ 句:排除说明句和「★号条款」这类引用句;
- 斜体下划线句:只在文件自己声明这种标注方式时才算(S06、S08、S09),否则它常常只是模板里的填空处。
- S03 正文的 ★ 句**正好 14 条**,和文件自述的「★ 项共 14 条」一致。
- **提示词**:`spikes/s4-llm/prompts/extract_substantive/`,有 v1、v2 两个版本。要求只能引用句子编号,引文必须逐字复制。
- **三道校验**:
1. 编号必须在本分块里;
2. 引文必须是原句的子串,并满足最小长度;
3. **依据校验**(v2 新增):模型声称的依据,用确定性信号核对。★ 看原句里有没有 ★,斜体下划线看有没有 `<iu>`,「写明无效」看原句和相邻句里有没有无效类字样。
- 任何一道没通过,条目都进**待确认区**,不丢弃。
### 4.2 结果(10 份文件全量)
| 指标 | v1(无依据校验) | **v2 + 依据校验** |
|---|---|---|
| ★ 召回(严格 / 含相邻句) | 91.3% / 92.4%(84、85 / 92) | **93.5% / 93.5%**(86 / 92) |
| 斜体下划线召回(严格 / 含相邻句) | 91.3% / 99.8% | 91.7% / **99.8%** |
| 采纳条目数 | 1761 | 651 |
| 采纳条目属实(命中银标准,或证据句写明无效) | 36.7% | **95.2%** |
| 进待确认区 | 66 | 283 |
| 证据两道校验通过率 | 95.1% | 93.6% |
| 输入 / 输出 token | 61.1 万 / 11.4 万 | 63.6 万 / 5.2 万 |
| 费用(高峰 / 空闲) | 1.93 / 0.96 元 | **1.49 / 0.74 元** |
- **漏掉的 ★ 句(v2)**:共 6 条,其中 3 条是银标准的噪音:「备注:加★为核心技术参数」「非标★、▲项为一般技术参数」「属于清单中打★品目的」。**真正漏掉的 3 条都是表格里的参数行**。扣除噪音后召回约 **96.6%**(86 / 89)。
- **v1 的主要问题是过度抽取**:合同履约、保密协议、付款等条款都被当成了「写明无效」。S09 有 208 条被标成「斜体下划线」,实际上全文只有 2 句。说明模型会**标错依据**,所以必须有第三道依据校验。
- **证据没通过的原因**:引文过短(少于 8 个字)、引文不是原句子串、编号不在本分块。都被校验拦下进了待确认区,没有一条错误出处进入正式清单。
- **耗时**:10 份文件 8 路并发,约 2 分钟;最大的 S06(199 页、28 块)单独串行累计 142 秒,并发后约 20 秒。
### 4.3 成本(供 D2 定价参考)
价格来源:DeepSeek 官方价目页(https://api-docs.deepseek.com/zh-cn/quick_start/pricing,2026-11-03 查询)。`deepseek-flash`(DeepSeek-V4.1-Flash-1017)每百万 tokens:输入缓存命中 0.04 元、未命中 2 元、输出 8 元(高峰);空闲时段半价。
| 口径 | 每份招标文件 |
|---|---|
| 本次实测(只做「实质性条款」一项抽取,v2) | 输入约 6.4 万 token、输出约 0.5 万 token;**约 0.15 元(高峰)/ 0.07 元(空闲)** |
| 推算:完整 M2 解读(资格、废标条款、评分、日期、参数、摘要等 5~6 项抽取) | 约 0.6~1.0 元(高峰),**推算值,未实测** |
- 换成深度思考模式或 `deepseek-v4-pro`,输入单价是 Flash 的 2.25~4.5 倍,还会多出思考 token。本次没有测,D2 定价前要补测「Pro 或思考模式能把召回提高多少」。
- 价格表里没有 `deepseek-chat` 这个名字,但接口仍然接受,实际由 Flash 提供服务。正式接入时应直接用 `deepseek-flash`,并把接口返回的 model 字段记进调用流水账(架构 §3.6)。
### 4.4 建议
1. **标注类条款用规则保召回**:★、*、文件声明了的斜体下划线,都能从规范化 PDF 上确定性地识别出来(S2)。所有这类句子都必须被某个条目覆盖;模型没覆盖的,系统自动生成一条「待确认」。模型负责归并、概括和判断依据,召回不再完全依赖模型。按 S2 的结果,这一类的召回可以做到接近 100%。
2. **「写明无效」类靠模型加依据校验**:这一类没有银标准,召回率还没测,需要人工标注 2~3 份文件。可以和样本缺口里的「人工标注 S03 / S06」一起做。
3. **数量核对**:文件写明了实质性条款数量的(S03「共 14 条」),和抽到的数量对比,不一致就提示(FR-M2-05 已有要求)。
4. 引文最小长度从 8 个字放宽到「不少于 6 个字,或整句」:表格单元格常常很短,「引文过短」是证据被拒的首要原因。
## 5. 对需求目标值的建议
| 需求 | 建议目标值 | 依据 |
|---|---|---|
| FR-M2-05 ★ / 标注类实质性条款召回 | **≥ 99%**(规则兜底 + 模型) | S2 格式识别 100%;S4 模型单独约 96.6% |
| FR-M2-05 「写明无效」类召回 | 先定 **≥ 90%**,人工标注后校准 | 尚无银标准 |
| FR-M2-05 采纳条目准确率(进正式清单的条目属实) | **≥ 95%** | S4 v2 实测 95.2% |
| FR-M2-06 出处页码正确率 | **100%** | 出处由句子编号决定,页码随编号而来;编号和引文都经过校验 |
| FR-M2-06 原句高亮准确率(抽查 50 条) | 正文 **≥ 98%**,表格 **≥ 95%** | 实测正文 97.5%、表格 78%,表格切句改进后再验收 |
| FR-M2-02 解析耗时(300 页) | 维持 10 分钟目标 | 转换 ≤ 2 秒,文字层约 2 秒 / 100 页,单项抽取并发后约 20 秒;完整解读按 5~6 项推算在 2~3 分钟内 |
## 6. 未覆盖的事项
- **S1**:内嵌 OLE 对象的 docx(本批样本里没有);以仿宋、楷体为主的文件的分页真值。
- **S3**:扫描件 OCR 页的坐标(S5 的范围)。
- **S4**:深度思考模式和 Pro 模型的对比;「写明无效」类的召回;资格、评分、日期等其他抽取项。
- S5(OCR)、S6(签章)、S7(任务队列)按 §10 的计划另做。
见 `vision-plan.md` v0.3:多行引用、字段格式校验、红章局部重读、手写低可信、半截值标记、签章位红色初筛;招标文件里的图片按重要程度分流(用户要求)。

View File

@@ -0,0 +1,243 @@
# 扫描件与图片识别计划 v0.3(OCR + 视觉模型)
> 2026-11-05。起因:国动真实投标文件查标时,扫描页上的内容一律成了「请人工核对」。本文盘点现状,回答「要不要视觉模型」,列出全系统里要识别图片的地方,给出分步计划。VS1–VS9 已确认(§7,`docs/decisions.md`);v0.2 按用户决定把视觉模型改为 DeepSeek Flash(VS2)。v0.3 按国动投标文件上的初测(`tech-validation.md` §7)补了校验规则,招标文件里的图片按重要程度分流(§3.1,用户要求)。
## 进度
| 步 | 状态 | 说明 |
|---|---|---|
| 0 验证 | 基本完成 | 国动投标文件的 OCR 和视觉初测(`tech-validation.md` §7.1、§7.2);用户的 7 张证书(§7.3,提示词对着这 7 张改过,满分说明不了泛化)。还差签章标注 |
| 1 OCR 进文字层 | **已完成**(2026-11-05) | 架构 §13 SK15;国动端到端见 `check-eval.md` §16。规则检查不看扫描附件和插图上的识别文字(第一次跑报出 21 个附件噪音后改的) |
| 2 资质库识别 | **已完成**(2026-11-05) | 架构 §13 SK16。按用户要求简化(VS11、VS12):一张图按一张证书处理;不单设识别入口,「添加资质」对话框最上面传证书照片,识别后填进下面的表单。产品接口上 7 张证书实测见下 |
| 3–5 | 未开始 | |
### 第 2 步落地记录(2026-11-05)
- **流程**:传照片(PDF / JPG / PNG / WEBP)→ `POST /qualification-scans` 排队 → worker 任务 `recognize_qualification`(`qualification/recognize.py`):每页图片按 EXIF 转正 → OCR 服务 → 转正后的图 + 带编号的 OCR 行交给 DeepSeek Flash(提示词 `vision.cert_fields` v1)→ 逐字段校验 → 前端轮询拿结果、填表。PDF 只识别前 10 页;不扣额度,每个企业每天 100 次;没开 OCR 时直接说明,可以手动填。
- **校验**:所引 OCR 行拼起来要包含字段值(日期按年月日比);所引行置信度低于 0.9 标「识别不确定」;对不上标「不一致」;「甲方」「本公司」这类不当证书主体。只有一致的才预填,其余只给建议、点「采用」才填。被打码的字段写「被遮挡,请手动填写」。
- **辅助**:名称写成「证书标题(软件名或专业)」;软著取登记号当证书编号、默认长期有效;只写了「有效期:五年」时给「按发证日期推算」;有效期起没写时给「发证日期 填入」;证书主体和企业名称不一致(曾用名、子公司)、人员证书、产品证书都提醒;编号和已有记录相同时可以「改为更新它」(换证)。
- **留痕**:保存时识别值记在 `qualification.ai_values`,识别用的照片挂成附件;没保存的识别 24 小时后删除(建下一次识别时顺手清)。迁移 0014。
- **产品接口实测**(真实 OCR + DeepSeek,7 张证书):每张 2~4 秒,全部识别成功,结果和 `tech-validation.md` §7.3 一致。**局限**:培训证书的编号中间几位打了码,OCR 这次跳过了打码的字、置信度不低,读成「FRX11032897」并预填了,两层防护都没拦住。所以界面要求逐项核对,识别出的值不能当确定值。
## 0. 结论
- **OCR 和视觉模型都还没有接。** 架构里写的是「PaddleOCR 独立服务 + 多模态大模型补充」(架构 §1、§3.5、AR1),技术验证 S5(OCR)、S6(签章检测)一直排在 M3 之后(C10),没有做。
- **要用视觉模型,但它不能代替 OCR。** OCR 给文字和坐标,保证原句高亮、出处可查(红线 2);视觉模型负责「理解」:这页扫描件是什么材料、证书上的字段、签章有没有、图里的表格。视觉模型的结论要能对上同一页的 OCR 文字,对不上的进待确认区。
- **视觉模型用 DeepSeek Flash,不另加供应商**(VS2)。v0.1 以为 DeepSeek 不收图片,实测是错的:`/models` 返回 `deepseek-flash`(DeepSeek-V4.1-Flash)的 `input_modalities` 是 `text`、`image`,`deepseek-chat` 直接收 OpenAI 格式的 `image_url`。见 §2.1。
- 除了读标、查标、资质库,还有一处 P0 缺口:**签章有无(FR-M3-04)完全没做**,`rules.py` 把它列在「还没查」里。它也要靠视觉。
## 1. 现状
| 地方 | 现在怎么做 | 代码 |
|---|---|---|
| 判断扫描页 | 每页记图片占比(文字层 v5),字少、图多的页算 `empty` | `document/pipeline/text_layer.py` `Page.text_source` |
| 流水线的 OCR 阶段 | 只有阶段名,不做事;招标文件超过一半是扫描页就报 `ocr_required` 失败 | `normalize.py`、`runner.py` 的 TODO(S5) |
| 整份扫描的招标文件、补遗 | 解析失败:「扫描件识别还没有上线」 | `core/errors.py` `ocr_required` |
| 整份扫描的投标文件 | 检查失败:`bid_text_missing` | `check/runner.py:148` |
| 投标文件里的扫描页 | C10 兜底:要附证明材料而文字部分没找到,出提示级「第 X–Y 页是扫描件……请人工核对」;覆盖范围列出扫描页 | `check/match.py`、`check/findings.py` |
| 资质匹配 | 证书多在扫描页上,只能靠资质库记录(QL2,降一级);主体不一致(D14)、业绩时间窗口(D15)留到「OCR 之后」 | `check/qualification.py` |
| 招标文件正文里的图片 | **没有处理,也没有告诉用户**:截图的评分表、参数表、资格要求不会进清单 | — |
| 签章有无 | 没做 | `check/rules.py` `RULES_NOT_YET` |
| 资质库 | 手工录入,扫描件只当附件存着 | `qualification/` |
| 模型调用 | 提示词可以声明 `capability: vision`,但 `openai_compat.py` 只发文字消息(模型本身收图片,§2.1) | `ai/providers/openai_compat.py` |
国动那份投标文件(`check-eval.md` §14、§15):183 页里约 82 页是扫描件(资格证明、合同、劳动合同),12 条「请人工核对」只有 4 条有用。
## 2. OCR 和视觉模型怎么分工
| | OCR(自部署) | 视觉模型(DeepSeek Flash,私有化可选) |
|---|---|---|
| 产出 | 每行文字 + 坐标框 + 置信度 | 结构化理解:页面是什么材料、证书字段、签章有无、图片类型 |
| 出处 | 能高亮原句,满足红线 2 | 给不出可靠坐标,**不能单独当出处** |
| 风险 | 错字(红章压字、手写、低清、旋转) | 会「顺手改正」或编出没写的值,日期、编号尤其危险 |
| 成本 | CPU 上跑,按算力算 | 按 token 计费,逐页调用较贵 |
| 私有化 | 镜像自带模型,离线可用 | 客户要有 GPU 部署多模态模型,没有就降级 |
**组合方式:先 OCR,再把「页面图片 + 带编号的 OCR 行」一起交给视觉模型。** 视觉模型输出的每个字段都要引用 OCR 行号,服务端沿用三道校验:
1. 编号校验:引用的行号在这一页上;
2. 原文校验:字段值(日期、编号按规则归一化后)出现在所引的 OCR 行里;
3. 不一致处理:视觉模型读出的值和 OCR 行对不上(例如 OCR 把 2028 认成 2026),**不采信任何一边**,标「识别结果不一致」,进待确认 / 要用户填写。
初测(`tech-validation.md` §7.2)后补的四条:
4. **可以引用多行**:住所、合同期限、长合同名常跨两行(初测 16 个不一致里占 10 个),按所引各行拼接后的文字核对。
5. **字段格式校验**:值在所引行里也可能是错的(「用人单位 = 甲方」「合同期限 = 1 种工时制度」)。按字段类型校验:单位名要以「公司 / 中心 / 局……」结尾,日期能解析,期限有起止日期,编号有字母数字。不合格的进待确认。
6. **半截值**:所引的行挨着印章、或者值明显不完整(单位名没有后缀),标「可能不完整」,不当作确定值比对(例如不拿半截单位名去判主体不一致)。
7. **手写区域低可信**:手写的签订日期,OCR 和模型可能一致但都错(第 169 页手写的「11 月」两边都读成「1 月」)。OCR 置信度低于阈值、或模型标了手写的值,最多出提示级。
这样出处始终落在 OCR 的坐标框上,视觉模型只负责「理解」和交叉校验。没有文字的判断(签章有没有、这是一张拓扑图)只能做提示,不当成原文证据。
### 2.1 DeepSeek Flash 的图片能力(2026-11-05 实测)
| 测试 | 结果 |
|---|---|
| `GET /models` | `deepseek-flash`(DeepSeek-V4.1-Flash):`input_modalities: ["text", "image"]`,上下文 1M |
| `deepseek-chat` / `deepseek-reasoner` 收 `image_url`(base64) | 都返回 200,实际模型都是 `deepseek-flash` |
| 虚构的中文证书图:倾斜 1.5°、模糊、椒盐噪声、JPEG 55 质量、红色圆章,要求输出 8 个字段的 JSON(`json_object` 模式) | 名称、编号、主体、等级、发证机构、发证日期、有效期至、有无印章 **8 / 8 全对**;输入 393 token、输出 72 token |
这只是一张合成图,说明不了真实扫描件上的准确率;坐标框也没测(不打算靠它出坐标,§2)。S5 在扫描评测集上正式测。
**没有理由否决 DeepSeek Flash**:能力够、一张证书不到 500 token,和抽取共用一家供应商,还省掉一套供应商接入、协议(红线 10)和路由。仍要留意的两点:① 合成图上的满分不代表真实扫描件,手写签名、红章压字、低清复印件要在 S5 / S6 里专门测;② 只有一家供应商,DeepSeek 限流或故障时抽取和视觉一起停,降级候选列表(架构 §3.6)以后再配。
## 3. 全系统里要识别图片的地方
按模块挖了一遍,分三档:**P0 缺口**(MVP 范围内,现在没做或做得不够)、**MVP 内的增强**、**以后版本**(按「以后的功能只留结构」的原则,只保证能复用同一层,不展开设计)。
### 3.1 读标(M2)
| # | 场景 | 现在 | 要做的 | 档 |
|---|---|---|---|---|
| R1 | 整份扫描的招标文件(盖章扫描版、纸质转电子) | 解析失败 | OCR 进文字层,抽取照常;扫描页上的斜体下划线标注认不出,覆盖范围写明「实质性条款可能漏,请复核」,★ 按 OCR 常见误认(`*`、`☆`)归一 | P0(FR-M2-01「PDF 扫描版」) |
| R2 | 补遗、更正公告是盖章扫描件 | 解析失败 | 同 R1 | P0(FR-M2-10) |
| R3 | 文字版招标文件里的部分扫描页(盖章页、附件) | 当空页跳过 | 同 R1,逐页补 | P0 |
| R4 | 正文里的截图:评分表、参数表、资格要求的平台截图 | 静默忽略 | 图片区域单独 OCR,按单元格切句进文字层,可引用、可高亮;重要的再交给视觉模型理解(见下面的分流) | P0(重要的一定做) |
| R5 | 正文里的图纸:拓扑图、平面图、效果图、二维码、logo | 静默忽略 | 装饰类(logo、二维码、印章、页眉图标)跳过;重要的图纸交给视觉模型,给出「图片说明(AI 生成,非原文)」,图里的文字(OCR)可引用;不重要的只在覆盖范围列出 | P0(重要的一定做) |
| R6 | 扫描件上的标注格式(下划线、加粗) | — | 视觉模型认标注,作为依据校验的信号 | 以后,跟读标专项(`extract-status.md`)一起做 |
**招标文件里的图片怎么分流**(用户要求:不是每张都做全套,重要的一定做)。OCR 便宜(CPU 约 0.6 秒 / 页),视觉模型按页计费,所以:
1. **都做 OCR**:扫描页整页识别;文字页里面积超过页面 5% 的图片区域都识别。小图(logo、图标)不识别。
2. **判断重不重要**,满足任一条就算重要,交给视觉模型:
- 图片在要求类章节里:资格条件、评分标准、采购需求 / 技术参数、商务要求、报价 / 清单、合同条款、投标文件格式;
- 图片的 OCR 文字像表格(多行多列、有数字、单位、「★」「≥」)或带要求类字眼(「应」「须」「不得」「无效」「分」);
- 整页扫描的页(补遗、盖章的更正页、扫描附件)。
3. **视觉模型做什么**:判断图片类型(表格截图、架构 / 拓扑图、平面图、效果图、装饰);表格截图按行列还原,交给后面的抽取;图纸给一段说明。
4. **不重要的**(章节外的效果图、宣传图)不调视觉模型,覆盖范围列出「第 X 页有 N 张图没有细读」。
### 3.2 查标(M3)
| # | 场景 | 现在 | 要做的 | 档 |
|---|---|---|---|---|
| C1 | 投标文件里的扫描页(资格证明、合同、证书) | C10 兜底「请人工核对」 | OCR 进文字层:文件要在(按标题找)、响应匹配都能用上扫描页上的文字;C10 只留给 OCR 也读不出的页 | P0 |
| C2 | 整份扫描的投标文件(纸质投标扫描、盖章后扫描) | 检查失败 | 同 C1;报价规则在 OCR 文字上跑,数字的处理见 §4.6 | P0 |
| C3 | 扫描页是什么材料 | 分不出 | **附件索引**:视觉模型给每页(或连续几页)分类——营业执照、资质证书、ISO 体系认证、软著、社保缴纳证明、纳税证明、审计报告、信用查询截图、合同、中标通知书、发票、身份证复印件、人员证书、授权书、其他;文件要在、证明材料缺失(D02)按索引判断,不再说「可能在扫描件里」 | P0(FR-M3-05「每条证明材料都要找到」) |
| C4 | 投标文件里的证书字段 | 只能靠资质库 | 证书类页面抽名称、编号、等级、发证机构、有效期、证书主体;资质匹配有了投标侧出处,不再降一级(QL2 只用于真没识别出的情况);主体和投标人不一致(D14)可以做 | P0 |
| C5 | 签章有无 | 没做 | 从文字层找签章位(「(盖章)」「法定代表人(签字或盖章)」「投标人(公章)」),截取附近区域:红色像素做初筛(不调模型),视觉模型判断有没有印章、手写签名、电子签章;只报「有 / 没看到 / 看不清」,界面写明不鉴定真伪(红线 7) | **P0 缺口**(FR-M3-04、S6、G12) |
| C6 | 业绩合同扫描件:合同金额、签订日期、甲方、项目名称 | — | 字段抽取同 C4;按评分项的时间窗口核对(D15) | 结构在 MVP 留好,规则按 QL7 放 V1 |
| C7 | 人员证书、社保证明、学历职称 | — | 页面分类先认出来(C3);人员匹配是 V1 | 以后 |
### 3.3 资质库(M6-08)及其他
| # | 场景 | 现在 | 要做的 | 档 |
|---|---|---|---|---|
| Q1 | 新增资质 | 手工填全部字段,扫描件只作附件 | **添加资质的对话框最上面传证书照片,识别后填进下面的表单**(只有这一个入口,VS12):名称、类型、等级、证书编号、发证机构、证书主体、有效期。识别出的字段标「识别」,鼠标移上去在缩略图上高亮出处;用户核对后保存才入库(人工确认) | MVP 增强(用户要求) |
| Q2 | 证书主体和本企业不一致 | 没有主体字段 | 识别出的主体和企业名称不一致时提示(子公司、曾用名),不阻断 | 随 Q1 |
| Q3 | ~~一份 PDF 里汇编了多张证书~~ | — | **不做**(VS11):一张图按一张证书处理,认出几张时只填第一张并提示 | — |
| Q4 | 换证:传了新证书 | — | 证书编号或名称 + 类别匹配到已有记录时,建议「更新这条」而不是新增,旧附件保留 | 随 Q1 |
| Q5 | 企业主体信息(营业执照:名称、统一社会信用代码) | 注册时手填企业名 | 传营业执照自动填;给 C4、Q2 的主体比对提供依据 | MVP 增强,可选 |
| L1 | M4 资产库:历史标书、业绩合同、人员证书入库 | V1 | 复用同一层 | 以后 |
| L2 | M5 查重:扫描版的历史标书 | V1/V2 | 复用 OCR 文字层 | 以后 |
| L3 | M1 标讯附件是扫描件 | P1/P2 | 复用 R1 | 以后 |
运营后台不看业务内容,不涉及。
## 4. 设计
### 4.1 流水线
现有的 OCR 阶段落实为(读标、查标共用 `normalize.pages`):
```
文字层(不变) → 逐页决定:
扫描页(text_source = empty) → 整页 OCR
文字页里有大图(按图片区域面积和位置) → 只 OCR 图片区域,和已有文字重叠的去掉
其他 → 跳过
→ 渲染(pypdfium2,约 200 dpi)
→ 转正:0° 识别出大量竖长框时试 90° / 270°,取「字数 × 平均置信度」最高者(初测横放报表判对)
→ 红章:先找红章位置,只对章附近的区域取红色通道当灰度图重读,和原图结果按行取优(整页用红色通道有的页变差,§7.1)
→ OCR 服务(独立容器 bid-doctor-ocr,HTTP 接口,worker 并发调用)
→ OCR 行换算回 PDF 坐标,按同样规则切句,并入文字层(text_layer v6)
→ [视觉] 页面分类、证书字段、图片类型(只对需要的页)
```
- **句子编号稳定**:页上原有文字句的编号不变,OCR 句接在后面编号(`p18s3` 之后是 OCR 的 `p18s4`…)。句子带 `source: "ocr"` 和置信度。
- `document_page.text_source` 增加 `ocr`、`mixed`,加 OCR 平均置信度;`empty` 只剩 OCR 也读不出的页。
- 阶段可续跑、幂等,和其他阶段一样;OCR 结果按页缓存(同一份规范化 PDF 不重复识别)。
- 招标文件不再因为扫描件失败;`ocr_required` 只在 OCR 服务不可用时报,按基础设施错误退回额度。
### 4.2 OCR 引擎
**初定 RapidOCR + PP-OCRv6 small 模型(onnxruntime,CPU)**:国动投标文件上 0.6 秒 / 页,合成扫描件字符差 0.1%~1.1%,坐标准(`tech-validation.md` §7.1)。不用装 PaddlePaddle,镜像小。许可证(RapidOCR Apache 2.0,模型随 PaddleOCR 发布)在做镜像时按架构 §6 的策略核实。表格截图(R4)要行列结构:先用 OCR 框的坐标聚成行列,不够再试 PP-StructureV3。
不用云 OCR 接口:私有化用不了,而且多一个处理方。
### 4.3 视觉模型接入
- **模型**:`vision` 能力用 DeepSeek Flash 不思考模式(`deepseek-chat`),和抽取同一个供应商、同一套配置(VS2、M1)。业务代码仍只声明 `capability: vision`,以后要换模型改 `BD_AI_MODELS` 就行,不改代码。
- **消息格式**:`complete_json` 加可选的图片入参(OpenAI 兼容的 `image_url`,用 base64 data URL,不给模型可访问的外链)。图片在 worker 内存里渲染,长边压到模型够用的分辨率,不另存。
- **假模型**:`fake` provider 加视觉任务的确定性输出,测试和 CI 照旧不调真实模型。
- **提示词**(带版本):`vision.page_classify`、`vision.cert_fields`、`vision.signature`、`vision.figure_kind`。输入都带 OCR 行编号,输出要引用行号(§2)。
- `ai_call` 照常记录;图片只记哈希。
### 4.4 资质库识别(Q1–Q4)
- 上传附件后发起识别任务(几秒;PDF 多页的按页分类后逐张抽)。结果存在附件上(`qualification_file.extracted`:识别值、引用的 OCR 行、模型和提示词版本),用户保存时记下最终值——识别值和人工值都留痕,同读标的 `ai_values`。
- `qualification` 加「证书主体」字段(迁移)。
- 前端:资质库新增改成「先传文件」,表单字段带识别标记,扫描件预览可高亮出处;识别不出的字段留空,不填猜测值(红线 5)。
### 4.5 查标的附件索引和签章(C3–C5)
- 附件索引:每个投标分册版本算一次,存进文字层旁边(同版本复用,和 `prepare_bid` 一样)。证明材料类清单项先查索引,找到了就有投标侧出处(页面分类引用的 OCR 标题行)。
- 证书字段(C4)复用资质库识别的提示词和校验,结果进资质匹配,出处是投标文件的页和 OCR 行。
- 签章(C5):签章位来自文字层;红色像素初筛先过一遍,只有初筛拿不准和要找手写签名的位置才调视觉模型。**电子投标的 CA 签章常在加密上传时才加**,投标人检查的那一版往往还没盖章——要用户说明「这一版盖过章了吗」,没盖过的不查签章,只进需人工核对(P2)。
### 4.6 定级和红线
- **OCR 文字当原文用,但界面标「识别文字」**:出处卡片显示 OCR 标记和置信度,高亮框画在扫描图上。
- **低置信度页上的结论降一级**(同 QL2 的思路);整页置信度太低的仍按 C10「请人工核对」。
- **OCR 读出的数字**:大小写金额、合计、超限价这几条规则在 OCR 文字上跑时,只有 OCR 和视觉模型读出的数字一致才保留原级别,否则降为提示「识别的金额可能有误,请核对」。报价仍然只读不生成(红线 6)。
- **不鉴定真伪**:证书、签章都只看「有没有、写了什么」,不去外部系统核验(红线 5、7)。
- **个人信息**:身份证复印件只分类,不抽号码;人员证书在 V1 之前只分类。
- **覆盖范围**:报告和查标页写明 OCR 了哪些页、哪些页置信度低、哪些图片没读、视觉模型没开时哪些检查没做。
### 4.7 计费
不单独计费,沿用现在的扣点(VS5):OCR 和视觉调用并入「解析 1 次 / 检查 N 点」,靠每个任务的页数上限和硬预算控制成本(同查标第五轮的硬预算);成本记进 `ai_call`,S5 实测后回填 D2 的单次成本。资质库识别不扣点,按企业限次(防刷)。额度页的扣点规则说明不用改。
### 4.8 私有化
OCR 容器随离线包交付(模型打进镜像)。客户没有 GPU 部署视觉模型时:OCR 照常,页面分类退化成按 OCR 标题关键词分类,证书字段只用 OCR + 规则抽(准确率低,全部进待确认),签章退回需人工核对。覆盖范围写明。
## 5. 分步计划
| 步 | 内容 | 验收 | 依赖 |
|---|---|---|---|
| **0 验证** | **初测已做**(2026-11-05,国动投标文件,`tech-validation.md` §7):OCR 速度、字准、转正、红章;DeepSeek Flash 页面分类、字段、交叉校验、图表概述、印章初判。**还差**:证书扫描件上的字段准确率(等用户的证书);签章有无的标注和准确率;扫描评测集(开发集投标文件做成「扫描版」,金标准沿用,留出一部分不看) | 选定 OCR 引擎(已初定);证书字段按字段统计准确率;签章按准确率定级 | 证书样本(§8) |
| **1 OCR 进文字层** | OCR 容器、`normalize` 的 OCR 阶段、text_layer v6、`document_page` 迁移、出处卡片的「识别文字」标记、覆盖范围;去掉 `ocr_required` / `bid_text_missing` 的「未上线」失败 | R1–R3、C1、C2 走通;开发集文字版不退化;扫描版开发集的查标召回和文字版差距可量化 | 0 |
| **2 资质库识别** | 模型调用的图片入参、`vision.cert_fields`、附件识别任务、证书主体字段、自动填表、多证拆分、换证合并 | Q1–Q4;字段级准确率(按字段统计,不合格的字段留空而不是填错) | 0(和 1 可以并行) |
| **3 附件索引 + 投标侧证书** | `vision.page_classify`、附件索引、证明材料按索引判断、证书字段进资质匹配、D14 主体不一致 | 国动重跑:12 条「请人工核对」里的噪音消失、有用的 4 条还在;扫描版开发集证明材料缺失(D02)召回 | 1、2 |
| **4 签章有无** | 签章位识别、红色初筛、`vision.signature`、「这一版盖过章了吗」、规则 `FORM.SIGNATURE_MISSING` | 按 S6 准确率定级(G12:不够就降为请核对) | 1、0 的 S6 |
| **5 读标里的图片** | R4 图片区域 OCR、§3.1 的重要程度分流、表格截图还原行列进抽取、R5 图纸说明和覆盖说明、扫描件上 ★ 的归一 | 构造含截图评分表、参数表的虚构招标文件,截图里的要求进清单、出处高亮在图上 | 1 |
| 以后 | R6、C6 的规则、C7、L1–L3 | 只保证复用 | — |
建议顺序 0 → 1 / 2 并行 → 3 → 4 → 5。资质库识别(2)独立、见效快,也是视觉链路的第一个真实用户场景,适合先上线。
## 6. 影响面
- 迁移:`document_page`(text_source 取值、置信度)、`qualification`(证书主体)、`qualification_file`(识别结果)、附件索引的存放(对象存储里和文字层放一起,库里只存键)。
- 接口:资质附件识别(发起 / 取结果)、文字层句子带 `source`、检查的「这一版盖过章了吗」选项;改 `openapi.yaml` 后重新生成客户端。
- 部署:Compose 加 `ocr` 服务;`.env.example` 加 `BD_OCR_URL`;视觉沿用现有的 `BD_AI_*` 配置,`BD_ENV=prod` 时的假模型检查已经覆盖。
- 文字层版本升级:已有的招标文件不自动重解析;投标分册下次检查时按 v6 重抽(和 v5 一样)。
## 7. 决定(2026-11-05 已确认)
VS2 按用户决定,VS5 用户重申不单独计费,其余按建议。
| # | 事项 | 决定 |
|---|---|---|
| VS1 | OCR 怎么部署 | 自部署 PaddleOCR 系(独立容器),SaaS 和私有化同一套;具体模型 S5 定。不用云 OCR 接口 |
| VS2 | 视觉模型 | **DeepSeek Flash**(`deepseek-chat` 不思考模式),和抽取同一家,不另加供应商(实测见 §2.1) |
| VS3 | 视觉模型的结论怎么用 | 字段必须引用同一页的 OCR 行并通过校验,对不上进待确认;没有文字依据的判断(签章、图片类型)只做提示或按 S6 定级 |
| VS4 | OCR 读出的报价数字 | OCR 和视觉读数一致才按原级别报,否则降为提示 |
| VS5 | 计费 | 不单独计费,沿用现在的扣点;按页数上限和硬预算控成本;资质库识别不扣点、按企业限次 |
| VS6 | 签章检查和 CA 签章 | 检查时让用户说明这一版是否已盖章;未盖章的不查签章,只进需人工核对 |
| VS7 | 私有化没有视觉模型 | 允许,降级运行(§4.8),覆盖范围写明 |
| VS8 | 个人信息 | 身份证复印件只分类不抽号码;人员证书 V1 前只分类 |
| VS9 | 顺序 | 0 → 1、2 并行 → 3 → 4 → 5 |
## 8. 需要准备的样本
- ~~真实扫描的招标文件~~:用户说明招标文件通常是文字稿,这次不提供;招标文件里的图片、扫描页照样按 §3.1 处理,评测用构造的虚构样例;
- 本企业的证书扫描件 10–20 张(资质库识别评测,不进 git):用户在找;
- 盖过章的投标文件 1 份(签章检测),最好有对应的未盖章版本。