Procházet zdrojové kódy

docs: 采集指标 spec 改为方案A(沿既有 expected_indicators),补齐管理端/展示/驱动解析

Sisyphus před 3 dny
rodič
revize
c295ce309d

+ 73 - 118
docs/superpowers/specs/2026-10-09-report-type-indicator-driven-parsing-design.md

@@ -1,162 +1,117 @@
-# 报告类型「采集指标」定义与驱动解析 — 设计文档
+# 报告类型「采集指标」补齐 — 管理端编辑 + 完整度展示 + 驱动解析
 
 - 日期:2026-10-09
-- 方案:方案2「分层」(DAN 保持 Java 解析器不动;指标清单驱动非 DAN 的 LLM/脚本解析;DAN 侧做归一 + 完整度校验)
+- 方案:**方案A(沿既有实现)**——以既有 `report_type_registry.expected_indicators`(JSON) + `health_report_drafts.collect_validation`(JSON) 为准,**不新建表**、不引入 `indicator_definitions` 目录关联;本期只补齐三个缺口:管理端可见/可改、报告管理页展示、非 DAN 解析由清单驱动。
 - 状态:待用户评审
 
-## 1. 背景与问题
+## 1. 背景
 
-后台「报告类型管理」(`cfc-web` `ReportTypeManagement.vue` + `ReportParserAdminController`)目前只能维护两样东西:**报告类型 CRUD** 与**指纹规则 CRUD**。它**无法声明"某类报告应当采集哪些指标"**。
+远端提交 `1e2fc417c`(作者 `liaoxg`,2026-10-08)已实现「每类报告定义采集指标项并接入采集完整性校验」:
 
-由此导致的已观测问题(对 5 份已上传 DAN 报告审计):
+- **定义**:`report_type_registry.expected_indicators`(TEXT/JSON 数组,纯指标名);`DatabaseInitializer.registerCollectExpectedIndicators()` 代码**种子**了 10 个类型(`dan_a1/a2/b2/b3/b4/b5/b6/c1`、`gut_flora`、`gut_flora_beijing`),并幂等补注册对应类型行。
+- **校验**:`ReportCollectService.runCollectValidation(...)` 在采集后按清单校验(归一化 + 包含 + CJK 二元组重叠匹配),结果写 `health_report_drafts.collect_validation`(`typeId/expected/matched/pass/missing`);DAN 与非 DAN 两条路径均已接入。
+- **DB**:`ensureColumn`(迁移 354)新增两列并同步 `schema.sql`。
 
-- `dan_b4`(wisdom)两份报告只采到 `selfConcept`(6) + `selfDriving`(3)=9 项;**`growthMindset`(成长型思维)0 项**——解析器 `parseGrowthMindset` 只提取"含数字行",而该章节为无数字图文,故整节漏采。
-- `dan_a2`(mind)两份报告:**情绪稳定性 4 分测验只采到 1 项**(自卑=4),缺 抑郁/焦虑/依赖;并**混入 5 项 `category=auto` 垃圾**(联系电话/报告编码/您测评的具体情况/身高/体重)。
-- 全部 DAN 上传 `parsed_summary` 长度为 0;`parsed_suggestions` 异常巨大(B4 21129 字符)→ 疑 `extractSection` 失配。
+**尚未完成的缺口(= 本设计范围)**:
 
-根因:**没有"应采指标"的声明**,所以既不能驱动解析器去采全,也不能判断一次采集是否完整。本设计新增该声明能力,并让声明**驱动解析**与**完整性校验**。
+1. 管理端**看不到、也改不了** `expected_indicators`(后端实体已含该字段、`/types/list` 会返回、`/types/save` 接收实体而自动映射,但前端 `ReportTypeManagement.vue` 未提供编辑)。
+2. 前端**不展示** `collect_validation`(报告管理页读 `report-summary`,该接口未暴露校验字段)。
+3. 该清单**未驱动解析**(非 DAN 走 LangGraph LLM 时未传入清单)。
 
 ## 2. 目标 / 非目标
 
 ### 目标
