调整风险人员总览异常点与疑似违规数口径

This commit is contained in:
wkc
2026-03-19 17:37:20 +08:00
parent c33f411c8b
commit f858fbdcbc
12 changed files with 308 additions and 14 deletions

View File

@@ -20,10 +20,14 @@ public class CcdiProjectEmployeeRiskAggregateVO {
private Integer modelCount;
private Integer hitCount;
private String topRuleCode;
private String topRuleName;
private String riskPoint;
private String riskLevelCode;
private String riskLevelName;

View File

@@ -105,8 +105,8 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
item.setName(aggregate.getStaffName());
item.setIdNo(aggregate.getStaffIdCard());
item.setDepartment(aggregate.getDeptName());
item.setRiskCount(defaultZero(aggregate.getRuleCount()));
item.setRiskPoint(aggregate.getTopRuleName());
item.setRiskCount(defaultZero(aggregate.getHitCount()));
item.setRiskPoint(aggregate.getRiskPoint());
item.setActionLabel(ACTION_LABEL);
return item;
}

View File

@@ -9,8 +9,10 @@
<result property="deptName" column="dept_name"/>
<result property="ruleCount" column="rule_count"/>
<result property="modelCount" column="model_count"/>
<result property="hitCount" column="hit_count"/>
<result property="topRuleCode" column="top_rule_code"/>
<result property="topRuleName" column="top_rule_name"/>
<result property="riskPoint" column="risk_point"/>
<result property="riskLevelCode" column="risk_level_code"/>
<result property="riskLevelName" column="risk_level_name"/>
<result property="riskLevelSort" column="risk_level_sort"/>
@@ -56,8 +58,10 @@
dept.dept_name,
agg.rule_count,
agg.model_count,
agg.hit_count,
rule_pick.rule_code as top_rule_code,
rule_pick.rule_name as top_rule_name,
risk_points.risk_point,
case
when agg.rule_count >= 5 then 'HIGH'
when agg.rule_count between 2 and 4 then 'MEDIUM'
@@ -79,7 +83,8 @@
max(base.staff_name) as staff_name,
max(base.dept_id) as dept_id,
count(distinct base.rule_code) as rule_count,
count(distinct base.model_code) as model_count
count(distinct base.model_code) as model_count,
count(1) as hit_count
from (
<include refid="resolvedEmployeeRiskBaseSql"/>
) base
@@ -130,6 +135,23 @@
)
) chosen
) rule_pick on rule_pick.staff_id_card = agg.staff_id_card
left join (
select
grouped.staff_id_card,
group_concat(grouped.rule_name order by grouped.hit_count desc, grouped.rule_code asc separator '、') as risk_point
from (
select
base.staff_id_card,
base.rule_code,
max(base.rule_name) as rule_name,
count(1) as hit_count
from (
<include refid="resolvedEmployeeRiskBaseSql"/>
) base
group by base.staff_id_card, base.rule_code
) grouped
group by grouped.staff_id_card
) risk_points on risk_points.staff_id_card = agg.staff_id_card
</sql>
<select id="selectDashboardBaseByProjectId" resultType="com.ruoyi.ccdi.project.domain.CcdiProject">

View File

@@ -16,8 +16,13 @@ class CcdiProjectOverviewMapperSqlTest {
assertTrue(xml.contains("count(distinct base.rule_code)"));
assertTrue(xml.contains("count(distinct base.model_code)"));
assertTrue(xml.contains("count(1) as hit_count"));
assertTrue(xml.contains("agg.hit_count"));
assertTrue(xml.contains("when agg.rule_count >= 5 then 'HIGH'"));
assertTrue(xml.contains("when agg.rule_count between 2 and 4 then 'MEDIUM'"));
assertTrue(xml.contains("group_concat("));
assertTrue(xml.contains("as risk_point"));
assertTrue(xml.contains("order by grouped.hit_count desc, grouped.rule_code asc"));
}
@Test

View File

