From 5de46eabc5db67e47b7df97fb86f4665a3c25bf7 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 31 Mar 2026 20:58:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=BC=82=E5=B8=B8=E8=B4=A6?= =?UTF-8?q?=E6=88=B7=E6=B5=81=E6=B0=B4=E8=BF=94=E5=9B=9E=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...abnormal-account-backend-implementation.md | 7 +++ ...r-abnormal-account-backend-verification.md | 35 ++++++++++- .../services/statement_service.py | 2 +- .../tests/test_statement_service.py | 60 +++++++++++++++++++ 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/docs/reports/implementation/2026-03-31-lsfx-mock-server-abnormal-account-backend-implementation.md b/docs/reports/implementation/2026-03-31-lsfx-mock-server-abnormal-account-backend-implementation.md index 4931466b..b4dd428e 100644 --- a/docs/reports/implementation/2026-03-31-lsfx-mock-server-abnormal-account-backend-implementation.md +++ b/docs/reports/implementation/2026-03-31-lsfx-mock-server-abnormal-account-backend-implementation.md @@ -73,6 +73,13 @@ 在 `all` 模式安全噪声测试中,原有用例只清空了旧规则维度,未同步清空新增的 `abnormal_account_hit_rules`。本次已将该测试夹具补齐,保证它继续只验证“月固定收入 + 安全噪声”的原始语义。 +在合并到 `dev` 后的运行态验证中,又发现 `getBSByLogId` 返回前统一回填主绑定时,会把异常账户样本原本正确的 `accountMaskNo` 覆盖成主账号,导致 HTTP 实际返回数据无法体现异常账户事实。对此补充了以下修正: + +- 新增回归用例 `test_get_bank_statement_should_preserve_abnormal_account_mask_no` +- 将 `StatementService._apply_primary_binding(...)` 调整为只兜底缺失账号,不覆盖已有的异常账户样本账号 + +修正后,接口返回中的异常账户流水可以保留各自独立的账号,与异常账户事实保持一致。 + ## 5. 结果 异常账户命中计划、最小账户事实、样本生成器和服务层主链路均已落地,现有 Mock 服务可以为同一个 `logId` 稳定提供异常账户命中流水样本。 diff --git a/docs/tests/records/2026-03-31-lsfx-mock-server-abnormal-account-backend-verification.md b/docs/tests/records/2026-03-31-lsfx-mock-server-abnormal-account-backend-verification.md index a566b429..0de06532 100644 --- a/docs/tests/records/2026-03-31-lsfx-mock-server-abnormal-account-backend-verification.md +++ b/docs/tests/records/2026-03-31-lsfx-mock-server-abnormal-account-backend-verification.md @@ -20,6 +20,21 @@ cd lsfx-mock-server 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. 验证结果摘要 - `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/ -v`:`84 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. 过程说明 - 回归期间发现 `all` 模式安全噪声测试未同步清空新增的异常账户规则维度,导致异常账户样本被计入噪声断言 - 已通过补齐测试夹具方式修正,随后重新执行聚焦回归和全量回归,结果均通过 +- 合并到 `dev` 后的 HTTP 验证又发现 `getBSByLogId` 返回前会覆盖异常账户样本账号,已通过新增回归用例与最小实现修正 ## 4. 进程清理 -本轮只执行了 `pytest` 命令,未启动额外前端、后端或 Mock 服务进程,因此无需清理残留进程。 +本轮验证过程中临时启动了 `lsfx-mock-server` 的 `python3 main.py --rule-hit-mode all` 进程,验证完成后已主动停止,无残留端口占用。 diff --git a/lsfx-mock-server/services/statement_service.py b/lsfx-mock-server/services/statement_service.py index b3f089df..a51f2d14 100644 --- a/lsfx-mock-server/services/statement_service.py +++ b/lsfx-mock-server/services/statement_service.py @@ -216,7 +216,7 @@ class StatementService: """将解析出的主绑定统一回填到已有流水记录。""" for statement in statements: 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: """获取银行流水列表。""" diff --git a/lsfx-mock-server/tests/test_statement_service.py b/lsfx-mock-server/tests/test_statement_service.py index 20004a8c..c5fdc357 100644 --- a/lsfx-mock-server/tests/test_statement_service.py +++ b/lsfx-mock-server/tests/test_statement_service.py @@ -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) +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(): """同一 logId 的流水只能落在 FileRecord 绑定的员工及亲属身份证内。""" file_service = FileService(staff_identity_repository=FakeStaffIdentityRepository())