-1. 管理端可为每个报告类型定义一组成员指标(**采集指标**),成员来自既有指标目录 `indicator_definitions`。
-2. **驱动解析**:
-   - 非 DAN 类型(走 `cfc-langgraph` LLM/脚本)——把该类型的指标清单注入解析提示词/Schema,使 LLM 按清单提取。
-   - DAN 类型——保持现有 Java 解析器,但解析结果按清单**归一化**(补 `code`、对齐 `name`)并**计算缺失**。
-3. **完整度可见**:管理端可对已上传报告查看「应采 / 已采 / 缺失 / 多余」。
-4. 全程**幂等**(迁移可重复执行),不回归现有采集链路。
+1. 管理端可**查看并编辑**每个报告类型的 `expected_indicators`(沿用既有 JSON 存储)。
+2. 「报告管理」页可展示每份报告的**采集完整度**(应采/已采/缺失/通过)。
+3. 非 DAN 类型解析时,`expected_indicators` **驱动** LangGraph LLM 解析(注入清单)。
+4. 修正既有种子覆盖行为,使管理员编辑**不被启动种子抹除**。
 
 ### 非目标(本期不做)
-- 不改写 DAN Java 解析器的抽取逻辑(`parseA2Report/parseB4Report/parseGrowthMindset/...` 保持不变,避免回归)。
-- 不把 DAN 解析改为 LLM 驱动(列为 P3,目标态,另议)。
-- 不做报告展示的视觉/图表改版。
-- 不修复第 1 节列出的解析器缺陷本身(growthMindset 漏采、auto 垃圾、summary 失配)——**本设计只新增"声明 + 校验"能力把它们暴露出来**,缺陷修复单独排期。
-
-> 说明:本期交付后,第 1 节的漏采 / 垃圾项会在「完整度」中显示为"缺失 / 多余",但解析结果内容不会自动变化。若用户希望在 P1 同时修复这些缺陷,需单独确认(会触及 DAN 解析器,超出本 spec 的非目标)。
-
-## 3. 关键现状(实现事实)
-
-- **两条解析路径**(`ReportCollectService.collect`):
-  - `family=dan` → `collectDanReport` → `DanReportParseService.parse(fis, dimension)`(按维度硬编码),产出 `DanParsedReport{reportType, dimension, items:DataItem[]{code,name,value,category}, extra, summary, suggestions}`;经 `DanReportUploadService.confirmUpload` 入库(结果 / 展示块 / 汇总 / 五维)。
-  - 非 DAN → `LangGraphReportParseService.parseByScript / parseWithDetect`(Post `cfc-langgraph` `/api/v1/report/parse`;`postParse(path, body)` 只放 `file_path`),失败回退 `parseWithOpencode`。
-- **统一载荷**:`ParsedReportPayload.Payload`(含 `indicators: List<Indicator>`);`Indicator{category, indicatorName, indicatorValue, unit, refRange, status, symptoms, collectionDate}`,**无 `code` 字段**。
-- **指标目录**:`indicator_definitions(id, domain, category, code, name, data_type, options, unit, ref_range, sort_order, status)`,`code` 唯一;实体 `IndicatorDefinition`;既有端点 `/api/admin/indicator/{list,create,update,delete,values/list}`(`IndicatorAdminController`);管理页 `cfc-web-src/src/views/admin/indicators/index.vue`。
-- **报告类型表**:`report_type_registry`(`type_id` 唯一、`family`、`display_name`、`parser_path`…)与 `report_fingerprint`(`type_id` + `pattern` + `weight` + `is_exclusion`),DDL 见 `src/main/resources/schema.sql:4764` 起。
-- **迁移机制**:`DatabaseInitializer` 内顺序 `jdbcTemplate.execute(...)`(幂等 `CREATE TABLE IF NOT EXISTS` / `ALTER TABLE`,异常吞掉打 WARN);新迁移追加在末尾并同步 `schema.sql`。
-- **前端 API**:`cfc-web-src/src/api/reportParser.js`(`/api/admin/report-parser/*`)。
-- **DAN 展示分组**:`ReportBlockAssembler.assembleDanByType` 依据 `DAN_CATEGORY_TITLES`。
-
-## 4. 设计(方案2 分层)
-
-### 4.1 数据模型
-
-**A. 扩充指标目录 `indicator_definitions`(纯数据,幂等种子)**
-
-- 新增 `domain='dan'`,`category` 取 DAN 分类名(`bigFive/social/emotion/ability/overall/cognitive/executiveFunction/learningMotivation/learningStrategy/selfConcept/behavior/family/growthMindset/selfDriving/hollandInterest` 等)。
-- `code` 用稳定 slug(如 `dan_growth_mindset`),`name` **必须与 Java 解析器 `DataItem.name` 完全一致**(这是归一化匹配键),`data_type` 取 `number|text|score`,`status=1`。
-- 仅新增,不修改既有 body/mind/wisdom 指标。
-
-**B. 新增关联表 `report_type_indicator`**
-
-```sql
-CREATE TABLE IF NOT EXISTS report_type_indicator (
-  id BIGINT AUTO_INCREMENT PRIMARY KEY,
-  type_id VARCHAR(64) NOT NULL COMMENT '关联 report_type_registry.type_id',
-  indicator_code VARCHAR(64) NOT NULL COMMENT '关联 indicator_definitions.code',
-  sort_order INT DEFAULT 0 COMMENT '展示/校验顺序',
-  status TINYINT(1) DEFAULT 1 COMMENT '0=停用 1=启用',
-  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-  UNIQUE KEY uk_type_indicator (type_id, indicator_code),
-  INDEX idx_rti_type (type_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告类型采集指标关联表';
-```
+- 不新建 `report_type_indicator` 表,不把 `expected_indicators` 改为引用 `indicator_definitions.code`(属方案C,另议)。
+- 不改 DAN Java 解析器抽取逻辑(`parseA2Report/parseB4Report/...`)。
+- 不改既有匹配算法(复用 `ReportCollectService` 的归一化 + CJK 二元组匹配)。
+- 不删除既有种子数据。
+- 不做报告展示视觉/图表改版。
 
