# 异常账户模型接入银行流水打标后端 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.
>
> 仓库约束：当前仓库明确禁止开启 subagent，执行时统一使用 `superpowers:executing-plans`。

**Goal:** 在后端正式接入异常账户模型，新增 `ccdi_account_info`、两条对象型打标规则、最小可命中测试数据，并通过 Java 测试和 MySQL MCP SQL 校验确认命中口径正确。

**Architecture:** 复用现有 `CcdiBankTagServiceImpl -> CcdiBankTagAnalysisMapper.xml -> ccdi_bank_statement_tag_result` 主链路，不新增并行模块。两条规则统一作为 `OBJECT` 规则落到员工身份证号维度，`reason_detail` 保留账户级异常快照；测试层同时保留 Java 自动化测试与 MySQL MCP 真实 SQL 验证。

**Tech Stack:** Java 21, Spring Boot 3, MyBatis XML, MyBatis Plus, MySQL, JUnit 5, Mockito, MySQL MCP

---

## File Map

**Create:**

- `sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql`
  - 建表 `ccdi_account_info`，补模型与规则元数据
- `sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql`
  - 插入最小命中与反样本测试数据
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java`
  - 校验模型、规则编码、结果类型与业务口径文本
- `docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md`
  - 记录后端实施与验证结果

**Modify:**

- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
  - 新增两个对象型规则分发分支
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java`
  - 新增两个 Mapper 方法签名
- `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
  - 新增两条对象型真实 SQL
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
  - 覆盖新规则分发与结果入库
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewEmployeeResultBuilderTest.java`
  - 校验新增规则能进入员工风险聚合快照
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiBankTagRuleSqlMetadataTest.java`
  - 补模型/规则元数据断言

**No Change Expected:**

- `ruoyi-ui/`
  - 本轮前端不新增接口和页面改动
- `lsfx-mock-server/`
  - 本轮不扩展 Mock 样本

## Task 1: 锁定模型、规则与落库契约

**Files:**

- Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiBankTagRuleSqlMetadataTest.java`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`

- [ ] **Step 1: 先写 SQL 元数据测试**

新增 `CcdiAbnormalAccountRuleSqlMetadataTest`，至少覆盖：

```java
assertTrue(sql.contains("ABNORMAL_ACCOUNT"));
assertTrue(sql.contains("SUDDEN_ACCOUNT_CLOSURE"));
assertTrue(sql.contains("DORMANT_ACCOUNT_LARGE_ACTIVATION"));
assertTrue(sql.contains("'OBJECT'"));
```

- [ ] **Step 2: 再写 Service 分发失败测试**

在 `CcdiBankTagServiceImplTest` 中新增两个断言，约束新规则走对象型 Mapper：

```java
verify(analysisMapper).selectSuddenAccountClosureObjects(40L);
verify(analysisMapper).selectDormantAccountLargeActivationObjects(40L);
```

- [ ] **Step 3: 运行定向测试，确认失败点正确**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiAbnormalAccountRuleSqlMetadataTest,CcdiBankTagServiceImplTest test
```

Expected:

- FAIL，提示缺少规则元数据脚本内容或缺少 Mapper / Service 分发

- [ ] **Step 4: 最小化补充规则元数据和方法签名**

按以下顺序补代码：

1. 新增规则元数据脚本文件骨架
2. 在 `CcdiBankTagAnalysisMapper.java` 增加两个方法：

```java
List<BankTagObjectHitVO> selectSuddenAccountClosureObjects(@Param("projectId") Long projectId);
List<BankTagObjectHitVO> selectDormantAccountLargeActivationObjects(@Param("projectId") Long projectId);
```

3. 在 `CcdiBankTagServiceImpl.java` 中新增两个 `case`

- [ ] **Step 5: 重新运行定向测试**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiAbnormalAccountRuleSqlMetadataTest,CcdiBankTagServiceImplTest test
```

Expected:

- PASS 或仅剩 SQL 实现相关失败

- [ ] **Step 6: 提交本任务**

```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java \
  ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java \
  sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql
git commit -m "补充异常账户模型规则骨架"
```

## Task 2: 落地 `ccdi_account_info` 建表与规则元数据脚本

**Files:**

- Create: `sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiBankTagRuleSqlMetadataTest.java`

- [ ] **Step 1: 先补脚本结构测试**

为建表 SQL 增加断言，至少包含：

```java
assertTrue(sql.contains("create table if not exists `ccdi_account_info`"));
assertTrue(sql.contains("`account_no`"));
assertTrue(sql.contains("`owner_type`"));
assertTrue(sql.contains("`effective_date`"));
assertTrue(sql.contains("`invalid_date`"));
```

- [ ] **Step 2: 运行测试确认失败**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiAbnormalAccountRuleSqlMetadataTest,CcdiBankTagRuleSqlMetadataTest test
```

