# 兰溪流水 Mock 第二期稳定随机命中设计

## 1. 背景

当前仓库已经具备两条基础能力：

- `lsfx-mock-server` 已支持按 `logId` 稳定随机命中第一期规则子集，并通过 Mock 流水为主工程提供可重复联调输入。
- 主工程第二期真实规则已在 [`docs/plans/backend/2026-03-20-bank-tag-real-rule-phase2-backend-implementation.md`](/Users/wkc/Desktop/ccdi/ccdi/docs/plans/backend/2026-03-20-bank-tag-real-rule-phase2-backend-implementation.md) 中明确范围，但 `lsfx-mock-server` 尚未补齐第二期命中输入与配套基线。

本次目标不是重写主工程打标逻辑，而是在保留第一期稳定随机命中方案的前提下，为第二期补齐可联调、可重复、可命中真实 SQL 的输入数据。

## 2. 目标

- 保留现有第一期 `logId -> 稳定随机规则子集` 行为不变。
- 为第二期规则新增稳定随机命中计划。
- 让“拉取本行信息 / 获取流水列表 -> 入库到本地 -> 执行重打标”后，最终标签结果中可以查到抽中的第二期规则命中。
- 对依赖采购、资产等外部事实表的规则，补最小且幂等的本地数据库基线。

## 3. 非目标

- 不新增主工程打标补丁逻辑。
- 不把采购类、资产类规则伪造成纯银行流水规则。
- 不引入“指定特殊 `logId` 固定命中全部第二期规则”的双轨模式。
- 不扩展第二期范围外的新规则。

## 4. 方案对比

### 4.1 方案 A：稳定随机命中 + 第二期基线编排

保留现有 `FileService -> StatementService -> 缓存分页` 主链路；对可由银行流水驱动的第二期规则继续通过 Mock 流水样本触发；对依赖采购、资产等外部事实表的规则，通过本地数据库最小基线补齐真实 SQL 输入。

优点：

- 与主工程真实 SQL 数据来源一致。
- 能同时覆盖“Mock 取数正确”和“真实规则可命中”。
- 保持现有第一期链路结构不变，扩展成本最低。

缺点：

- 需要同时维护流水样本和数据库基线两类输入。

### 4.2 方案 B：只改 Mock 流水

只扩充 `lsfx-mock-server` 返回的流水数据，不补采购、资产等业务表。

缺点：

- 第二期中存在真实依赖 `ccdi_purchase_transaction`、`ccdi_asset_info` 的规则，最终会出现 Mock 看似有数据、真实 SQL 仍然无法命中的偏差。

### 4.3 方案 C：只补数据库基线

Mock 仅返回普通流水，把第二期命中主要交给数据库灌数。

缺点：

- 无法证明“兰溪本地接口获取流水列表并入库”的链路本身正确。
- 偏离本次验收口径。

## 5. 推荐方案

采用方案 A。

原因：

- 第二期联调必须同时证明两件事：Mock 取数入库链路没问题，真实 SQL 也确实能命中。
- 采购类和资产类规则本来就不是纯流水规则，直接补最小事实基线才符合真实业务口径。
- 继续使用“同一 `logId` 稳定随机命中一部分规则”可以复用现有测试与排障习惯。

## 6. 模块边界与数据流

### 6.1 `FileService`

继续负责生成并持久化规则命中计划。

在现有字段基础上新增两组规则计划：

- `phase2_statement_hit_rules`
- `phase2_baseline_hit_rules`

职责边界：

- 只负责决定“这个 `logId` 抽中了哪些第二期规则”。
- 不直接构造流水明细，也不直接写数据库业务基线。

### 6.2 `StatementService` 与 `statement_rule_samples.py`

继续负责把命中计划转成最小流水样本，并与噪声流水合并后返回。

职责边界：

- 只生成能通过 `ccdi_bank_statement` 真实入库后触发的第二期样本。
- 不为采购、资产等外部事实规则伪造流水替代。

### 6.3 第二期基线服务

在 `lsfx-mock-server/services/` 下新增一个轻量服务，负责根据 `phase2_baseline_hit_rules` 幂等写入本地 MySQL 最小事实数据。

职责边界：

- 只补真实 SQL 所需输入。
- 不直接写标签结果表，不参与打标逻辑计算。

### 6.4 主工程

主工程 `CcdiFileUploadServiceImpl -> CcdiBankStatement -> CcdiBankTagServiceImpl` 链路保持不变。

验收以真实链路为准：

1. 拉取本行信息或通过兰溪接口获取流水。
2. 流水成功入库到 `ccdi_bank_statement`。
3. 执行项目重打标。
4. 在 `ccdi_bank_statement_tag_result` 或结果接口中看到抽中的第二期规则命中。

## 7. 第二期规则分层

### 7.1 流水样本直接驱动

以下规则以 `ccdi_bank_statement` 为核心输入，使用 Mock 流水样本驱动：

- `LOW_INCOME_RELATIVE_LARGE_TRANSACTION`
- `MULTI_PARTY_GAMBLING_TRANSFER`
- `MONTHLY_FIXED_INCOME`
- `FIXED_COUNTERPARTY_TRANSFER`
- `SALARY_QUICK_TRANSFER`
- `SALARY_UNUSED`

要求：

- 样本必须落在同一个真实员工域或其亲属域上。
- 时间窗口、金额区间、对手方稳定性等聚合条件必须在同一对象范围内闭环。
- 互斥规则不得使用同一员工样本组。

### 7.2 数据库基线驱动