-**C. 迁移**:新增 `migrateReportTypeIndicator()` 于 `DatabaseInitializer`(建表 + 种子 DAN 指标,均幂等),末尾调用;`schema.sql` 同步追加。
+## 3. 现状关键事实
 
-### 4.2 后端(Spring Boot)
+- 实体:`ReportTypeRegistry.expectedIndicators`(String);`HealthReportDraft.collectValidation`(String)+ `confirmedReportId`。
+- 管理端接口:`POST /api/admin/report-parser/types/list`(返回 `ReportTypeRegistry`,含 `expectedIndicators`);`POST /types/save`(`@RequestBody ReportTypeRegistry`,`updateById/insert` 自动写入 `expectedIndicators`)。
+- 校验落点:`ReportCollectService`:
+  - 非 DAN:`runCollectValidation(draftId, validationTypeId, ParsedReportPayload.Payload payload)`;
+  - DAN:`runCollectValidation(draftId, registry.getTypeId(), DanParsedReport parsed)`;
+  - 统一 `runCollectValidation(draftId, typeId, Set<String> collectedNames)` → `loadExpectedIndicators(typeId)`(读 registry JSON)→ 计算 `matched/missing` → `updateCollectValidation(draftId, json)`。
+- 采集闭环:`collectDanReport` 中 `markCollectCompleted(draftId, upload.getId())` → `health_report_drafts.confirmed_report_id = dan 上传 id`。
+- 报告管理页:`ReportManagement.vue`(`/report-management`,权限 `health:reports`),列表来自 `POST /api/admin/report-summary/list`,行含 `reportType / sourceTable / source_id / detailRoute`。
 
