补充异常账户命中计划与账户事实
This commit is contained in:
@@ -48,6 +48,11 @@ PHASE2_BASELINE_RULE_CODES = [
|
||||
"SUPPLIER_CONCENTRATION",
|
||||
]
|
||||
|
||||
ABNORMAL_ACCOUNT_RULE_CODES = [
|
||||
"SUDDEN_ACCOUNT_CLOSURE",
|
||||
"DORMANT_ACCOUNT_LARGE_ACTIVATION",
|
||||
]
|
||||
|
||||
MONTHLY_FIXED_INCOME_ISOLATED_LARGE_TRANSACTION_RULE_CODES = {
|
||||
"SINGLE_LARGE_INCOME",
|
||||
"CUMULATIVE_INCOME",
|
||||
@@ -127,6 +132,8 @@ class FileRecord:
|
||||
phase1_hit_rules: List[str] = field(default_factory=list)
|
||||
phase2_statement_hit_rules: List[str] = field(default_factory=list)
|
||||
phase2_baseline_hit_rules: List[str] = field(default_factory=list)
|
||||
abnormal_account_hit_rules: List[str] = field(default_factory=list)
|
||||
abnormal_accounts: List[dict] = field(default_factory=list)
|
||||
|
||||
|
||||
class FileService:
|
||||
@@ -213,6 +220,9 @@ class FileService:
|
||||
"phase2_baseline_hit_rules": self._pick_rule_subset(
|
||||
rng, PHASE2_BASELINE_RULE_CODES, 2, 4
|
||||
),
|
||||
"abnormal_account_hit_rules": self._pick_rule_subset(
|
||||
rng, ABNORMAL_ACCOUNT_RULE_CODES, 1, len(ABNORMAL_ACCOUNT_RULE_CODES)
|
||||
),
|
||||
}
|
||||
|
||||
def _build_all_compatible_rule_hit_plan(self) -> dict:
|
||||
@@ -222,6 +232,7 @@ class FileService:
|
||||
"phase1_hit_rules": list(PHASE1_RULE_CODES),
|
||||
"phase2_statement_hit_rules": list(PHASE2_STATEMENT_RULE_CODES),
|
||||
"phase2_baseline_hit_rules": list(PHASE2_BASELINE_RULE_CODES),
|
||||
"abnormal_account_hit_rules": list(ABNORMAL_ACCOUNT_RULE_CODES),
|
||||
}
|
||||
|
||||
def _build_monthly_fixed_income_isolated_rule_hit_plan(self) -> dict:
|
||||
@@ -284,6 +295,52 @@ class FileService:
|
||||
file_record.phase2_baseline_hit_rules = list(
|
||||
rule_hit_plan.get("phase2_baseline_hit_rules", [])
|
||||
)
|
||||
file_record.abnormal_account_hit_rules = list(
|
||||
rule_hit_plan.get("abnormal_account_hit_rules", [])
|
||||
)
|
||||
file_record.abnormal_accounts = self._build_abnormal_accounts(
|
||||
log_id=file_record.log_id,
|
||||
staff_id_card=file_record.staff_id_card,
|
||||
abnormal_account_hit_rules=file_record.abnormal_account_hit_rules,
|
||||
)
|
||||
|
||||
def _build_abnormal_accounts(
|
||||
self,
|
||||
*,
|
||||
log_id: int,
|
||||
staff_id_card: str,
|
||||
abnormal_account_hit_rules: List[str],
|
||||
) -> List[dict]:
|
||||
"""按命中规则生成最小异常账户事实。"""
|
||||
if not abnormal_account_hit_rules:
|
||||
return []
|
||||
|
||||
rng = random.Random(f"abnormal-account:{log_id}")
|
||||
accounts = []
|
||||
for index, rule_code in enumerate(abnormal_account_hit_rules, start=1):
|
||||
account_no = f"622200{rng.randint(10**9, 10**10 - 1)}"
|
||||
account_fact = {
|
||||
"account_no": account_no,
|
||||
"owner_id_card": staff_id_card,
|
||||
"account_name": "测试员工工资卡",
|
||||
"status": 1,
|
||||
"effective_date": "2025-01-01",
|
||||
"invalid_date": None,
|
||||
"rule_code": rule_code,
|
||||
}
|
||||
if rule_code == "SUDDEN_ACCOUNT_CLOSURE":
|
||||
account_fact["status"] = 2
|
||||
account_fact["effective_date"] = "2024-01-01"
|
||||
account_fact["invalid_date"] = "2026-03-20"
|
||||
elif rule_code == "DORMANT_ACCOUNT_LARGE_ACTIVATION":
|
||||
account_fact["status"] = 1
|
||||
account_fact["effective_date"] = "2025-01-01"
|
||||
account_fact["invalid_date"] = None
|
||||
|
||||
account_fact["account_no"] = f"{account_no[:-2]}{index:02d}"
|
||||
accounts.append(account_fact)
|
||||
|
||||
return accounts
|
||||
|
||||
def _rebalance_all_mode_group_rule_plans(self, group_id: int) -> None:
|
||||
"""同项目存在多文件时,隔离月固定收入样本,避免被其他正向流入规则污染。"""
|
||||
@@ -332,6 +389,8 @@ class FileService:
|
||||
phase1_hit_rules: List[str] = None,
|
||||
phase2_statement_hit_rules: List[str] = None,
|
||||
phase2_baseline_hit_rules: List[str] = None,
|
||||
abnormal_account_hit_rules: List[str] = None,
|
||||
abnormal_accounts: List[dict] = None,
|
||||
parsing: bool = True,
|
||||
status: int = -5,
|
||||
) -> FileRecord:
|
||||
@@ -366,6 +425,8 @@ class FileService:
|
||||
phase1_hit_rules=list(phase1_hit_rules or []),
|
||||
phase2_statement_hit_rules=list(phase2_statement_hit_rules or []),
|
||||
phase2_baseline_hit_rules=list(phase2_baseline_hit_rules or []),
|
||||
abnormal_account_hit_rules=list(abnormal_account_hit_rules or []),
|
||||
abnormal_accounts=[dict(account) for account in (abnormal_accounts or [])],
|
||||
parsing=parsing,
|
||||
status=status,
|
||||
)
|
||||
@@ -444,6 +505,12 @@ class FileService:
|
||||
phase1_hit_rules=rule_hit_plan.get("phase1_hit_rules", []),
|
||||
phase2_statement_hit_rules=rule_hit_plan.get("phase2_statement_hit_rules", []),
|
||||
phase2_baseline_hit_rules=rule_hit_plan.get("phase2_baseline_hit_rules", []),
|
||||
abnormal_account_hit_rules=rule_hit_plan.get("abnormal_account_hit_rules", []),
|
||||
abnormal_accounts=self._build_abnormal_accounts(
|
||||
log_id=log_id,
|
||||
staff_id_card=identity_scope["staff_id_card"],
|
||||
abnormal_account_hit_rules=rule_hit_plan.get("abnormal_account_hit_rules", []),
|
||||
),
|
||||
)
|
||||
|
||||
# 存储记录
|
||||
@@ -775,6 +842,12 @@ class FileService:
|
||||
phase1_hit_rules=rule_hit_plan.get("phase1_hit_rules", []),
|
||||
phase2_statement_hit_rules=rule_hit_plan.get("phase2_statement_hit_rules", []),
|
||||
phase2_baseline_hit_rules=rule_hit_plan.get("phase2_baseline_hit_rules", []),
|
||||
abnormal_account_hit_rules=rule_hit_plan.get("abnormal_account_hit_rules", []),
|
||||
abnormal_accounts=self._build_abnormal_accounts(
|
||||
log_id=log_id,
|
||||
staff_id_card=identity_scope["staff_id_card"],
|
||||
abnormal_account_hit_rules=rule_hit_plan.get("abnormal_account_hit_rules", []),
|
||||
),
|
||||
parsing=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -163,6 +163,29 @@ def test_fetch_inner_flow_persists_primary_binding_record(monkeypatch):
|
||||
assert record.total_records == 200
|
||||
|
||||
|
||||
def test_fetch_inner_flow_should_attach_abnormal_account_rule_plan():
|
||||
service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
|
||||
|
||||
response = service.fetch_inner_flow(
|
||||
{
|
||||
"groupId": 1001,
|
||||
"customerNo": "customer_abnormal_account",
|
||||
"dataChannelCode": "test_code",
|
||||
"requestDateId": 20240101,
|
||||
"dataStartDateId": 20240101,
|
||||
"dataEndDateId": 20240131,
|
||||
"uploadUserId": 902001,
|
||||
}
|
||||
)
|
||||
log_id = response["data"][0]
|
||||
record = service.file_records[log_id]
|
||||
|
||||
assert hasattr(record, "abnormal_account_hit_rules")
|
||||
assert hasattr(record, "abnormal_accounts")
|
||||
assert isinstance(record.abnormal_account_hit_rules, list)
|
||||
assert isinstance(record.abnormal_accounts, list)
|
||||
|
||||
|
||||
def test_generate_log_id_should_retry_when_random_value_conflicts(monkeypatch):
|
||||
"""随机 logId 命中已存在记录时必须重试并返回未占用值。"""
|
||||
service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
|
||||
|
||||
Reference in New Issue
Block a user