Expected:

- FAIL，提示脚本字段或规则元数据缺失

- [ ] **Step 3: 写最小建表与元数据脚本**

在 `2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql` 中按以下顺序补内容：

1. `CREATE TABLE IF NOT EXISTS ccdi_account_info`
2. 插入模型 `ABNORMAL_ACCOUNT`
3. 插入两条规则元数据
4. 业务口径文本与设计文档保持一致

- [ ] **Step 4: 重新运行元数据测试**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiAbnormalAccountRuleSqlMetadataTest,CcdiBankTagRuleSqlMetadataTest test
```

Expected:

- PASS

- [ ] **Step 5: 提交本任务**

```bash
git add sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiBankTagRuleSqlMetadataTest.java
git commit -m "补充异常账户模型建表和规则元数据"
```

## Task 3: 先写两条规则 SQL 的失败测试

**Files:**

- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewEmployeeResultBuilderTest.java`
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`

- [ ] **Step 1: 写对象型结果断言**

在 `CcdiBankTagServiceImplTest` 中新增两个测试，断言对象型结果内容：

```java
verify(resultMapper).insertBatch(argThat(results -> results.stream().anyMatch(item ->
    "ABNORMAL_ACCOUNT".equals(item.getModelCode())
        && "SUDDEN_ACCOUNT_CLOSURE".equals(item.getRuleCode())
        && "OBJECT".equals(item.getResultType())
        && "STAFF_ID_CARD".equals(item.getObjectType())
)));
```

- [ ] **Step 2: 写聚合层断言**

在 `CcdiProjectOverviewEmployeeResultBuilderTest` 中补断言，确保新增规则能进入 `hitRulesJson`：

```java
assertTrue(result.getHitRulesJson().contains("SUDDEN_ACCOUNT_CLOSURE"));
assertTrue(result.getHitRulesJson().contains("DORMANT_ACCOUNT_LARGE_ACTIVATION"));
```

- [ ] **Step 3: 运行测试确认失败**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiBankTagServiceImplTest,CcdiProjectOverviewEmployeeResultBuilderTest test
```

Expected:

- FAIL，提示 SQL 尚未返回结果或规则未写入正确字段

- [ ] **Step 4: 暂不修实现，先保存失败断言**

保持测试失败状态，进入下一任务补最小 SQL 实现。

## Task 4: 实现 `SUDDEN_ACCOUNT_CLOSURE` 最小闭环

**Files:**

- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`

- [ ] **Step 1: 在 XML 中新增查询骨架**

新增 `selectSuddenAccountClosureObjects`，结果列固定为：

```sql
select
    'STAFF_ID_CARD' as objectType,
    staff.id_card as objectKey,
    concat(...) as reasonDetail
```

- [ ] **Step 2: 按设计补完整过滤条件**

SQL 需要覆盖：

- `owner_type = 'EMPLOYEE'`
- `status = 2`
- `invalid_date is not null`
- 统计窗口 `[invalid_date - 30天, invalid_date)`
- 按员工身份证号 + 账号聚合

- [ ] **Step 3: 运行定向测试**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiBankTagServiceImplTest test
```

Expected:

- 至少 `SUDDEN_ACCOUNT_CLOSURE` 相关断言 PASS

- [ ] **Step 4: 提交本任务**

```bash
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml \
  ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java \
  ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java
git commit -m "实现突然销户打标规则"
```

## Task 5: 实现 `DORMANT_ACCOUNT_LARGE_ACTIVATION` 最小闭环

**Files:**

- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`

- [ ] **Step 1: 在 XML 中新增查询骨架**

新增 `selectDormantAccountLargeActivationObjects`，结果列同样返回：

```sql
'STAFF_ID_CARD' as objectType,
staff.id_card as objectKey,
concat(...) as reasonDetail
```

- [ ] **Step 2: 补完整命中条件**

SQL 需要覆盖：

- `owner_type = 'EMPLOYEE'`
- `status = 1`
- `effective_date is not null`
- `first_tx_date >= effective_date + 6个月`
- `total_amount >= 500000 or max_single_amount >= 100000`

- [ ] **Step 3: 运行定向测试**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiBankTagServiceImplTest,CcdiProjectOverviewEmployeeResultBuilderTest test
```

Expected:

- PASS

- [ ] **Step 4: 提交本任务**