-- 实体 `ReportTypeIndicator`(`@TableName("report_type_indicator")`)+ Mapper。
-- 服务 `ReportTypeIndicatorService`:
-  - `List<IndicatorDefinition> listByType(String typeId)`:按 `sort_order` 关联目录返回(过滤停用)。
-  - `saveForType(String typeId, List<String> codes)`:`@Transactional` 先删后插(整表替换该类型的清单)。
-  - `Map<String,Object> completeness(String typeId, String sourceTable, Long sourceId)`:见 4.5。
-- `ReportParserAdminController` 新增端点(沿用 `/api/admin/report-parser` 前缀与 `Result`):
-  - `POST /indicators/list` `{typeId}` → 已绑定指标(含目录 name/unit)。
-  - `POST /indicators/save` `{typeId, indicatorCodes:[...]}` → 整表替换。
-  - `POST /indicators/completeness` `{typeId, sourceTable, sourceId}`(或 `{summaryId}`,服务端据此解析 type_id 与数据源)→ 完整度:`{expected[], collected[], missing[], extra[]}`。
-  - (目录选择复用既有 `/api/admin/indicator/list`,不新增。)
-- `LangGraphReportParseService`:`postParse` 的 body 支持附加字段;新增重载 `parseByScript(String pdfPath, List<IndicatorDefinition> indicators)`,把 `indicators:[{code,name,category,unit}]` 放入请求体(老调用保持兼容)。
-- `ReportCollectService`:非 DAN 分支在调用 `parseByScript/parseWithDetect/parseWithOpencode` 前,按 `typeId` 取清单并下传(清单为空则行为不变)。
-- `ParsedReportPayload.Indicator` 新增 `code` 字段(Jackson 向后兼容;用于按 code 归一)。
+### 3.1 关键现状代码位置
+- `cfc-backend/.../config/DatabaseInitializer.java`:`registerCollectExpectedIndicators()` / `ensureReportTypeRow(...)` / `seedExpectedIndicator(...)` / `jsonArray(...)`。
+- `cfc-backend/.../service/ReportCollectService.java`:`runCollectValidation(...)`、`loadExpectedIndicators(...)`、`indicatorMatches(...)`。
+- `cfc-backend/src/main/java/com/etotem/cfc/entity/ReportTypeRegistry.java`(`expectedIndicators`)、`entity/HealthReportDraft.java`(`collectValidation`)。
+- `cfc-web/src/views/admin/ReportTypeManagement.vue`、`ReportManagement.vue`;`cfc-web/src/api/reportParser.js`。
 
-### 4.3 cfc-langgraph(非 DAN 的 LLM 驱动,P2)
+## 4. 设计(方案A)
 
-- `app/api/report_parse.py`:`ParseRequest` 增 `indicators: Optional[list] = None`,透传给 `ReportParseAgent.parse`。
-- `app/agents/report_parse_agent.py`:当 `indicators` 非空时,在解析提示词/Schema 中注入「仅提取以下指标;未能提取的返回 null」,输出 `indicators[]` 携带 `code`。
-- 兼容性:`indicators` 缺省时行为与现状一致。
+### 4.1 种子策略调整(关键,必须先做)
 
-### 4.4 管理端前端(cfc-web)
+现状 `seedExpectedIndicator(...)` 注释为「每次启动覆盖,保证与代码一致」,**会抹掉管理员编辑**。改为:
 
-- `ReportTypeManagement.vue`:类型行「更多」菜单新增 **「采集指标」** 对话框:
-  - 左侧候选 = `getIndicatorList()`(`/api/admin/indicator/list`,可按 domain 过滤);右侧已选(可拖拽排序)。
-  - 保存调 `saveTypeIndicators({typeId, indicatorCodes})`。
-- `reportParser.js` 新增:`getTypeIndicators`、`saveTypeIndicators`、`getCompleteness`。
-- **「报告管理」页(`ReportManagement.vue`,路由 `/report-management`,权限 `health:reports`)**:表格新增「采集完整度」列(徽标:`已采/应采`,缺失>0 标红/hover 展示缺失明细),行操作「详情」中展示「应采/已采/缺失/多余」完整清单。数据由 `getCompleteness({summaryId})` 按需拉取。**该页仅展示,不改变解析行为。**
+- **仅当 `expected_indicators` 为 `NULL` 或空字符串时写入**(insert-if-absent);已存在(含管理员编辑)则**跳过**。
+- 效果:内置 10 类型首次部署自动补种;此后管理员编辑持久保留;代码新增类型仍会自动补种。
+- 既有 `ensureReportTypeRow` 行为不变(仍幂等补注册类型行)。
 
-### 4.5 归一化与完整度校验
+### 4.2 管理端编辑(前端为主)
 
-**归一化(DAN 与非 DAN 统一)**:对一条解析结果,逐个已采项按顺序匹配清单:①`code` 相等 → 命中;②否则 `name`(DAN 为 `DataItem.name`;非 DAN 为 `Indicator.indicatorName`)与目录 `name` 相等 → 命中;命中则写回 `code`,并采用目录 `name` 作为规范名。未命中的已采项视为**多余**(如 `category=auto` 垃圾)。
+- **后端**:`/types/list`、`/types/save` 已支持,无需新增字段。仅补**可选校验**:`/types/save` 收到非空 `expectedIndicators` 时校验其为合法 JSON 数组(否则返回 400),避免脏数据破坏校验。
+- **前端 `ReportTypeManagement.vue`**:在类型编辑对话框新增「采集指标」区——标签(Tag)列表,支持逐个添加/删除自由文本指标名;保存时 `JSON.stringify` 为 `expectedIndicators` 随 `/types/save` 提交;打开时从 `/types/list` 行回填并 `JSON.parse`。
+- `reportParser.js` 无需改(复用 `getReportTypes`/`saveReportType`)。
 
