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
}
}