@@ -63,14 +63,15 @@ class CcdiProjectOverviewServiceImplTest {
aggregate.setStaffIdCard("330000000000000001");
aggregate.setDeptName("信息二部");
aggregate.setRuleCount(5);
aggregate.setTopRuleName("大额单笔收入");
aggregate.setHitCount(8);
aggregate.setRiskPoint("大额单笔收入、疑似兼职");
when(overviewMapper.selectRiskPeopleOverviewByProjectId(40L)).thenReturn(List.of(aggregate));
CcdiProjectRiskPeopleOverviewVO overview = service.getRiskPeopleOverview(40L);
assertEquals(1, overview.getOverviewList().size());
assertEquals(5, overview.getOverviewList().getFirst().getRiskCount());
assertEquals("大额单笔收入", overview.getOverviewList().getFirst().getRiskPoint());
assertEquals(8, overview.getOverviewList().getFirst().getRiskCount());
assertEquals("大额单笔收入、疑似兼职", overview.getOverviewList().getFirst().getRiskPoint());
assertEquals("查看详情", overview.getOverviewList().getFirst().getActionLabel());
}

View File

@@ -81,7 +81,9 @@
- 统计“员工本人 + 员工亲属”
- 若命中亲属,则归并到所属员工名下
4. 风险人员总览中的 `疑似违规数`
- 统计员工命中的去重规则数
- 统计归并到该员工名下的打标明细数量
- 包含员工本人命中与亲属命中
- 同一规则多次命中按多条明细累计
5. 员工风险等级口径
- 命中规则数 `>= 5`:高风险
- 命中规则数 `2-4`:中风险
@@ -188,7 +190,7 @@
"idNo": "330000000000000001",
"department": "信息二部",
"riskCount": 5,
"riskPoint": "大额单笔收入",
"riskPoint": "大额单笔收入、疑似兼职",
"actionLabel": "查看详情"
}
]
@@ -248,8 +250,10 @@
- `deptName`
- `ruleCount`
- `modelCount`
- `hitCount`
- `topRuleCode`
- `topRuleName`
- `riskPoint`
- `riskLevelCode`
- `riskLevelName`
- `riskLevelSort`
@@ -258,6 +262,7 @@
- `ruleCount = count(distinct rule_code)`
- `modelCount = count(distinct model_code)`
- `hitCount = count(1)`,表示归并到员工名下的打标明细数
- `riskLevelCode` 根据 `ruleCount` 映射为 `HIGH/MEDIUM/LOW`
- `riskLevelName` 映射为 `高风险/中风险/低风险`
- `riskLevelSort` 仅供 SQL 排序使用,`HIGH=1``MEDIUM=2``LOW=3`
@@ -349,7 +354,7 @@ Mapper 只负责以下查询:
建议采用“公共子查询 + 外层聚合”的方式:
1. 先构造标签结果到员工身份证的归并明细
2. 再按员工身份证聚合规则数、模型数和代表性规则
2. 再按员工身份证聚合规则数、模型数和核心异常点拼接结果
3. 最后外层补部门名称、风险等级和排序字段
建议公共子查询输出字段:
@@ -360,16 +365,18 @@ Mapper 只负责以下查询:
- `rule_name`
- `model_code`
### 9.4 代表性异常点选择
风险人员总览中的“疑似违规数”使用员工聚合后的 `hitCount`风险等级、TOP10 和项目人数回写继续使用 `ruleCount`
### 9.4 核心异常点拼接策略
`风险人员总览.riskPoint` 采用以下稳定选择策略:
1. 先按员工 + 规则维度统计命中次数
2. 按命中次数倒序
3. 再按 `rule_code` 升序
4. 取第一条 `rule_name`
4. 将排序后的全部 `rule_name``、` 拼接成字符串
这样不依赖数据库非确定性行为,不会因为同条规则多次命中而随机波动。
这样既能展示员工命中的多条核心异常点,也不依赖数据库非确定性行为,不会因为同条规则多次命中而随机波动。
### 9.5 TOP10 筛选规则
@@ -444,8 +451,8 @@ TOP10 查询仅保留:
2. 风险人员总览
- 校验员工本人命中可正常归并
- 校验亲属命中可正常归并到员工
- 校验 `riskCount = 去重规则数`
- 校验 `riskPoint` 选择稳定
- 校验 `riskCount = 打标明细数`
- 校验 `riskPoint` 多规则拼接顺序稳定
3. TOP10
- 校验仅返回中高风险
- 校验排序规则正确

View File