-**完整度**:`应采 = 清单成员`;`已采 = 命中的清单成员`;`缺失 = 应采 − 已采`;`多余 = 已采项 − 应采`。返回计数与明细。
+### 4.3 「报告管理」页展示完整度
 
-**计算时机**:**按需计算**(管理端查询时),不写入采集流程——零风险、不改 `collect` 事务边界。
+- **关联键**:报告管理行(`report_summary`,`source_table/source_id`)→ 草稿 `health_report_drafts`,以 `confirmed_report_id = source_id` 关联(DAN:`source_table='dan_report_uploads'`;非 DAN:对应 `health_*` 表)。读取该草稿的 `collect_validation`。
+- **后端**:在 `/api/admin/report-summary/list`(或 detail)结果补充 `collectValidation` 字段;实现方式二选一(见 §9 待决):①list 批量左连接草稿;②新增 `POST /api/admin/report-parser/indicators/completeness {summaryId}` 按行查询。
+- **前端** `ReportManagement.vue`:表格新增「采集完整度」列——徽标 `已采/应采`(`pass=true` 绿,`pass=false` 红并显示缺失数),行「详情」抽屉列出 应采 / 已采 / 缺失 明细。
 
-**type_id 解析**(报告管理页行 → 具体类型):行来自 `report_summary`(含 `source_table/source_id/detail_route`)。
-- DAN:`source_table='dan_report_uploads'`,由 upload 的 `report_type`(A2/B4…)映射到注册表 `type_id`(`dan_a2`/`dan_b4`…)。
-- 非 DAN:由 `report_summary` 的类型信息(或 `type_id` 列/关联草稿)解析到注册表 `type_id`。
+### 4.4 LangGraph 驱动(P2)
 
-**数据源**:
-- DAN:`dan_report_uploads.parsed_items`(JSON `DataItem[]`)。
-- 非 DAN:草稿 `health_report_drafts.payload`(JSON `Payload.indicators`)或对应已入库报告的指标。
-- 清单为空 → 直接返回 `{expected:[], collected:[], missing:[], extra:[]}`,不报错。
+- `ReportCollectService` 非 DAN 分支:解析前按 `typeId` 读取 `expected_indicators`,下传 `LangGraphReportParseService`(DAN 路径不变)。
+- `LangGraphReportParseService`:`postParse` 的 body 增加 `indicators`;新增重载 `parseByScript(String pdfPath, List<String> indicators)`(旧调用保持兼容)。
+- `cfc-langgraph`:`app/api/report_parse.py` `ParseRequest` 增 `indicators: Optional[list]`;`app/agents/report_parse_agent.py` 当清单非空时在提示词/Schema 注入「仅提取以下指标,缺失返回 null」。缺省时行为与现状一致。
 
 ## 5. 数据流
 
 ```
-[管理端] 报告类型 → 采集指标对话框 → /indicators/save → report_type_indicator
-                                                    ↑ 引用 indicator_definitions.code
-[采集] Draft.collect
-  ├─ family=dan  → Java 解析(不变)
-  └─ 非 dan      → 取清单 → LangGraph /report/parse(+indicators) → LLM 按清单抽取
-                        → 归一化(补 code / 对齐 name)
-[管理端] 报告管理页 → /indicators/completeness → 归一化对比 → 应采/已采/缺失/多余
+[管理端] 报告类型编辑对话框 → /types/save(expectedIndicators) → report_type_registry.expected_indicators
+[采集]  Draft.collect
+  ├─ family=dan  → Java 解析 → runCollectValidation(parsed) → health_report_drafts.collect_validation
+  └─ 非 dan      → 取 expected_indicators → LangGraph /report/parse(+indicators) → LLM 按清单抽取
+                   → runCollectValidation(payload) → collect_validation
+[管理端] 报告管理页 → report_summary + 关联草稿 collect_validation → 完整度列/明细
 ```
 
 ## 6. 错误处理
 
