Răsfoiți Sursa

chore: auto bump version and changelog [skip ci]

iwt 2 săptămâni în urmă
părinte
comite
1d5614b0a1

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-cd4fb3619e8d890ebd3d3bcdf8195b7a51cec355
+18cd1e87e70409e8b969510896e51d691060d60b

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1801",
+  "version": "1.0.1802",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1801",
+      "version": "1.0.1802",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1802",
+  "version": "1.0.1803",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 144 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,150 @@
 
 ---
 
+## v1.0.1803 (2026-10-27)
+
+### 新功能
+- 指标多期展示第一段收尾(报告详情对照/趋势入口/bugfix/降级)
+- 新增报告指标对照接口 + 期次列表接口,字典列表默认过滤停用项
+- IndicatorTrendService 接入时序服务 + 变化计算单点化
+- 新增 IndicatorTimelineService 指标时序服务(timeline/listPeriods/pairCompare)
+
+### 其他
+- (新时间轴页属第二段,须过 L1/L2/L3 gate 后才可上线)
+- 前端(§6.6 红线:只消费后端 trendCode,禁止按 delta 正负自判改善/恶化):
+- - report-detail:合并键改 definitionId(definitionId→item + name→definitionId 双索引)、
+-   徽标由 trendCode+movement 推导、pending 文案调整、分组「较上次 ±N」、指标行「N 期 ›」趋势入口
+- - IndicatorChangeRow:comparable===false 独立分支(恒灰无箭头)、change==null 中性兜底、
+-   displayLine 为空时隐藏对照行
+- - IndicatorIncompleteBanner:visible 判定顺序修正(pc===0&&tc===0 提到 pc===tc 之前)
+- - indicator-curve:补 IndicatorIncompleteBanner 注册/数据字段/赋值,空态改「暂无该指标数据」
+- - indicator-timeline:补 getPeriodKey(模板引用但缺失)、headChange 空值防护
+- - indicator-quicklook:change 缺失时中性兜底「暂无对比」
+- - body-detail:/api/health/indicators 未下发 trendCode → 颜色恒中性灰、箭头仅表方向;
+-   缺 definitionId 时 toast 明示原因
+- - TrendChart(pages/health/components):_buildLayout 布局复用、缺失期/NaN 断线、pointTap 点选
+- - utils/api.js:getIndicatorTimeline 委托 getIndicatorTrend(同 URL 唯一 request 入口)
+- - components/TrendChart.vue:孤立「暂无趋势数据」→「暂无该指标数据」(P0 红线)
+- 后端:
+- - IndicatorChangeService.batchResolveHistory:上一期 + 历史期数一次查询,严禁 N+1
+- - IndicatorTimelineService.timeline():totalReportCount/olderReportCount 提到观测为空判断之前;
+-   每个请求 definitionId 都返回 item,无观测 → chartMode='empty'(Q17 保留)
+- 验证:mvn clean compile BUILD SUCCESS;
+- IndicatorTimelineServiceTest 9 / HealthReportServiceIndicatorsTest 3 /
+- IndicatorChangeServiceSemanticTest 20 / IndicatorControllerTest 2 / IndicatorTrendServiceTest 9 全通过;
+- verify-vue-compile 390/390 PASS;audit-mp-antipatterns ERROR 0;audit-duplicate-api-calls 0 error。
+- 
+- - 在 §4.48.1 脚注区增加 SQL 预算小节(报告指标 ≤5、quickLookup/sourceTypes 恒 1 次查询),
+-   并说明上一期与 periodCount 全部内存计算,避免后续维护者再写成逐指标回查。
+- - 注明 missingPeriods 固定 2 次查询的优化结果(原先 1+缺失期数会 N+1)。
+- 
+- - quickLookup:每个 definitionId 调一次 changeService.resolvePrevious(1 次 SQL),
+-   40 个指标 = 40 次查询。改为复用开头已加载的 rows 内存求解上一期
+-   (新增 resolvePreviousInMemory,语义与 resolvePrevious 严格一致:
+-   同来源 + 数值非空 + reportDate 严格早于本期,取最晚一条),
+-   查询数从 1+N 降为 1。
+- - listSourceTypes:每个 sourceType 再查 2 次(periodCount / latestReportDate),
+-   即 1+2N。改为一次取回 (sourceType, reportDate) 后内存计算,
+-   查询数从 1+2N 降为 1;同日去重后得 periodCount,取最大值得 latestReportDate。
+- 单测补充:quickLookup 内存求解上一期(多期数据取到正确的上一期而非最早一期)、
+- 本期日期未知时不猜上一期、listSourceTypes 单查询算准 periodCount/latestReportDate,
+- 并断言 valueMapper.selectList 只被调用 1 次(锁死无 N+1)。
+- 
+-   返回 Result.error(400, ...) 而非默认 500,与 GlobalExceptionHandler 对
+-   IllegalArgumentException 的映射(400)保持一致,避免同类错误两套码。
+- - IndicatorTrendService.changeRanking:scope=pair 分支捕获 IllegalArgumentException
+-   时回填 code=400(参数/期次非法属客户端错误),与控制器口径统一。
+- - 新增 IndicatorControllerTest:API 边界契约单测,覆盖 scope=pair 缺参 400
+-   且不触达 service、参数齐全时正确透传 12 个入参。
+- 
+-   "命中字典之后" 提到之前 —— 未命中字典的指标同样可能带异常状态,
+-   原实现会让 summary.abnormalCount 少于实际异常项数(残缺横幅与异常徽标口径失真)。
+- - HealthReportServiceIndicatorsTest:
+-   ① 三档形态用例补齐"命中字典但无上一期且有更早报告 → pending_backfill"这一档
+-      (此前该档因 definitionId=null 被短路,未真正走到 pending_backfill 分支),
+-      并新增"未命中字典 → previous/change 恒 null"用例;
+-  ② summary 断言改用实际契约字段 changedCount(原误写 improvedCount);
+-  ③ spy 桩改用 doReturn(...).when(...),避免打桩时真实调用 batchResolve 被计入调用次数;
+-  ④ SQL 预算用例断言服务层恒 3 次协作调用且绝不直接访问 indicator_values,
+-      验证查询数与指标条数无关(无 N+1)。
+- 
+- - §4.48 后新增 4.48.1 小节:/api/health/report/indicators、/api/indicator/periods、
+-   /api/admin/indicator/backfill 三个新增端点,以及 trend/change-ranking/
+-   quick-lookup/source-types 四个改造端点的入参出参变更说明。
+- - 标注 /api/health/indicator/list 已被 /api/health/report/indicators 取代
+-   (老接口保留不动,cfc-web 仍在调用)。
+- - 记录 SQL ≤ 5 次无 N+1 约束、chartMode 判定表、magnitude 排序口径变更(|Δ| → |Δ%|)、
+-   QP17 status=0 剔除规则。
+- 
+- - IndicatorChangeServiceSemanticTest(新增):20 条语义用例 semantic01..semantic20,
+-   覆盖全部 5 个 trendCode 分支 + in_range_is_better 四分支 + 微小基数护栏 +
+-   基数为 0 + 单位不一致 + 非数值 + 跨期跳比。
+- - IndicatorTimelineServiceTest(新增):chartMode 在 n=0/1/2/5/25 下的
+-   empty/single/pair/line/line_aggregated;pairCompare 参数校验与排序;
+-   status=0 剔除;deltaPct=null 恒定排最后。
+- - HealthReportServiceIndicatorsTest(新增):报告指标三档形态(有上期/首次记录/跨期跳比)
+-   + 42 项指标 SQL ≤ 5 次(无 N+1)。
+- - IndicatorTrendServiceTest(改):适配新签名,覆盖 reportDate=null 不被当作最新值、
+-   status=0 剔除、magnitude 按 |Δ%| 排序且 null 排最后。
+- 
+- - HealthReportController 新增 POST /api/health/report/indicators:
+-   返回报告指标 + previous(上一期观测)+ change(五档形态)+ summary 汇总,
+-   权限校验沿用 getReportDetail(管理员 / 本人 / 被授权成员),无 subjectId 的历史报告
+-   仅本人上传者或家庭管理员可见。
+- - IndicatorController 新增 POST /api/indicator/periods(期次列表);
+-   /trend、/change-ranking、/quick-lookup 透传新参数并补 scope=pair 的缺参校验。
+- - HealthReportService 新增 getReportIndicatorsWithPrevious:
+-   批量解析 definition + 批量取上一期,严禁 N+1,SQL 恒定 ≤ 5 次。
+- - IndicatorMappingService.resolveBatch + IndicatorMappingRuleMapper.selectRulesWithDefinitions:
+-   改为 indicator_mapping_rules JOIN indicator_definitions 单次联合查询,
+-   raw_name → definition_id 解析与 definition 字段一次带出(省掉 selectBatchIds)。
+- - IndicatorService.listDefinitions:status 传 null 时默认按 status=1 过滤,
+-   防止自动创建的停用 definition 冒进管理端字典列表(QP17)。
+- 
+- - trend():签名扩展为 8 参(sourceType/fromDate/toDate/granularity/sourceKind),
+-   委托 IndicatorTimelineService.timeline(),老 3 参调用保留(转发)。
+- - changeRanking():新增 scope=pair + fromReportDate/toReportDate + filter + page/pageSize;
+-   scope=pair 路由到 IndicatorTimelineService.pairCompare;
+-   老路径(scope=adjacent|first-vs-latest,或只传 limit 不传 page/pageSize)保持兼容。
+-   magnitude 排序口径由 |Δ| 改为 |Δ%| 降序,deltaPct=null 恒定排最后(Q14)。
+- - quickLookup():修复 reportDate=null 的观测被当作最新值的 bug;
+-   新增 previous/change/latestDateSource/latestReportId/goodDirection/valueType/sparkline。
+- - listSourceTypes():出参新增 periodCount / latestReportDate。
+- - trend/changeRanking/quickLookup 三个方法统一经 IndicatorChangeService.loadActiveDefinitions()
+-   取 definition(内部 status=1 过滤),拿不到的观测直接剔除,不降级为"指标"+id 假名称(QP17)。
+- - IndicatorChangeService.normUnit 由 private 提升为 public,供 IndicatorTimelineService 复用
+-   单位一致性判定,避免重复实现归一化规则。
+- 
+- - timeline():按 definitionId 分组输出 points / latest / change / missingPeriods,
+-   refLow/refHigh(definition.refRange + indicator_values.refRange_snapshot 快照优先),
+-   pointsCount / totalReportCount / olderReportCount / chartMode / unitMismatch。
+-   支持 sourceType / fromDate / toDate / granularity(raw|month) / sourceKind(all|report|device)。
+- - listPeriods():期次列表(dateSource 取该期最差来源,reportDate=null 恒定排最后),
+-   供双期对比页 picker 使用;2 次查询完成,不分页。
+- - pairCompare():双期对比(scope=pair),含 sameDateWarning / from / to 元信息、
+-   skipGaps(内存计算,零额外查询)、improved/worsened/flat/incomparable 汇总、
+-   filter + orderBy + 分页。
+- - computeMissingPeriods 重写为 2 次查询(原来 1+缺失期数,N+1),
+-   并用 Map<Date, row> 修掉 Date 与 String 比较恒不相等导致行内容丢失的问题。
+- - 读路径全部经 IndicatorChangeService.loadActiveDefinitions()(status=1 过滤),
+-   拿不到 definition 的观测直接剔除,不降级为"指标"+id(QP17)。
+- 
+
+### 文档
+- 补充 N+1 约束与缺失期计算说明到 API_REFERENCE §4.48.1
+- 同步指标多期展示 T02 后端接口到 API_REFERENCE
+
+### 性能优化
+- 消除 quickLookup / listSourceTypes 的 N+1 查询
+
+### Bug 修复
+- scope=pair 缺参返回 400 明确错误码 + 补控制器契约单测
+- 修正报告指标 summary 异常计数漏算 + 强化三档形态与 SQL 预算单测
+
+### 测试
+- 固化 20 条语义用例 + 时序/对照接口单测
+
+
 ## v1.0.1802 (2026-10-27)
 
 ### 文档

