Files
bid-doctor/docs/architecture/qualification-plan.md
YANG JIANKUAN 54cbb556d0
Some checks failed
ci / server (push) Has been cancelled
ci / web (push) Has been cancelled
ci / contract (push) Has been cancelled
feat(ui): 查标第 6 步前端补齐
- 读标页:每条清单项写最近一次检查的结论,点投标侧出处看投标原文;
  「已响应」也能反馈判断有误(漏报);证书要求写资质库里的情况
  (新接口 GET /projects/{id}/qualification-matches,资质库空时不写)
- 概览:检查记录卡(历次检查、各级问题数、报告下载)、文件卡
- 工作台:「致命」列、统计可点筛选、待处理的致命问题卡(?f= 定位到问题)
- 日期输入加日历弹层(DateInput,Reka UI Calendar)
- 查标页:? 键显示快捷键说明;反馈对话框抽成 FeedbackDialog 共用
- 扫描件提示去掉多余空格

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

190 lines
19 KiB
Markdown
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.

# 资质库和资质匹配实现方案 v0.1(2026-11-04)
> 范围:查标实现计划(`m3-plan.md`)第 4 步,即 FR-M6-08 最小资质库 + FR-M3-05 资质匹配检查。
> 依据:`03-functional-requirements.md`、`05-it-integration-domain.md` R07 / R08、决策 G4 / C10 / DM4、`data-model.md`(`qualification` `qualification_file`)、`openapi.yaml`(`/qualifications`)、原型 `qualifications.html` 和 `results.html` 的 f6、评测规范 `evals/check/GUIDELINES.md` D11 / D12 / D14 / D15。
> 状态:QL1–QL7 已全部按建议确认(2026-11-04,`docs/decisions.md`)。
## 0. 结论速览
- **表、接口、原型都已经有了**。基线迁移里已建 `qualification` 和 `qualification_file` 两张表;`openapi.yaml` 里已定义 7 个操作;原型资质库页的交互也已定稿。资质库本身是一个普通的增删改模块,工作量主要在前端页面和附件。
- **难点在匹配,不在资质库**。从样本看(§1),企业资质在政府采购项目里多数是**评分项**,不是资格条件;而且大量「证书」要求是**产品证书**(节能认证、CCC、商用密码)或**人员证书**,资质库管不了,必须先排除。
- **匹配不调模型**:按 IT 集成常见证书建一张类别词表,招标侧的要求、投标文字层、资质库三方都映射到类别再比较。认不出类别的,不做判断,写进检查覆盖范围。
- **证据有两个来源,可信度不同**:投标文字层(比如投标文件里的「资质情况表」)上读到的有效期和等级,有原文出处,按清单项定级;只有资质库记录、投标文件那边是扫描件或没写日期时,**降一级**,说明里写明「按资质库记录」。
- **资质库是空的就不做资质库核对**,只在覆盖范围里写一句。不能因为用户没录,就报一片「未覆盖」。
## 1. 样本里的资质要求长什么样
在 S01–S10 读标结果里筛出和证书有关的清单项(`evals/out/runs/fix3`),分成四种:
| 种类 | 例子 | 清单类别 | 资质库能不能管 |
|---|---|---|---|
| 企业资质(资格条件) | 电子与智能化工程专业承包资质、《安全生产许可证》(S01) | 资格条件 + 无效 | **能**,致命 |
| 企业资质(评分项) | ISO 27001 / 9001 / 20000(S09)、CCRC 信息安全服务资质一级、CCIA 一级(S01) | 评分 | **能**,重要 |
| 软件著作权(评分项) | 「虚拟化云平台软件计算机软件著作权登记证书(1 分)」、「软件著作权证书(3 分)」 | 评分 | 部分:软著长期有效、没有等级,只能看有没有 |
| 产品证书 | 节能产品认证、CCC、商用密码产品认证、IPv6 Ready、网络安全专用产品检测证书 | 技术参数 / 无效 | **不能**:证书是厂商的、跟着产品走 |
| 人员证书 | 「虚拟化维护人员具有虚拟化认证证书」 | 技术参数 | **不能**:人员库是 V1 |
结论:
1. 政府采购项目不能把和项目无关的资质设为资格条件,所以企业资质出现在资格条件里的少(10 份里只有 S01),多数在评分项里。这样一来,资质问题大多是「重要」,不是「致命」。
2. 产品证书和人员证书必须先排除,否则资质库会对它们报「未覆盖」,造成大量误报。
3. 真实投标文件里的证书基本都是盖章扫描件。不少 IT 投标文件另附一张文字的「企业资质情况表」,列出证书名称、编号和有效期,这是投标文字层上唯一能读到有效期的地方。
## 2. 资质库(FR-M6-08)
### 2.1 数据
用基线里已有的两张表,**不加列、不加迁移**:
- `qualification`:名称、类型(资质证书 / 体系认证 / 软件著作权 / 其他)、等级、证书编号、发证机构、有效期起止(「有效期至」为空表示长期有效)。
- `qualification_file`:附件。对象键 `t/<tenant>/q/<qualification>/<file>`,用**企业级数据密钥**加密(DM4)。
证书类别(§3.1)在用到时从名称和类型算出来,不存库。词表改了以后,旧记录不用迁移。
### 2.2 接口
按 `openapi.yaml` 现有的 7 个操作实现:列表(可按类型筛)、新增、修改、删除(连同附件)、挂附件(走现有的分片上传,`purpose = qualification_file`)、附件下载地址、删除附件。只加两处:
- `Qualification` 响应加一个字段 `late_for`:进行中的项目里,投标截止日晚于这张证书「有效期至」的项目(项目编号、名称、截止日)。原型里的「N 张证书会在进行中项目的投标截止前到期」提醒就靠这个字段。「进行中」指状态是跟进中、编制中、已检查,且截止日不早于今天。
- 列表接口加关键词参数 `q`,按名称、编号、发证机构搜索。
权限点用已有的 `qualification.edit`。增删改和附件操作都写审计:`qualification.create / update / delete`、`qualification_file.attach / delete`,修改记录前后的值。不扣额度。
### 2.3 校验
- 名称必填;「有效期至」必须晚于「有效期起」。
- 证书编号、「有效期至」:原型要求必填(长期有效的打开开关),**界面按原型必填,接口保持可空**,因为以后从投标文件批量导入时不一定读得全。
- 附件:PDF / JPG / PNG,单个不超过 20 MB,一条资质可以挂多个附件。
### 2.4 删除
删除是硬删:同时删行、删对象、写审计。原型在确认框之外还有「撤销」提示,**这一步不做撤销**(QL6):要撤销就得软删除,而软删除和「彻底删除」(G10)冲突;确认框已经够了。已导出的报告不受影响,因为报告里存的是生成时的快照。
企业注销时按 `data-model.md` §6.1 的顺序删资质库,企业级密钥一并销毁。
### 2.5 前端
照原型 `qualifications.html` 实现:侧栏「资质库」(图标 `award`)、类型分段筛选、关键词搜索、按「有效期至」排序(长期有效的排最后)、到期提醒条、新增 / 编辑对话框(日期选择器上标出进行中项目的截止日)、附件上传和预览、删除确认。页面底部注明「业绩、人员、历史标书等资产库在 V1 提供」。
## 3. 资质匹配(FR-M3-05)
### 3.1 证书类别词表
`check/qualification.py` 里放一张词表,规则集版本跟着升(2026.11-r4)。每个类别包括:别名(含国标等同写法)、等级写法,以及等级怎么比(数字越大越高,还是「一级」最高)。初版覆盖 IT 集成常见的企业资质:
| 类别 | 别名例子 | 等级 |
|---|---|---|
| ISO 9001 质量 | GB/T 19001 | 无 |
| ISO 14001 环境 | GB/T 24001 | 无 |
| ISO 45001 职业健康安全 | OHSAS 18001、GB/T 45001、GB/T 28001 | 无 |
| ISO 27001 信息安全 | GB/T 22080 | 无 |
| ISO 20000 IT 服务 | GB/T 24405 | 无 |
| ISO 22301 业务连续性 | GB/T 30146 | 无 |
| CMMI | 能力成熟度模型集成 | 1–5,大为高 |
| 信息系统建设和服务能力 | CS、CS1–CS5 | 1–5,大为高 |
| ITSS 运维 | 信息技术服务标准、运行维护 | 一级最高 |
| CCRC 信息安全服务资质 | 中国网络安全审查技术与认证中心;子类:安全集成、风险评估、应急处理、软件安全开发等 | 一级最高 |
| CCIA 信息系统业务安全服务资质 | 中国网络安全产业联盟 | 一级最高 |
| 电子与智能化工程专业承包 | 建筑业企业资质 | 一级 > 二级 |
| 建筑智能化系统设计专项 | 工程设计资质 | 甲 > 乙 |
| 涉密信息系统集成 | 涉密资质 | 甲 > 乙 |
| 安全生产许可证 | | 无 |
| 软件著作权 | 计算机软件著作权登记证书、软著 | 无,长期有效 |
**排除词**:要求里出现「产品」「设备」「所投」「厂商」「制造商」「节能」「环境标志」「CCC」「3C」「商用密码产品」「人员」「工程师」「项目经理」这类主体时,判定为产品证书或人员证书,不进资质匹配,仍按现在的流程处理(找响应、扫描件兜底)。
认不出类别、但清单类别是资格条件或评分项、文字里又有「证书 / 资质 / 认证」字样的,计入覆盖范围:「N 条资质要求没认出是哪类证书,有效期和等级没有核对」。
### 3.2 三方怎么对上
对每条进入资质匹配的清单项(类别是资格条件或评分项,且认出了证书类别):
1. **招标侧**:类别、要求的等级(「CS3 级及以上」「一级」)、子类(CCRC 的「安全集成」)。要求原文就是招标侧出处。
2. **投标文字层**:在响应匹配找到的证据句附近,以及投标文件里写着同一类别的表格行中,读证书编号、等级、有效期(「有效期至」「有效期:A 至 B」「截止日期」)。日期解析复用 `rules.py` 里的写法。只取**和这个类别同一行、或同一段落里只有这一张证书**的日期;附近出现两张以上证书、日期归属不清时不读。
3. **资质库**:同一类别的记录。同类有几条(旧证书和换发后的新证书)时取最有利的一条:有效期最晚、等级最高。
有效期比较用项目基本信息里的**投标截止日**(人工改过的为准),不用今天。「有效期至」当天仍算有效,即 `有效期至 < 截止日` 才算过期。项目没有截止日时,不比有效期,写进覆盖范围。
### 3.3 规则和定级
「按清单项定级」:资格条件 → 致命,评分项 → 重要(沿用 `findings.severity`)。
| 规则 | 什么时候报 | 证据来源 | 级别 |
|---|---|---|---|
| `QUAL.EXPIRED` | 证书在投标截止日已过期 | 投标文字层 | 按清单项 |
| | | 只有资质库 | 降一级(致命 → 重要,重要不变) |
| `QUAL.LEVEL_LOW` | 等级能比较,且低于要求 | 投标文字层 | 按清单项 |
| | | 只有资质库 | 降一级 |
| `QUAL.NOT_COVERED` | 投标文字层没找到(结论是「未找到」或「扫描件」),而且资质库**有记录但没有这一类** | 资质库 | 重要(不给致命:资质库可能没录全) |
| (不报问题) | 资质库里有、而且有效、等级够 | | 扫描件兜底那条「请人工核对」保留,说明里补一句:资质库里有《X》(编号…,有效期至…),请核对扫描件里附的是这一张 |
规定:
- **投标文字层和资质库冲突时,以投标文件为准**。投标文件里的日期已过期、资质库里有换发后的新证书时,仍按投标文字层报 `QUAL.EXPIRED`,说明里写「资质库里有有效期至 … 的新证书,投标文件附的可能是旧证书」。评审看的是投标文件。
- **一条清单项只出一个资质问题**。资质问题比扫描件兜底的「请人工核对」更具体,有资质问题时代替那一条;投标文件是文字版且确实没找到(「未找到响应」,致命)时,保留原问题,只在说明里补资质库的情况(「资质库里有这张证书,可能忘了附」)。
- **软件著作权**不查有效期和等级,只在扫描件兜底说明里补「资质库里有 N 件软件著作权」。要求里写了具体软件名的,也不按名称硬配:软件名的写法太散。
- **CCRC 子类**:要求写了子类(安全集成),资质库记录的名称里没有这个子类,按 `QUAL.NOT_COVERED` 报,说明写「资质库里的 CCRC 证书没写服务类别,请核对是不是『安全集成』」,级别是提示。
- **资质库是空的**:所有资质库规则都不跑,覆盖范围写「资质库还没有录入资质,有效期和等级没有按资质库核对」。查标页在覆盖范围旁边放一个去资质库的链接。
- 说明只写事实,不写整改建议。原型 f6 里「请上传换发后的证书」这一句去掉,改成「按资质库记录,有效期至 2026年11月15日,早于投标截止日 11月20日」。
### 3.4 问题的存法和出处
- `finding.type = qualification`,`detector = rule`,`rule_code` 是上面的编号,`checklist_item_id` 填对应清单项,`match_key = rule:QUAL.EXPIRED:<清单项>`,复查沿用按现有机制走。
- `detail` 里存:`kind`(过期 / 等级不足 / 未覆盖)、`source`(`bid_text` / `library`)、截止日、要求的等级,以及用到的资质库记录的**快照**(编号、名称、等级、有效期、记录 id)。资质库记录以后改了、删了,旧检查的问题和报告不变。
- 出处:招标侧照常挂清单项的原文出处;投标文字层来源的挂投标侧句子;资质库来源的**没有投标侧出处**,界面和报告显示「依据:资质库记录」块(名称、编号、有效期,能点开那条记录和附件)。这一点要你确认(QL2)。
- 覆盖范围加一节「资质核对」:资质库有几条、本次核对了几条资质要求、几条按资质库判断、几条没认出类别、产品证书和人员证书各排除了几条。
### 3.5 在流水线里的位置
放在 `runner.py` 的响应匹配和扫描件兜底之后、`rules.run` 之前,读一次本企业的资质库,不调模型、不计费。复查时重新算:资质库可能改过,结果不复用。
## 4. 不在这一步做的
| 不做 | 原因 |
|---|---|
| D14 证书主体和投标人不一致(子公司、曾用名) | 不在 FR-M3-05 的三种预警里;证书主体只在扫描件上;资质库挂在企业主体下,主体本来就一致。等 OCR(S5)后再看 |
| D15 业绩 / 人员证明的时间窗口 | 业绩库、人员库是 V1 |
| 产品证书核对 | 厂商的证书,不是用户的资质(红线 5:不从外部补数据) |
| 从投标文件或附件里自动识别、批量导入资质 | 要 OCR / 多模态;MVP 只手工录入(01-scope-roles:「手工录入 + 有效期」) |
| 读标清单上显示「资质库:ISO 9001 有效期至 …」 | 已在第 6 步做(2026-11-05):`GET /projects/{id}/qualification-matches`,按投标截止日判断,资质库空时不显示 |
| 软著按软件名称配对、按件数核对评分 | 写法太散,认不准的不报 |
## 5. 评测
- **类别识别**:把 S01–S10 读标结果里和证书有关的清单项全部人工标注(企业资质 / 软著 / 产品证书 / 人员证书 / 无关,再加类别和等级),约几十条,作为词表的单元测试。读标留出集的 S03、S06、S09 不在开发里看,词表写完后跑一次,只用来报数字。
- **规则**:每条规则配正例和反例单元测试:截止日当天到期不报;同类有新证书也有旧证书取新的;等级写法认不出时不比较;产品证书不进匹配;资质库空不报。
- **评测集**:生成器现在不生成证书文字。给风格 A / B 的规矩投标文件加一张「企业资质情况表」(虚构的证书、编号、有效期),注入 D12(有效期早于截止日;整张表的证书都过期)和「等级不足」两类问题,按 GUIDELINES 的流程请 Fable 复核、你抽查。资质库那一侧在评测里用虚构的资质库模拟,分「空 / 全 / 缺一类 / 有过期」四档。
- **局限**:评测集里的证书表是看着词表生成的,分数说明不了泛化。国动的真实投标文件到了以后,请你按自己公司的真实证书录资质库,再跑一次,这才是这一步真正的检验。
## 6. 工作量和顺序
1. 后端 `qualification` 模块:增删改、附件、`late_for`、审计,集成测试(含行级安全、企业级密钥)。约半天。
2. 前端资质库页。约半天。
3. 类别词表、标注样本、`check/qualification.py`、接入流水线、覆盖范围、问题渲染和报告。约 1 天。
4. 评测集加证书表和注入,复核。约半天。
## 7. 待确认事项
| 编号 | 事项 | 建议 |
|---|---|---|
| QL1 | 匹配方式 | **规则 + 证书类别词表,不调模型**。认不出的不判断、写进覆盖范围。理由:资质名称是有限集合,规则可解释,不花钱 |
| QL2 | 只有资质库记录、没有投标侧出处时能不能出问题 | **能出,降一级**,出处显示「依据:资质库记录」。资质库是用户自己录的数据,不是 AI 结论,和红线 2「AI 结论要能回溯原文」不冲突;如果不出,扫描件为主的真实投标文件就基本查不出过期 |
| QL3 | 「未覆盖」怎么报 | 资质库**有记录但没有这一类**时报「重要」;资质库是空的不报,只写覆盖范围 |
| QL4 | 投标文件和资质库冲突时 | **以投标文件为准**,说明里提一句资质库里有新证书 |
| QL5 | 免费版能不能用资质库 | **能用**。01-scope-roles 把资质库写在专业版下,但它不花额度、没有成本;免费版只有 1 个项目,用来试用完整的查标链路,缺了资质库体验就不完整 |
| QL6 | 删除要不要撤销 | **不要**,只留确认框;撤销需要软删除,和「彻底删除」冲突 |
| QL7 | D14(主体不一致)、D15(时间窗口) | **不在这一步做**,评测规范里这两类的实现阶段改为「OCR 之后 / V1」 |
## 8. 落地记录(2026-11-04)
和上文设计不一致、或实现时补的,记在这里:
- **一类证书一个问题**(改 §3.3「一条清单项只出一个资质问题」):一条评分项常写几类证书(S07「ITSS 三级得 1 分;CS3 及以上得 2 分」),一张过期、一张等级不够是两件事,合成一条时规则编号只能取一个。改为每条清单项里每类证书出一个问题,标题写证书名(「证书等级低于要求:信息系统建设和服务能力等级」),`match_key = rule:QUAL.*:<清单项>:<证书类别>`。
- **评分项不逐条找响应的也查**:applicability 把没写「提供……证书」的评分项跳过了(「评分标准本身」),而企业资质多数就在评分项里。资质匹配改为看所投包里的全部清单项;这类条目没有响应结论,投标文字层和资质库的过期、等级照常提示,「未覆盖」不报(不知道投标人报不报这一项分)。
- **等级按证书各读各的**:要求里属于这一类证书的那一段(从它的名称到下一类证书的名称)里读等级;投标侧先看短的格子(等级栏),证书编号里常带等级字样(「CS3-3201-…」);大写数字(「贰级」)也认。
- **响应错位规则收窄**(`check/deviation.py`):资质情况表的名称格(「安全生产许可证」,比要求短、以证书名结尾)不算「照抄的要求格」,右边的编号、有效期不算「响应对不上」。端到端测试里发现的。
- **营业执照、法人证书、联合体的资质认定规则**不算「没认出类别的资质要求」,不进覆盖范围的计数。
- **出处**:资质问题挂清单项自己的招标侧出处(`Hit.tender_refs`);问题接口的 `rule` 改为规则问题都带(原来挂了清单项就不带);报告模板 v2 在问题明细里写「依据:资质库记录《…》」。
- **前端**:资质库页和对话框(日期可直接键入,接受 2026/11/20、2026年11月20日等写法;日历弹层第 6 步已加);查标页资质问题显示「依据:资质库记录」块,墨色(用户录入的数据,不用 AI 出处的蓝铅笔色)。
- 评测见 `check-eval.md` §13。