# 结果总览员工结果表设计文档

**模块**: 初核项目详情 - 结果总览
**日期**: 2026-03-20
**状态**: 已确认

## 一、背景

当前结果总览页已接通的查询包括：

- 风险仪表盘
- 风险人员总览
- 模型预警次数统计
- 命中模型涉及人员

现状中，这些查询依赖 `ccdi_bank_statement_tag_result`、`ccdi_bank_statement`、`ccdi_base_staff`、`ccdi_staff_fmy_relation` 的运行时连表归并与聚合。页面访问时会重复执行重 SQL，导致响应时间偏长，尤其是模型区和风险人员总览区块。

本轮目标是新增一张结果总览结果表，把“项目内每个员工的模型触发情况”预先沉淀下来，再基于该表编写页面查询 SQL，避免结果总览页面运行时直接连回源表。

## 二、目标

本次设计目标如下：

1. 新增一张结果总览员工结果表，记录项目内每个员工的结果快照。
2. 员工亲属的流水异常命中需归并计入该员工本人。
3. 页面查询只依赖项目表和结果总览员工结果表，不再运行时连 `标签结果表 + 流水表 + 员工表 + 亲属表`。
4. 命中结果写库成功后，在同一事务内按项目整块重算结果总览员工结果表。
5. 保持当前页面统计口径不变，尤其是模型卡片 `warningCount` 仍定义为原始标签命中次数。

## 三、范围

### 3.1 本次范围

- 设计结果总览员工结果表结构
- 设计员工本人和亲属命中的归并口径
- 设计命中结果写库后的同步重算链路
- 设计结果总览 4 类查询从结果表取数的方案
- 后续输出前后端实施计划与实施记录

### 3.2 不在本次范围

- 不覆盖结果总览风险明细区块
- 不引入异步刷新、缓存或消息队列
- 不做增量更新，只做按项目整块重算
- 不新增多张平行结果表
- 不保留中高风险 TOP10 区块及其专用查询设计

## 四、当前口径约束

本次设计确认以下业务口径：

1. 结果表粒度为 `项目 + 员工`，一行代表一个项目内一个归并后的员工结果。
2. 员工亲属的流水异常命中，必须归并到对应员工本人名下。
3. 命中结果写库成功后，结果表需要在同一事务内同步重算完成。
4. 重算方式采用“按项目整块重算当前已接通区块所需数据”。
5. 模型卡片中的 `warningCount` 定义保持现状，为原始标签命中次数，而不是员工人数或规则去重数。

## 五、方案对比

### 5.1 方案 A：直接存页面结果快照

做法：

- 按项目保存仪表盘、风险人员总览、模型卡片、模型人员列表等页面结果快照

问题：

- `命中模型涉及人员` 存在 `ANY / ALL / keyword / deptId / pageNum` 等动态筛选
- 一旦直接存页面结果，筛选和分页要么退化为内存处理，要么继续依赖复杂 JSON 查询
- 无法满足“先保存员工触发情况，再根据结果表写 SQL”的要求

### 5.2 方案 B：单表保存项目内每个员工的结果快照

做法：

- 新增一张结果总览员工结果表
- 以 `project_id + staff_id_card` 为唯一口径
- 每行保存员工基础信息、风险统计、模型命中快照、规则命中快照
- 页面查询全部基于该表聚合或筛选

优点：

- 符合“先存员工触发情况，再写页面 SQL”的要求
- 页面查询可以收敛为单表查询
- 兼容 `ANY / ALL`、关键字、部门、分页等动态筛选
- 与当前统计口径兼容，不需要改业务定义

缺点：

- 命中结果写库后需要按项目整块重算该表
- 表中会存在部分汇总冗余字段和 JSON 快照字段

### 5.3 方案 C：多张专用结果表

做法：

- 仪表盘、风险人员、模型卡片、模型人员分别建独立结果表

问题：

- 不符合本轮“创建一张结果总览结果表”的目标
- 数据维护面更大，DDL 和重算逻辑更分散

### 5.4 推荐方案

采用方案 B：新增一张“结果总览员工结果表”，以员工为核心粒度预聚合，再由页面查询基于该表出数。

## 六、结果表设计

建议表名：

- `ccdi_project_overview_employee_result`

建议主键与唯一性约束：

- 主键：`id`
- 唯一键：`uk_project_staff (project_id, staff_id_card)`

建议字段如下。

### 6.1 基础标识字段

- `project_id`
- `staff_id_card`
- `staff_id`
- `staff_name`
- `staff_code`
- `dept_id`
- `dept_name`

### 6.2 风险统计字段

- `rule_count`
  - 该员工命中的规则数，按 `rule_code` 去重
- `model_count`
  - 该员工命中的模型数，按 `model_code` 去重
- `hit_count`
  - 该员工归并后的原始标签命中总次数
- `risk_level_code`
  - 依据现有规则推导 `HIGH / MEDIUM / LOW`
- `top_rule_code`
- `top_rule_name`
- `risk_point`
  - 按命中次数降序、规则编码升序拼接规则名

### 6.3 模型与规则快照字段

- `model_codes_csv`
  - 员工命中的全部模型编码，逗号分隔，统一按编码排序
- `model_names_json`
  - 员工命中的模型名称数组
- `hit_rules_json`
  - 员工命中的规则数组，元素建议至少包含：
    - `ruleCode`
    - `ruleName`
    - `riskLevel`
    - `modelCode`
    - `modelName`
    - `hitCount`
