接通Mock随机命中流水生成链路
This commit is contained in:
@@ -5,7 +5,7 @@ import uuid
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from services.statement_rule_samples import (
|
from services.statement_rule_samples import (
|
||||||
build_large_transaction_seed_statements,
|
build_seed_statements_for_rule_plan,
|
||||||
resolve_identity_cards,
|
resolve_identity_cards,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -144,14 +144,21 @@ class StatementService:
|
|||||||
"""生成指定数量的流水记录。"""
|
"""生成指定数量的流水记录。"""
|
||||||
primary_enterprise_name, primary_account_no = self._resolve_primary_binding(log_id)
|
primary_enterprise_name, primary_account_no = self._resolve_primary_binding(log_id)
|
||||||
record = self.file_service.get_file_record(log_id) if self.file_service is not None else None
|
record = self.file_service.get_file_record(log_id) if self.file_service is not None else None
|
||||||
|
rule_plan = {
|
||||||
|
"large_transaction_hit_rules": (
|
||||||
|
list(record.large_transaction_hit_rules) if record is not None else []
|
||||||
|
),
|
||||||
|
"phase1_hit_rules": list(record.phase1_hit_rules) if record is not None else [],
|
||||||
|
}
|
||||||
if record is not None and record.staff_id_card:
|
if record is not None and record.staff_id_card:
|
||||||
allowed_identity_cards = tuple([record.staff_id_card, *record.family_id_cards])
|
allowed_identity_cards = tuple([record.staff_id_card, *record.family_id_cards])
|
||||||
else:
|
else:
|
||||||
allowed_identity_cards = resolve_identity_cards(log_id)
|
allowed_identity_cards = resolve_identity_cards(log_id)
|
||||||
rng = random.Random(f"statement:{log_id}")
|
rng = random.Random(f"statement:{log_id}")
|
||||||
seeded_statements = build_large_transaction_seed_statements(
|
seeded_statements = build_seed_statements_for_rule_plan(
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
log_id=log_id,
|
log_id=log_id,
|
||||||
|
rule_plan=rule_plan,
|
||||||
primary_enterprise_name=primary_enterprise_name,
|
primary_enterprise_name=primary_enterprise_name,
|
||||||
primary_account_no=primary_account_no,
|
primary_account_no=primary_account_no,
|
||||||
staff_id_card=record.staff_id_card if record is not None else None,
|
staff_id_card=record.staff_id_card if record is not None else None,
|
||||||
|
|||||||
@@ -170,3 +170,30 @@ def test_upload_status_and_bank_statement_share_same_primary_binding(client, mon
|
|||||||
assert statements
|
assert statements
|
||||||
assert all(item["leName"] == status_log["enterpriseNameList"][0] for item in statements)
|
assert all(item["leName"] == status_log["enterpriseNameList"][0] for item in statements)
|
||||||
assert all(item["accountMaskNo"] == status_log["accountNoList"][0] for item in statements)
|
assert all(item["accountMaskNo"] == status_log["accountNoList"][0] for item in statements)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inner_flow_bank_statement_should_keep_same_rule_subset(client):
|
||||||
|
fetch_response = client.post(
|
||||||
|
"/watson/api/project/getJZFileOrZjrcuFile",
|
||||||
|
data={
|
||||||
|
"groupId": 1001,
|
||||||
|
"customerNo": "customer_subset",
|
||||||
|
"dataChannelCode": "test_code",
|
||||||
|
"requestDateId": 20240101,
|
||||||
|
"dataStartDateId": 20240101,
|
||||||
|
"dataEndDateId": 20240131,
|
||||||
|
"uploadUserId": 902001,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
log_id = fetch_response.json()["data"][0]
|
||||||
|
|
||||||
|
page1 = client.post(
|
||||||
|
"/watson/api/project/getBSByLogId",
|
||||||
|
data={"groupId": 1001, "logId": log_id, "pageNow": 1, "pageSize": 10},
|
||||||
|
).json()
|
||||||
|
page2 = client.post(
|
||||||
|
"/watson/api/project/getBSByLogId",
|
||||||
|
data={"groupId": 1001, "logId": log_id, "pageNow": 1, "pageSize": 10},
|
||||||
|
).json()
|
||||||
|
|
||||||
|
assert page1["data"]["bankStatementList"] == page2["data"]["bankStatementList"]
|
||||||
|
|||||||
@@ -22,11 +22,28 @@ class FakeStaffIdentityRepository:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_generate_statements_should_include_seeded_samples_before_noise():
|
def test_generate_statements_should_include_seeded_samples_before_noise_when_rule_plan_exists():
|
||||||
"""生成流水时必须先混入固定命中样本,而不是纯随机噪声。"""
|
"""存在规则命中计划时,生成流水必须先混入被选中的命中样本。"""
|
||||||
service = StatementService()
|
file_service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
|
||||||
|
service = StatementService(file_service=file_service)
|
||||||
|
|
||||||
statements = service._generate_statements(group_id=1000, log_id=20001, count=30)
|
response = file_service.fetch_inner_flow(
|
||||||
|
{
|
||||||
|
"groupId": 1001,
|
||||||
|
"customerNo": "customer_seeded_samples",
|
||||||
|
"dataChannelCode": "test_code",
|
||||||
|
"requestDateId": 20240101,
|
||||||
|
"dataStartDateId": 20240101,
|
||||||
|
"dataEndDateId": 20240131,
|
||||||
|
"uploadUserId": 902001,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
log_id = response["data"][0]
|
||||||
|
record = file_service.file_records[log_id]
|
||||||
|
record.large_transaction_hit_rules = ["HOUSE_OR_CAR_EXPENSE"]
|
||||||
|
record.phase1_hit_rules = []
|
||||||
|
|
||||||
|
statements = service._generate_statements(group_id=1000, log_id=log_id, count=30)
|
||||||
|
|
||||||
assert len(statements) >= 30
|
assert len(statements) >= 30
|
||||||
assert any(item["userMemo"] == "购买房产首付款" for item in statements)
|
assert any(item["userMemo"] == "购买房产首付款" for item in statements)
|
||||||
@@ -137,6 +154,33 @@ def test_generate_statements_should_fill_noise_up_to_requested_count():
|
|||||||
assert len(statements) == 80
|
assert len(statements) == 80
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_statements_should_follow_rule_hit_plan_from_file_record():
|
||||||
|
file_service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
|
||||||
|
statement_service = StatementService(file_service=file_service)
|
||||||
|
|
||||||
|
response = file_service.fetch_inner_flow(
|
||||||
|
{
|
||||||
|
"groupId": 1001,
|
||||||
|
"customerNo": "customer_rule_plan",
|
||||||
|
"dataChannelCode": "test_code",
|
||||||
|
"requestDateId": 20240101,
|
||||||
|
"dataStartDateId": 20240101,
|
||||||
|
"dataEndDateId": 20240131,
|
||||||
|
"uploadUserId": 902001,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
log_id = response["data"][0]
|
||||||
|
record = file_service.file_records[log_id]
|
||||||
|
record.large_transaction_hit_rules = ["HOUSE_OR_CAR_EXPENSE"]
|
||||||
|
record.phase1_hit_rules = ["GAMBLING_SENSITIVE_KEYWORD"]
|
||||||
|
|
||||||
|
statements = statement_service._generate_statements(group_id=1001, log_id=log_id, count=200)
|
||||||
|
|
||||||
|
assert any("房产首付款" in item["userMemo"] for item in statements)
|
||||||
|
assert any("游戏" in item["userMemo"] for item in statements)
|
||||||
|
assert not any("购汇" in item["userMemo"] for item in 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())
|
||||||
@@ -267,6 +311,14 @@ def test_get_bank_statement_contains_large_transaction_hit_samples(monkeypatch):
|
|||||||
)
|
)
|
||||||
log_id = response["data"][0]
|
log_id = response["data"][0]
|
||||||
record = file_service.file_records[log_id]
|
record = file_service.file_records[log_id]
|
||||||
|
record.large_transaction_hit_rules = [
|
||||||
|
"HOUSE_OR_CAR_EXPENSE",
|
||||||
|
"TAX_EXPENSE",
|
||||||
|
"CUMULATIVE_INCOME",
|
||||||
|
"FREQUENT_CASH_DEPOSIT",
|
||||||
|
"LARGE_TRANSFER",
|
||||||
|
]
|
||||||
|
record.phase1_hit_rules = []
|
||||||
staff_id_card = record.staff_id_card
|
staff_id_card = record.staff_id_card
|
||||||
family_id_card = record.family_id_cards[0]
|
family_id_card = record.family_id_cards[0]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user