dev #2
@@ -73,6 +73,13 @@
|
|||||||
|
|
||||||
在 `all` 模式安全噪声测试中,原有用例只清空了旧规则维度,未同步清空新增的 `abnormal_account_hit_rules`。本次已将该测试夹具补齐,保证它继续只验证“月固定收入 + 安全噪声”的原始语义。
|
在 `all` 模式安全噪声测试中,原有用例只清空了旧规则维度,未同步清空新增的 `abnormal_account_hit_rules`。本次已将该测试夹具补齐,保证它继续只验证“月固定收入 + 安全噪声”的原始语义。
|
||||||
|
|
||||||
|
在合并到 `dev` 后的运行态验证中,又发现 `getBSByLogId` 返回前统一回填主绑定时,会把异常账户样本原本正确的 `accountMaskNo` 覆盖成主账号,导致 HTTP 实际返回数据无法体现异常账户事实。对此补充了以下修正:
|
||||||
|
|
||||||
|
- 新增回归用例 `test_get_bank_statement_should_preserve_abnormal_account_mask_no`
|
||||||
|
- 将 `StatementService._apply_primary_binding(...)` 调整为只兜底缺失账号,不覆盖已有的异常账户样本账号
|
||||||
|
|
||||||
|
修正后,接口返回中的异常账户流水可以保留各自独立的账号,与异常账户事实保持一致。
|
||||||
|
|
||||||
## 5. 结果
|
## 5. 结果
|
||||||
|
|
||||||
异常账户命中计划、最小账户事实、样本生成器和服务层主链路均已落地,现有 Mock 服务可以为同一个 `logId` 稳定提供异常账户命中流水样本。
|
异常账户命中计划、最小账户事实、样本生成器和服务层主链路均已落地,现有 Mock 服务可以为同一个 `logId` 稳定提供异常账户命中流水样本。
|
||||||
|
|||||||
@@ -20,6 +20,21 @@ cd lsfx-mock-server
|
|||||||
python3 -m pytest tests/test_statement_service.py -v
|
python3 -m pytest tests/test_statement_service.py -v
|
||||||
```
|
```
|
||||||
|
|
||||||
|
合并到 `dev` 后补充执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pytest lsfx-mock-server/tests/test_statement_service.py::test_get_bank_statement_should_preserve_abnormal_account_mask_no -v
|
||||||
|
python3 -m pytest lsfx-mock-server/tests/test_file_service.py lsfx-mock-server/tests/test_statement_service.py -v
|
||||||
|
python3 main.py --rule-hit-mode all
|
||||||
|
```
|
||||||
|
|
||||||
|
启动服务后,使用标准库 `urllib` 调用了以下两个接口做运行态核验:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /watson/api/project/getJZFileOrZjrcuFile
|
||||||
|
POST /watson/api/project/getBSByLogId
|
||||||
|
```
|
||||||
|
|
||||||
## 2. 验证结果摘要
|
## 2. 验证结果摘要
|
||||||
|
|
||||||
- `tests/test_file_service.py::test_fetch_inner_flow_should_attach_abnormal_account_rule_plan`:通过
|
- `tests/test_file_service.py::test_fetch_inner_flow_should_attach_abnormal_account_rule_plan`:通过
|
||||||
@@ -28,12 +43,30 @@ python3 -m pytest tests/test_statement_service.py -v
|
|||||||
- `python3 -m pytest tests/test_file_service.py tests/test_statement_service.py -v`:`43 passed`
|
- `python3 -m pytest tests/test_file_service.py tests/test_statement_service.py -v`:`43 passed`
|
||||||
- `python3 -m pytest tests/ -v`:`84 passed`
|
- `python3 -m pytest tests/ -v`:`84 passed`
|
||||||
- `python3 -m pytest tests/test_statement_service.py -v`:`26 passed`
|
- `python3 -m pytest tests/test_statement_service.py -v`:`26 passed`
|
||||||
|
- `python3 -m pytest lsfx-mock-server/tests/test_statement_service.py::test_get_bank_statement_should_preserve_abnormal_account_mask_no -v`:通过
|
||||||
|
- `python3 -m pytest lsfx-mock-server/tests/test_file_service.py lsfx-mock-server/tests/test_statement_service.py -v`:`44 passed`
|
||||||
|
|
||||||
|
运行态 HTTP 验证结果:
|
||||||
|
|
||||||
|
- `logId=16724`
|
||||||
|
- 返回流水总数:`200`
|
||||||
|
- `SUDDEN_ACCOUNT_CLOSURE` 命中样本:`3` 条
|
||||||
|
- `DORMANT_ACCOUNT_LARGE_ACTIVATION` 命中样本:`3` 条
|
||||||
|
- 销户规则账号:`6222006485425901`
|
||||||
|
日期:`2026-02-18`、`2026-03-07`、`2026-03-18`
|
||||||
|
结论:全部落在销户日前 30 天窗口内
|
||||||
|
- 休眠激活规则账号:`6222004693652802`
|
||||||
|
日期:`2025-07-01`、`2025-07-10`、`2025-07-19`
|
||||||
|
累计金额:`560000.0`
|
||||||
|
单笔最大金额:`260000.0`
|
||||||
|
结论:满足开户满 6 个月后激活、累计金额阈值和单笔最大金额阈值
|
||||||
|
|
||||||
## 3. 过程说明
|
## 3. 过程说明
|
||||||
|
|
||||||
- 回归期间发现 `all` 模式安全噪声测试未同步清空新增的异常账户规则维度,导致异常账户样本被计入噪声断言
|
- 回归期间发现 `all` 模式安全噪声测试未同步清空新增的异常账户规则维度,导致异常账户样本被计入噪声断言
|
||||||
- 已通过补齐测试夹具方式修正,随后重新执行聚焦回归和全量回归,结果均通过
|
- 已通过补齐测试夹具方式修正,随后重新执行聚焦回归和全量回归,结果均通过
|
||||||
|
- 合并到 `dev` 后的 HTTP 验证又发现 `getBSByLogId` 返回前会覆盖异常账户样本账号,已通过新增回归用例与最小实现修正
|
||||||
|
|
||||||
## 4. 进程清理
|
## 4. 进程清理
|
||||||
|
|
||||||
本轮只执行了 `pytest` 命令,未启动额外前端、后端或 Mock 服务进程,因此无需清理残留进程。
|
本轮验证过程中临时启动了 `lsfx-mock-server` 的 `python3 main.py --rule-hit-mode all` 进程,验证完成后已主动停止,无残留端口占用。
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class StatementService:
|
|||||||
"""将解析出的主绑定统一回填到已有流水记录。"""
|
"""将解析出的主绑定统一回填到已有流水记录。"""
|
||||||
for statement in statements:
|
for statement in statements:
|
||||||
statement["leName"] = primary_enterprise_name
|
statement["leName"] = primary_enterprise_name
|
||||||
statement["accountMaskNo"] = primary_account_no
|
statement["accountMaskNo"] = statement.get("accountMaskNo") or primary_account_no
|
||||||
|
|
||||||
def get_bank_statement(self, request: Union[Dict, object]) -> Dict:
|
def get_bank_statement(self, request: Union[Dict, object]) -> Dict:
|
||||||
"""获取银行流水列表。"""
|
"""获取银行流水列表。"""
|
||||||
|
|||||||
@@ -311,6 +311,66 @@ def test_generate_statements_should_follow_abnormal_account_rule_plan_from_file_
|
|||||||
assert any("销户" in item["userMemo"] or "异常账户" in item["userMemo"] for item in statements)
|
assert any("销户" in item["userMemo"] or "异常账户" in item["userMemo"] for item in statements)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_bank_statement_should_preserve_abnormal_account_mask_no():
|
||||||
|
file_service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
|
||||||
|
statement_service = StatementService(file_service=file_service)
|
||||||
|
|
||||||
|
response = file_service.fetch_inner_flow(
|
||||||
|
{
|
||||||
|
"groupId": 1001,
|
||||||
|
"customerNo": "customer_abnormal_api",
|
||||||
|
"dataChannelCode": "test_code",
|
||||||
|
"requestDateId": 20240101,
|
||||||
|
"dataStartDateId": 20240101,
|
||||||
|
"dataEndDateId": 20240131,
|
||||||
|
"uploadUserId": 902001,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
log_id = response["data"][0]
|
||||||
|
record = file_service.file_records[log_id]
|
||||||
|
record.abnormal_account_hit_rules = [
|
||||||
|
"SUDDEN_ACCOUNT_CLOSURE",
|
||||||
|
"DORMANT_ACCOUNT_LARGE_ACTIVATION",
|
||||||
|
]
|
||||||
|
record.abnormal_accounts = [
|
||||||
|
{
|
||||||
|
"account_no": "6222000000000001",
|
||||||
|
"owner_id_card": record.staff_id_card,
|
||||||
|
"account_name": "测试员工工资卡",
|
||||||
|
"status": 2,
|
||||||
|
"effective_date": "2024-01-01",
|
||||||
|
"invalid_date": "2026-03-20",
|
||||||
|
"rule_code": "SUDDEN_ACCOUNT_CLOSURE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account_no": "6222000000000002",
|
||||||
|
"owner_id_card": record.staff_id_card,
|
||||||
|
"account_name": "测试员工工资卡",
|
||||||
|
"status": 1,
|
||||||
|
"effective_date": "2025-01-01",
|
||||||
|
"invalid_date": None,
|
||||||
|
"rule_code": "DORMANT_ACCOUNT_LARGE_ACTIVATION",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
response = statement_service.get_bank_statement(
|
||||||
|
{
|
||||||
|
"groupId": 1001,
|
||||||
|
"logId": log_id,
|
||||||
|
"pageNow": 1,
|
||||||
|
"pageSize": 500,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
statements = response["data"]["bankStatementList"]
|
||||||
|
abnormal_statements = [
|
||||||
|
item for item in statements if "销户" in item["userMemo"] or "激活" in item["userMemo"]
|
||||||
|
]
|
||||||
|
|
||||||
|
assert abnormal_statements
|
||||||
|
assert any(item["accountMaskNo"] == "6222000000000001" for item in abnormal_statements)
|
||||||
|
assert any(item["accountMaskNo"] == "6222000000000002" for item in abnormal_statements)
|
||||||
|
|
||||||
|
|
||||||
def test_generate_statements_should_stay_within_single_employee_scope_per_log_id():
|
def test_generate_statements_should_stay_within_single_employee_scope_per_log_id():
|
||||||
"""同一 logId 的流水只能落在 FileRecord 绑定的员工及亲属身份证内。"""
|
"""同一 logId 的流水只能落在 FileRecord 绑定的员工及亲属身份证内。"""
|
||||||
file_service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
|
file_service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
|
||||||
|
|||||||
Reference in New Issue
Block a user