- `model_hit_summary_json`
  - 员工按模型汇总的数组，元素建议至少包含：
    - `modelCode`
    - `modelName`
    - `warningCount`
    - `ruleCount`
    - `hitRuleList`

其中：

- `warningCount` 表示该员工在该模型下的原始标签命中次数
- `hitRuleList` 用于页面“异常标签”展示

### 6.4 审计字段

- `create_by`
- `create_time`
- `update_by`
- `update_time`

## 七、员工归并与重算链路

### 7.1 归并口径

员工结果重算时，继续沿用当前结果总览链路中的归并规则：

1. 员工本人命中，归并到本人。
2. 当命中对象为空或为亲属对象时，通过流水身份证号或亲属身份证号映射到员工本人。
3. 无法归并到员工本人的命中记录，不进入结果总览员工结果表。

本轮不调整归并逻辑，只把这一步从页面查询阶段前移到结果表重算阶段。

### 7.2 重算触发时机

触发点放在命中结果写库成功之后、事务提交之前。

触发顺序固定为：

1. 写入当前项目标签结果
2. 删除当前项目历史员工结果
3. 基于当前项目全量标签结果，按员工归并口径重算员工结果
4. 批量插入新的员工结果
5. 同事务刷新项目表中的高/中/低风险人数
6. 提交事务

### 7.3 重算方式

本次采用按项目整块重算，不做增量更新。

原因：

- 需求已明确接受按项目整块重算
- 这样实现路径最短，且避免增量口径漂移
- 同事务内先删后插更容易保证一致性

## 八、页面查询改造方案

改造后，结果总览页查询只依赖：

- `ccdi_project`
- `ccdi_project_overview_employee_result`

### 8.1 风险仪表盘

来源：

- `ccdi_project.target_count`
- 员工结果表按 `risk_level_code` 的人数统计

查询方式：

- 高风险人数：结果表中 `risk_level_code = 'HIGH'`
- 中风险人数：结果表中 `risk_level_code = 'MEDIUM'`
- 低风险人数：结果表中 `risk_level_code = 'LOW'`
- 无风险人数：`target_count - high - medium - low`

### 8.2 风险人员总览

来源：

- 员工结果表单表查询

返回字段：

- `staff_name`
- `staff_id_card`
- `dept_name`
- `hit_count`
- `risk_level_code`
- `model_count`
- `risk_point`

排序延续现有口径：

- 风险等级
- 模型数
- 规则数
- 员工身份证号

### 8.3 模型预警次数统计

来源：

- 员工结果表中的 `model_hit_summary_json`

查询逻辑：

1. 先按项目过滤员工结果表
2. 展开每位员工的模型汇总
3. 按 `modelCode` 聚合

统计口径：

- `warningCount` = 各员工该模型 `warningCount` 求和
- `peopleCount` = 命中该模型的员工人数

排序口径：

- `warningCount desc`
- `model_code asc`

### 8.4 命中模型涉及人员

来源：

- 员工结果表中的 `model_codes_csv`
- `model_names_json`
- `hit_rules_json`
- `model_hit_summary_json`

筛选规则：

- `keyword`：匹配员工姓名或工号
- `deptId`：精确匹配部门
- `modelCodes`：
  - `ANY`：命中任一所选模型即可
  - `ALL`：必须同时命中全部所选模型

实现建议：

- `ANY / ALL` 筛选优先基于 `model_codes_csv`
- `modelNames` 和 `hitTagList` 展示基于 JSON 快照组装

这样可以把：

- 结构化筛选
- 模型条件判断
- 分页排序

都控制在结果表层完成，避免回源连表。

## 九、索引建议

为保证页面查询稳定，建议增加如下索引：

- 唯一键：`(project_id, staff_id_card)`
- 普通索引：`(project_id, risk_level_code)`
- 普通索引：`(project_id, dept_id)`
- 普通索引：`(project_id, staff_name)`
- 普通索引：`(project_id, staff_code)`

本轮先不扩展模型专用索引，优先依赖整项目重算后的结果表降本。如果后续 `model_codes_csv` 筛选成为瓶颈，再评估是否引入模型筛选辅助列。

## 十、测试与验收口径

本次设计的验收以“结果表口径与当前业务口径一致”为准，重点验证：

1. 同一项目下，结果表员工数应等于当前可归并出的员工数。
2. 员工亲属的流水异常命中必须归并到员工本人。
3. 风险仪表盘的高/中/低人数与结果表按等级统计一致。
4. 风险人员总览展示字段与结果表字段映射一致。
5. 模型卡片 `warningCount` 必须等于原始标签命中次数汇总。
6. 模型卡片 `peopleCount` 必须等于命中该模型的员工人数。
7. 命中模型涉及人员在 `ANY / ALL / keyword / deptId` 条件下的结果，必须与旧逻辑一致。

## 十一、结论

本次设计采用“单张结果总览员工结果表”作为结果总览页的唯一预聚合载体。

核心结论如下：

1. 结果表粒度为 `项目 + 员工`。
2. 员工亲属命中统一归并到员工本人。
3. 命中结果写库成功后，同一事务内按项目整块重算结果表。
4. 页面查询不再直接运行重连表聚合 SQL，而是基于结果表聚合或筛选。
5. 模型卡片 `warningCount` 继续保持原始标签命中次数口径。

该方案满足“最短路径实现、口径一致、页面查询降本”的目标，后续可据此输出前后端实施计划并进入实现阶段。
