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() flxg := asMap(getMapValue(apiData, "FLXGDEA9")) if flxg == nil { return info } level := xypString(flxg, "level") 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 flxg7e8fMap(apiData map[string]interface{}) map[string]interface{} { return asMap(getMapValue(apiData, "FLXG7E8F")) } 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 }