以下规则需要外部事实表配合：

- `HOUSE_REGISTRATION_MISMATCH`
- `PROPERTY_FEE_REGISTRATION_MISMATCH`
- `TAX_ASSET_REGISTRATION_MISMATCH`
- `SUPPLIER_CONCENTRATION`

处理方式：

- 三条资产不匹配规则：
  - Mock 生成对应交易流水。
  - 基线服务在 `ccdi_asset_info` 中补“故意缺失或不匹配”的最小资产事实。
- `SUPPLIER_CONCENTRATION`：
  - 直接补 `ccdi_purchase_transaction` 聚合命中基线。
  - 不伪造银行流水替代。

## 8. 第二期样本设计

### 8.1 `LOW_INCOME_RELATIVE_LARGE_TRANSACTION`

- 选择一个真实员工及其亲属身份证范围。
- 亲属收入基线维持低收入事实。
- 生成大额累计转入或转出流水，确保超过低收入亲属的大额交易阈值。

### 8.2 `MULTI_PARTY_GAMBLING_TRANSFER`

- 同一员工对象、同一天、多名不同对手方。
- 每笔金额落在 `MULTI_PARTY_AMT_MIN ~ MULTI_PARTY_AMT_MAX` 区间。
- 命中依赖“多人 + 多次 + 同日 + 区间金额”，不依赖敏感词。

### 8.3 `MONTHLY_FIXED_INCOME`

- 为同一员工构造连续 3 至 4 个月的固定收入转入。
- 排除工资代发主体和工资关键词。
- 每月金额稳定高于 `MONTHLY_FIXED_INCOME`。

### 8.4 `FIXED_COUNTERPARTY_TRANSFER`

- 为同一员工与固定对手方构造季度稳定转入。
- 每季累计金额落在 `FIXED_COUNTERPARTY_TRANSFER_MIN ~ FIXED_COUNTERPARTY_TRANSFER_MAX` 区间。

### 8.5 `SALARY_QUICK_TRANSFER`

- 先生成可识别工资入账。
- 再在 24 小时内生成大额转出，满足比例条件。

### 8.6 `SALARY_UNUSED`

- 先生成工资入账。
- 后续 30 天不生成可计入“工资使用”的消费或转账支出。
- 与 `SALARY_QUICK_TRANSFER` 不共用同一员工对象。

### 8.7 资产不匹配三条规则

- `HOUSE_REGISTRATION_MISMATCH`：生成购房交易流水，但不提供可匹配房产登记。
- `PROPERTY_FEE_REGISTRATION_MISMATCH`：生成物业缴费流水，但不提供对应房产登记。
- `TAX_ASSET_REGISTRATION_MISMATCH`：生成大额纳税流水，但不提供与税费相符的资产登记。

### 8.8 `SUPPLIER_CONCENTRATION`

- 为同一申请人写入多笔采购记录。
- 使单一供应商采购额占比显著超过 70%。
- 使用固定业务主键和稳定供应商集合，确保可重复验证。

## 9. 实现落点

- Modify: `lsfx-mock-server/services/file_service.py`
- Modify: `lsfx-mock-server/services/statement_service.py`
- Modify: `lsfx-mock-server/services/statement_rule_samples.py`
- Add: `lsfx-mock-server/services/phase2_baseline_service.py`
- Add/Modify: `lsfx-mock-server/tests/test_file_service.py`
- Add/Modify: `lsfx-mock-server/tests/test_statement_service.py`
- Add/Modify: `lsfx-mock-server/tests/integration/test_full_workflow.py`
- Add: `sql/migration/2026-03-20-lsfx-mock-phase2-hit-baseline.sql`

## 10. 幂等策略

- 规则命中计划继续由 `logId` 决定，不新增第二随机源。
- 流水样本继续由 `StatementService` 缓存，同一 `logId` 重复分页读取结果一致。
- 采购基线使用固定业务主键，按固定主键删后重建。
- 资产基线使用固定特征组合，确保可精确清理与重建。
- 所有基线写入都必须只作用于本次选中的员工域，避免污染无关人员。

## 11. 验证设计

### 11.1 Mock 单元测试

- 第二期规则池抽样稳定性。
- 第二期流水样本只装配被抽中的规则。
- 第二期数据库基线写入的幂等性。

### 11.2 Mock 集成测试

- `getJZFileOrZjrcuFile -> getBSByLogId` 同一 `logId` 下结果稳定。
- 抽中第二期规则时，流水样本与基线写入同步成立。

### 11.3 主工程端到端验证

- 拉取本行信息后流水成功入库。
- 执行项目重打标后，第二期目标规则出现在结果中。
- 明细型规则可通过流水详情接口回查。
- 对象型规则可在标签结果表中看到正确 `objectType/objectKey/reasonDetail`。

## 12. 风险与边界

- 不为了联调成功去修改主工程真实 SQL。
- 不把 `SUPPLIER_CONCENTRATION` 伪造成纯流水命中。
- 不引入兜底、补丁或兼容性双轨逻辑。
- 若第二期真实 SQL 与现有基线字段口径不一致，按验证失败暴露，不在 Mock 侧偷偷修正业务规则。

## 13. 结论

本方案采用“保留第一期稳定随机命中方案 + 为第二期补齐稳定随机命中计划、最小流水样本和幂等数据库基线”的最短路径实现。

这样可以同时保证：

- 兰溪本地接口能够正确返回并入库存量流水；
- 第二期新增模型所需输入事实完整；
- 主工程重打标后能够命中新增模型的真实规则结果。
