Files
ccdi/docs/design/2026-03-20-lsfx-mock-random-hit-rule-design.md

399 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 兰溪流水 Mock 随机命中规则设计文档
**模块**: `lsfx-mock-server`
**日期**: 2026-03-20
## 一、背景
当前 `lsfx-mock-server` 已接通以下主链路:
1. `getJZFileOrZjrcuFile` 创建 `logId` 并保存主绑定、员工及亲属身份范围
2. `getBSByLogId``logId` 生成流水并分页返回
3. 同一 `logId` 首次生成的流水结果会缓存,后续分页查询保持稳定
现有问题有两类:
1. 大额交易命中样本当前为固定全量注入,不符合“随机命中一部分”的联调需求
2. 第一期新增真实规则虽然已在主项目后端落地,但 `lsfx-mock-server` 还没有对应的随机命中样本,导致“获取流水列表并存储到兰溪本地”这条链路无法稳定覆盖新增规则
本次需求要求:
1. 大额交易和新增第一期规则都改为随机命中一部分
2. 随机结果对同一个 `logId` 必须稳定,不能每次查询重新变化
3. `getJZFileOrZjrcuFile -> getBSByLogId` 生成出的流水可以被兰溪本地接口正确存储
4. 必要时允许直接修改关联数据库最小量数据,使新增模型可以正确打标
## 二、目标
本次设计目标如下:
1.`lsfx-mock-server` 的固定全量命中样本改造成“按 `logId` 稳定随机命中规则子集”
2. 保持现有 `FileService -> StatementService -> cache` 主链路不变
3. 将样本职责拆到“规则命中计划”和“规则样本拼装”两个层次,避免随机逻辑散落
4. 让大额交易模型与第一期新增规则都具备“随机命中一部分”的流水样本
5. 对无法通过银行流水接口伪造的规则,采用最小数据库基线补齐,不增加兼容性补丁链路
## 三、范围
### 3.1 本次范围
- 修改 `lsfx-mock-server/services/file_service.py`
- 修改 `lsfx-mock-server/services/statement_service.py`
- 修改 `lsfx-mock-server/services/statement_rule_samples.py`
- 修改 `lsfx-mock-server` 相关单元测试与集成测试
- 按最小范围调整关联数据库中的基线业务数据,使非流水表规则可被正确打标
- 补充设计文档与后续实施计划文档
### 3.2 不在本次范围
- 不新增新的 Mock 服务模块
- 不把 Mock 服务改造成实时从数据库拼装整套流水
- 不改造主项目银行流水打标架构
- 不引入兼容性双轨逻辑
- 不为前端新增页面或交互
## 四、现状分析
### 4.1 Mock 服务现状
当前 `lsfx-mock-server` 中:
- `FileService.fetch_inner_flow()` 负责生成 `logId`、主绑定、日期范围和员工亲属身份
- `StatementService._generate_statements()` 会混入固定大额交易命中样本,再补随机噪声流水
- `StatementService.get_bank_statement()` 首次生成后缓存 200 条流水,同一 `logId` 分页结果稳定
这套结构已经满足“同一 `logId` 查询稳定”的基础要求,但样本生成仍是“默认全量命中”,不满足当前需求。
### 4.2 主项目规则现状
根据 [docs/plans/backend/2026-03-20-bank-tag-real-rule-phase1-backend-implementation.md](/Users/wkc/Desktop/ccdi/ccdi/docs/plans/backend/2026-03-20-bank-tag-real-rule-phase1-backend-implementation.md) 与 [docs/design/2026-03-20-bank-tag-real-rule-two-phase-design.md](/Users/wkc/Desktop/ccdi/ccdi/docs/design/2026-03-20-bank-tag-real-rule-two-phase-design.md),第一期真实规则已覆盖:
- `GAMBLING_SENSITIVE_KEYWORD`
- `SPECIAL_AMOUNT_TRANSACTION`
- `SUSPICIOUS_INCOME_KEYWORD`
- `FOREX_BUY_AMT`
- `FOREX_SELL_AMT`
- `LARGE_PURCHASE_TRANSACTION`
- `STOCK_TFR_LARGE`
- `LARGE_STOCK_TRADING`
- `WITHDRAW_CNT`
其中:
- `GAMBLING_SENSITIVE_KEYWORD`
- `SPECIAL_AMOUNT_TRANSACTION`
- `SUSPICIOUS_INCOME_KEYWORD`
- `FOREX_BUY_AMT`
- `FOREX_SELL_AMT`
- `STOCK_TFR_LARGE`
- `LARGE_STOCK_TRADING`
- `WITHDRAW_CNT`
都可以通过 Mock 银行流水直接构造命中条件。
`LARGE_PURCHASE_TRANSACTION` 的真实 SQL 查询的是 `ccdi_purchase_transaction`,不是 `ccdi_bank_statement`,因此不适合伪造成银行流水命中。
### 4.3 本次关键约束
用户已明确确认以下约束:
1. 大额交易和新增第一期规则都改为随机命中一部分,而不是固定全量命中
2. 随机结果采用“同一个 `logId` 首次生成时随机决定,后续保持不变”
因此本次设计必须同时满足:
- 有随机性
- 可复现
- 可测试
- 不因重复查询导致结果漂移
## 五、方案对比
### 5.1 方案 A按规则包稳定随机命中推荐
做法:
- 把“大额交易”和“第一期新增规则”拆成多个独立规则包
- `fetch_inner_flow()` 生成 `logId` 时,基于 `logId` 计算一份稳定的规则命中计划
- `getBSByLogId` 首次查该 `logId` 时,根据命中计划拼装命中样本并补噪声流水
优点:
- 同一 `logId` 结果稳定
- 不同 `logId` 命中子集不同,随机性自然
- 可以按规则粒度精确控制命中范围
- 测试可以直接断言“规则命中计划稳定”
缺点:
- 需要把现有固定全量样本生成器改造成可按规则装配的结构
### 5.2 方案 B按场景模板随机命中
做法:
- 预置若干整套场景模板,如“偏大额交易”“偏新增规则”“混合命中”
- 每个 `logId` 只随机选择一套模板
优点:
- 实现快
缺点:
- 随机粒度粗
- 容易出现某些规则长期捆绑命中,不够贴近真实联调需求
### 5.3 方案 C重度依赖数据库动态拼装流水
做法:
- 先写大量数据库测试数据
- Mock 服务每次从数据库读取对象关系与业务记录,再动态拼装流水
优点:
- 表面上更接近生产链路
缺点:
- 耦合高,调试成本大
- 超出本次最短路径要求
- 会把 Mock 服务从“可控样本生成器”变成“数据库依赖型服务”
### 5.4 结论
采用方案 A。
## 六、总体设计
### 6.1 设计原则
1. 随机决策只在 `FileService` 做一次
2. 流水生成只在 `StatementService` 做一次并缓存
3. 样本模板只在 `statement_rule_samples.py` 维护
4. 不新增新的服务层或中间模块
5. 对不能通过银行流水构造的规则,使用最小数据库基线补齐
### 6.2 数据流
改造后的数据流如下:
1. `getJZFileOrZjrcuFile` 调用 `FileService.fetch_inner_flow()`
2. `FileService` 创建 `FileRecord` 时,基于 `logId` 生成并保存规则命中计划
3. `getBSByLogId` 调用 `StatementService.get_bank_statement()`
4. `StatementService` 首次查询该 `logId` 时,从 `FileRecord` 读取命中计划
5. `statement_rule_samples.py` 根据命中计划生成对应规则样本
6. `StatementService` 再补足噪声流水到固定 200 条
7. 首次生成的结果缓存到内存,后续分页继续复用同一份流水
### 6.3 规则命中计划设计
规则命中计划按两大类保存:
- `largeTransactionHitRules`
- `phase1HitRules`
规则命中计划保存的是规则代码集合,不保存具体流水明细。
同一个 `logId` 的命中计划由固定种子生成,例如:
- `Random("rule-plan:{logId}")`
由此保证:
- 同一个 `logId` 命中子集稳定
- 不同 `logId` 命中子集大概率不同
### 6.4 职责划分
#### `FileService`
职责:
- 创建 `logId`
- 绑定主体、账号、员工及亲属身份
- 生成并保存规则命中计划
不负责:
- 生成具体流水明细
- 执行规则样本拼装
#### `StatementService`
职责:
- 首次读取 `logId` 时,根据规则命中计划生成流水
- 补足噪声流水
- 分页返回并缓存结果
不负责:
- 决定本次命中哪些规则
#### `statement_rule_samples.py`
职责:
- 按规则代码生成对应命中样本
- 提供统一入口,按命中计划拼装样本集合
不负责:
- 随机选择规则
- 缓存或分页
## 七、规则包设计
### 7.1 大额交易规则包
保留现有已实现的大额交易命中样本,但改为按规则粒度单独开关,不再默认全量注入。
每条规则对应一个独立样本 builder由命中计划决定是否加入。
### 7.2 第一期新增规则包
本次通过银行流水构造的第一期规则包为:
- `GAMBLING_SENSITIVE_KEYWORD`
- `SPECIAL_AMOUNT_TRANSACTION`
- `SUSPICIOUS_INCOME_KEYWORD`
- `FOREX_BUY_AMT`
- `FOREX_SELL_AMT`
- `STOCK_TFR_LARGE`
- `LARGE_STOCK_TRADING`
- `WITHDRAW_CNT`
每个规则包只生成命中该规则所需的最小流水集合,不附带额外未请求逻辑。
### 7.3 `LARGE_PURCHASE_TRANSACTION` 边界
`LARGE_PURCHASE_TRANSACTION` 不放入 Mock 银行流水规则包中,原因如下:
1. 主项目真实 SQL 查询来源是 `ccdi_purchase_transaction`
2. `getBSByLogId` 返回的是银行流水,无法自然映射采购业务表
3. 若将其伪造成银行流水命中,会偏离真实打标逻辑
因此该规则采用“数据库最小基线补齐”的方式处理,不强行混入 Mock 流水。
## 八、随机策略设计
### 8.1 随机约束
为避免“完全不命中”或“一次性全命中”的极端情况,本次采用有边界的随机策略:
- 大额交易规则包:随机命中 2 到 4 条
- 第一期新增流水规则包:随机命中 2 到 4 条
- 两大类都至少命中 1 条
- `WITHDRAW_CNT` 允许独立命中,不要求与明细型规则绑定
未命中的规则不生成伪样本,只保留普通噪声流水。
### 8.2 稳定性要求
同一个 `logId`
- 首次生成命中计划后固定不变
- 首次生成流水后缓存结果固定不变
- 后续分页查询不得重新随机
## 九、数据库基线设计
### 9.1 可直接复用的数据
继续复用当前 `StaffIdentityRepository` 从真实数据库读取的:
- 员工身份证
- 有效亲属身份证
这部分已经满足 `SPECIAL_AMOUNT_TRANSACTION``WITHDRAW_CNT` 等规则的对象关系基础。
### 9.2 需要最小补齐的数据
只补齐真实打标必需、但无法通过 Mock 流水自然构造的最小数据:
1. 若被选中的员工缺少足够的亲属关系区分数据,则补齐最小亲属关系记录,确保能区分“配偶/子女”和“非配偶子女”
2.`LARGE_PURCHASE_TRANSACTION` 补最小量 `ccdi_purchase_transaction` 业务数据,使真实 SQL 可命中
### 9.3 明确不做的事
- 不大规模预置 `ccdi_bank_statement`
- 不让 Mock 服务实时依赖数据库生成整套流水
- 不为数据库不存在的业务链路追加补丁式兼容方案
## 十、错误处理
1. 若数据库中无法读取员工及亲属身份,保持现有失败语义,直接暴露错误
2. 若某次规则命中计划生成异常,不写入 `FileRecord`
3.`getBSByLogId` 首次生成流水失败,不写入缓存,避免脏缓存
4. 若某类规则本次未被随机命中,不视为错误,只是本次子集未覆盖
5. `LARGE_PURCHASE_TRANSACTION` 不因未出现在 Mock 流水中而报错,其命中依赖数据库基线数据
## 十一、测试设计
### 11.1 `tests/test_file_service.py`
验证:
- `fetch_inner_flow()` 会生成并保存规则命中计划
- 同一 `logId` 的命中计划稳定
- 命中计划中同时包含大额交易与第一期新增规则两类结果
### 11.2 `tests/test_statement_service.py`
验证:
- 按命中计划只生成对应规则子集样本,而不是固定全量命中
- 同一 `logId` 重复查询时结果不漂移
- 不同 `logId` 大概率得到不同命中子集
- 命中规则对应的样本字段能被主项目真实 SQL 识别
### 11.3 `tests/integration/test_full_workflow.py`
验证:
- `getJZFileOrZjrcuFile -> getBSByLogId` 主链路可正常返回
- 返回结果既包含随机命中样本,也包含普通噪声流水
- 上传状态与流水接口仍共享同一主绑定信息
### 11.4 数据库基线验证
`LARGE_PURCHASE_TRANSACTION` 单独验证:
- 基线业务数据补齐后,主项目真实规则 SQL 可命中
- 不将该规则混入 Mock 流水命中断言
## 十二、实施与文档要求
后续进入实施计划阶段时,本次仅产出后端实施计划文档:
1. 后端实施计划:`docs/plans/backend/`
原因如下:
- 本次改造范围仅涉及 `lsfx-mock-server`、相关测试与最小数据库基线补齐
- 不涉及前端页面、接口封装、交互或展示调整
- 用户已明确要求本次只需要后端实施计划
后端实施计划需覆盖:
- `lsfx-mock-server` 随机命中规则改造
- 单元测试与集成测试调整
- `LARGE_PURCHASE_TRANSACTION` 相关数据库基线补齐
- 实施记录与验证记录沉淀
同时补充实施记录文档,记录本次具体改动内容与验证结果。
## 十三、结论
本次采用“按 `logId` 稳定随机命中规则包”的方式改造 `lsfx-mock-server`
- 保留现有主链路
- 不新增模块
- 不引入补丁式兼容方案
- 让大额交易和第一期新增规则都能随机命中一部分
- 对同一个 `logId` 保持稳定、可复现、可联调
对无法通过银行流水自然构造的 `LARGE_PURCHASE_TRANSACTION`,采用最小数据库基线补齐,不伪造银行流水,以确保整个方案逻辑正确并与主项目真实打标链路一致。