```bash
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml \
  ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java \
  ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewEmployeeResultBuilderTest.java
git commit -m "实现休眠账户大额启用打标规则"
```

## Task 6: 补最小测试数据 SQL

**Files:**

- Create: `sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql`
- Modify: `docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md`

- [ ] **Step 1: 先写测试数据注释骨架**

在 SQL 文件中先划分 4 组样本块：

- 员工 A：命中 `SUDDEN_ACCOUNT_CLOSURE`
- 员工 B：命中 `DORMANT_ACCOUNT_LARGE_ACTIVATION`
- 员工 C：休眠不足 6 个月，不命中
- 员工 D：销户前 30 天无流水，不命中

- [ ] **Step 2: 写最小测试数据**

按顺序补数据：

1. 员工基础数据
2. 项目内流水数据
3. `ccdi_account_info` 账户数据
4. 必要的清理 SQL

- [ ] **Step 3: 记录导入命令**

在实施记录中先预填导入方式：

```bash
bin/mysql_utf8_exec.sh sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql
bin/mysql_utf8_exec.sh sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql
```

- [ ] **Step 4: 提交本任务**

```bash
git add sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql \
  docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md
git commit -m "补充异常账户规则测试数据"
```

## Task 7: 用 MySQL MCP 执行真实 SQL 校验口径

**Files:**

- Modify: `docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md`

- [ ] **Step 1: 导入建表和测试数据脚本**

Run:

```bash
bin/mysql_utf8_exec.sh sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql
bin/mysql_utf8_exec.sh sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql
```

Expected:

- PASS，脚本执行成功且无乱码

- [ ] **Step 2: 使用 MySQL MCP 执行 `SUDDEN_ACCOUNT_CLOSURE` 对应 SQL**

要求：

- 直接执行 Mapper 中的真实查询等价 SQL
- 校验仅返回员工 A
- 校验 `reasonDetail` 中包含销户日期、最后交易日、累计金额和单笔最大金额

- [ ] **Step 3: 使用 MySQL MCP 执行 `DORMANT_ACCOUNT_LARGE_ACTIVATION` 对应 SQL**

要求：

- 直接执行 Mapper 中的真实查询等价 SQL
- 校验仅返回员工 B
- 校验员工 C 未命中
- 校验 `reasonDetail` 中包含开户日期、首次交易日期、累计金额或单笔最大金额快照

- [ ] **Step 4: 将 SQL 验证结果写入实施记录**

记录：

- 实际执行 SQL 摘要
- 命中对象
- 未命中对象
- 与业务口径的对照结论

- [ ] **Step 5: 提交本任务**

```bash
git add docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md
git commit -m "补充异常账户规则SQL校验记录"
```

## Task 8: 跑完整后端验证并收尾

**Files:**

- Modify: `docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md`

- [ ] **Step 1: 运行后端定向测试**

Run:

```bash
mvn -pl ccdi-project -Dtest=CcdiAbnormalAccountRuleSqlMetadataTest,CcdiBankTagRuleSqlMetadataTest,CcdiBankTagServiceImplTest,CcdiProjectOverviewEmployeeResultBuilderTest test
```

Expected:

- PASS

- [ ] **Step 2: 如需联调打标主链路，启动后端并验证后主动关闭**

Run:

```bash
mvn -pl ruoyi-admin -am package -DskipTests
cd ruoyi-admin/target && java -jar ruoyi-admin.jar
```

验证完成后关闭进程。

- [ ] **Step 3: 完善实施记录**

记录：

- 最终改动文件
- 测试结果
- MySQL MCP 校验结论
- 若启动过进程，明确已关闭

- [ ] **Step 4: 最终提交**

```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java \
  ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java \
  ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiBankTagRuleSqlMetadataTest.java \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java \
  ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewEmployeeResultBuilderTest.java \
  sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql \
  sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql \
  docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md
git commit -m "完成异常账户模型后端接入"
```

## Final Verification

- [ ] 运行：

```bash
mvn -pl ccdi-project -Dtest=CcdiAbnormalAccountRuleSqlMetadataTest,CcdiBankTagRuleSqlMetadataTest,CcdiBankTagServiceImplTest,CcdiProjectOverviewEmployeeResultBuilderTest test
```

- [ ] 使用 MySQL MCP 执行两条规则真实 SQL，确认正样本命中、反样本不命中
- [ ] 确认结果写入 `ccdi_bank_statement_tag_result`
- [ ] 确认新增编码全为大写
- [ ] 如启动过后端进程，验证结束后主动关闭