+ 145 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1802
+> 当前版本: v1.0.1803
 
 ## 历史版本
 
@@ -8,6 +8,150 @@
 
 ---
 
+## v1.0.1803 (2026-10-27)
+
+### 新功能
+- 指标多期展示第一段收尾(报告详情对照/趋势入口/bugfix/降级)
+- 新增报告指标对照接口 + 期次列表接口,字典列表默认过滤停用项
+- IndicatorTrendService 接入时序服务 + 变化计算单点化
+- 新增 IndicatorTimelineService 指标时序服务(timeline/listPeriods/pairCompare)
+
+### 其他
+- (新时间轴页属第二段,须过 L1/L2/L3 gate 后才可上线)
+- 前端(§6.6 红线:只消费后端 trendCode,禁止按 delta 正负自判改善/恶化):
+- - report-detail:合并键改 definitionId(definitionId→item + name→definitionId 双索引)、
+-   徽标由 trendCode+movement 推导、pending 文案调整、分组「较上次 ±N」、指标行「N 期 ›」趋势入口
+- - IndicatorChangeRow:comparable===false 独立分支(恒灰无箭头)、change==null 中性兜底、
+-   displayLine 为空时隐藏对照行
+- - IndicatorIncompleteBanner:visible 判定顺序修正(pc===0&&tc===0 提到 pc===tc 之前)
+- - indicator-curve:补 IndicatorIncompleteBanner 注册/数据字段/赋值,空态改「暂无该指标数据」
+- - indicator-timeline:补 getPeriodKey(模板引用但缺失)、headChange 空值防护
+- - indicator-quicklook:change 缺失时中性兜底「暂无对比」
+- - body-detail:/api/health/indicators 未下发 trendCode → 颜色恒中性灰、箭头仅表方向;
+-   缺 definitionId 时 toast 明示原因
+- - TrendChart(pages/health/components):_buildLayout 布局复用、缺失期/NaN 断线、pointTap 点选
+- - utils/api.js:getIndicatorTimeline 委托 getIndicatorTrend(同 URL 唯一 request 入口)
+- - components/TrendChart.vue:孤立「暂无趋势数据」→「暂无该指标数据」(P0 红线)
+- 后端:
+- - IndicatorChangeService.batchResolveHistory:上一期 + 历史期数一次查询,严禁 N+1
+- - IndicatorTimelineService.timeline():totalReportCount/olderReportCount 提到观测为空判断之前;
+-   每个请求 definitionId 都返回 item,无观测 → chartMode='empty'(Q17 保留)
+- 验证:mvn clean compile BUILD SUCCESS;
+- IndicatorTimelineServiceTest 9 / HealthReportServiceIndicatorsTest 3 /
+- IndicatorChangeServiceSemanticTest 20 / IndicatorControllerTest 2 / IndicatorTrendServiceTest 9 全通过;
+- verify-vue-compile 390/390 PASS;audit-mp-antipatterns ERROR 0;audit-duplicate-api-calls 0 error。
+- 
+- - 在 §4.48.1 脚注区增加 SQL 预算小节(报告指标 ≤5、quickLookup/sourceTypes 恒 1 次查询),
+-   并说明上一期与 periodCount 全部内存计算,避免后续维护者再写成逐指标回查。
+- - 注明 missingPeriods 固定 2 次查询的优化结果(原先 1+缺失期数会 N+1)。
+- 
+- - quickLookup:每个 definitionId 调一次 changeService.resolvePrevious(1 次 SQL),
+-   40 个指标 = 40 次查询。改为复用开头已加载的 rows 内存求解上一期
+-   (新增 resolvePreviousInMemory,语义与 resolvePrevious 严格一致:
+-   同来源 + 数值非空 + reportDate 严格早于本期,取最晚一条),
+-   查询数从 1+N 降为 1。
+- - listSourceTypes:每个 sourceType 再查 2 次(periodCount / latestReportDate),
+-   即 1+2N。改为一次取回 (sourceType, reportDate) 后内存计算,
+-   查询数从 1+2N 降为 1;同日去重后得 periodCount,取最大值得 latestReportDate。
+- 单测补充:quickLookup 内存求解上一期(多期数据取到正确的上一期而非最早一期)、
+- 本期日期未知时不猜上一期、listSourceTypes 单查询算准 periodCount/latestReportDate,
+- 并断言 valueMapper.selectList 只被调用 1 次(锁死无 N+1)。
+- 
+-   返回 Result.error(400, ...) 而非默认 500,与 GlobalExceptionHandler 对
+-   IllegalArgumentException 的映射(400)保持一致,避免同类错误两套码。
+- - IndicatorTrendService.changeRanking:scope=pair 分支捕获 IllegalArgumentException
+-   时回填 code=400(参数/期次非法属客户端错误),与控制器口径统一。
+- - 新增 IndicatorControllerTest:API 边界契约单测,覆盖 scope=pair 缺参 400
+-   且不触达 service、参数齐全时正确透传 12 个入参。
+- 
+-   "命中字典之后" 提到之前 —— 未命中字典的指标同样可能带异常状态,
+-   原实现会让 summary.abnormalCount 少于实际异常项数(残缺横幅与异常徽标口径失真)。
+- - HealthReportServiceIndicatorsTest:
+-   ① 三档形态用例补齐"命中字典但无上一期且有更早报告 → pending_backfill"这一档
+-      (此前该档因 definitionId=null 被短路,未真正走到 pending_backfill 分支),
+-      并新增"未命中字典 → previous/change 恒 null"用例;
+-  ② summary 断言改用实际契约字段 changedCount(原误写 improvedCount);
+-  ③ spy 桩改用 doReturn(...).when(...),避免打桩时真实调用 batchResolve 被计入调用次数;
+-  ④ SQL 预算用例断言服务层恒 3 次协作调用且绝不直接访问 indicator_values,
+-      验证查询数与指标条数无关(无 N+1)。
+- 
+- - §4.48 后新增 4.48.1 小节:/api/health/report/indicators、/api/indicator/periods、
+-   /api/admin/indicator/backfill 三个新增端点,以及 trend/change-ranking/
+-   quick-lookup/source-types 四个改造端点的入参出参变更说明。
+- - 标注 /api/health/indicator/list 已被 /api/health/report/indicators 取代
+-   (老接口保留不动,cfc-web 仍在调用)。
+- - 记录 SQL ≤ 5 次无 N+1 约束、chartMode 判定表、magnitude 排序口径变更(|Δ| → |Δ%|)、
+-   QP17 status=0 剔除规则。
+- 
+- - IndicatorChangeServiceSemanticTest(新增):20 条语义用例 semantic01..semantic20,
+-   覆盖全部 5 个 trendCode 分支 + in_range_is_better 四分支 + 微小基数护栏 +
+-   基数为 0 + 单位不一致 + 非数值 + 跨期跳比。
+- - IndicatorTimelineServiceTest(新增):chartMode 在 n=0/1/2/5/25 下的
+-   empty/single/pair/line/line_aggregated;pairCompare 参数校验与排序;
+-   status=0 剔除;deltaPct=null 恒定排最后。
+- - HealthReportServiceIndicatorsTest(新增):报告指标三档形态(有上期/首次记录/跨期跳比)
+-   + 42 项指标 SQL ≤ 5 次(无 N+1)。
+- - IndicatorTrendServiceTest(改):适配新签名,覆盖 reportDate=null 不被当作最新值、
+-   status=0 剔除、magnitude 按 |Δ%| 排序且 null 排最后。
+- 
+- - HealthReportController 新增 POST /api/health/report/indicators:
+-   返回报告指标 + previous(上一期观测)+ change(五档形态)+ summary 汇总,
+-   权限校验沿用 getReportDetail(管理员 / 本人 / 被授权成员),无 subjectId 的历史报告
+-   仅本人上传者或家庭管理员可见。
+- - IndicatorController 新增 POST /api/indicator/periods(期次列表);
+-   /trend、/change-ranking、/quick-lookup 透传新参数并补 scope=pair 的缺参校验。
+- - HealthReportService 新增 getReportIndicatorsWithPrevious:
+-   批量解析 definition + 批量取上一期,严禁 N+1,SQL 恒定 ≤ 5 次。
+- - IndicatorMappingService.resolveBatch + IndicatorMappingRuleMapper.selectRulesWithDefinitions:
+-   改为 indicator_mapping_rules JOIN indicator_definitions 单次联合查询,
+-   raw_name → definition_id 解析与 definition 字段一次带出(省掉 selectBatchIds)。
+- - IndicatorService.listDefinitions:status 传 null 时默认按 status=1 过滤,
+-   防止自动创建的停用 definition 冒进管理端字典列表(QP17)。
+- 
+- - trend():签名扩展为 8 参(sourceType/fromDate/toDate/granularity/sourceKind),
+-   委托 IndicatorTimelineService.timeline(),老 3 参调用保留(转发)。
+- - changeRanking():新增 scope=pair + fromReportDate/toReportDate + filter + page/pageSize;
+-   scope=pair 路由到 IndicatorTimelineService.pairCompare;
+-   老路径(scope=adjacent|first-vs-latest,或只传 limit 不传 page/pageSize)保持兼容。
+-   magnitude 排序口径由 |Δ| 改为 |Δ%| 降序,deltaPct=null 恒定排最后(Q14)。
+- - quickLookup():修复 reportDate=null 的观测被当作最新值的 bug;
+-   新增 previous/change/latestDateSource/latestReportId/goodDirection/valueType/sparkline。
+- - listSourceTypes():出参新增 periodCount / latestReportDate。
+- - trend/changeRanking/quickLookup 三个方法统一经 IndicatorChangeService.loadActiveDefinitions()
+-   取 definition(内部 status=1 过滤),拿不到的观测直接剔除,不降级为"指标"+id 假名称(QP17)。
+- - IndicatorChangeService.normUnit 由 private 提升为 public,供 IndicatorTimelineService 复用
+-   单位一致性判定,避免重复实现归一化规则。
+- 
+- - timeline():按 definitionId 分组输出 points / latest / change / missingPeriods,
+-   refLow/refHigh(definition.refRange + indicator_values.refRange_snapshot 快照优先),
+-   pointsCount / totalReportCount / olderReportCount / chartMode / unitMismatch。
+-   支持 sourceType / fromDate / toDate / granularity(raw|month) / sourceKind(all|report|device)。
+- - listPeriods():期次列表(dateSource 取该期最差来源,reportDate=null 恒定排最后),
+-   供双期对比页 picker 使用;2 次查询完成,不分页。
+- - pairCompare():双期对比(scope=pair),含 sameDateWarning / from / to 元信息、
+-   skipGaps(内存计算,零额外查询)、improved/worsened/flat/incomparable 汇总、
+-   filter + orderBy + 分页。
+- - computeMissingPeriods 重写为 2 次查询(原来 1+缺失期数,N+1),
+-   并用 Map<Date, row> 修掉 Date 与 String 比较恒不相等导致行内容丢失的问题。
+- - 读路径全部经 IndicatorChangeService.loadActiveDefinitions()(status=1 过滤),
+-   拿不到 definition 的观测直接剔除,不降级为"指标"+id(QP17)。
+- 
+
+### 文档
+- 补充 N+1 约束与缺失期计算说明到 API_REFERENCE §4.48.1
+- 同步指标多期展示 T02 后端接口到 API_REFERENCE
+
+### 性能优化
+- 消除 quickLookup / listSourceTypes 的 N+1 查询
+
+### Bug 修复
+- scope=pair 缺参返回 400 明确错误码 + 补控制器契约单测
+- 修正报告指标 summary 异常计数漏算 + 强化三档形态与 SQL 预算单测
+
+### 测试
+- 固化 20 条语义用例 + 时序/对照接口单测
+
+
 ## v1.0.1802 (2026-10-27)
 
 ### 文档