feat: 添加可配置场景注册表与规则引擎
- 新增场景基类、配置加载、注册表与原语模块 - 添加 r10_refund_split 规则及场景 JSON Schema - 扩展 scan 引擎与 scenarios API - 新增场景注册表/配置/集成测试 - 更新前端 App、api、labels 支持新场景
This commit is contained in:
@@ -8,6 +8,9 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from app.scenarios.base import BaseScenario, ClueDraft, ScanOutcome
|
||||
from app.scenarios.registry import register_scenario
|
||||
|
||||
|
||||
@dataclass
|
||||
class CohortPoint:
|
||||
@@ -83,3 +86,50 @@ def build_rationale(finding: ChurnFinding, mismatch: float) -> str:
|
||||
return (
|
||||
f"未见明显断崖退订,但佣金与业务质量不匹配度为 {mismatch:.0%},建议关注。"
|
||||
)
|
||||
|
||||
|
||||
@register_scenario
|
||||
class ChurnFraudScenario(BaseScenario):
|
||||
"""场景二养卡骗补检测的插件封装:时序断崖 + 佣金质量不匹配 → 线索草稿。"""
|
||||
|
||||
code = "R9"
|
||||
title = "疑似养卡骗补(脉冲增长+规律退订)"
|
||||
risk_domain = "成本"
|
||||
label = "养卡骗补"
|
||||
score_threshold = 0.5 # 与原 run_churn_scan 的 score>=0.5 一致
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
retention_curve: list[CohortPoint],
|
||||
commission_paid: float,
|
||||
active_ratio: float,
|
||||
zero_usage_ratio: float,
|
||||
channel_key: str,
|
||||
) -> None:
|
||||
self.retention_curve = retention_curve
|
||||
self.commission_paid = commission_paid
|
||||
self.active_ratio = active_ratio
|
||||
self.zero_usage_ratio = zero_usage_ratio
|
||||
self.channel_key = channel_key
|
||||
|
||||
def scan(self, session, *, data_version_id=None) -> ScanOutcome:
|
||||
finding = detect_pulse_decay(self.retention_curve)
|
||||
mismatch = commission_quality_mismatch(
|
||||
self.commission_paid, self.active_ratio, self.zero_usage_ratio
|
||||
)
|
||||
score = churn_risk_score(finding, mismatch)
|
||||
draft = ClueDraft(
|
||||
score=score,
|
||||
rationale=build_rationale(finding, mismatch),
|
||||
evidence={
|
||||
"cliff_month": finding.cliff_month,
|
||||
"max_drop": finding.max_drop,
|
||||
"commission_paid": self.commission_paid,
|
||||
"active_ratio": self.active_ratio,
|
||||
"zero_usage_ratio": self.zero_usage_ratio,
|
||||
"mismatch": mismatch,
|
||||
},
|
||||
subjects={"channel": self.channel_key},
|
||||
amount_involved=self.commission_paid,
|
||||
)
|
||||
return ScanOutcome(scanned_count=len(self.retention_curve), drafts=[draft])
|
||||
|
||||
Reference in New Issue
Block a user