@@ -0,0 +1,57 @@
# Risk People Overview Risk Count Backend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将风险人员总览中的疑似违规数从去重规则数调整为员工本人及其亲属命中的打标明细数量。
**Architecture:** 保持风险等级、TOP10 和项目风险人数回写口径继续基于去重规则数,不做范围外调整。仅在 `ccdi-project` 结果总览员工聚合 SQL 中增加单独的打标数量字段,并在风险人员总览服务映射时使用该字段作为 `riskCount`
**Tech Stack:** Java 21, Spring Boot 3, MyBatis XML, Maven, JUnit 5, Mockito
---
### Task 1: 锁定疑似违规数新口径
**Files:**
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java`
- [ ] **Step 1: Write the failing test**
调整服务测试,断言 `riskCount` 读取独立的打标数量字段,而不是 `ruleCount`。调整 SQL 结构测试,锁定员工聚合查询包含 `hit_count` 聚合。
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewMapperSqlTest
```
Expected:
- `FAIL`
- 原因是聚合 VO 与 SQL 尚未提供打标数量字段
- [ ] **Step 3: Write minimal implementation**
为员工聚合 VO 新增 `hitCount` 字段Mapper XML 增加 `count(1) as hit_count` 聚合并映射到结果集Service 构建风险人员总览项时改为优先使用 `hitCount` 作为 `riskCount`
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewMapperSqlTest
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectEmployeeRiskAggregateVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml
git commit -m "调整风险人员总览疑似违规数口径"
```

View File

@@ -0,0 +1,57 @@
# Risk People Overview Risk Point Backend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将风险人员总览中的核心异常点从单条代表性规则调整为员工命中多条规则时的稳定拼接结果。
**Architecture:** 保持现有风险人数统计、风险等级划分和排序规则不变,只调整 `ccdi-project` 结果总览聚合 SQL 与服务映射。核心异常点改为先按员工和规则统计命中次数,再按命中次数倒序、规则编码升序拼接多条规则名称,直接返回给前端展示。
**Tech Stack:** Java 21, Spring Boot 3, MyBatis XML, Maven, JUnit 5, Mockito
---
### Task 1: 锁定多规则拼接行为
**Files:**
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java`
- [ ] **Step 1: Write the failing test**
在服务测试中新增或调整用例,断言 `riskPoint` 取拼接后的多规则字符串;在 SQL 结构测试中锁定 `group_concat``risk_point` 聚合片段。
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewMapperSqlTest
```
Expected:
- `FAIL`
- 服务层仍只映射单条规则SQL 尚未包含多规则拼接
- [ ] **Step 3: Write minimal implementation**
为员工聚合 VO 增加 `riskPoint` 字段;在 Mapper XML 中新增员工维度规则拼接子查询Service 改为使用聚合后的 `riskPoint`
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewMapperSqlTest
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectEmployeeRiskAggregateVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml
git commit -m "修正风险人员总览核心异常点多规则展示"
```

View File

@@ -0,0 +1,41 @@
# Risk People Overview Risk Count Frontend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 确认风险人员总览前端在后端调整疑似违规数口径后继续直接展示接口返回的 `riskCount`
**Architecture:** 不修改 `RiskPeopleSection.vue` 表格结构和字段绑定,不新增前端兼容逻辑,只做回归验证确保“疑似违规数”仍绑定 `riskCount` 字段。
**Tech Stack:** Vue 2, Element UI, Node.js
---
### Task 1: 回归确认疑似违规数字段绑定
**Files:**
- Verify: `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue`
- Verify: `ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js`
- [ ] **Step 1: Review current binding**
确认“疑似违规数”列继续绑定 `riskCount`,不新增前端计算。
- [ ] **Step 2: Run regression check**
Run:
```bash
cd ruoyi-ui
node tests/unit/preliminary-check-risk-people-binding.test.js
```
Expected:
- `PASS`
- [ ] **Step 3: Commit**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js
git commit -m "确认风险人员总览疑似违规数前端展示口径"
```

View File

@@ -0,0 +1,41 @@
# Risk People Overview Risk Point Frontend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 确认风险人员总览前端在后端返回多条核心异常点拼接字符串后无需额外改造即可正常展示。
**Architecture:** 保持 `RiskPeopleSection.vue` 表格结构、字段绑定和接口契约不变,不新增补丁逻辑。前端仅做回归验证,确保 `riskPoint` 继续作为普通字符串渲染。
**Tech Stack:** Vue 2, Element UI, Node.js
---
### Task 1: 回归确认风险人员总览字段绑定
**Files:**
- Verify: `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue`
- Verify: `ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js`
- [ ] **Step 1: Review current binding**
确认“核心异常点”列仍绑定 `riskPoint`,不拆分数组,不新增格式化逻辑。
- [ ] **Step 2: Run regression check**
Run:
```bash
cd ruoyi-ui
node tests/unit/preliminary-check-risk-people-binding.test.js
```
Expected:
- `PASS`
- [ ] **Step 3: Commit**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js
git commit -m "确认风险人员总览核心异常点前端展示口径"
```