-- 清单为空:解析与现状完全一致(不注入、不校验),不报错。
-- 指标 code 在目录中不存在(被删):`save` 拒绝或忽略,`completeness` 记为无法解析并告警日志。
-- LangGraph 不支持 `indicators`(旧版本):服务端兼容缺省;若调用失败按现有回退链处理,不新增失败点。
-- 迁移:沿用 `DatabaseInitializer` 幂等模式(WARN 非错误)。
+- `expected_indicators` 为空 / 类型未注册:`runCollectValidation` 现行已跳过,不报错。
+- `/types/save` 传入非法 JSON 数组:返回 400,不落库。
+- 报告行关联不到草稿(历史数据 / `confirmed_report_id` 缺失):完整度列显示 `-`,不报错。
+- LangGraph 不支持 `indicators`:缺省兼容;调用失败按现有回退链,不新增失败点。
 
 ## 7. 测试策略
 
-- 后端单测:`saveForType` 整表替换语义;`completeness` 四种计数(应采/已采/缺失/多余)边界(空清单、全缺失、含垃圾项)。
-- 迁移:重复启动应用,确认建表/种子幂等、无 ERROR。
-- DAN 回归:对 5 份样本上传调用 `/indicators/completeness`,验证能识别出 `growthMindset` 缺失、A2 `auto` 多余(即"缺陷可被观测")。
-- LangGraph:pytest——传入 `indicators` 时输出含 `code`;不传时行为不变。
-- 前端:`npm run build` 通过;对话框保存后回显正确。
+- 后端:种子"仅空时写入"——先种子、改值、重启,验证不被覆盖;`/types/save` 非法 JSON 400。
+- 完整度查询:对 5 份样本 DAN 上传,验证能识别 `growthMindset` 缺失、A2 `auto` 多余(复用既有 `collect_validation`)。
+- LangGraph:pytest——传 `indicators` 输出受清单约束;不传时行为不变。
+- 前端:`npm run build` 通过;对话框回填/保存正确;报告管理页徽标与明细正确。
 
 ## 8. 交付计划(P1 + P2 同属本期)
 
-- **P1 定义 + 校验(DAN 零改动)**:数据模型(表 + 种子)、后端实体/服务/端点(`indicators/list|save`、`completeness`)、`ReportTypeManagement.vue` 采集指标对话框、`ReportManagement.vue` 完整度列/明细。**不含解析行为变更**。
-- **P2 驱动 LLM 解析(本期)**:`cfc-langgraph` `indicators` 注入 + `LangGraphReportParseService`/`ReportCollectService` 下传清单 + 归一化写入 `code`。
-- **P3 目标态(另议,本期不做)**:DAN 解析器改为读清单驱动抽取(需评估回归)。
+- **P1 管理端 + 展示**:种子策略调整(仅空写入)、`/types/save` JSON 校验、`ReportTypeManagement.vue` 采集指标编辑、`ReportManagement.vue` 完整度列/明细 + 后端字段暴露。
+- **P2 驱动解析(本期)**:`ReportCollectService` 下传清单 + `LangGraphReportParseService` + `cfc-langgraph` 注入。
+- **P3(另议,不做)**:DAN 解析器改为读清单驱动。
 
 ## 9. 风险与待决
 
-- **匹配脆弱性**:归一化依赖 name 完全一致;种子 `name` 必须逐字对齐解析器输出。若不齐,会误报"缺失/多余"。缓解:种子以解析器实测输出为准生成。
-- **P3 回归风险**:DAN 换驱动 = 大改;本期不做。
-- **指标目录语义**:DAN 指标放入 `domain='dan'`,与 body/mind/wisdom 隔离。
-- **已决**:完整度展示在「报告管理」页(`/report-management`);P2 纳入本期交付(P1+P2 一并实现)。
+- **种子覆盖 vs 管理员编辑**:已定"仅空时写入",避免抹除编辑;代价是这 10 个预置类型的清单不再随代码自动更新(管理员可手动改)。
+- **报告 → 草稿关联可靠性**:依赖 `confirmed_report_id = report_summary.source_id`;若历史数据缺失则完整度不可得。
+- **指标名匹配**:沿用 bigram 宽容匹配;管理员自填名若与解析输出差异过大仍会误报缺失。
+- **待决**:完整度在后端用「list 批量连接」还是「按行新端点」;LangGraph 注入用「严格 Schema」还是「提示词提示」。