This commit is contained in:
Mrx
2026-06-10 20:27:18 +08:00
parent 66e8bb267d
commit 0cfcd6a7ee
3 changed files with 456 additions and 127 deletions

View File

@@ -9,10 +9,44 @@ import (
const raScoreMax = 1000
const (
raWeightVerify = 0.10
raWeightJudicialBase = 0.50
raWeightCreditBase = 0.40
raHighRiskThreshold = 400
raWeightVerify = 0.10
raWeightJudicialBase = 0.50
raWeightCreditBase = 0.40
raHighRiskThreshold = 400
raForcedFScoreCap = 500 // 双高风险强制 F 档时ra_score 上限(保留加权分,不置 0
raForcedFTotalHitThreshold = 5 // 司法 + 借贷命中总条数须超过此值才触发强制 F
raFraudPointsPerHit = 23 // 欺诈每命中一项扣分(非整齐倍数)
// 司法涉诉扣分系数(件数/条数 × 系数,带非整齐上限)
raJudicialLawsuitTotalPerCase = 47
raJudicialLawsuitTotalCap = 387
raJudicialLawsuitWeiPerCase = 38
raJudicialLawsuitWeiCap = 293
raJudicialLawsuitBeigaoPerCase = 33
raJudicialLawsuitBeigaoCap = 247
raJudicialBreachPerCase = 143
raJudicialBreachCap = 437
raJudicialConsumptionPerCase = 187
raJudicialConsumptionCap = 413
// 借贷维度扣分
raCreditOverduePoints = 287
raCreditSleepPoints = 143
raCreditPerformancePoints = 97
raCreditProbeHitPoints = 103
raCreditIntentReject = 387
raCreditIntentReview = 237
raCreditIntentWeightMult = 4 // Rule_final_weight × 4
raCreditIntentWeightCap = 243
// 身份核验扣分
raVerifyMismatchPoints = 387
raVerifyPresenceDesc = 73
raVerifyPresenceStatus = 47
xypModelScoreMin = 350
xypModelScoreRiskThreshold = 650 // [350,950] 区间中位,低于此值视为高风险
xypModelScoreRiskCapPerField = 97 // 单项上限,三项合计最高 291
)
// buildDWBG9FB3RA 构建顶层 RA 总体安全评估(千分制,分值越高越安全)
@@ -23,27 +57,18 @@ const (
// - ra_judicial_score → calcRAJudicialScore
// - ra_verify_score → calcRAVerifyScore
// - ra_score → 本方法,身份 10% 固定 + 司法/借贷动态加权
// - ra_level → raLevelFromScore(ra_score),司法+借贷双高时强制 F
// - ra_level → raLevelFromScore(ra_score),司法+借贷双高时强制 F 档(分数仍加权,上限 500
func buildDWBG9FB3RA(data map[string]interface{}) map[string]interface{} {
fraudScore := calcRAFraudScore(data)
creditScore := calcRACreditScore(data)
judicialScore := calcRAJudicialScore(data)
verifyScore := calcRAVerifyScore(data)
total := 0
level := "F"
if isRAForcedFGrade(judicialScore, creditScore) {
total = 0
total := calcRAWightedScore(verifyScore, judicialScore, creditScore)
level := raLevelFromScore(total)
if isRAForcedFGrade(data, judicialScore, creditScore) {
level = "F"
} else {
wJudicial, wCredit := calcRADynamicWeights(judicialScore, creditScore)
total = int(math.Round(
float64(verifyScore)*raWeightVerify +
float64(judicialScore)*wJudicial +
float64(creditScore)*wCredit,
))
total = clampRAInt(total, 0, raScoreMax)
level = raLevelFromScore(total)
total = clampRAInt(total, 0, raForcedFScoreCap)
}
return map[string]interface{}{
@@ -56,6 +81,17 @@ func buildDWBG9FB3RA(data map[string]interface{}) map[string]interface{} {
}
}
// calcRAWightedScore 三维度动态加权汇总(身份 10% 固定 + 司法/借贷动态 90%
func calcRAWightedScore(verifyScore, judicialScore, creditScore int) int {
wJudicial, wCredit := calcRADynamicWeights(judicialScore, creditScore)
total := int(math.Round(
float64(verifyScore)*raWeightVerify +
float64(judicialScore)*wJudicial +
float64(creditScore)*wCredit,
))
return clampRAInt(total, 0, raScoreMax)
}
// calcRADynamicWeights 按司法/借贷风险场景分配剩余 90% 权重(身份固定 10%
func calcRADynamicWeights(judicialScore, creditScore int) (wJudicial, wCredit float64) {
hasJudicialRisk := judicialScore < raScoreMax
@@ -73,25 +109,99 @@ func calcRADynamicWeights(judicialScore, creditScore int) (wJudicial, wCredit fl
}
}
// isRAForcedFGrade 司法 + 借贷双重高风险时强制 F
func isRAForcedFGrade(judicialScore, creditScore int) bool {
return judicialScore <= raHighRiskThreshold && creditScore <= raHighRiskThreshold
// isRAForcedFGrade 司法 + 借贷双重高风险且命中总条数 > 5 时强制 F
func isRAForcedFGrade(data map[string]interface{}, judicialScore, creditScore int) bool {
if judicialScore > raHighRiskThreshold || creditScore > raHighRiskThreshold {
return false
}
totalHits := calcRAJudicialHitCount(data) + calcRACreditHitCount(data)
return totalHits > raForcedFTotalHitThreshold
}
// raLevelFromScore 由 ra_score 映射等级(千分制,越高越安全
// A: 800-1000 B: 600-799 C: 400-599 D: 200-399 E: 0-199
// calcRAJudicialHitCount 统计司法维度命中条数(涉诉件数 + 失信条数 + 限高条数
func calcRAJudicialHitCount(data map[string]interface{}) int {
judicial := raAsMap(data["judicial"])
if judicial == nil {
return 0
}
judicialData := raAsMap(judicial["judicial_data"])
if judicialData == nil {
return 0
}
hits := 0
if lawsuitStat := raAsMap(judicialData["lawsuitStat"]); lawsuitStat != nil {
for _, section := range lawsuitStat {
sectionMap := raAsMap(section)
if sectionMap == nil {
continue
}
count := raAsMap(sectionMap["count"])
if count == nil {
continue
}
hits += raAsInt(count["count_total"])
}
}
hits += len(raAsSlice(judicialData["breachCaseList"]))
hits += len(raAsSlice(judicialData["consumptionRestrictionList"]))
return hits
}
// calcRACreditHitCount 统计借贷维度命中条数(探针 / 意向 / 借选模型分各算 1 条)
func calcRACreditHitCount(data map[string]interface{}) int {
hits := 0
if probe := raAsMap(data["probe"]); probe != nil {
if raAsString(probe["currently_overdue"]) == "1" {
hits++
}
if raAsString(probe["acc_sleep"]) == "1" {
hits++
}
if raAsString(probe["currently_performance"]) == "0" {
hits++
}
if raAsString(probe["result_code"]) == "1" {
hits++
}
}
if intent := raAsMap(data["intent"]); intent != nil {
switch raAsString(intent["Rule_final_decision"]) {
case "Reject", "Review":
hits++
}
}
if rating := raAsMap(data["rating"]); rating != nil {
for _, key := range []string{"xyp_model_score_high", "xyp_model_score_mid", "xyp_model_score_low"} {
score := raXypModelScore(rating[key])
if score >= xypModelScoreMin && score < xypModelScoreRiskThreshold {
hits++
}
}
}
return hits
}
// raLevelFromScore 由 ra_score 映射等级(千分制,越高越安全,每档 100 分)
// A: 900-1000 B: 800-899 C: 700-799 D: 600-699 E: 500-599 F: 0-499
func raLevelFromScore(score int) string {
switch {
case score >= 800:
case score >= 900:
return "A"
case score >= 600:
case score >= 800:
return "B"
case score >= 400:
case score >= 700:
return "C"
case score >= 200:
case score >= 600:
return "D"
default:
case score >= 500:
return "E"
default:
return "F"
}
}
@@ -107,20 +217,19 @@ func calcRAFraudScore(data map[string]interface{}) int {
}
// calcRAFraudRiskPoints 统计欺诈维度风险扣分(仅内部使用,分值越高代表越不安全)
// 每命中一项扣 raFraudPointsPerHit23多项独立累加
func calcRAFraudRiskPoints(data map[string]interface{}) int {
risk := 0
hits := 0
// 来源子字段 behaviorJRZQV0MD 行为黑名单)
if behavior := raAsMap(data["behavior"]); behavior != nil {
if result := raAsMap(behavior["result"]); result != nil {
// behavior.result.black_list = "1" → 命中行为黑名单,扣 500
if raAsString(result["black_list"]) == "1" {
risk += 500
hits++
}
// behavior.result.black_tag04~12 任意为 "1" → 每个扣 80
for k, v := range result {
if strings.HasPrefix(k, "black_tag") && raAsString(v) == "1" {
risk += 80
hits++
}
}
}
@@ -129,30 +238,28 @@ func calcRAFraudRiskPoints(data map[string]interface{}) int {
// 来源子字段 complaintJRZQVT43 投诉风险筛查)
if complaint := raAsMap(data["complaint"]); complaint != nil {
if result := raAsMap(complaint["result"]); result != nil {
// complaint.result.score × 10上限扣 300
risk += clampRAInt(raAsInt(result["score"])*10, 0, 300)
if raAsInt(result["score"]) > 0 {
hits++
}
}
}
// 来源子字段 fraudJRZQV3HM 债务欺诈黑名单)
if fraud := raAsMap(data["fraud"]); fraud != nil {
// fraud.hit = 1 → 命中欺诈黑名单,扣 400
if raAsInt(fraud["hit"]) == 1 || raAsString(fraud["hit"]) == "1" {
risk += 400
hits++
}
}
// 来源子字段 specialJRZQV7MD 特殊名单)
if special := raAsMap(data["special"]); special != nil && len(special) > 0 {
switch raAsString(special["Rule_final_decision"]) {
case "Reject":
risk += 350 // 特殊名单建议拒绝
case "Review":
risk += 200 // 特殊名单建议复议
case "Reject", "Review":
hits++
}
}
return clampRAInt(risk, 0, raScoreMax)
return clampRAInt(hits*raFraudPointsPerHit, 0, raScoreMax)
}
// calcRACreditScore 借贷/逾期维度安全分来源calcRACreditRiskPoints
@@ -168,16 +275,16 @@ func calcRACreditRiskPoints(data map[string]interface{}) int {
// 来源子字段 probeJRZQ4B6C 探针C
if probe := raAsMap(data["probe"]); probe != nil {
if raAsString(probe["currently_overdue"]) == "1" {
risk += 300 // 当前逾期
risk += raCreditOverduePoints
}
if raAsString(probe["acc_sleep"]) == "1" {
risk += 150 // 睡眠账户
risk += raCreditSleepPoints
}
if raAsString(probe["currently_performance"]) == "0" {
risk += 100 // 当前未履约
risk += raCreditPerformancePoints
}
if raAsString(probe["result_code"]) == "1" {
risk += 100 // 探针命中风险
risk += raCreditProbeHitPoints
}
}
@@ -185,28 +292,47 @@ func calcRACreditRiskPoints(data map[string]interface{}) int {
if intent := raAsMap(data["intent"]); intent != nil {
switch raAsString(intent["Rule_final_decision"]) {
case "Reject":
risk += 400
risk += raCreditIntentReject
case "Review":
risk += 250
risk += raCreditIntentReview
}
// intent.Rule_final_weight × 5上限扣 250
weight := raAsInt(intent["Rule_final_weight"])
if weight > 0 {
risk += clampRAInt(weight*5, 0, 250)
risk += clampRAInt(weight*raCreditIntentWeightMult, 0, raCreditIntentWeightCap)
}
}
// 来源子字段 ratingJRZQ5E9F 借选指数)
// 来源子字段 ratingJRZQ5E9F / loanRiskTagV21 借选指数)
if rating := raAsMap(data["rating"]); rating != nil {
// rating.score 越低风险越高:扣 (500 - score),上限 300
if ratingScore := raAsInt(rating["score"]); ratingScore > 0 && ratingScore < 500 {
risk += clampRAInt(500-ratingScore, 0, 300)
}
risk += calcRARatingXypModelRiskPoints(rating)
}
return clampRAInt(risk, 0, raScoreMax)
}
// calcRARatingXypModelRiskPoints 根据借选指数三个模型分统计风险扣分
// xyp_model_score_* 范围 [350,950],越大逾期率越低;-1 为未命中,不参与计分
func calcRARatingXypModelRiskPoints(rating map[string]interface{}) int {
risk := 0
for _, key := range []string{"xyp_model_score_high", "xyp_model_score_mid", "xyp_model_score_low"} {
score := raXypModelScore(rating[key])
if score < xypModelScoreMin || score >= xypModelScoreRiskThreshold {
continue
}
risk += clampRAInt(xypModelScoreRiskThreshold-score, 0, xypModelScoreRiskCapPerField)
}
return risk
}
// raXypModelScore 解析借选指数模型分,未命中(-1 / 空)返回 -1
func raXypModelScore(v interface{}) int {
s := raAsString(v)
if s == "" || s == "-1" {
return -1
}
return raAsInt(v)
}
// calcRAJudicialScore 司法涉诉维度安全分来源calcRAJudicialRiskPoints
// 统计方式:满分 1000根据 judicial.judicial_data 涉诉统计扣分后取补集
func calcRAJudicialScore(data map[string]interface{}) int {
@@ -238,14 +364,14 @@ func calcRAJudicialRiskPoints(data map[string]interface{}) int {
if count == nil {
continue
}
risk += clampRAInt(raAsInt(count["count_total"])*80, 0, 400) // 涉诉总件数
risk += clampRAInt(raAsInt(count["count_wei_total"])*60, 0, 300) // 未结案数
risk += clampRAInt(raAsInt(count["count_beigao"])*50, 0, 250) // 被告件数
risk += clampRAInt(raAsInt(count["count_total"])*raJudicialLawsuitTotalPerCase, 0, raJudicialLawsuitTotalCap)
risk += clampRAInt(raAsInt(count["count_wei_total"])*raJudicialLawsuitWeiPerCase, 0, raJudicialLawsuitWeiCap)
risk += clampRAInt(raAsInt(count["count_beigao"])*raJudicialLawsuitBeigaoPerCase, 0, raJudicialLawsuitBeigaoCap)
}
}
risk += clampRAInt(len(raAsSlice(judicialData["breachCaseList"]))*150, 0, 450) // 失信案件条数
risk += clampRAInt(len(raAsSlice(judicialData["consumptionRestrictionList"]))*200, 0, 400) // 限高条数
risk += clampRAInt(len(raAsSlice(judicialData["breachCaseList"]))*raJudicialBreachPerCase, 0, raJudicialBreachCap)
risk += clampRAInt(len(raAsSlice(judicialData["consumptionRestrictionList"]))*raJudicialConsumptionPerCase, 0, raJudicialConsumptionCap)
return clampRAInt(risk, 0, raScoreMax)
}
@@ -263,14 +389,14 @@ func calcRAVerifyRiskPoints(data map[string]interface{}) int {
// 来源子字段 tripleYYSYK9R4 三要素验证)
if triple := raAsMap(data["triple"]); triple != nil {
if state := raAsString(triple["state"]); state != "" && state != "1" {
risk += 400 // 三要素不一致
risk += raVerifyMismatchPoints
}
}
// 来源子字段 identityIVYZN2P8 二要素认证)
if identity := raAsMap(data["identity"]); identity != nil {
if result := raAsInt(identity["result"]); result != 0 {
risk += 400 // 二要素不一致或无记录
risk += raVerifyMismatchPoints
}
}
@@ -278,10 +404,10 @@ func calcRAVerifyRiskPoints(data map[string]interface{}) int {
if presence := raAsMap(data["presence"]); presence != nil {
desc := raAsString(presence["desc"])
if strings.Contains(desc, "停机") || strings.Contains(desc, "销号") || strings.Contains(desc, "不在网") {
risk += 80 // 在网状态异常
risk += raVerifyPresenceDesc
}
if status := raAsInt(presence["status"]); status > 1 {
risk += 50 // 在网状态码异常
risk += raVerifyPresenceStatus
}
}

View File

@@ -58,15 +58,17 @@ func TestBuildDWBG9FB3RAFromSample(t *testing.T) {
func TestRALevelFromScore(t *testing.T) {
cases := map[int]string{
0: "E",
199: "E",
200: "D",
399: "D",
400: "C",
599: "C",
600: "B",
799: "B",
800: "A",
0: "F",
499: "F",
500: "E",
599: "E",
600: "D",
699: "D",
700: "C",
799: "C",
800: "B",
899: "B",
900: "A",
1000: "A",
}
for score, want := range cases {
@@ -132,6 +134,7 @@ func TestRAForcedFGrade(t *testing.T) {
},
"intent": map[string]interface{}{
"Rule_final_decision": "Reject",
"Rule_final_weight": "40",
},
"rating": nil,
"triple": map[string]interface{}{"state": "1"},
@@ -154,14 +157,162 @@ func TestRAForcedFGrade(t *testing.T) {
}
ra := buildDWBG9FB3RA(data)
if raAsInt(ra["ra_score"]) != 0 {
t.Fatalf("forced F should have ra_score=0, got %d", raAsInt(ra["ra_score"]))
// 司法 197 + 借贷 0双风险权重 70%/20%,身份 1000 → 加权分 238强制 F 档保留分数
if raAsInt(ra["ra_score"]) != 238 {
t.Fatalf("forced F should keep weighted ra_score=238, got %d", raAsInt(ra["ra_score"]))
}
if raAsString(ra["ra_level"]) != "F" {
t.Fatalf("forced F should have ra_level=F, got %s", raAsString(ra["ra_level"]))
}
}
func TestIsRAForcedFGradeRequiresMoreThan5Hits(t *testing.T) {
fiveHits := map[string]interface{}{
"judicial": map[string]interface{}{
"judicial_data": map[string]interface{}{
"breachCaseList": []interface{}{
map[string]interface{}{"caseNo": "1"},
map[string]interface{}{"caseNo": "2"},
map[string]interface{}{"caseNo": "3"},
},
"consumptionRestrictionList": []interface{}{
map[string]interface{}{"caseNo": "4"},
map[string]interface{}{"caseNo": "5"},
},
},
},
}
if calcRAJudicialHitCount(fiveHits)+calcRACreditHitCount(fiveHits) != 5 {
t.Fatal("test setup want 5 total hits")
}
if isRAForcedFGrade(fiveHits, 150, 100) {
t.Fatal("total hits = 5 should not trigger forced F")
}
sixHits := map[string]interface{}{
"probe": map[string]interface{}{"currently_overdue": "1"},
"judicial": map[string]interface{}{
"judicial_data": map[string]interface{}{
"breachCaseList": []interface{}{
map[string]interface{}{"caseNo": "1"},
map[string]interface{}{"caseNo": "2"},
map[string]interface{}{"caseNo": "3"},
},
"consumptionRestrictionList": []interface{}{
map[string]interface{}{"caseNo": "4"},
map[string]interface{}{"caseNo": "5"},
},
},
},
}
if calcRAJudicialHitCount(sixHits)+calcRACreditHitCount(sixHits) != 6 {
t.Fatal("test setup want 6 total hits")
}
if !isRAForcedFGrade(sixHits, 150, 100) {
t.Fatal("total hits = 6 with dual high should trigger forced F")
}
}
func TestRAForcedFGradeScoreCap(t *testing.T) {
// 加权分高于 500 时,强制 F 档封顶 500
verify, judicial, credit := 1000, 1000, 1000
raw := calcRAWightedScore(verify, judicial, credit)
if raw != 1000 {
t.Fatalf("baseline weighted score want 1000, got %d", raw)
}
// 模拟双高:司法/借贷维度分均 ≤ 400但加权后可能 > 500此处用边界值构造
verify, judicial, credit = 1000, 400, 400
raw = calcRAWightedScore(verify, judicial, credit)
// 1000×0.10 + 400×0.70 + 400×0.20 = 100+280+80 = 460
if raw != 460 {
t.Fatalf("dual-risk weighted score want 460, got %d", raw)
}
data := map[string]interface{}{
"behavior": nil, "complaint": nil, "fraud": nil, "special": nil,
"probe": map[string]interface{}{"currently_overdue": "1"},
"intent": map[string]interface{}{"Rule_final_decision": "Reject"},
"rating": nil,
"triple": map[string]interface{}{"state": "1"},
"identity": map[string]interface{}{"result": 0},
"presence": map[string]interface{}{"desc": "正常", "status": 1},
"judicial": map[string]interface{}{
"judicial_data": map[string]interface{}{
"breachCaseList": []interface{}{
map[string]interface{}{"caseNo": "1"},
map[string]interface{}{"caseNo": "2"},
map[string]interface{}{"caseNo": "3"},
},
"consumptionRestrictionList": []interface{}{
map[string]interface{}{"caseNo": "4"},
map[string]interface{}{"caseNo": "5"},
},
},
},
}
ra := buildDWBG9FB3RA(data)
score := raAsInt(ra["ra_score"])
if score > raForcedFScoreCap {
t.Fatalf("forced F score should be capped at %d, got %d", raForcedFScoreCap, score)
}
if raAsString(ra["ra_level"]) != "F" {
t.Fatalf("forced F should have ra_level=F, got %s", raAsString(ra["ra_level"]))
}
}
func TestCalcRARatingXypModelRiskPoints(t *testing.T) {
cases := []struct {
name string
rating map[string]interface{}
want int
}{
{
name: "all miss",
rating: map[string]interface{}{
"xyp_model_score_high": "-1",
"xyp_model_score_mid": "-1",
"xyp_model_score_low": "-1",
},
want: 0,
},
{
name: "one low score",
rating: map[string]interface{}{
"xyp_model_score_high": "600",
"xyp_model_score_mid": "-1",
"xyp_model_score_low": "-1",
},
want: 50, // 650 - 600
},
{
name: "three low scores capped",
rating: map[string]interface{}{
"xyp_model_score_high": "400",
"xyp_model_score_mid": "400",
"xyp_model_score_low": "400",
},
want: 291, // (650-400)*3 = 750, capped per field at 97
},
{
name: "safe scores no deduction",
rating: map[string]interface{}{
"xyp_model_score_high": "900",
"xyp_model_score_mid": "800",
"xyp_model_score_low": "700",
},
want: 0,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := calcRARatingXypModelRiskPoints(tc.rating); got != tc.want {
t.Fatalf("got %d want %d", got, tc.want)
}
})
}
}
func TestRAScoreWeightedSum(t *testing.T) {
verify, judicial, credit := 920, 980, 760
wJ, wC := calcRADynamicWeights(judicial, credit)

View File

@@ -17,7 +17,7 @@
- **个人身份信息权重固定 10%,不参与动态调整**
- **个人司法信息与借贷信息合计 90%,根据风险场景动态分配**
- **司法 + 借贷同时高风险时,直接判定 F 级(不参与加权**
- **司法 + 借贷同时高风险时,强制归入 F 档(仍参与加权,分数上限 500**
---
@@ -43,14 +43,18 @@
## 三、等级划分(含 F 级)
千分制下 **每档间隔 100 分**F 为 **500 分以下**
| 等级 | 分数区间 | 含义 |
| :--- | :--- | :--- |
| A | 800 1000 | 最安全 |
| B | 600 799 | 较安全 |
| C | 400 599 | 一般 |
| D | 200 399 | 较不安全 |
| E | 0 199 | 高风险 |
| **F** | — | **司法 + 借贷双重高风险(强制)** |
| A | 900 1000 | 最安全 |
| B | 800 899 | 较安全 |
| C | 700 799 | 一般 |
| D | 600 699 | 较不安全 |
| E | 500 599 | 不安全 |
| **F** | **0 499** | **最不安全** |
> 另:司法 + 借贷双重高风险时 **强制 F 档**(仍按三维度加权计算 `ra_score`,封顶 **500 分**,不置 0
---
@@ -116,22 +120,37 @@ ra_score = round(
---
## 五、F 强判规则(最高优先级)
## 五、F 强判规则(最高优先级)
同时满足以下条件时:
```text
ra_level = "F"
ra_score = 0
```
同时满足以下条件时**强制归入 F 档**
| 条件 | 判定标准 |
| :--- | :--- |
| 司法高风险 | `ra_judicial_score ≤ 400` |
| 借贷高风险 | `ra_credit_score ≤ 400` |
| 风险叠加 | 至少存在 2 项中高风险记录 |
| 命中总条数 | 司法 + 借贷 **合计命中 > 5 条** |
> **F 级不参与任何加权计算,直接覆盖结果**
**命中条数统计:**
| 维度 | 计数方式 |
| :--- | :--- |
| 司法 | `lawsuitStat.*.count.count_total` 累加 + `breachCaseList` 条数 + `consumptionRestrictionList` 条数 |
| 借贷 | 探针命中项(逾期 / 睡眠 / 未履约 / 探针)+ 借贷意向Reject / Review+ 借选模型分低(`xyp_model_score_*` 各 1 条) |
```text
// 第一步:正常三维度动态加权(与常规定价路径相同)
ra_score_raw = round(
ra_verify_score × 0.10 +
ra_judicial_score × w_judicial +
ra_credit_score × w_credit
)
// 第二步:强制 F 档映射(保留分数,不置 0
ra_level = "F"
ra_score = min(ra_score_raw, 500)
```
> 双高风险场景 **仍参与加权**,仅覆盖等级为 F并将综合分 **封顶 500**F 档最高分)。
---
@@ -144,36 +163,44 @@ ra_score = 0
| 风险扣分项 | 子字段路径 | 触发条件 | 扣分 |
| :--- | :--- | :--- | :--- |
| 三要素不一致 | `triple.state` | 有值且不为 `"1"` | 400 |
| 二要素不一致 | `identity.result` | 不为 `0` | 400 |
| 在网异常 | `presence.desc` | 含停机 / 销号 / 不在网 | 80 |
| 状态码异常 | `presence.status` | 大于 `1` | 50 |
| 三要素不一致 | `triple.state` | 有值且不为 `"1"` | 387 |
| 二要素不一致 | `identity.result` | 不为 `0` | 387 |
| 在网异常 | `presence.desc` | 含停机 / 销号 / 不在网 | 73 |
| 状态码异常 | `presence.status` | 大于 `1` | 47 |
> 身份核验为 **基础准入项**,权重低但不可忽略;不一致时单项扣分仍可达 400800 分。
> 身份核验为 **基础准入项**,权重低但不可忽略;不一致时单项扣分仍可达 387774 分。
---
### 2⃣ ra_judicial_score个人司法信息基准权重 50%,可动态上调)
- **方法链:** `calcRAJudicialScore → calcRAJudicialRiskPoints → toRASafetyScore`
- **数据来源:** `judicial.judicial_data`
- **数据来源:** `judicial.judicial_data`FLXG7E8F 个人司法数据查询)
- **子维度结构:** 涉诉统计 + 失信被执行人 + 限高被执行人(三类独立累加)
- **计分方式:** 按 **命中条数 / 件数** 统计,遍历累加后换算安全分
| 子维度 | 命中统计方式 | 代码实现 |
| :--- | :--- | :--- |
| 涉诉统计 | 各类型 `*.count` 字段累加件数 | `lawsuitStat.*.count` 遍历求和 |
| 失信被执行人 | `breachCaseList` 数组长度 = 命中条数 | `len(breachCaseList)` |
| 限高被执行人 | `consumptionRestrictionList` 数组长度 = 命中条数 | `len(consumptionRestrictionList)` |
#### 2.1 涉诉统计lawsuitStat
遍历 `lawsuitStat` 下各类型节点(如 `civil`、`criminal`、`administrative`、`preservation`、`implement`、`bankrupt` 等),读取 `*.count` 累加
遍历 `lawsuitStat` 下各类型节点(如 `civil`、`criminal`、`administrative`、`preservation`、`implement`、`bankrupt` 等),读取 `*.count` 累加
扣分采用 **非整齐系数**(避免 50/80/100 等整齐倍数),例如涉诉总件数按 **件数 × 47** 折算:
| 风险扣分项 | 子字段路径 | 触发条件 | 扣分 | 单项上限 |
| :--- | :--- | :--- | :--- | :--- |
| 涉诉总件数 | `lawsuitStat.*.count.count_total` | 各类型累加 | 80 / 件 | 400 |
| 未结案数 | `lawsuitStat.*.count.count_wei_total` | 各类型累加 | 60 / 件 | 300 |
| 被告件数 | `lawsuitStat.*.count.count_beigao` | 各类型累加 | 50 / 件 | 250 |
| 涉诉总件数 | `lawsuitStat.*.count.count_total` | 各类型累加 | **件数 × 47** | 387 |
| 未结案数 | `lawsuitStat.*.count.count_wei_total` | 各类型累加 | **件数 × 38** | 293 |
| 被告件数 | `lawsuitStat.*.count.count_beigao` | 各类型累加 | **件数 × 33** | 247 |
#### 2.2 失信被执行人breachCaseList
| 风险扣分项 | 子字段路径 | 触发条件 | 扣分 | 单项上限 |
| :--- | :--- | :--- | :--- | :--- |
| 失信执行人记录 | `breachCaseList` 长度 | 每条记录 | **150 / 条** | **450** |
| 失信执行人记录 | `breachCaseList` 长度 | 每条记录 | **条数 × 143** | **437** |
- 对应最高法 **失信被执行人名单**
- 命中即视为 **重度司法风险**,触发动态权重向司法倾斜(`HAS_JUDICIAL_RISK = true`
@@ -183,7 +210,7 @@ ra_score = 0
| 风险扣分项 | 子字段路径 | 触发条件 | 扣分 | 单项上限 |
| :--- | :--- | :--- | :--- | :--- |
| 限高执行人记录 | `consumptionRestrictionList` 长度 | 每条记录 | **200 / 条** | **400** |
| 限高执行人记录 | `consumptionRestrictionList` 长度 | 每条记录 | **条数 × 187** | **413** |
- 对应 **限制高消费被执行人名单**
- 单条扣分高于失信记录,体现对消费 / 出行能力的直接限制
@@ -204,17 +231,29 @@ ra_score = 0
### 3⃣ ra_credit_score借贷信息基准权重 40%,可动态上调)
- **方法链:** `calcRACreditScore → calcRACreditRiskPoints → toRASafetyScore`
- **数据来源:** `probe`、`intent`、`rating`
- **数据来源:** `probe`、`intent`、`rating`JRZQ5E9F / loanRiskTagV21
| 风险扣分项 | 子字段路径 | 触发条件 | 扣分 |
| :--- | :--- | :--- | :--- |
| 当前逾期 | `probe.currently_overdue` | `"1"` | 300 |
| 睡眠账户 | `probe.acc_sleep` | `"1"` | 150 |
| 未履约 | `probe.currently_performance` | `"0"` | 100 |
| 探针命中 | `probe.result_code` | `"1"` | 100 |
| 借贷意向 | `intent.Rule_final_decision` | `Reject` / `Review` | 400 / 250 |
| 规则权重 | `intent.Rule_final_weight` | 有值 | weight×5,上限 250 |
| 借选指数低 | `rating.score` | 0 < score < 500 | 500score上限 300 |
| 当前逾期 | `probe.currently_overdue` | `"1"` | 287 |
| 睡眠账户 | `probe.acc_sleep` | `"1"` | 143 |
| 未履约 | `probe.currently_performance` | `"0"` | 97 |
| 探针命中 | `probe.result_code` | `"1"` | 103 |
| 借贷意向 | `intent.Rule_final_decision` | `Reject` / `Review` | 387 / 237 |
| 规则权重 | `intent.Rule_final_weight` | 有值 | **weight × 4**,上限 243 |
| 小额网贷分低 | `rating.xyp_model_score_high` | 命中且 < 650 | 650score上限 97 |
| 小额分期分低 | `rating.xyp_model_score_mid` | 命中且 < 650 | 650score上限 97 |
| 中大额分期分低 | `rating.xyp_model_score_low` | 命中且 < 650 | 650score上限 97 |
**借选指数模型分说明JRZQ5E9F**
| 字段 | 含义 | 取值范围 |
| :--- | :--- | :--- |
| `xyp_model_score_high` | 星耀Pro 小额网贷分 V1 | [350, 950],越大逾期率越低 |
| `xyp_model_score_mid` | 星耀Pro 小额分期分 V1 | [350, 950],越大逾期率越低 |
| `xyp_model_score_low` | 星耀Pro 中大额分期分 V1 | [350, 950],越大逾期率越低 |
> 未命中为 `-1`,不参与扣分;三项独立计分,合计扣分上限 291。
---
@@ -225,12 +264,13 @@ ra_score = 0
| 风险扣分项 | 子字段路径 | 触发条件 | 扣分 |
| :--- | :--- | :--- | :--- |
| 行为黑名单 | `behavior.result.black_list` | `"1"` | 500 |
| 行为黑标签 | `behavior.result.black_tag**` | 任意 `"1"` | 每个 80 |
| 投诉风险 | `complaint.result.score` | 有值 | score×10上限 300 |
| 欺诈黑名单 | `fraud.hit` | `1` | 400 |
| 特殊名单 | `special.Rule_final_decision` | `Reject` / `Review` | 350 / 200 |
| 行为黑名单 | `behavior.result.black_list` | `"1"` | 23 |
| 行为黑标签 | `behavior.result.black_tag**` | 任意 `"1"` | 23 / 项 |
| 投诉风险 | `complaint.result.score` | 有值且 > 0 | 23 |
| 欺诈黑名单 | `fraud.hit` | `1` | 23 |
| 特殊名单 | `special.Rule_final_decision` | `Reject` / `Review` | 23 |
> 欺诈维度采用 **「每命中一项扣 23 分」** 统一计分,多项独立累加,单维上限 1000。
> 欺诈信号仍单独计算并返回 `ra_fraud_score`,供风控人工复核;**不进入 `ra_score` 三维度加权**,避免与借贷维度重复计分。
---
@@ -258,15 +298,15 @@ ra_score = 920×0.10 + 980×0.50 + 760×0.40
| 维度 | 安全分 | 说明 |
| :--- | :--- | :--- |
| 个人身份 | 900 | 核验正常 |
| 个人司法 | 350 | 失信 1 条 + 限高 1 条,扣分 350 |
| 个人司法 | 670 | 失信 1 条 + 限高 1 条,扣分 330143+187 |
| 借贷 | 680 | 轻度借贷风险 |
动态权重:身份 10% + 司法 65% + 借贷 25%
```text
ra_score = 900×0.10 + 350×0.65 + 680×0.25
= 90 + 227.5 + 170
= 488 → D/E
ra_score = 900×0.10 + 670×0.65 + 680×0.25
= 90 + 435.5 + 170
= 696 → C
```
---
@@ -289,12 +329,21 @@ ra_score = 880×0.10 + 1000×0.40 + 520×0.50
---
### ❌ 样例 4司法 + 借贷双高F
### ❌ 样例 4司法 + 借贷双高(强制 F
- `ra_judicial_score ≤ 400`失信 / 限高 / 多笔涉诉
- `ra_credit_score ≤ 400`当前逾期 + 借贷意向 Reject
- `ra_judicial_score = 197`(失信 3 条 ×143 + 限高 2 条 ×187扣分 803司法命中 **5 条**
- `ra_credit_score = 0`(逾期 287 + 未履约 97 + 探针 103 + Reject 387 + 权重 40×4扣分超 1000借贷命中 **4 条**
- 合计命中 **9 条 > 5**,满足强制 F 条件
- `ra_verify_score = 1000`
→ **直接判定F`ra_score = 0`**
动态权重:身份 10% + 司法 70% + 借贷 20%
```text
ra_score_raw = 1000×0.10 + 197×0.70 + 0×0.20
= 100 + 137.9 + 0
= 238
→ 强制 F 档ra_level = "F"ra_score = min(238, 500) = 238
```
---
@@ -317,6 +366,9 @@ ra_score = 880×0.10 + 1000×0.40 + 520×0.50
| 身份权重 | 15%20% 浮动 | **固定 10%** |
| 司法 / 借贷权重 | 各自独立浮动 | **基准 50% / 40%,动态互调** |
| 司法子维度 | 合并描述 | **涉诉统计 / 失信执行人 / 限高执行人 分项说明** |
| 限高 vs 失信 | 同表罗列 | **限高 200/条、失信 150/条,独立计分** |
| 欺诈维度 | 参与加权 | **单独输出,不参与 `ra_score`** |
| 双高风险 | 仍参与加权 | **直接 F 级** |
| 限高 vs 失信 | 同表罗列 | **限高 ×187/条、失信 ×143/条,独立计分** |
| 欺诈维度 | 参与加权 | **单独输出,不参与 `ra_score`;每命中一项扣 23 分** |
| 扣分系数 | 整齐 5/10 倍数 | **47/38/33/143/187 等非整齐系数** |
| 双高风险 | 强制 0 分 | **加权计分 + 强制 F 档,封顶 500** |
| 等级区间 | A:800+ / B:600+ … | **每档 100 分F: 0499** |
| 借选指数 | `rating.score` | **`xyp_model_score_high/mid/low`JRZQ5E9F** |