View File

@@ -0,0 +1,32 @@
# 风险人员总览疑似违规数口径调整实施记录
## 本次改动
- 为结果总览员工聚合中间 VO 新增 `hitCount` 字段,用于承接归并到员工名下的打标明细数。
- 调整 [`CcdiProjectOverviewMapper.xml`](/Users/wkc/Desktop/ccdi/ccdi/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml) 员工聚合 SQL新增 `count(1) as hit_count`
- 调整 [`CcdiProjectOverviewServiceImpl.java`](/Users/wkc/Desktop/ccdi/ccdi/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java) 风险人员总览映射逻辑,`riskCount` 改为读取 `hitCount`
- 保持 `ruleCount` 继续仅用于风险等级计算、TOP10 排序和项目高/中/低风险人数统计,不扩散影响范围。
- 同步补充本次后端、前端计划文档,并更新结果总览风险接口设计文档口径说明。
## 口径说明
- `riskCount`:员工本人及其亲属归并到该员工名下的打标明细数量。
- `ruleCount`:员工命中的去重规则数,仅用于风险等级和相关统计,不再直接展示为“疑似违规数”。
## 前端处理结论
- [`RiskPeopleSection.vue`](/Users/wkc/Desktop/ccdi/ccdi/ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue) 仍直接渲染接口返回的 `riskCount`
- 本次无需增加前端计算或格式化补丁,只需回归验证字段绑定未变化。
## 验证情况
- 后端定向测试通过:
- `mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewMapperSqlTest`
- 前端回归检查通过:
- `cd ruoyi-ui && node tests/unit/preliminary-check-risk-people-binding.test.js`
## 未包含内容
- 未调整风险等级分级口径
- 未调整中高风险 TOP10 接口逻辑
- 未调整项目风险人数回写逻辑

View File

@@ -0,0 +1,27 @@
# 风险人员总览核心异常点多规则展示实施记录
## 本次改动
- 调整结果总览后端员工风险聚合 VO新增 `riskPoint` 字段承接多规则拼接结果。
- 调整 [`CcdiProjectOverviewMapper.xml`](/Users/wkc/Desktop/ccdi/ccdi/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml) 中的风险人员总览聚合 SQL。
- 核心异常点改为先按员工和规则统计命中次数,再按命中次数倒序、`rule_code` 升序,将全部命中规则名称用 `、` 拼接。
- 调整 [`CcdiProjectOverviewServiceImpl.java`](/Users/wkc/Desktop/ccdi/ccdi/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java) 映射逻辑,风险人员总览直接返回拼接后的 `riskPoint`
- 同步更新结果总览设计文档与本次后端、前端实施计划文档。
## 前端处理结论
- [`RiskPeopleSection.vue`](/Users/wkc/Desktop/ccdi/ccdi/ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue) 现有“核心异常点”列已经直接渲染 `riskPoint` 字符串。
- 本次无需改动前端组件结构或字段绑定,只需回归验证现有展示链路。
## 验证情况
- 后端定向测试通过:
- `mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewMapperSqlTest`
- 前端回归检查通过:
- `cd ruoyi-ui && node tests/unit/preliminary-check-risk-people-binding.test.js`
## 未包含内容
- 未调整风险人数分级口径
- 未调整中高风险 TOP10 接口与排序
- 未新增前端格式化补丁或兼容分支