Files
tyapi-server/internal/domains/api/services/processors/dwbg/dwbg6a2c_map.go
2026-07-08 21:56:39 +08:00

675 lines
19 KiB
Go

package dwbg
import (
"fmt"
"strconv"
"strings"
"time"
"tyapi-server/internal/domains/api/dto"
"go.uber.org/zap"
)
// transformToSinanReport 将子接口数据组合为司南报告格式。
func transformToSinanReport(apiData map[string]interface{}, params dto.DWBG6A2CReq, log *zap.Logger) map[string]interface{} {
_ = log
report := emptySinanReport(params)
mergeInto(report, "baseInfo", buildSinanBaseInfo(apiData, params))
mergeInto(report, "standLiveInfo", buildSinanStandLiveInfo(apiData))
mergeInto(report, "antiFraudInfo", buildSinanAntiFraudInfo(apiData))
mergeInto(report, "securityInfo", buildSinanSecurityInfo(apiData))
mergeInto(report, "applicationStatistics", buildApplicationStatistics(apiData))
mergeInto(report, "lendingStatistics", buildLendingStatistics(apiData))
mergeInto(report, "performanceStatistics", buildPerformanceStatistics(apiData))
mergeInto(report, "overdueRecord", buildOverdueRecord(apiData))
mergeInto(report, "creditDetail", buildCreditDetail(apiData))
mergeInto(report, "rentalBehavior", buildRentalBehavior(apiData))
mergeInto(report, "riskSupervision", buildSinanRiskSupervision(apiData))
report["judicialLeaseReport"] = buildJudicialLeaseReport(apiData)
calcSinanRisk(report, apiData)
return report
}
func buildSinanBaseInfo(apiData map[string]interface{}, params dto.DWBG6A2CReq) map[string]interface{} {
base := emptyBaseInfo(params)
if age, sex := calculateAgeAndSex(params.IDCard); age > 0 {
base["age"] = age
base["sex"] = sex
}
if yysyh6f3 := asMap(getMapValue(apiData, "YYSYH6F3")); yysyh6f3 != nil {
if address := xypString(yysyh6f3, "address"); address != "" {
base["location"] = address
}
if sex := xypString(yysyh6f3, "sex"); sex != "" {
base["sex"] = sex
}
if birthday := xypString(yysyh6f3, "birthday"); birthday != "" {
if age := ageFromBirthday(birthday); age > 0 {
base["age"] = age
}
}
if channel := xypString(yysyh6f3, "channel"); channel != "" {
base["channel"] = convertChannelName(channel)
}
}
if base["location"] == "" {
if loc := getLocationFromIDCard(params.IDCard); loc != "" {
base["location"] = loc
}
}
if yysy35ta := asMap(getMapValue(apiData, "YYSY35TA")); yysy35ta != nil {
prov := xypString(yysy35ta, "prov")
city := xypString(yysy35ta, "city")
if prov != "" && city != "" {
base["phoneArea"] = prov + "-" + city
} else if prov != "" {
base["phoneArea"] = prov
}
if base["channel"] == "" {
if name := xypString(yysy35ta, "name"); name != "" {
base["channel"] = convertChannelName(name)
}
}
}
if yysye7v5 := asMap(getMapValue(apiData, "YYSYE7V5")); yysye7v5 != nil {
base["status"] = convertStatusFromOnlineStatus(yysye7v5)
if base["channel"] == "" {
if ch := xypString(yysye7v5, "channel"); ch != "" {
base["channel"] = convertChannelName(ch)
}
}
}
return base
}
func ageFromBirthday(birthday string) int {
birthday = strings.TrimSpace(birthday)
layouts := []string{"2006-01-02", "20060102", "2006/01/02"}
for _, layout := range layouts {
if t, err := time.Parse(layout, birthday); err == nil {
now := time.Now()
age := now.Year() - t.Year()
if now.YearDay() < t.YearDay() {
age--
}
if age > 0 {
return age
}
}
}
return 0
}
func buildSinanStandLiveInfo(apiData map[string]interface{}) map[string]interface{} {
info := emptyStandLiveInfo()
if yysyh6f3 := asMap(getMapValue(apiData, "YYSYH6F3")); yysyh6f3 != nil {
finalAuth, verification := mapYYSYH6F3StandLive(xypString(yysyh6f3, "result"))
info["finalAuthResult"] = finalAuth
info["verification"] = verification
}
if yysyp0t4 := asMap(getMapValue(apiData, "YYSYP0T4")); yysyp0t4 != nil {
info["inTime"] = mapYYSYP0T4InTime(xypString(yysyp0t4, "time"), apiData)
}
return info
}
func mapYYSYH6F3StandLive(result string) (finalAuthResult, verification string) {
switch result {
case "0":
return "0", "0"
case "1":
return "1", "1"
case "2":
return "1", "-1"
default:
return "1", "-1"
}
}
func mapYYSYP0T4InTime(timeRange string, apiData map[string]interface{}) string {
timeRange = strings.TrimSpace(timeRange)
if timeRange == "" {
if yysye7v5 := asMap(getMapValue(apiData, "YYSYE7V5")); yysye7v5 != nil {
status := convertStatusFromOnlineStatus(yysye7v5)
if status != 1 {
return "99"
}
}
return "-1"
}
switch timeRange {
case "[0,3)":
return "0"
case "[3,6)":
return "3"
case "[6,12)":
return "6"
case "[12,24)":
return "12"
case "[24,-1)", "[24,+)", "[24,Inf)":
return "24"
default:
return "-1"
}
}
func buildSinanAntiFraudInfo(apiData map[string]interface{}) map[string]interface{} {
info := emptyAntiFraudInfo()
flxg := asMap(getMapValue(apiData, "FLXG8B4D"))
if flxg == nil {
return info
}
data := flxg
if nested, ok := flxg["data"].(map[string]interface{}); ok {
data = nested
}
for _, field := range []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} {
if val := xypString(data, field); val != "" {
info[field] = val
}
}
return info
}
func buildSinanSecurityInfo(apiData map[string]interface{}) map[string]interface{} {
info := emptySecurityInfo()
level := flxgdea9Level(apiData)
if level == "" || level == "0" {
return info
}
for k, v := range parseSecurityInfoLevel(level) {
info[k] = v
}
return info
}
func parseSecurityInfoLevel(level string) map[string]interface{} {
out := emptySecurityInfo()
for _, raw := range strings.Split(level, ",") {
t := strings.TrimSpace(raw)
if t == "" {
continue
}
switch {
case t == "A":
out["escape"] = 1
out["icase"] = 1
case t == "A1", t == "A2", t == "A3", t == "A4", t == "A5":
out["front"] = 1
out["icase"] = 1
case t == "C3":
out["drug"] = 1
out["takeDrug"] = 1
out["icase"] = 1
case t == "D", t == "D1", t == "D2", t == "D3", t == "D4", t == "D5":
out["icase"] = 1
out["ikey"] = 1
case strings.HasPrefix(t, "B") || strings.HasPrefix(t, "C"):
out["icase"] = 1
case t == "E":
out["itrancase"] = 1
}
}
return out
}
func buildApplicationStatistics(apiData map[string]interface{}) map[string]interface{} {
stats := emptyApplicationStatistics()
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
if m == nil {
return stats
}
total := decodeXypInt(m, "xyp_cpl0001")
consumption := decodeXypInt(m, "xyp_cpl0007")
online := decodeXypInt(m, "xyp_cpl0008")
other := total - consumption - online
if other < 0 {
other = 0
}
v180 := decodeXypInt(m, "xyp_cpl0013")
v360 := decodeXypInt(m, "xyp_t01dezhbc") + decodeXypInt(m, "xyp_t01dezhba")
last12 := v180
if v360 > last12 {
last12 = v360
}
stats["totalApplicationCount"] = total
stats["consumptionInstallmentApplicationCount"] = consumption
stats["onlineLoanApplicationCount"] = online
stats["otherApplicationCount"] = other
stats["applicationCountLastMonth"] = decodeXypInt(m, "xyp_cpl0011")
stats["applicationCountLast3Months"] = decodeXypInt(m, "xyp_cpl0012")
stats["applicationCountLast6Months"] = v180
stats["applicationCountLast12Months"] = last12
stats["lastApplicationDate"] = reverseYYYYMMFromCpl0046(m)
stats["daysSinceLastApplication"] = formatXypInterval(m, "xyp_cpl0046")
if stats["daysSinceLastApplication"] == "0" {
stats["daysSinceLastApplication"] = "-"
}
return stats
}
func buildLendingStatistics(apiData map[string]interface{}) map[string]interface{} {
stats := emptyLendingStatistics()
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
if m == nil {
return stats
}
v360Count := decodeXypInt(m, "xyp_t01cczhzz")
v360Amount := formatXypInterval(m, "xyp_t01achzbz")
stats["totalLendingInstitutionCount"] = decodeXypInt(m, "xyp_cpl0001")
stats["installmentLendingInstitutionCount"] = decodeXypInt(m, "xyp_cpl0007")
stats["onlineLendingInstitutionCount"] = decodeXypInt(m, "xyp_cpl0008")
stats["lastLendingDate"] = reverseYYYYMMFromCpl0046(m)
stats["daysSinceLastLending"] = formatXypInterval(m, "xyp_cpl0046")
if stats["daysSinceLastLending"] == "0" {
stats["daysSinceLastLending"] = "-"
}
stats["lendingCountLastMonth"] = decodeXypInt(m, "xyp_t01ccezzz")
stats["lendingAmountLastMonth"] = formatXypInterval(m, "xyp_t01acezzz")
stats["lendingCountLast3Months"] = decodeXypInt(m, "xyp_t01ccfzzz")
stats["lendingAmountLast3Months"] = formatXypInterval(m, "xyp_t01acfzzz")
stats["lendingCountLast6Months"] = decodeXypInt(m, "xyp_t01ccgzzz")
stats["lendingAmountLast6Months"] = formatXypInterval(m, "xyp_t01acgzzz")
stats["lendingCountLast12Months"] = v360Count
stats["lendingAmountLast12Months"] = v360Amount
stats["lendingCountLast24Months"] = v360Count
stats["lendingAmountLast24Months"] = v360Amount
return stats
}
func buildPerformanceStatistics(apiData map[string]interface{}) map[string]interface{} {
stats := emptyPerformanceStatistics()
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
if m == nil {
return stats
}
v360Count := decodeXypInt(m, "xyp_t01cchzzc")
v360Amount := formatXypInterval(m, "xyp_t01achzzc")
v180Exception := decodeXypInt(m, "xyp_cpl0026")
v360Exception := decodeXypInt(m, "xyp_t01cczhza")
exception12 := v180Exception
if v360Exception > exception12 {
exception12 = v360Exception
}
stats["creditLoanDuration"] = decodeXypInt(m, "xyp_cpl0045")
stats["settledLoanOrderCount"] = decodeXypInt(m, "xyp_cpl0002")
stats["daysSinceLastPerformance"] = formatXypInterval(m, "xyp_cpl0068")
if stats["daysSinceLastPerformance"] == "0" {
stats["daysSinceLastPerformance"] = "-"
}
stats["normalRepaymentRatio"] = formatNormalRepaymentRatio(m)
stats["performanceCountLastMonth"] = decodeXypInt(m, "xyp_cpl0023")
stats["performanceAmountLastMonth"] = formatXypInterval(m, "xyp_cpl0039")
stats["repaymentExceptionCountLastMonth"] = decodeXypInt(m, "xyp_cpl0022")
stats["performanceCountLast3Months"] = decodeXypInt(m, "xyp_cpl0025")
stats["performanceAmountLast3Months"] = formatXypInterval(m, "xyp_cpl0041")
stats["repaymentExceptionCountLast3Months"] = decodeXypInt(m, "xyp_cpl0024")
stats["performanceCountLast6Months"] = decodeXypInt(m, "xyp_cpl0027")
stats["performanceAmountLast6Months"] = formatXypInterval(m, "xyp_cpl0043")
stats["repaymentExceptionCountLast6Months"] = v180Exception
stats["performanceCountLast12Months"] = v360Count
stats["performanceAmountLast12Months"] = v360Amount
stats["repaymentExceptionCountLast12Months"] = exception12
stats["performanceCountLast24Months"] = v360Count
stats["performanceAmountLast24Months"] = v360Amount
stats["repaymentExceptionCountLast24Months"] = exception12
return stats
}
func buildOverdueRecord(apiData map[string]interface{}) map[string]interface{} {
record := emptyOverdueRecord()
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
if m == nil {
return record
}
currentOverdue := decodeXypInt(m, "xyp_cpl0071")
if xypString(m, "xyp_cpl0044") == "1" {
if currentOverdue > 0 {
record["currentOverdueInstitution"] = fmt.Sprintf("%d(未结清)", currentOverdue)
} else {
record["currentOverdueInstitution"] = "1(未结清)"
}
} else if currentOverdue > 0 {
record["currentOverdueInstitution"] = formatXypInterval(m, "xyp_cpl0071")
}
record["currentOverdueCount"] = currentOverdue
record["totalOverdueAmount"] = formatOverdueAmountHyphen(m, "xyp_cpl0072")
record["lastOverdueDate"] = "-"
m1Signal := 0
for _, key := range []string{"xyp_cpl0029", "xyp_cpl0030", "xyp_cpl0031"} {
if xypString(m, key) == "1" {
m1Signal++
}
}
m1Plus6 := m1Signal
if m1Plus6 > 6 {
m1Plus6 = 6
}
m1Plus12 := m1Signal
if m1Plus12 > 12 {
m1Plus12 = 12
}
record["m1PlusCountLast6Months"] = m1Plus6
record["m1PlusCountLast12Months"] = m1Plus12
record["m1PlusCountLast24Months"] = m1Plus12
repay360 := decodeXypInt(m, "xyp_t01cchzzc")
if xypString(m, "xyp_cpl0044") != "1" && repay360 > 0 {
record["m0PlusCountLast6Months"] = decodeXypInt(m, "xyp_cpl0027")
record["m0PlusCountLast12Months"] = repay360
record["m0PlusCountLast24Months"] = repay360
}
record["totalAmountLast6Months"] = formatXypInterval(m, "xyp_cpl0042")
record["totalAmountLast12Months"] = formatXypInterval(m, "xyp_t01aahzza")
record["totalAmountLast24Months"] = record["totalAmountLast12Months"]
return record
}
func buildCreditDetail(apiData map[string]interface{}) map[string]interface{} {
detail := emptyCreditDetail()
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
if m == nil {
return detail
}
detail["maxOnlineLoanCredit"] = formatXypNumericString(m, "xyp_t01aahzbz")
detail["avgOnlineLoanCredit"] = formatXypNumericString(m, "xyp_t01adhzbc")
detail["maxConsumptionInstallmentCredit"] = formatXypNumericString(m, "xyp_t01aazhaz")
detail["avgConsumptionInstallmentCredit"] = formatXypNumericString(m, "xyp_t01adgzzc")
return detail
}
var rentalPeriodMap = map[string]string{
"3Days": "d3",
"7Days": "d7",
"14Days": "d14",
"1Month": "m1",
"3Months": "m3",
"6Months": "m6",
"12Months": "m12",
}
func buildRentalBehavior(apiData map[string]interface{}) map[string]interface{} {
behavior := emptyRentalBehavior()
jrzq := asMap(getMapValue(apiData, "JRZQ1D09"))
if jrzq == nil {
return behavior
}
for periodLabel, apiPeriod := range rentalPeriodMap {
behavior[fmt.Sprintf("rentalApplicationCountLast%s", periodLabel)] = formatRentalPair(
rentalNestedValue(jrzq, apiPeriod, "id", "allnum"),
rentalNestedValue(jrzq, apiPeriod, "cell", "allnum"),
)
behavior[fmt.Sprintf("rentalApplicationInstitutionsLast%s", periodLabel)] = formatRentalPair(
rentalNestedValue(jrzq, apiPeriod, "id", "orgnum"),
rentalNestedValue(jrzq, apiPeriod, "cell", "orgnum"),
)
behavior[fmt.Sprintf("rentalApplicationCountLast%sWeekend", periodLabel)] = formatRentalPair(
rentalNestedValue(jrzq, apiPeriod, "id", "weekend_allnum"),
rentalNestedValue(jrzq, apiPeriod, "cell", "weekend_allnum"),
)
behavior[fmt.Sprintf("rentalApplicationInstitutionsLast%sWeekend", periodLabel)] = formatRentalPair(
rentalNestedValue(jrzq, apiPeriod, "id", "weekend_orgnum"),
rentalNestedValue(jrzq, apiPeriod, "cell", "weekend_orgnum"),
)
behavior[fmt.Sprintf("rentalApplicationCountLast%sNight", periodLabel)] = formatRentalPair(
rentalNestedValue(jrzq, apiPeriod, "id", "night_allnum"),
rentalNestedValue(jrzq, apiPeriod, "cell", "night_allnum"),
)
behavior[fmt.Sprintf("rentalApplicationInstitutionsLast%sNight", periodLabel)] = formatRentalPair(
rentalNestedValue(jrzq, apiPeriod, "id", "night_orgnum"),
rentalNestedValue(jrzq, apiPeriod, "cell", "night_orgnum"),
)
}
return behavior
}
func rentalNestedValue(jrzq map[string]interface{}, period, dim, field string) string {
if periodData := asMap(jrzq[period]); periodData != nil {
if dimData := asMap(periodData[dim]); dimData != nil {
if val := xypString(dimData, field); val != "" {
return val
}
}
}
flatKey := fmt.Sprintf("alc_%s_%s_%s", period, dim, field)
if val := getStringValue(jrzq, flatKey); val != "" {
return val
}
return "0"
}
func formatRentalPair(idVal, cellVal string) string {
if idVal == "" {
idVal = "0"
}
if cellVal == "" {
cellVal = "0"
}
return idVal + "/" + cellVal
}
func buildSinanRiskSupervision(apiData map[string]interface{}) map[string]interface{} {
supervision := buildRiskSupervision(apiData, zap.NewNop())
idCount := parseIntValue(supervision["rentalRiskListIdCardRelationsPhones"])
phoneCount := parseIntValue(supervision["rentalRiskListPhoneRelationsIdCards"])
if idCount > 1 || phoneCount > 1 {
supervision["details"] = "存在中介关联风险"
} else {
supervision["details"] = "无"
}
return supervision
}
func asMap(val interface{}) map[string]interface{} {
if val == nil {
return nil
}
m, ok := val.(map[string]interface{})
if !ok {
return nil
}
return m
}
func jrzqMap(apiData map[string]interface{}) map[string]interface{} {
return asMap(getMapValue(apiData, "JRZQ5E9F"))
}
func jrzq1d09Map(apiData map[string]interface{}) map[string]interface{} {
return asMap(getMapValue(apiData, "JRZQ1D09"))
}
func maxTotMons(jrzq map[string]interface{}) int {
maxVal := 0
for _, period := range []string{"m6", "m12"} {
for _, dim := range []string{"id", "cell"} {
if periodData := asMap(jrzq[period]); periodData != nil {
if dimData := asMap(periodData[dim]); dimData != nil {
maxVal = maxInt(maxVal, parseIntValue(dimData["tot_mons"]))
}
}
flatKey := fmt.Sprintf("alc_%s_%s_tot_mons", period, dim)
maxVal = maxInt(maxVal, parseIntValue(jrzq[flatKey]))
}
}
return maxVal
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
func rentalHasApplication(jrzq map[string]interface{}) bool {
for _, period := range []string{"m12"} {
idVal := parseIntValue(rentalNestedValue(jrzq, period, "id", "allnum"))
cellVal := parseIntValue(rentalNestedValue(jrzq, period, "cell", "allnum"))
if idVal > 0 || cellVal > 0 {
return true
}
}
return false
}
func hitBankOverdueRecord(m map[string]interface{}) int {
if xypString(m, "xyp_cpl0044") == "1" {
return 1
}
if decodeXypInt(m, "xyp_cpl0071") > 0 {
return 1
}
if decodeXypInt(m, "xyp_cpl0072") > 0 {
return 1
}
return 0
}
func hitRelationRisk(jrzq map[string]interface{}) int {
if jrzq == nil {
return 0
}
idCount := parseIntValue(jrzq["idcard_relation_phone"])
phoneCount := parseIntValue(jrzq["phone_relation_idard"])
if idCount > 1 || phoneCount > 1 {
return 1
}
return 0
}
func hitNewRiskFeature(m map[string]interface{}) int {
raw := xypString(m, "xyp_model_score_mid")
if raw == "" || raw == "-1" {
return 0
}
score, err := strconv.ParseFloat(raw, 64)
if err != nil {
return 0
}
const threshold = 350.0 + (950.0-350.0)/3.0
if score < threshold {
return 1
}
return 0
}
func calcMultiQuery(m map[string]interface{}) int {
windows := []struct {
months int
key string
}{
{6, "xyp_cpl0013"},
{3, "xyp_cpl0012"},
{1, "xyp_cpl0011"},
}
for _, w := range windows {
if decodeXypInt(m, w.key) > 0 {
return w.months
}
}
return 0
}
func calcDeductFail(m map[string]interface{}) int {
windows := []struct {
months int
key string
}{
{6, "xyp_cpl0026"},
{3, "xyp_cpl0024"},
{1, "xyp_cpl0022"},
}
for _, w := range windows {
if decodeXypInt(m, w.key) > 0 {
return w.months
}
}
return 0
}
func antiFraudHit(data map[string]interface{}) bool {
if data == nil {
return false
}
nested := data
if d, ok := data["data"].(map[string]interface{}); ok {
nested = d
}
for _, field := range []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} {
val := xypString(nested, field)
if val != "" && val != "0" {
return true
}
}
return false
}
func securityLevelHit(level string) bool {
level = strings.TrimSpace(level)
return level != "" && level != "0"
}
func industryBlacklistHit(level string) bool {
for _, raw := range strings.Split(level, ",") {
if isLevelDClass(strings.TrimSpace(raw)) {
return true
}
}
return false
}
func isLevelDClass(token string) bool {
switch token {
case "D", "D1", "D2", "D3", "D4", "D5":
return true
default:
return false
}
}
func groupFraudHit(data map[string]interface{}) bool {
if data == nil {
return false
}
nested := data
if d, ok := data["data"].(map[string]interface{}); ok {
nested = d
}
for _, field := range []string{"deceiver", "gamblerBanker"} {
val := strings.ToUpper(xypString(nested, field))
if val == "B" || val == "C" || val == "D" {
return true
}
}
return false
}