# IIT / CRA Agent 指标口径与 SSOT 对照表(Phase 0) > 文档版本:v1.0 > 创建日期:2026-03-08 > 适用范围:IIT 业务端大盘 / 报告与关键事件 / AI 实时工作流水 / AI 对话 > 目标:先统一“同名指标”的定义和数据源,再进入修复,避免反复打补丁 --- ## 1. 背景与问题定义 当前线上/测试反馈的核心不是单点报错,而是同一指标在多个页面口径不一致,例如: - 大盘通过率与趋势通过率不一致 - 报告页健康分与大盘健康分不一致 - 时间线问题数与待处理 eQuery 不一致 - 报告中 D1/D2/D3D4/D6 与下方维度评分对不上 这些问题在工程上属于**口径漂移(metric drift)**,必须先冻结 SSOT(Single Source of Truth),再排查数据同步、规则执行、前端展示。 --- ## 2. 指标分层模型(必须统一) IIT 指标按 4 层定义,禁止跨层混算: 1. **事实层(Raw Facts)**:REDCap 原始记录与事件数据 2. **执行层(QC Results)**:`qc_field_status` / `qc_event_status` 的规则执行结果 3. **聚合层(Project Stats)**:`iit_qc_project_stats`、`iit_record_summary` 的聚合快照 4. **呈现层(UI/API)**:Dashboard/Reports/AiStream/AiChat 约束: - 呈现层不允许重新发明计算公式,只消费聚合层或执行层 - 同一名称指标只能有一个“主口径” - 不同用途的指标必须显式命名(例如“按受试者通过率”与“按事件通过率”) --- ## 3. 核心指标 SSOT 对照表(冻结版) ## 3.1 项目健康度与通过率 | 指标名 | 业务定义 | SSOT 来源 | 当前代码入口 | 备注 | |---|---|---|---|---| | healthScore | 健康度总分(0-100) | `iit_qc_project_stats.health_score` | `iitQcCockpitService.getStats()` | 不允许前端 fallback 推导 | | healthGrade | 健康度等级 | `iit_qc_project_stats.health_grade` | `iitQcCockpitService.getStats()` | 同上 | | passRate (大盘) | 按受试者通过率 | `passed_records / total_records` | `iitQcCockpitService.getStats()` | 保留 1 位小数 | | passRate (趋势) | 按日志条目通过率(旧) | `iit_qc_logs` 分组计算 | `iitQcCockpitController.getTrend()` | 与大盘不是同一口径,必须重命名或替换 | 结论: - 当前“通过率”至少有两种口径,UI 未标注,必然引发“100% vs 33%”类问题。 ## 3.2 D1/D2/D3D4/D6 报表 | 报表 | 业务定义 | SSOT 来源 | 当前查询入口 | |---|---|---|---| | D1 筛选入选 | 入排规则是否通过 | `qc_field_status`(D1) + `record_summary`(受试者全集) | `iitQcCockpitService.getEligibilityReport()` | | D2 完整性 | 缺失字段率 | `qc_field_status`(D2) | `iitQcCockpitService.getCompletenessReport()` | | D3/D4 质疑跟踪 | eQuery 生命周期 | `iit_equeries` | `iitQcCockpitService.getEqueryLogReport()` | | D6 方案偏离 | 访视超窗等偏离 | `qc_field_status`(D6) | `iitQcCockpitService.getDeviationReport()` | 结论: - D 类报表与大盘维度评分来自不同聚合路径时,必须明确“时点一致性”(同一批次同一时刻)。 ## 3.3 AI 实时工作流水 | 指标 | 业务定义 | SSOT 来源 | 当前入口 | 风险 | |---|---|---|---|---| | timeline total | 时间线受试者总数 | `qc_field_status` 聚合 + pass 补充 | `iitQcCockpitController.getTimeline()` | 与 eQuery 总数不是同一概念 | | red/yellow 问题数 | 每受试者 FAIL/WARNING 汇总 | `qc_field_status` | `getTimeline()` | 应与 field-issues 可对账 | | 事件中文名 | event display label | `qc_event_status.event_label` | `getTimeline()` LEFT JOIN | 空值时会回退技术名 | ## 3.4 AI 对话 | 语义工具 | 主要数据源 | 适用问题 | 当前实现 | |---|---|---|---| | `read_report` | `QcReportService` 缓存报告 | 通过率、问题统计、趋势摘要 | `ToolsService` | | `look_up_data` | REDCap 原始数据 | 单患者字段值、原始记录核查 | `ToolsService` | | `check_quality` | `QcExecutor` 实时执行 | 用户明确要求“重跑质控” | `ToolsService` | | `search_knowledge` | 项目知识库(RAG) | 方案文本、入排标准文本 | `ToolsService` | 结论: - 对话错误不等于“模型幻觉”,优先排查是否选错工具或工具查询不完整。 --- ## 4. 现存口径漂移点(已识别) 1. **趋势口径漂移** - 趋势接口仍从 `iit_qc_logs` 计算通过率;大盘通过率来自 `iit_qc_project_stats`。 - 导致“卡片 100%,趋势 33%”。 2. **健康分 fallback 风险** - 大盘前端在 `healthScore` 缺失时回退为 `Math.round(passRate)`。 - 数据未准备好时会把通过率误当健康分,造成“待处理质疑很多但仍 100 分”。 3. **报告缓存时点风险** - `read_report` 优先读缓存报告(默认有效期 24h),若未及时刷新会滞后于大盘/流水。 4. **“总数”语义混淆** - 时间线问题数、eQuery 待处理数、D3D4 报表数本质是不同对象,UI 文案未区分。 5. **展示字段名兼容风险** - 若 `eventLabel/fieldLabel` 空,前端回退技术字段名;用户会误判为“事件名错误”。 --- ## 5. 冻结规则(修复期强制执行) 1. 页面上所有“通过率”必须标注口径: - 按受试者(record) - 按事件(record-event) - 按规则检查条目(field-level checks) 2. 健康度评分只允许来自 `healthScoreEngine` 落库结果,不允许前端推算。 3. 报告页、大盘、AI 对话必须显示“统计快照时间”,便于用户识别时点差异。 4. UI 文案新增说明: - “待处理质疑”来自 `iit_equeries` - “时间线问题总数”来自 `qc_field_status` 5. 涉及 D1/D2/D3D4/D6 的变更,必须附带“口径回归测试”记录。 --- ## 6. Phase 1 进入条件 满足以下条件后,才进入根因排查与代码修复: - [ ] 本文“SSOT 对照表”已被团队确认(产品/研发/质控) - [ ] 每个页面核心指标已标注口径 - [ ] 已选定“最小复现项目”与冻结时间窗口 Phase 1 执行手册见: `docs/03-业务模块/IIT Manager Agent/09-技术评审报告/2026-03-08-IIT-CRA-最小复现项目对账执行手册-Phase1.md`