diff --git a/internal/domains/api/services/processors/dwbg/dwbg8b4d_processor.go b/internal/domains/api/services/processors/dwbg/dwbg8b4d_processor.go index 567cbda..d4b4282 100644 --- a/internal/domains/api/services/processors/dwbg/dwbg8b4d_processor.go +++ b/internal/domains/api/services/processors/dwbg/dwbg8b4d_processor.go @@ -4,13 +4,23 @@ import ( "context" "encoding/json" "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/zhicha" + "tyapi-server/internal/shared/logger" + + "go.uber.org/zap" ) // ProcessDWBG8B4DRequest DWBG8B4D API处理方法 - 谛听多维报告 +// 通过调用多个API组合成谛听报告格式 func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { var paramsDto dto.DWBG8B4DReq @@ -22,47 +32,4354 @@ func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) + log := logger.GetGlobalLogger() + log.Info("开始处理谛听多维报告请求", + zap.String("name", paramsDto.Name), + zap.String("id_card", maskIDCard(paramsDto.IDCard)), + zap.String("mobile_no", maskMobile(paramsDto.MobileNo)), + ) + + // 并发调用多个API收集数据 + apiData := collectAPIData(ctx, paramsDto, deps, log) + + // 导出apiData为JSON文件,方便调试 + if err := exportAPIDataToJSON(apiData, paramsDto.IDCard, log); err != nil { + log.Warn("导出API数据到JSON文件失败", zap.Error(err)) + } + + // 将API数据转换为谛听报告格式 + report := transformToDitingReport(apiData, paramsDto, log) + + // 将响应数据转换为JSON字节 + respBytes, err := json.Marshal(report) if err != nil { + log.Error("序列化谛听报告响应失败", zap.Error(err)) return nil, errors.Join(processors.ErrSystem, err) } - encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) - } - encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) - } - reqData := map[string]interface{}{ - "name": encryptedName, - "idCard": encryptedIDCard, - "phone": encryptedMobileNo, - "accessoryUrl": paramsDto.AuthorizationURL, + log.Info("谛听多维报告处理完成") + return respBytes, nil +} + +// collectAPIData 并发调用多个API收集数据 +func collectAPIData(ctx context.Context, params dto.DWBG8B4DReq, deps *processors.ProcessorDependencies, log *zap.Logger) map[string]interface{} { + // 开发模式:从本地JSON文件读取数据(节省API调用成本) + if isDevelopmentMode() { + if mockData := loadMockAPIDataFromFile(log); mockData != nil { + log.Info("开发模式:从本地JSON文件加载API数据", zap.Int("api_count", len(mockData))) + return mockData + } + log.Warn("开发模式:无法从本地JSON文件加载数据,将调用远程API") } - respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI103", reqData) + apiData := make(map[string]interface{}) + + // 定义需要调用的API列表 + type apiCallInfo struct { + apiCode string + params map[string]interface{} + } + + apiCalls := []apiCallInfo{ + // 运营商三要素简版V政务版 (YYSYH6D2Req: id_card, name, mobile_no) + { + apiCode: "YYSYH6D2", + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + "mobile_no": params.MobileNo, + }, + }, + // 手机在网时长B (YYSY8B1CReq: mobile_no) + { + apiCode: "YYSY8B1C", + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + }, + }, + // 公安二要素认证即时版 (IVYZ9K7FReq: id_card, name) + { + apiCode: "IVYZ9K7F", + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + }, + }, + // 涉赌涉诈风险评估 (FLXG8B4DReq: mobile_no, id_card, bank_card, authorized) + { + apiCode: "FLXG8B4D", + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "authorized": "1", + }, + }, + // 特殊名单验证B (JRZQ8A2DReq: mobile_no, id_card, name, authorized) + { + apiCode: "JRZQ8A2D", + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "id_card": params.IDCard, + "name": params.Name, + "authorized": "1", + }, + }, + // 3C租赁申请意向 (JRZQ1D09Req: mobile_no, id_card, name, authorized) + { + apiCode: "JRZQ1D09", + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "id_card": params.IDCard, + "name": params.Name, + "authorized": "1", + }, + }, + // 个人司法涉诉查询 (FLXG7E8FReq: name, id_card, mobile_no) + { + apiCode: "FLXG7E8F", + params: map[string]interface{}{ + "name": params.Name, + "id_card": params.IDCard, + "mobile_no": params.MobileNo, + }, + }, + // 借贷意向验证A (JRZQ6F2AReq: name, id_card, mobile_no) + { + apiCode: "JRZQ6F2A", + params: map[string]interface{}{ + "name": params.Name, + "id_card": params.IDCard, + "mobile_no": params.MobileNo, + }, + }, + // 借选指数评估 (JRZQ5E9FReq: mobile_no, id_card, name, authorized) + { + apiCode: "JRZQ5E9F", + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + "id_card": params.IDCard, + "name": params.Name, + "authorized": "1", + }, + }, + // 公安不良人员名单(加强版) (FLXGDEA9Req: id_card, name, authorized) + { + apiCode: "FLXGDEA9", + params: map[string]interface{}{ + "id_card": params.IDCard, + "name": params.Name, + "authorized": "1", + }, + }, + // 手机号归属地核验 (YYSY9E4AReq: mobile_no) + { + apiCode: "YYSY9E4A", + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + }, + }, + // 手机在网状态V即时版 (YYSYE7V5Req: mobile_no) + { + apiCode: "YYSYE7V5", + params: map[string]interface{}{ + "mobile_no": params.MobileNo, + }, + }, + } + + // 并发调用所有API + type processorResult struct { + apiCode string + data interface{} + err error + } + + results := make(chan processorResult, len(apiCalls)) + var wg sync.WaitGroup + + for _, apiCall := range apiCalls { + wg.Add(1) + go func(ac apiCallInfo) { + defer wg.Done() + defer func() { + if r := recover(); r != nil { + log.Error("调用API时发生panic", + zap.String("api_code", ac.apiCode), + zap.Any("panic", r), + ) + results <- processorResult{ac.apiCode, nil, fmt.Errorf("处理器panic: %v", r)} + } + }() + + paramsBytes, err := json.Marshal(ac.params) if err != nil { - if errors.Is(err, zhicha.ErrDatasource) { - return nil, errors.Join(processors.ErrDatasource, err) + log.Warn("序列化API参数失败", + zap.String("api_code", ac.apiCode), + zap.Error(err), + ) + results <- processorResult{ac.apiCode, nil, err} + return + } + + data, err := callProcessor(ctx, ac.apiCode, paramsBytes, deps) + results <- processorResult{ac.apiCode, data, err} + }(apiCall) + } + + // 等待所有goroutine完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集结果 + successCount := 0 + for result := range results { + if result.err != nil { + log.Warn("调用API失败,将使用默认值", + zap.String("api_code", result.apiCode), + zap.Error(result.err), + ) + apiData[result.apiCode] = nil } else { - return nil, errors.Join(processors.ErrSystem, err) + apiData[result.apiCode] = result.data + successCount++ } } - // 过滤响应数据,删除指定字段 - if respMap, ok := respData.(map[string]interface{}); ok { - delete(respMap, "reportUrl") - delete(respMap, "multCourtInfo") - delete(respMap, "judiciaRiskInfos") - } + log.Info("API调用完成", + zap.Int("total", len(apiCalls)), + zap.Int("success", successCount), + zap.Int("failed", len(apiCalls)-successCount), + ) - // 将响应数据转换为JSON字节 - respBytes, err := json.Marshal(respData) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) - } - - return respBytes, nil + return apiData +} + +// callProcessor 调用指定的处理器 +func callProcessor(ctx context.Context, apiCode string, params []byte, deps *processors.ProcessorDependencies) (interface{}, error) { + // 通过CombService获取处理器 + if combSvc, ok := deps.CombService.(interface { + GetProcessor(apiCode string) (processors.ProcessorFunc, bool) + }); ok { + processor, exists := combSvc.GetProcessor(apiCode) + if !exists { + return nil, fmt.Errorf("未找到处理器: %s", apiCode) + } + respBytes, err := processor(ctx, params, deps) + if err != nil { + return nil, err + } + var data interface{} + if err := json.Unmarshal(respBytes, &data); err != nil { + return nil, fmt.Errorf("解析响应失败: %w", err) + } + return data, nil + } + + return nil, fmt.Errorf("无法获取处理器: %s,CombService不支持GetProcessor方法", apiCode) +} + +// exportAPIDataToJSON 将API数据导出为JSON文件,方便调试 +func exportAPIDataToJSON(apiData map[string]interface{}, idCard string, log *zap.Logger) error { + // 创建导出目录(如果不存在) + exportDir := "api_data_export" + if err := os.MkdirAll(exportDir, 0755); err != nil { + return fmt.Errorf("创建导出目录失败: %w", err) + } + + // 使用身份证号后4位和时间戳生成文件名 + idCardSuffix := "" + if len(idCard) >= 4 { + idCardSuffix = idCard[len(idCard)-4:] + } + timestamp := time.Now().Format("20060102_150405") + filename := fmt.Sprintf("api_data_%s_%s.json", idCardSuffix, timestamp) + filepath := filepath.Join(exportDir, filename) + + // 将apiData序列化为格式化的JSON + jsonData, err := json.MarshalIndent(apiData, "", " ") + if err != nil { + return fmt.Errorf("序列化API数据失败: %w", err) + } + + // 写入文件 + if err := os.WriteFile(filepath, jsonData, 0644); err != nil { + return fmt.Errorf("写入JSON文件失败: %w", err) + } + + log.Info("API数据已导出到JSON文件", + zap.String("filepath", filepath), + zap.String("filename", filename), + ) + + return nil +} + +// transformToDitingReport 将API数据转换为谛听报告格式 +func transformToDitingReport(apiData map[string]interface{}, params dto.DWBG8B4DReq, log *zap.Logger) map[string]interface{} { + report := make(map[string]interface{}) + + // 1. baseInfo (基本信息) + report["baseInfo"] = buildBaseInfo(apiData, params, log) + + // 2. standLiveInfo (身份信息核验) + report["standLiveInfo"] = buildStandLiveInfo(apiData, log) + + // 3. checkSuggest (审核建议) + report["checkSuggest"] = buildCheckSuggest(apiData, log) + + // 4. fraudScore (反欺诈评分) + report["fraudScore"] = buildFraudScore(apiData, log) + + // 5. creditScore (信用评分) + report["creditScore"] = buildCreditScore(apiData, log) + + // 6. verifyRule (验证规则) + report["verifyRule"] = buildVerifyRule(apiData, log) + + // 7. fraudRule (反欺诈规则) + report["fraudRule"] = buildFraudRule(apiData, log) + + // 8. riskWarning (规则风险提示) + report["riskWarning"] = buildRiskWarning(apiData, log) + + // 9. elementVerificationDetail (要素、运营商、公安重点人员核查产品) + report["elementVerificationDetail"] = buildElementVerificationDetail(apiData, log) + + // 10. riskSupervision (关联风险监督) + report["riskSupervision"] = buildRiskSupervision(apiData, log) + + // 11. overdueRiskProduct (逾期风险产品) + report["overdueRiskProduct"] = buildOverdueRiskProduct(apiData, log) + + // 12. multCourtInfo (司法风险核验产品) + report["multCourtInfo"] = buildMultCourtInfo(apiData, log) + + // 13. loanEvaluationVerificationDetail (借贷评估产品) + report["loanEvaluationVerificationDetail"] = buildLoanEvaluationVerificationDetail(apiData, log) + + // 14. leasingRiskAssessment (租赁风险评估产品) + report["leasingRiskAssessment"] = buildLeasingRiskAssessment(apiData, log) + + return report +} + +// buildBaseInfo 构建基本信息 +func buildBaseInfo(apiData map[string]interface{}, params dto.DWBG8B4DReq, log *zap.Logger) map[string]interface{} { + baseInfo := make(map[string]interface{}) + + // 首先从传参中获取并脱敏姓名、身份证、手机号(这些字段始终从传参获取) + baseInfo["name"] = maskName(params.Name) + baseInfo["idCard"] = maskIDCard(params.IDCard) + baseInfo["phone"] = maskMobile(params.MobileNo) + + // 从运营商三要素获取数据 + yysyData := getMapValue(apiData, "YYSYH6D2") + if yysyData != nil { + if yysyMap, ok := yysyData.(map[string]interface{}); ok { + + // 从身份证号计算年龄和性别 + if age, sex := calculateAgeAndSex(params.IDCard); age > 0 { + baseInfo["age"] = age + baseInfo["sex"] = sex + } + + // 从运营商三要素获取户籍所在地(address字段) + if address, ok := yysyMap["address"].(string); ok && address != "" { + baseInfo["location"] = address + } else { + // 如果address不存在,尝试从身份证号获取 + if location := getLocationFromIDCard(params.IDCard); location != "" { + baseInfo["location"] = location + } + } + + // 从运营商数据获取 + if channel, ok := yysyMap["channel"].(string); ok { + baseInfo["channel"] = convertChannel(channel) + } + } + } + + // 从手机号归属地核验API获取归属地 + yysy9e4aData := getMapValue(apiData, "YYSY9E4A") + if yysy9e4aData != nil { + if yysy9e4aMap, ok := yysy9e4aData.(map[string]interface{}); ok { + var provinceName, cityName string + if province, ok := yysy9e4aMap["provinceName"].(string); ok { + provinceName = province + } + if city, ok := yysy9e4aMap["cityName"].(string); ok { + cityName = city + } + if provinceName != "" && cityName != "" { + baseInfo["phoneArea"] = provinceName + "-" + cityName + } else if provinceName != "" { + baseInfo["phoneArea"] = provinceName + } + } + } + + // 从手机在网状态V即时版获取号码状态 + yysy4b21Data := getMapValue(apiData, "YYSYE7V5") + if yysy4b21Data != nil { + if yysy4b21Map, ok := yysy4b21Data.(map[string]interface{}); ok { + baseInfo["status"] = convertStatusFromOnlineStatus(yysy4b21Map) + } + } else { + // 如果手机在网状态API失败,尝试从运营商三要素的result转换 + if yysyData != nil { + if yysyMap, ok := yysyData.(map[string]interface{}); ok { + if result, ok := yysyMap["result"].(string); ok { + baseInfo["status"] = convertStatus(result) + } else { + baseInfo["status"] = -1 + } + } + } + } + + // 如果数据缺失,使用默认值(name、idCard、phone已经在开头设置了,这里只需要设置其他字段) + if _, exists := baseInfo["age"]; !exists { + baseInfo["age"] = 0 + } + if _, exists := baseInfo["sex"]; !exists { + baseInfo["sex"] = "" + } + if _, exists := baseInfo["location"]; !exists { + baseInfo["location"] = "" + } + if _, exists := baseInfo["phoneArea"]; !exists { + baseInfo["phoneArea"] = "" + } + if _, exists := baseInfo["channel"]; !exists { + baseInfo["channel"] = "" + } + if _, exists := baseInfo["status"]; !exists { + baseInfo["status"] = -1 + } + + return baseInfo +} + +// buildStandLiveInfo 构建身份信息核验 +func buildStandLiveInfo(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + standLiveInfo := make(map[string]interface{}) + + // 从公安二要素获取实名核验结果(必须从数据源获取,无默认值) + ivyzData := getMapValue(apiData, "IVYZ9K7F") + if ivyzData != nil { + if ivyzMap, ok := ivyzData.(map[string]interface{}); ok { + // 尝试从data字段获取(兼容旧格式) + var status string + if data, ok := ivyzMap["data"].(map[string]interface{}); ok { + if statusVal, ok := data["status"].(string); ok { + status = statusVal + } + } else { + // 如果没有data字段,直接使用desc或result字段(新格式) + if desc, ok := ivyzMap["desc"].(string); ok && desc != "" { + status = desc + } else if result, ok := ivyzMap["result"].(float64); ok { + // result为0表示一致,非0表示不一致 + if result == 0 { + status = "一致" + } else { + status = "不一致" + } + } else if result, ok := ivyzMap["result"].(string); ok && result != "" { + // result也可能是字符串类型 + if result == "0" { + status = "一致" + } else { + status = "不一致" + } + } + } + + if status != "" { + // "一致" -> "0", "不一致" -> "1" + if status == "一致" { + standLiveInfo["finalAuthResult"] = "0" + } else { + standLiveInfo["finalAuthResult"] = "1" + } + } + } + } + + // 从运营商三要素获取三要素核验结果 + yysyData := getMapValue(apiData, "YYSYH6D2") + if yysyData != nil { + if yysyMap, ok := yysyData.(map[string]interface{}); ok { + if result, ok := yysyMap["result"].(string); ok { + // "0" -> "0" (一致), "1" -> "1" (不一致) + if result == "0" { + standLiveInfo["verification"] = "0" + } else { + standLiveInfo["verification"] = "1" + } + } + } + } + + // 从手机在网时长获取在网时长 + yysy8b1cData := getMapValue(apiData, "YYSY8B1C") + if yysy8b1cData != nil { + if yysy8b1cMap, ok := yysy8b1cData.(map[string]interface{}); ok { + if inTime, ok := yysy8b1cMap["inTime"].(string); ok { + standLiveInfo["inTime"] = inTime + } else if inTime, ok := yysy8b1cMap["inTime"].(float64); ok { + standLiveInfo["inTime"] = strconv.FormatFloat(inTime, 'f', -1, 64) + } + } + } + + // 默认值(finalAuthResult没有默认值,必须从数据源获取) + if _, exists := standLiveInfo["verification"]; !exists { + standLiveInfo["verification"] = "-1" + } + if _, exists := standLiveInfo["inTime"]; !exists { + standLiveInfo["inTime"] = "-1" + } + + return standLiveInfo +} + +// buildCheckSuggest 构建审核建议 +func buildCheckSuggest(apiData map[string]interface{}, log *zap.Logger) string { + // 根据风险评分和规则计算审核建议 + fraudScore := getFraudScore(apiData) + creditScore := getCreditScore(apiData) + riskCounts := getTotalRiskCounts(apiData) + + // 高风险情况 + if fraudScore >= 80 || creditScore < 500 || riskCounts >= 5 { + return "建议拒绝" + } + + // 中风险情况 + if fraudScore >= 60 || creditScore < 800 || riskCounts >= 3 { + return "建议复议" + } + + // 低风险情况 + return "建议通过" +} + +// buildFraudScore 构建反欺诈评分 +func buildFraudScore(apiData map[string]interface{}, log *zap.Logger) int { + score := getFraudScore(apiData) + if score == -1 { + return -1 + } + return score +} + +// buildCreditScore 构建信用评分 +func buildCreditScore(apiData map[string]interface{}, log *zap.Logger) int { + score := getCreditScore(apiData) + if score == -1 { + return -1 + } + return score +} + +// buildVerifyRule 构建验证规则(根据司法相关风险判断) +func buildVerifyRule(apiData map[string]interface{}, log *zap.Logger) string { + // 先构建riskWarning以获取司法风险字段 + riskWarning := buildRiskWarning(apiData, log) + + // 检查高风险情况(优先级最高) + // 1. 命中刑事案件 + if hitCriminalRisk, ok := riskWarning["hitCriminalRisk"].(int); ok && hitCriminalRisk == 1 { + return "高风险" + } + + // 2. 命中执行案件/失信案件/限高案件 + if hitExecutionCase, ok := riskWarning["hitExecutionCase"].(int); ok && hitExecutionCase == 1 { + return "高风险" + } + + // 3. 命中破产清算 + if hitBankruptcyAndLiquidation, ok := riskWarning["hitBankruptcyAndLiquidation"].(int); ok && hitBankruptcyAndLiquidation == 1 { + return "高风险" + } + + // 检查中风险情况(且不是高风险) + // 1. 命中民事案件 + if hitCivilCase, ok := riskWarning["hitCivilCase"].(int); ok && hitCivilCase == 1 { + return "中风险" + } + + // 2. 命中行政案件 + if hitAdministrativeCase, ok := riskWarning["hitAdministrativeCase"].(int); ok && hitAdministrativeCase == 1 { + return "中风险" + } + + // 3. 命中保全审查 + if hitPreservationReview, ok := riskWarning["hitPreservationReview"].(int); ok && hitPreservationReview == 1 { + return "中风险" + } + + // 其他情况为低风险 + return "低风险" +} + +// buildFraudRule 构建反欺诈规则(综合考虑fraudScore、特殊名单和借选指数风险) +func buildFraudRule(apiData map[string]interface{}, log *zap.Logger) string { + fraudScore := getFraudScore(apiData) + + // 如果fraudScore无效,检查特殊名单和借选指数 + if fraudScore == -1 { + // 检查特殊名单 + jrzq8a2dData := getMapValue(apiData, "JRZQ8A2D") + if jrzq8a2dData != nil { + if jrzq8a2dMap, ok := jrzq8a2dData.(map[string]interface{}); ok { + if decision, ok := jrzq8a2dMap["Rule_final_decision"].(string); ok && decision == "Reject" { + return "高风险" + } + } + } + // 检查借选指数 + jrzq5e9fData := getMapValue(apiData, "JRZQ5E9F") + if jrzq5e9fData != nil { + if jrzq5e9fMap, ok := jrzq5e9fData.(map[string]interface{}); ok { + if xypCpl0081, ok := jrzq5e9fMap["xyp_cpl0081"].(string); ok && xypCpl0081 != "" { + if score, err := strconv.ParseFloat(xypCpl0081, 64); err == nil && score < 500 { + return "高风险" + } + } + } + } + return "低风险" + } + + // 根据fraudScore判断风险等级 + if fraudScore >= 80 { + return "高风险" + } else if fraudScore >= 60 { + return "中风险" + } + return "低风险" +} + +// buildRiskWarning 构建规则风险提示 +func buildRiskWarning(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + riskWarning := make(map[string]interface{}) + + // 初始化所有风险字段为0 + riskFields := []string{ + "sfhyfxRiskHighCounts", "noPhoneDuration", "jdpgRiskMiddleCounts", + "idCardRiskMiddleCounts", "yqfxRiskMiddleCounts", "isDisrupSocial", + "sfhyfxRiskCounts", "hitCivilCase", "isAntiFraudInfo", + "zlfxpgRiskHighCounts", "gazdyrhyRiskMiddleCounts", "hitAdministrativeCase", + "frequentBankApplications", "yqfxRiskHighCounts", "isTrafficRelated", + "phoneThreeElementMismatch", "highDebtPressure", "hitCompensationCase", + "idCardRiskHighCounts", "yqfxRiskCounts", "idCardTwoElementMismatch", + "isKeyPerson", "zlfxpgRiskMiddleCounts", "shortPhoneDurationSlight", + "hitBankruptcyAndLiquidation", "highFraudGangLevel", "isEconomyFront", + "jdpgRiskHighCounts", "hasCriminalRecord", "veryFrequentRentalApplications", + "frequentNonBankApplications", "idCardRiskCounts", "frequentApplicationRecent", + "hitDirectlyUnderCase", "hitPreservationReview", "hitExecutionCase", + "gazdyrhyRiskCounts", "shortPhoneRiskMiddleCounts", "hitCurrentOverdue", + "jdpgRiskCounts", "idCardPhoneProvinceMismatch", "hitHighRiskBankLastTwoYears", + "shortPhoneRiskCounts", "sfhyfxRiskMiddleCounts", "gazdyrhyRiskHighCounts", + "frequentRentalApplications", "shortPhoneDuration", "zlfxpgRiskCounts", + "moreFrequentBankApplications", "hitHighRiskNonBankLastTwoYears", + "shortPhoneRiskHighCounts", "moreFrequentNonBankApplications", "hitCriminalRisk", + } + + for _, field := range riskFields { + riskWarning[field] = 0 + } + + // ========== 要素核查风险 ========== + // 身份证二要素信息对比结果不一致(高风险) + ivyzData := getMapValue(apiData, "IVYZ9K7F") + if ivyzData != nil { + if ivyzMap, ok := ivyzData.(map[string]interface{}); ok { + if data, ok := ivyzMap["data"].(map[string]interface{}); ok { + if status, ok := data["status"].(string); ok { + if status != "一致" { + riskWarning["idCardTwoElementMismatch"] = 1 + riskWarning["idCardRiskHighCounts"] = 1 + riskWarning["idCardRiskCounts"] = 1 + } + } + } + } + } + + // 手机三要素简版不一致(高风险) + yysyData := getMapValue(apiData, "YYSYH6D2") + if yysyData != nil { + if yysyMap, ok := yysyData.(map[string]interface{}); ok { + if result, ok := yysyMap["result"].(string); ok { + if result != "0" { + riskWarning["phoneThreeElementMismatch"] = 1 + } + } + } + } + + // ========== 运营商核验风险 ========== + // 手机在网时长极短(高风险) + yysy8b1cData := getMapValue(apiData, "YYSY8B1C") + if yysy8b1cData != nil { + if yysy8b1cMap, ok := yysy8b1cData.(map[string]interface{}); ok { + var inTimeStr string + if inTime, ok := yysy8b1cMap["inTime"].(string); ok { + inTimeStr = inTime + } else if inTime, ok := yysy8b1cMap["inTime"].(float64); ok { + inTimeStr = strconv.FormatFloat(inTime, 'f', -1, 64) + } + + if inTimeStr == "0" { + riskWarning["shortPhoneDuration"] = 1 + riskWarning["shortPhoneRiskCounts"] = 1 + riskWarning["shortPhoneRiskHighCounts"] = 1 + } else if inTimeStr == "3" { + riskWarning["shortPhoneDurationSlight"] = 1 + riskWarning["shortPhoneRiskCounts"] = 1 + riskWarning["shortPhoneRiskMiddleCounts"] = 1 + } + } + } + + // 手机在网状态为风险号(高风险) + yysy4b21Data := getMapValue(apiData, "YYSYE7V5") + if yysy4b21Data != nil { + if yysy4b21Map, ok := yysy4b21Data.(map[string]interface{}); ok { + var statusInt int + if statusVal, ok := yysy4b21Map["status"]; ok { + switch v := statusVal.(type) { + case int: + statusInt = v + case float64: + statusInt = int(v) + case string: + if parsed, err := strconv.Atoi(v); err == nil { + statusInt = parsed + } + } + } + + // status=1表示不在网,需要判断是否为风险号 + if statusInt == 1 { + if desc, ok := yysy4b21Map["desc"].(string); ok { + if strings.Contains(desc, "风险") { + riskWarning["noPhoneDuration"] = 1 + } + } + } + } + } + + // 身份证号手机号归属省不一致(中风险) + yysy9e4aData := getMapValue(apiData, "YYSY9E4A") + if yysy9e4aData != nil && yysyData != nil { + if yysy9e4aMap, ok := yysy9e4aData.(map[string]interface{}); ok { + if yysyMap, ok := yysyData.(map[string]interface{}); ok { + // 从身份证号获取省份(前2位) + var idCardProvince string + if address, ok := yysyMap["address"].(string); ok && address != "" { + // 从地址中提取省份(简化处理,取前2-3个字符) + if len(address) >= 2 { + idCardProvince = address[:2] + // 处理"省"字 + idCardProvince = strings.TrimSuffix(idCardProvince, "省") + } + } + + // 从手机号归属地获取省份 + var phoneProvince string + if provinceName, ok := yysy9e4aMap["provinceName"].(string); ok { + phoneProvince = provinceName + // 处理"省"字 + phoneProvince = strings.TrimSuffix(phoneProvince, "省") + } + + // 判断省份是否一致 + if idCardProvince != "" && phoneProvince != "" && idCardProvince != phoneProvince { + riskWarning["idCardPhoneProvinceMismatch"] = 1 + } + } + } + } + + // ========== 公安重点人员核验风险 ========== + flxgdea9Data := getMapValue(apiData, "FLXGDEA9") + if flxgdea9Data != nil { + if flxgdea9Map, ok := flxgdea9Data.(map[string]interface{}); ok { + level, ok := flxgdea9Map["level"].(string) + if !ok { + // 尝试从其他可能的字段获取 + if levelVal, exists := flxgdea9Map["level"]; exists { + if levelStr, ok := levelVal.(string); ok { + level = levelStr + } else if levelFloat, ok := levelVal.(float64); ok { + level = strconv.FormatFloat(levelFloat, 'f', -1, 64) + } + } + } + + if level != "" && level != "0" { + // 解析level字段,判断风险类型 + levelParts := strings.Split(level, ",") + for _, part := range levelParts { + part = strings.TrimSpace(part) + if part == "" || part == "0" { + continue + } + + // 根据level代码判断风险类型 + if strings.HasPrefix(part, "A") { + riskWarning["hasCriminalRecord"] = 1 + } + if strings.HasPrefix(part, "B") { + riskWarning["isEconomyFront"] = 1 + } + if strings.HasPrefix(part, "C") { + riskWarning["isDisrupSocial"] = 1 + } + if strings.HasPrefix(part, "D") { + riskWarning["isKeyPerson"] = 1 + } + if strings.HasPrefix(part, "E") { + riskWarning["isTrafficRelated"] = 1 + } + } + + // 设置level字段 + riskWarning["level"] = level + } + } + } + + // 涉赌涉诈(中风险) + flxgData := getMapValue(apiData, "FLXG8B4D") + if flxgData != nil { + if flxgMap, ok := flxgData.(map[string]interface{}); ok { + if data, ok := flxgMap["data"].(map[string]interface{}); ok { + hasRisk := false + riskFields := []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} + for _, field := range riskFields { + if val, ok := data[field].(string); ok { + if val != "" && val != "0" { + hasRisk = true + break + } + } + } + if hasRisk { + riskWarning["isAntiFraudInfo"] = 1 + } + } + } + } + + // ========== 逾期风险 ========== + jrzq8a2dData := getMapValue(apiData, "JRZQ8A2D") + if jrzq8a2dData != nil { + if jrzq8a2dMap, ok := jrzq8a2dData.(map[string]interface{}); ok { + // 检查是否有高风险记录(近两年) + // 注意:字段值为"0"表示命中,字段值为"空"表示未命中 + // 数据源中字段名可能是简化的(如nbank_bad而不是id_nbank_bad) + // 优先检查完整字段名,如果没有则检查简化字段名 + if idData, ok := jrzq8a2dMap["id"].(map[string]interface{}); ok { + // 检查银行高风险(近两年)- 使用id_bank_lost或bank_lost + if checkRiskFieldValue(idData, "id_bank_lost") || checkRiskFieldValue(idData, "bank_lost") { + riskWarning["hitHighRiskBankLastTwoYears"] = 1 + } + // 检查非银高风险(近两年)- 使用id_nbank_lost或nbank_lost + if checkRiskFieldValue(idData, "id_nbank_lost") || checkRiskFieldValue(idData, "nbank_lost") { + riskWarning["hitHighRiskNonBankLastTwoYears"] = 1 + } + // 检查银行一般风险(当前逾期)- 使用id_bank_overdue或bank_overdue + if checkRiskFieldValue(idData, "id_bank_overdue") || checkRiskFieldValue(idData, "bank_overdue") { + riskWarning["hitCurrentOverdue"] = 1 + } + // 检查非银一般风险(当前逾期)- 使用id_nbank_overdue或nbank_overdue + if checkRiskFieldValue(idData, "id_nbank_overdue") || checkRiskFieldValue(idData, "nbank_overdue") { + riskWarning["hitCurrentOverdue"] = 1 + } + } + } + } + + // ========== 司法风险核验 ========== + flxg5a3bData := getMapValue(apiData, "FLXG7E8F") + if flxg5a3bData != nil { + if flxg5a3bMap, ok := flxg5a3bData.(map[string]interface{}); ok { + // 检查judicial_data字段 + if judicialData, ok := flxg5a3bMap["judicial_data"].(map[string]interface{}); ok { + // 检查lawsuitStat + if lawsuitStat, ok := judicialData["lawsuitStat"].(map[string]interface{}); ok { + // 检查民事案件(必须检查cases数组,只有cases数组存在且不为空才算命中) + if civil, ok := lawsuitStat["civil"].(map[string]interface{}); ok { + // 只检查cases数组,如果cases数组存在且不为空,才标记为命中 + if cases, ok := civil["cases"].([]interface{}); ok && len(cases) > 0 { + riskWarning["hitCivilCase"] = 1 + } + // 注意:如果只有count而没有cases数组,不标记为命中 + // 因为count可能只是统计信息,不代表实际有案件详情 + } + + // 检查刑事案件(必须检查cases数组,只有cases数组存在且不为空才算命中) + if criminal, ok := lawsuitStat["criminal"].(map[string]interface{}); ok { + // 只检查cases数组,如果cases数组存在且不为空,才标记为命中 + if cases, ok := criminal["cases"].([]interface{}); ok && len(cases) > 0 { + riskWarning["hitCriminalRisk"] = 1 + } + // 注意:如果只有count而没有cases数组,不标记为命中 + // 因为count可能只是统计信息,不代表实际有案件详情 + } + + // 检查执行案件(必须检查cases数组,只有cases数组存在且不为空才算命中) + if implement, ok := lawsuitStat["implement"].(map[string]interface{}); ok { + // 只检查cases数组,如果cases数组存在且不为空,才标记为命中 + if cases, ok := implement["cases"].([]interface{}); ok && len(cases) > 0 { + riskWarning["hitExecutionCase"] = 1 + } + // 注意:如果只有count而没有cases数组,不标记为命中 + // 因为count可能只是统计信息,不代表实际有案件详情 + } + + // 检查失信案件 + if breachCaseList, ok := judicialData["breachCaseList"].([]interface{}); ok && len(breachCaseList) > 0 { + riskWarning["hitExecutionCase"] = 1 + } + + // 检查限高案件 + if consumptionRestrictionList, ok := judicialData["consumptionRestrictionList"].([]interface{}); ok && len(consumptionRestrictionList) > 0 { + riskWarning["hitExecutionCase"] = 1 + } + + // 检查行政案件(必须检查cases数组,只有cases数组存在且不为空才算命中) + if administrative, ok := lawsuitStat["administrative"].(map[string]interface{}); ok { + // 只检查cases数组,如果cases数组存在且不为空,才标记为命中 + if cases, ok := administrative["cases"].([]interface{}); ok && len(cases) > 0 { + riskWarning["hitAdministrativeCase"] = 1 + } + // 注意:如果只有count而没有cases数组,不标记为命中 + // 因为count可能只是统计信息,不代表实际有案件详情 + } + + // 检查保全审查(必须检查cases数组,只有cases数组存在且不为空才算命中) + if preservation, ok := lawsuitStat["preservation"].(map[string]interface{}); ok { + // 只检查cases数组,如果cases数组存在且不为空,才标记为命中 + if cases, ok := preservation["cases"].([]interface{}); ok && len(cases) > 0 { + riskWarning["hitPreservationReview"] = 1 + } + // 注意:如果只有count而没有cases数组,不标记为命中 + // 因为count可能只是统计信息,不代表实际有案件详情 + } + + // 检查破产清算(必须检查cases数组,只有cases数组存在且不为空才算命中) + if bankrupt, ok := lawsuitStat["bankrupt"].(map[string]interface{}); ok { + // 只检查cases数组,如果cases数组存在且不为空,才标记为命中 + if cases, ok := bankrupt["cases"].([]interface{}); ok && len(cases) > 0 { + riskWarning["hitBankruptcyAndLiquidation"] = 1 + } + // 注意:如果只有count而没有cases数组,不标记为命中 + // 因为count可能只是统计信息,不代表实际有案件详情 + } + } + + // 检查直辖案件和赔偿案件(从案件类型中判断) + // 直辖案件:通常指直辖市法院审理的案件,可以通过法院名称判断 + // 赔偿案件:通常案由中包含"赔偿"字样 + if lawsuitStat, ok := judicialData["lawsuitStat"].(map[string]interface{}); ok { + // 检查所有案件类型 + caseTypes := []string{"civil", "criminal", "implement", "administrative", "preservation", "bankrupt"} + for _, caseType := range caseTypes { + if caseData, ok := lawsuitStat[caseType].(map[string]interface{}); ok { + if cases, ok := caseData["cases"].([]interface{}); ok { + for _, caseItem := range cases { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + // 检查是否为直辖案件(法院名称包含"直辖市"或特定直辖市名称) + if court, ok := caseMap["n_jbfy"].(string); ok { + // 检查是否为直辖市法院(北京、上海、天津、重庆) + if strings.Contains(court, "北京市") || strings.Contains(court, "上海市") || + strings.Contains(court, "天津市") || strings.Contains(court, "重庆市") { + riskWarning["hitDirectlyUnderCase"] = 1 + } + } + + // 检查是否为赔偿案件(案由中包含"赔偿") + if caseReason, ok := caseMap["n_laay"].(string); ok { + if strings.Contains(caseReason, "赔偿") { + riskWarning["hitCompensationCase"] = 1 + } + } + // 也检查案由树 + if caseReasonTree, ok := caseMap["n_laay_tree"].(string); ok { + if strings.Contains(caseReasonTree, "赔偿") { + riskWarning["hitCompensationCase"] = 1 + } + } + } + } + } + } + } + } + } + } + } + + // ========== 借贷评估风险 ========== + jrzq6f2aData := getMapValue(apiData, "JRZQ6F2A") + if jrzq6f2aData != nil { + if jrzq6f2aMap, ok := jrzq6f2aData.(map[string]interface{}); ok { + // 获取risk_screen_v2 + if riskScreenV2, ok := jrzq6f2aMap["risk_screen_v2"].(map[string]interface{}); ok { + // 获取variables + if variables, ok := riskScreenV2["variables"].([]interface{}); ok && len(variables) > 0 { + if variable, ok := variables[0].(map[string]interface{}); ok { + if variableValue, ok := variable["variableValue"].(map[string]interface{}); ok { + // 检查近期申请频率(近7天、近15天、近1个月) + checkRecentApplicationFrequency(variableValue, &riskWarning) + + // 检查银行/非银申请次数 + checkBankApplicationFrequency(variableValue, &riskWarning) + + // 检查偿债压力(从借选指数评估获取) + checkDebtPressure(apiData, &riskWarning) + + // 计算借贷评估风险计数 + calculateLoanRiskCounts(variableValue, &riskWarning) + } + } + } + } + } + } + + // ========== 租赁风险评估 ========== + jrzq1d09Data := getMapValue(apiData, "JRZQ1D09") + if jrzq1d09Data != nil { + if jrzq1d09Map, ok := jrzq1d09Data.(map[string]interface{}); ok { + // 检查3C租赁申请频率 + checkRentalApplicationFrequency(jrzq1d09Map, &riskWarning) + } + } + + // ========== 计算各种风险计数 ========== + // 计算sfhyfxRiskCounts(涉赌涉诈风险计数) + if val, ok := riskWarning["isAntiFraudInfo"].(int); ok && val == 1 { + riskWarning["sfhyfxRiskCounts"] = 1 + riskWarning["sfhyfxRiskHighCounts"] = 1 + } + + // 计算gazdyrhyRiskCounts(公安重点人员风险计数) + gazdyrhyRiskCount := 0 + if val, ok := riskWarning["hasCriminalRecord"].(int); ok && val == 1 { + gazdyrhyRiskCount++ + } + if val, ok := riskWarning["isEconomyFront"].(int); ok && val == 1 { + gazdyrhyRiskCount++ + } + if val, ok := riskWarning["isDisrupSocial"].(int); ok && val == 1 { + gazdyrhyRiskCount++ + } + if val, ok := riskWarning["isKeyPerson"].(int); ok && val == 1 { + gazdyrhyRiskCount++ + } + if val, ok := riskWarning["isTrafficRelated"].(int); ok && val == 1 { + gazdyrhyRiskCount++ + } + + if gazdyrhyRiskCount > 0 { + riskWarning["gazdyrhyRiskCounts"] = gazdyrhyRiskCount + if gazdyrhyRiskCount >= 3 { + riskWarning["gazdyrhyRiskHighCounts"] = 1 + } else { + riskWarning["gazdyrhyRiskMiddleCounts"] = 1 + } + } + + // 计算yqfxRiskCounts(逾期风险计数) + yqfxRiskCount := 0 + if val, ok := riskWarning["hitHighRiskBankLastTwoYears"].(int); ok && val == 1 { + yqfxRiskCount++ + } + if val, ok := riskWarning["hitHighRiskNonBankLastTwoYears"].(int); ok && val == 1 { + yqfxRiskCount++ + } + if val, ok := riskWarning["hitCurrentOverdue"].(int); ok && val == 1 { + yqfxRiskCount++ + } + + if yqfxRiskCount > 0 { + riskWarning["yqfxRiskCounts"] = yqfxRiskCount + if yqfxRiskCount >= 2 { + riskWarning["yqfxRiskHighCounts"] = 1 + } else { + riskWarning["yqfxRiskMiddleCounts"] = 1 + } + } + + // 计算zlfxpgRiskCounts(租赁风险评估风险计数) + zlfxpgRiskCount := 0 + if val, ok := riskWarning["frequentRentalApplications"].(int); ok && val == 1 { + zlfxpgRiskCount++ + } + if val, ok := riskWarning["veryFrequentRentalApplications"].(int); ok && val == 1 { + zlfxpgRiskCount++ + } + + if zlfxpgRiskCount > 0 { + riskWarning["zlfxpgRiskCounts"] = zlfxpgRiskCount + if zlfxpgRiskCount >= 2 { + riskWarning["zlfxpgRiskHighCounts"] = 1 + } else { + riskWarning["zlfxpgRiskMiddleCounts"] = 1 + } + } + + // 计算idCardRiskMiddleCounts(身份证风险中风险计数) + // 如果idCardPhoneProvinceMismatch为1,则idCardRiskMiddleCounts为1 + if val, ok := riskWarning["idCardPhoneProvinceMismatch"].(int); ok && val == 1 { + riskWarning["idCardRiskMiddleCounts"] = 1 + // 更新idCardRiskCounts + if currentCount, ok := riskWarning["idCardRiskCounts"].(int); ok { + riskWarning["idCardRiskCounts"] = currentCount + 1 + } else { + riskWarning["idCardRiskCounts"] = 1 + } + } + + // 计算总风险点数量(排除Counts字段和level字段) + totalRiskCounts := 0 + excludeFields := map[string]bool{ + "totalRiskCounts": true, + "level": true, + "sfhyfxRiskCounts": true, + "sfhyfxRiskHighCounts": true, + "sfhyfxRiskMiddleCounts": true, + "gazdyrhyRiskCounts": true, + "gazdyrhyRiskHighCounts": true, + "gazdyrhyRiskMiddleCounts": true, + "yqfxRiskCounts": true, + "yqfxRiskHighCounts": true, + "yqfxRiskMiddleCounts": true, + "zlfxpgRiskCounts": true, + "zlfxpgRiskHighCounts": true, + "zlfxpgRiskMiddleCounts": true, + "jdpgRiskCounts": true, + "jdpgRiskHighCounts": true, + "jdpgRiskMiddleCounts": true, + "idCardRiskCounts": true, + "idCardRiskHighCounts": true, + "idCardRiskMiddleCounts": true, + "shortPhoneRiskCounts": true, + "shortPhoneRiskHighCounts": true, + "shortPhoneRiskMiddleCounts": true, + } + + for _, field := range riskFields { + if excludeFields[field] { + continue + } + if count, ok := riskWarning[field].(int); ok { + totalRiskCounts += count + } + } + riskWarning["totalRiskCounts"] = totalRiskCounts + + // level字段需要从公安不良人员名单中提取 + level := getRiskLevel(apiData) + if level != "" { + riskWarning["level"] = level + } else { + riskWarning["level"] = "" + } + + return riskWarning +} + +// checkRiskField 检查风险字段并设置风险警告(已废弃,使用checkRiskFieldValue代替) +func checkRiskField(data map[string]interface{}, prefix string, fieldName string, riskWarning *map[string]interface{}, targetField string) { + if prefixData, ok := data[prefix].(map[string]interface{}); ok { + if checkRiskFieldValue(prefixData, fieldName) { + (*riskWarning)[targetField] = 1 + } + } +} + +// checkRiskFieldValue 检查风险字段值(字段值为"0"表示命中,字段值为"空"表示未命中) +func checkRiskFieldValue(data map[string]interface{}, fieldName string) bool { + if val, ok := data[fieldName].(string); ok { + // 字段值为"0"表示命中 + return val == "0" + } + return false +} + +// buildElementVerificationDetail 构建要素、运营商、公安重点人员核查产品 +func buildElementVerificationDetail(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + detail := make(map[string]interface{}) + + // 手机三要素简版风险标识 + yysyData := getMapValue(apiData, "YYSYH6D2") + if yysyData != nil { + if yysyMap, ok := yysyData.(map[string]interface{}); ok { + if result, ok := yysyMap["result"].(string); ok { + if result == "0" { + detail["sjsysFlag"] = 2 // 低风险 + } else { + detail["sjsysFlag"] = 1 // 高风险 + } + } + } + } + + // 手机三要素简版详情 + phoneCheckDetails := make(map[string]interface{}) + phoneCheckDetails["num"] = "1" + phoneCheckDetails["ele"] = "身份证号、手机号、姓名" + if yysyData != nil { + if yysyMap, ok := yysyData.(map[string]interface{}); ok { + if channel, ok := yysyMap["channel"].(string); ok { + phoneCheckDetails["phoneCompany"] = convertChannel(channel) + } + if result, ok := yysyMap["result"].(string); ok { + if result == "0" { + phoneCheckDetails["result"] = "一致" + } else { + phoneCheckDetails["result"] = "不一致" + } + } + } + } + detail["phoneCheckDetails"] = phoneCheckDetails + + // 身份证二要素风险标识 + ivyzData := getMapValue(apiData, "IVYZ9K7F") + if ivyzData != nil { + if ivyzMap, ok := ivyzData.(map[string]interface{}); ok { + // 尝试从data字段获取(兼容旧格式) + var status string + if data, ok := ivyzMap["data"].(map[string]interface{}); ok { + if statusVal, ok := data["status"].(string); ok { + status = statusVal + } + } else { + // 如果没有data字段,直接使用desc或result字段(新格式) + if desc, ok := ivyzMap["desc"].(string); ok { + status = desc + } else if result, ok := ivyzMap["result"].(float64); ok { + // result为0表示一致,非0表示不一致 + if result == 0 { + status = "一致" + } else { + status = "不一致" + } + } + } + + if status != "" { + if status == "一致" { + detail["sfzeysFlag"] = 2 // 低风险 + } else { + detail["sfzeysFlag"] = 1 // 高风险 + } + } + } + } + + // 身份证二要素验证详情 + personCheckDetails := make(map[string]interface{}) + personCheckDetails["num"] = "1" + personCheckDetails["ele"] = "身份证号、姓名" + if ivyzData != nil { + if ivyzMap, ok := ivyzData.(map[string]interface{}); ok { + // 尝试从data字段获取(兼容旧格式) + var status string + if data, ok := ivyzMap["data"].(map[string]interface{}); ok { + if statusVal, ok := data["status"].(string); ok { + status = statusVal + } + } else { + // 如果没有data字段,直接使用desc或result字段(新格式) + if desc, ok := ivyzMap["desc"].(string); ok { + status = desc + } else if result, ok := ivyzMap["result"].(float64); ok { + // result为0表示一致,非0表示不一致 + if result == 0 { + status = "一致" + } else { + status = "不一致" + } + } + } + + if status != "" { + personCheckDetails["result"] = status + } + } + } + detail["personCheckDetails"] = personCheckDetails + + // 手机在网时长风险标识 + yysy8b1cData := getMapValue(apiData, "YYSY8B1C") + if yysy8b1cData != nil { + if yysy8b1cMap, ok := yysy8b1cData.(map[string]interface{}); ok { + var inTimeStr string + if inTime, ok := yysy8b1cMap["inTime"].(string); ok { + inTimeStr = inTime + } else if inTime, ok := yysy8b1cMap["inTime"].(float64); ok { + inTimeStr = strconv.FormatFloat(inTime, 'f', -1, 64) + } + + if inTimeStr != "" { + // 根据在网时长判断风险 + if inTimeStr == "0" || inTimeStr == "3" { + detail["onlineRiskFlag"] = 1 // 高风险 + } else { + detail["onlineRiskFlag"] = 2 // 低风险 + } + + // 手机在网时长高级版 + onlineRiskList := make(map[string]interface{}) + onlineRiskList["num"] = "1" + if operators, ok := yysy8b1cMap["operators"].(string); ok { + onlineRiskList["lineType"] = operators + } + onlineRiskList["onLineTimes"] = formatOnlineTime(inTimeStr) + detail["onlineRiskList"] = onlineRiskList + } + } + } + + // 涉赌涉诈信息 + flxgData := getMapValue(apiData, "FLXG8B4D") + antiFraudInfo := make(map[string]interface{}) + if flxgData != nil { + if flxgMap, ok := flxgData.(map[string]interface{}); ok { + // 尝试从data字段获取(兼容旧格式) + var data map[string]interface{} + if dataVal, ok := flxgMap["data"].(map[string]interface{}); ok { + data = dataVal + } else { + // 如果没有data字段,直接使用flxgMap(新格式) + data = flxgMap + } + + if moneyLaundering, ok := data["moneyLaundering"].(string); ok { + antiFraudInfo["moneyLaundering"] = moneyLaundering + } + if deceiver, ok := data["deceiver"].(string); ok { + antiFraudInfo["deceiver"] = deceiver + } + if gamblerPlayer, ok := data["gamblerPlayer"].(string); ok { + antiFraudInfo["gamblerPlayer"] = gamblerPlayer + } + if gamblerBanker, ok := data["gamblerBanker"].(string); ok { + antiFraudInfo["gamblerBanker"] = gamblerBanker + } + if riskScore, ok := data["riskScore"].(string); ok { + antiFraudInfo["riskScore"] = riskScore + } + } + } + detail["antiFraudInfo"] = antiFraudInfo + + // 手机信息验证(phoneVailRiskFlag 和 phoneVailRisks) + yysy4b21Data := getMapValue(apiData, "YYSYE7V5") + phoneVailRisks := make(map[string]interface{}) + phoneVailRisks["num"] = "1" + if yysy4b21Data != nil { + if yysy4b21Map, ok := yysy4b21Data.(map[string]interface{}); ok { + var statusInt int + if statusVal, ok := yysy4b21Map["status"]; ok { + switch v := statusVal.(type) { + case int: + statusInt = v + case float64: + statusInt = int(v) + case string: + if parsed, err := strconv.Atoi(v); err == nil { + statusInt = parsed + } + } + } + + // status=1表示不在网,需要判断状态 + if statusInt == 1 { + detail["phoneVailRiskFlag"] = 1 // 高风险 + if desc, ok := yysy4b21Map["desc"].(string); ok { + phoneVailRisks["phoneStatus"] = desc + } else { + phoneVailRisks["phoneStatus"] = "不在网" + } + + // 获取运营商 + if channel, ok := yysy4b21Map["channel"].(string); ok { + phoneVailRisks["phoneCompany"] = convertChannelName(channel) + } + + // 获取在网时长 + if yysy8b1cData != nil { + if yysy8b1cMap, ok := yysy8b1cData.(map[string]interface{}); ok { + var inTimeStr string + if inTime, ok := yysy8b1cMap["inTime"].(string); ok { + inTimeStr = inTime + } else if inTime, ok := yysy8b1cMap["inTime"].(float64); ok { + inTimeStr = strconv.FormatFloat(inTime, 'f', -1, 64) + } + if inTimeStr != "" { + phoneVailRisks["phoneTimes"] = inTimeStr + "(单位:月)" + } + } + } + } else if statusInt == 0 { + detail["phoneVailRiskFlag"] = 2 // 低风险 + // 根据desc判断状态 + if desc, ok := yysy4b21Map["desc"].(string); ok { + if desc == "正常" { + phoneVailRisks["phoneStatus"] = "实号" + } else { + phoneVailRisks["phoneStatus"] = desc + } + } else { + phoneVailRisks["phoneStatus"] = "在网" + } + if channel, ok := yysy4b21Map["channel"].(string); ok { + phoneVailRisks["phoneCompany"] = convertChannelName(channel) + } + + // 获取在网时长(status=0时也要提取) + if yysy8b1cData != nil { + if yysy8b1cMap, ok := yysy8b1cData.(map[string]interface{}); ok { + var inTimeStr string + if inTime, ok := yysy8b1cMap["inTime"].(string); ok { + inTimeStr = inTime + } else if inTime, ok := yysy8b1cMap["inTime"].(float64); ok { + inTimeStr = strconv.FormatFloat(inTime, 'f', -1, 64) + } + if inTimeStr != "" { + phoneVailRisks["phoneTimes"] = inTimeStr + "(单位:月)" + } + } + } + } + } + } + detail["phoneVailRisks"] = phoneVailRisks + + // 身份证号手机号归属地(belongRiskFlag 和 belongRisks) + belongRisks := make(map[string]interface{}) + belongRisks["num"] = "1" + + // 重新获取数据 + yysy9e4aDataForBelong := getMapValue(apiData, "YYSY9E4A") + yysyDataForBelong := getMapValue(apiData, "YYSYH6D2") + + if yysy9e4aDataForBelong != nil && yysyDataForBelong != nil { + if yysy9e4aMap, ok := yysy9e4aDataForBelong.(map[string]interface{}); ok { + if yysyMapForBelong, ok := yysyDataForBelong.(map[string]interface{}); ok { + // 从身份证号地址获取省份和城市 + var personProvince, personCity string + if address, ok := yysyMapForBelong["address"].(string); ok && address != "" { + // 解析地址,提取省份和城市 + // 格式通常是:省份+市+县/区 + parts := strings.Split(address, "省") + if len(parts) > 1 { + personProvince = parts[0] + "省" + cityPart := parts[1] + if strings.Contains(cityPart, "市") { + cityParts := strings.Split(cityPart, "市") + if len(cityParts) > 0 { + personCity = cityParts[0] + "市" + } + } + } else { + // 尝试其他格式 + if strings.Contains(address, "省") { + provinceEnd := strings.Index(address, "省") + personProvince = address[:provinceEnd+1] + if provinceEnd+1 < len(address) { + cityPart := address[provinceEnd+1:] + if strings.Contains(cityPart, "市") { + cityEnd := strings.Index(cityPart, "市") + personCity = cityPart[:cityEnd+1] + } + } + } + } + } + + // 从手机号归属地获取省份和城市 + var phoneProvince, phoneCity string + if provinceName, ok := yysy9e4aMap["provinceName"].(string); ok { + phoneProvince = provinceName + } + if cityName, ok := yysy9e4aMap["cityName"].(string); ok { + phoneCity = cityName + } + + belongRisks["personProvence"] = personProvince + belongRisks["personCity"] = personCity + belongRisks["phoneProvence"] = phoneProvince + belongRisks["phoneCity"] = phoneCity + + // 获取手机卡类型 + if channel, ok := yysy9e4aMap["channel"].(string); ok { + belongRisks["phoneCardType"] = convertChannelName(channel) + } else if channel, ok := yysyMapForBelong["channel"].(string); ok { + belongRisks["phoneCardType"] = convertChannel(channel) + } + + // 判断归属地是否一致 + if personProvince != "" && phoneProvince != "" { + // 标准化省份名称(去除"省"字后比较) + personProvinceNorm := strings.TrimSuffix(personProvince, "省") + phoneProvinceNorm := strings.TrimSuffix(phoneProvince, "省") + if personProvinceNorm != phoneProvinceNorm { + detail["belongRiskFlag"] = 1 // 高风险 + } else { + detail["belongRiskFlag"] = 2 // 低风险 + } + } else { + detail["belongRiskFlag"] = 0 // 未查得 + } + } + } + } + detail["belongRisks"] = belongRisks + + // 公安重点人员核验产品(highRiskFlag 和 keyPersonCheckList) + flxgdea9Data := getMapValue(apiData, "FLXGDEA9") + keyPersonCheckList := make(map[string]interface{}) + keyPersonCheckList["num"] = "1" + keyPersonCheckList["fontFlag"] = 0 + keyPersonCheckList["jingJiFontFlag"] = 0 + keyPersonCheckList["fangAiFlag"] = 0 + keyPersonCheckList["zhongDianFlag"] = 0 + keyPersonCheckList["sheJiaoTongFlag"] = 0 + + if flxgdea9Data != nil { + if flxgdea9Map, ok := flxgdea9Data.(map[string]interface{}); ok { + level, ok := flxgdea9Map["level"].(string) + if !ok { + // 尝试从其他可能的字段获取 + if levelVal, exists := flxgdea9Map["level"]; exists { + if levelStr, ok := levelVal.(string); ok { + level = levelStr + } else if levelFloat, ok := levelVal.(float64); ok { + level = strconv.FormatFloat(levelFloat, 'f', -1, 64) + } + } + } + + if level != "" && level != "0" { + // 有风险,设置highRiskFlag为1(高风险) + detail["highRiskFlag"] = 1 + + // 解析level字段,填充keyPersonCheckList + levelParts := strings.Split(level, ",") + for _, part := range levelParts { + part = strings.TrimSpace(part) + if part == "" || part == "0" { + continue + } + + // 根据level代码判断风险类型 + if strings.HasPrefix(part, "A") { + keyPersonCheckList["fontFlag"] = 1 + } + if strings.HasPrefix(part, "B") { + keyPersonCheckList["jingJiFontFlag"] = 1 + } + if strings.HasPrefix(part, "C") { + keyPersonCheckList["fangAiFlag"] = 1 + } + if strings.HasPrefix(part, "D") { + keyPersonCheckList["zhongDianFlag"] = 1 + } + if strings.HasPrefix(part, "E") { + keyPersonCheckList["sheJiaoTongFlag"] = 1 + } + } + } else { + // 无风险,设置highRiskFlag为2(低风险) + detail["highRiskFlag"] = 2 + } + } + } + detail["keyPersonCheckList"] = keyPersonCheckList + + // 设置默认值 + if _, exists := detail["sjsysFlag"]; !exists { + detail["sjsysFlag"] = 0 + } + if _, exists := detail["sfzeysFlag"]; !exists { + detail["sfzeysFlag"] = 0 + } + if _, exists := detail["onlineRiskFlag"]; !exists { + detail["onlineRiskFlag"] = 0 + } + if _, exists := detail["highRiskFlag"]; !exists { + detail["highRiskFlag"] = 0 + } + if _, exists := detail["phoneVailRiskFlag"]; !exists { + detail["phoneVailRiskFlag"] = 0 + } + if _, exists := detail["belongRiskFlag"]; !exists { + detail["belongRiskFlag"] = 0 + } + + return detail +} + +// buildRiskSupervision 构建关联风险监督 +func buildRiskSupervision(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + riskSupervision := make(map[string]interface{}) + + // 从3C租赁申请意向获取数据 + jrzq1d09Data := getMapValue(apiData, "JRZQ1D09") + + // 设置默认值 + riskSupervision["rentalRiskListIdCardRelationsPhones"] = 0 + riskSupervision["rentalRiskListPhoneRelationsIdCards"] = 0 + riskSupervision["details"] = "" // 默认无(空字符串) + riskSupervision["leastApplicationTime"] = "" + + if jrzq1d09Data != nil { + if jrzq1d09Map, ok := jrzq1d09Data.(map[string]interface{}); ok { + // 同一身份证关联手机号数(从 idcard_relation_phone 获取) + // 注意:字段名是idcard_relation_phone,表示同一身份证关联的手机号数 + if idcardRelationPhone, ok := jrzq1d09Map["idcard_relation_phone"].(int); ok { + riskSupervision["rentalRiskListIdCardRelationsPhones"] = idcardRelationPhone + } else if idcardRelationPhone, ok := jrzq1d09Map["idcard_relation_phone"].(float64); ok { + riskSupervision["rentalRiskListIdCardRelationsPhones"] = int(idcardRelationPhone) + } else if idcardRelationPhone, ok := jrzq1d09Map["idcard_relation_phone"].(string); ok && idcardRelationPhone != "" { + if count, err := strconv.Atoi(idcardRelationPhone); err == nil { + riskSupervision["rentalRiskListIdCardRelationsPhones"] = count + } + } + + // 同一手机号关联身份证号数(从 phone_relation_idard 获取) + // 注意:字段名是phone_relation_idard,表示同一手机号关联的身份证号数 + if phoneRelationIdcard, ok := jrzq1d09Map["phone_relation_idard"].(int); ok { + riskSupervision["rentalRiskListPhoneRelationsIdCards"] = phoneRelationIdcard + } else if phoneRelationIdcard, ok := jrzq1d09Map["phone_relation_idard"].(float64); ok { + riskSupervision["rentalRiskListPhoneRelationsIdCards"] = int(phoneRelationIdcard) + } else if phoneRelationIdcard, ok := jrzq1d09Map["phone_relation_idard"].(string); ok && phoneRelationIdcard != "" { + if count, err := strconv.Atoi(phoneRelationIdcard); err == nil { + riskSupervision["rentalRiskListPhoneRelationsIdCards"] = count + } + } + + // 最近一次申请时间(从 least_application_time 获取) + if leastApplicationTime, ok := jrzq1d09Map["least_application_time"].(string); ok && leastApplicationTime != "" { + riskSupervision["leastApplicationTime"] = leastApplicationTime + } + } + } + + return riskSupervision +} + +// buildOverdueRiskProduct 构建逾期风险产品 +func buildOverdueRiskProduct(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + overdueRiskProduct := make(map[string]interface{}) + + // 设置默认值 + overdueRiskProduct["lyjlhyFlag"] = 0 + overdueRiskProduct["dkzhktjFlag"] = 0 + overdueRiskProduct["tsmdyzFlag"] = 0 + overdueRiskProduct["hasUnsettledOverdue"] = "未逾期" + overdueRiskProduct["currentOverdueInstitutionCount"] = "0" + overdueRiskProduct["currentOverdueAmount"] = "0" + overdueRiskProduct["settledInstitutionCount"] = "0" + overdueRiskProduct["totalLoanRepaymentAmount"] = "0" + overdueRiskProduct["totalLoanInstitutions"] = "0" + overdueRiskProduct["overdueLast1Day"] = "未逾期" + overdueRiskProduct["overdueLast7Days"] = "未逾期" + overdueRiskProduct["overdueLast14Days"] = "未逾期" + overdueRiskProduct["overdueLast30Days"] = "未逾期" + overdueRiskProduct["daysSinceLastSuccessfulRepayment"] = "0" + overdueRiskProduct["repaymentFailureCountLast7Days"] = "0" + overdueRiskProduct["repaymentFailureAmountLast7Days"] = "0" + overdueRiskProduct["repaymentSuccessCountLast7Days"] = "-" + overdueRiskProduct["repaymentSuccessAmountLast7Days"] = "-" + overdueRiskProduct["repaymentFailureCountLast14Days"] = "0" + overdueRiskProduct["repaymentFailureAmountLast14Days"] = "0" + overdueRiskProduct["repaymentSuccessCountLast14Days"] = "-" + overdueRiskProduct["repaymentSuccessAmountLast14Days"] = "-" + overdueRiskProduct["repaymentFailureCountLastMonth"] = "0" + overdueRiskProduct["repaymentFailureAmountLastMonth"] = "0" + overdueRiskProduct["repaymentSuccessCountLastMonth"] = "-" + overdueRiskProduct["repaymentSuccessAmountLastMonth"] = "-" + overdueRiskProduct["repaymentFailureCountLast3Months"] = "0" + overdueRiskProduct["repaymentFailureAmountLast3Months"] = "0" + overdueRiskProduct["repaymentSuccessCountLast3Months"] = "-" + overdueRiskProduct["repaymentSuccessAmountLast3Months"] = "-" + overdueRiskProduct["repaymentFailureCountLast6Months"] = "0" + overdueRiskProduct["repaymentFailureAmountLast6Months"] = "0" + overdueRiskProduct["repaymentSuccessCountLast6Months"] = "-" + overdueRiskProduct["repaymentSuccessAmountLast6Months"] = "-" + overdueRiskProduct["specialListVerification"] = []interface{}{} + + // 从借选指数评估获取数据(主要数据源) + jrzq5e9fData := getMapValue(apiData, "JRZQ5E9F") + if jrzq5e9fData != nil { + if jrzq5e9fMap, ok := jrzq5e9fData.(map[string]interface{}); ok { + // 当前是否存在逾期未结清 + if xypCpl0044, ok := jrzq5e9fMap["xyp_cpl0044"].(string); ok { + if xypCpl0044 == "1" { + overdueRiskProduct["hasUnsettledOverdue"] = "逾期" + } + } + + // 当前逾期机构数(需要区间化) + if xypCpl0071, ok := jrzq5e9fMap["xyp_cpl0071"].(string); ok && xypCpl0071 != "" { + overdueRiskProduct["currentOverdueInstitutionCount"] = convertXypCpl0071ToInterval(xypCpl0071) + } + + // 当前逾期金额(需要区间化) + if xypCpl0072, ok := jrzq5e9fMap["xyp_cpl0072"].(string); ok && xypCpl0072 != "" { + overdueRiskProduct["currentOverdueAmount"] = convertXypCpl0072ToInterval(xypCpl0072) + } + + // 已结清机构数(需要区间化) + if xypCpl0002, ok := jrzq5e9fMap["xyp_cpl0002"].(string); ok && xypCpl0002 != "" { + overdueRiskProduct["settledInstitutionCount"] = convertXypCpl0002ToInterval(xypCpl0002) + } + + // 贷款总机构数(需要区间化) + if xypCpl0001, ok := jrzq5e9fMap["xyp_cpl0001"].(string); ok && xypCpl0001 != "" { + overdueRiskProduct["totalLoanInstitutions"] = convertXypCpl0001ToInterval(xypCpl0001) + } + + // 贷款已还款总金额(使用xyp_t01aazzzc,需要区间化转换) + if xypT01aazzzc, ok := jrzq5e9fMap["xyp_t01aazzzc"].(string); ok && xypT01aazzzc != "" { + overdueRiskProduct["totalLoanRepaymentAmount"] = convertXypT01aazzzcToInterval(xypT01aazzzc) + } + + // 最近1天是否发生过逾期 + if xypCpl0028, ok := jrzq5e9fMap["xyp_cpl0028"].(string); ok { + if xypCpl0028 == "1" { + overdueRiskProduct["overdueLast1Day"] = "逾期" + } + } + + // 最近7天是否发生过逾期 + if xypCpl0029, ok := jrzq5e9fMap["xyp_cpl0029"].(string); ok { + if xypCpl0029 == "1" { + overdueRiskProduct["overdueLast7Days"] = "逾期" + } + } + + // 最近14天是否发生过逾期 + if xypCpl0030, ok := jrzq5e9fMap["xyp_cpl0030"].(string); ok { + if xypCpl0030 == "1" { + overdueRiskProduct["overdueLast14Days"] = "逾期" + } + } + + // 最近30天是否发生过逾期 + if xypCpl0031, ok := jrzq5e9fMap["xyp_cpl0031"].(string); ok { + if xypCpl0031 == "1" { + overdueRiskProduct["overdueLast30Days"] = "逾期" + } + } + + // 最近一次还款成功距离当前天数(需要区间化转换) + if xypCpl0068, ok := jrzq5e9fMap["xyp_cpl0068"].(string); ok && xypCpl0068 != "" { + overdueRiskProduct["daysSinceLastSuccessfulRepayment"] = convertXypCpl0068ToInterval(xypCpl0068) + } + + // 最近7天还款失败次数(需要区间化) + if xypCpl0018, ok := jrzq5e9fMap["xyp_cpl0018"].(string); ok && xypCpl0018 != "" { + overdueRiskProduct["repaymentFailureCountLast7Days"] = convertXypCpl0018ToInterval(xypCpl0018) + } + + // 最近7天还款失败金额(需要区间化) + if xypCpl0034, ok := jrzq5e9fMap["xyp_cpl0034"].(string); ok && xypCpl0034 != "" { + overdueRiskProduct["repaymentFailureAmountLast7Days"] = convertXypCpl0034ToInterval(xypCpl0034) + } + + // 最近7天还款成功次数(需要区间化) + if xypCpl0019, ok := jrzq5e9fMap["xyp_cpl0019"].(string); ok && xypCpl0019 != "" && xypCpl0019 != "0" { + overdueRiskProduct["repaymentSuccessCountLast7Days"] = convertXypCpl0019ToInterval(xypCpl0019) + } else { + overdueRiskProduct["repaymentSuccessCountLast7Days"] = "-" + } + + // 最近7天还款成功金额(需要区间化) + if xypCpl0035, ok := jrzq5e9fMap["xyp_cpl0035"].(string); ok && xypCpl0035 != "" && xypCpl0035 != "0" { + overdueRiskProduct["repaymentSuccessAmountLast7Days"] = convertXypCpl0035ToInterval(xypCpl0035) + } else { + overdueRiskProduct["repaymentSuccessAmountLast7Days"] = "-" + } + + // 最近14天还款失败次数(需要区间化) + if xypCpl0020, ok := jrzq5e9fMap["xyp_cpl0020"].(string); ok && xypCpl0020 != "" { + overdueRiskProduct["repaymentFailureCountLast14Days"] = convertXypCpl0020ToInterval(xypCpl0020) + } + + // 最近14天还款失败金额(需要区间化) + if xypCpl0036, ok := jrzq5e9fMap["xyp_cpl0036"].(string); ok && xypCpl0036 != "" { + overdueRiskProduct["repaymentFailureAmountLast14Days"] = convertXypCpl0036ToInterval(xypCpl0036) + } + + // 最近14天还款成功次数(需要区间化) + if xypCpl0021, ok := jrzq5e9fMap["xyp_cpl0021"].(string); ok && xypCpl0021 != "" && xypCpl0021 != "0" { + overdueRiskProduct["repaymentSuccessCountLast14Days"] = convertXypCpl0021ToInterval(xypCpl0021) + } else { + overdueRiskProduct["repaymentSuccessCountLast14Days"] = "-" + } + + // 最近14天还款成功金额(需要区间化) + if xypCpl0037, ok := jrzq5e9fMap["xyp_cpl0037"].(string); ok && xypCpl0037 != "" && xypCpl0037 != "0" { + overdueRiskProduct["repaymentSuccessAmountLast14Days"] = convertXypCpl0037ToInterval(xypCpl0037) + } else { + overdueRiskProduct["repaymentSuccessAmountLast14Days"] = "-" + } + + // 最近1个月还款失败次数(需要区间化) + if xypCpl0022, ok := jrzq5e9fMap["xyp_cpl0022"].(string); ok && xypCpl0022 != "" { + overdueRiskProduct["repaymentFailureCountLastMonth"] = convertXypCpl0022ToInterval(xypCpl0022) + } + + // 最近1个月还款失败金额(需要区间化) + if xypCpl0038, ok := jrzq5e9fMap["xyp_cpl0038"].(string); ok && xypCpl0038 != "" { + overdueRiskProduct["repaymentFailureAmountLastMonth"] = convertXypCpl0038ToInterval(xypCpl0038) + } + + // 最近1个月还款成功次数(需要区间化) + if xypCpl0023, ok := jrzq5e9fMap["xyp_cpl0023"].(string); ok && xypCpl0023 != "" && xypCpl0023 != "0" { + overdueRiskProduct["repaymentSuccessCountLastMonth"] = convertXypCpl0023ToInterval(xypCpl0023) + } else { + overdueRiskProduct["repaymentSuccessCountLastMonth"] = "-" + } + + // 最近1个月还款成功金额(需要区间化) + if xypCpl0039, ok := jrzq5e9fMap["xyp_cpl0039"].(string); ok && xypCpl0039 != "" && xypCpl0039 != "0" { + overdueRiskProduct["repaymentSuccessAmountLastMonth"] = convertXypCpl0039ToInterval(xypCpl0039) + } else { + overdueRiskProduct["repaymentSuccessAmountLastMonth"] = "-" + } + + // 最近3个月还款失败次数(需要区间化) + if xypCpl0024, ok := jrzq5e9fMap["xyp_cpl0024"].(string); ok && xypCpl0024 != "" { + overdueRiskProduct["repaymentFailureCountLast3Months"] = convertXypCpl0024ToInterval(xypCpl0024) + } + + // 最近3个月还款失败金额(需要区间化) + if xypCpl0040, ok := jrzq5e9fMap["xyp_cpl0040"].(string); ok && xypCpl0040 != "" { + overdueRiskProduct["repaymentFailureAmountLast3Months"] = convertXypCpl0040ToInterval(xypCpl0040) + } + + // 最近3个月还款成功次数(需要区间化) + if xypCpl0025, ok := jrzq5e9fMap["xyp_cpl0025"].(string); ok && xypCpl0025 != "" && xypCpl0025 != "0" { + overdueRiskProduct["repaymentSuccessCountLast3Months"] = convertXypCpl0025ToInterval(xypCpl0025) + } else { + overdueRiskProduct["repaymentSuccessCountLast3Months"] = "-" + } + + // 最近3个月还款成功金额(需要区间化) + if xypCpl0041, ok := jrzq5e9fMap["xyp_cpl0041"].(string); ok && xypCpl0041 != "" && xypCpl0041 != "0" { + overdueRiskProduct["repaymentSuccessAmountLast3Months"] = convertXypCpl0041ToInterval(xypCpl0041) + } else { + overdueRiskProduct["repaymentSuccessAmountLast3Months"] = "-" + } + + // 最近6个月还款失败次数(需要区间化) + if xypCpl0026, ok := jrzq5e9fMap["xyp_cpl0026"].(string); ok && xypCpl0026 != "" { + overdueRiskProduct["repaymentFailureCountLast6Months"] = convertXypCpl0026ToInterval(xypCpl0026) + } + + // 最近6个月还款失败金额(需要区间化) + if xypCpl0042, ok := jrzq5e9fMap["xyp_cpl0042"].(string); ok && xypCpl0042 != "" { + overdueRiskProduct["repaymentFailureAmountLast6Months"] = convertXypCpl0042ToInterval(xypCpl0042) + } + + // 最近6个月还款成功次数 + if xypCpl0027, ok := jrzq5e9fMap["xyp_cpl0027"].(string); ok && xypCpl0027 != "" && xypCpl0027 != "0" { + overdueRiskProduct["repaymentSuccessCountLast6Months"] = xypCpl0027 + } else { + overdueRiskProduct["repaymentSuccessCountLast6Months"] = "-" + } + + // 最近6个月还款成功金额 + if xypCpl0043, ok := jrzq5e9fMap["xyp_cpl0043"].(string); ok && xypCpl0043 != "" && xypCpl0043 != "0" { + overdueRiskProduct["repaymentSuccessAmountLast6Months"] = xypCpl0043 + } else { + overdueRiskProduct["repaymentSuccessAmountLast6Months"] = "-" + } + + // 判断风险标识 + hasOverdue := overdueRiskProduct["hasUnsettledOverdue"] == "逾期" + hasRecentOverdue := overdueRiskProduct["overdueLast7Days"] == "逾期" || + overdueRiskProduct["overdueLast14Days"] == "逾期" || + overdueRiskProduct["overdueLast30Days"] == "逾期" + + if hasOverdue || hasRecentOverdue { + overdueRiskProduct["lyjlhyFlag"] = 1 // 高风险 + overdueRiskProduct["dkzhktjFlag"] = 1 // 高风险 + } else { + overdueRiskProduct["lyjlhyFlag"] = 2 // 低风险 + overdueRiskProduct["dkzhktjFlag"] = 2 // 低风险 + } + } + } + + // 从特殊名单验证B获取数据 + jrzq8a2dData := getMapValue(apiData, "JRZQ8A2D") + if jrzq8a2dData != nil { + if jrzq8a2dMap, ok := jrzq8a2dData.(map[string]interface{}); ok { + // 构建specialListVerification列表 + specialListVerification := make([]interface{}, 0) + + // 检查各种特殊名单(字段名映射:源数据中字段名没有id_前缀) + specialListFields := []struct { + fieldName string + reason string + }{ + {"court_bad", "法院失信人"}, + {"court_executed", "法院被执行人"}, + {"bank_bad", "银行中风险"}, + {"bank_overdue", "银行一般风险"}, + {"bank_lost", "银行高风险"}, + {"nbank_bad", "非银中风险"}, + {"nbank_overdue", "非银一般风险"}, + {"nbank_lost", "非银高风险"}, + } + + // 检查id字段 + if idData, ok := jrzq8a2dMap["id"].(map[string]interface{}); ok { + for _, field := range specialListFields { + // 源数据中字段名是 court_executed,不是 id_court_executed + if val, ok := idData[field.fieldName].(string); ok && val == "0" { + // 根据源数据分析,val == "0" 表示命中 + specialListVerification = append(specialListVerification, map[string]interface{}{ + "mz": "通过身份证号查询" + field.reason, + "jg": "本人直接命中", + }) + // 添加命中次数 + allnumField := field.fieldName + "_allnum" + if allnumVal, ok := idData[allnumField].(string); ok && allnumVal != "" { + specialListVerification = append(specialListVerification, map[string]interface{}{ + "mz": field.reason + "命中次数", + "jg": allnumVal + "次", + }) + } + // 添加距今时间 + timeField := field.fieldName + "_time" + if timeVal, ok := idData[timeField].(string); ok && timeVal != "" { + specialListVerification = append(specialListVerification, map[string]interface{}{ + "mz": field.reason + "距今时间", + "jg": timeVal + "年", + }) + } + } + } + } + + // 检查cell字段(手机号相关) + if cellData, ok := jrzq8a2dMap["cell"].(map[string]interface{}); ok { + for _, field := range specialListFields { + // 源数据中字段名是 nbank_bad,不是 cell_nbank_bad + if val, ok := cellData[field.fieldName].(string); ok && val == "0" { + // 根据源数据分析,val == "0" 表示命中 + specialListVerification = append(specialListVerification, map[string]interface{}{ + "mz": "通过手机号查询" + field.reason, + "jg": "本人直接命中", + }) + // 添加命中次数 + allnumField := field.fieldName + "_allnum" + if allnumVal, ok := cellData[allnumField].(string); ok && allnumVal != "" { + specialListVerification = append(specialListVerification, map[string]interface{}{ + "mz": field.reason + "(手机号)命中次数", + "jg": allnumVal + "次", + }) + } + // 添加距今时间 + timeField := field.fieldName + "_time" + if timeVal, ok := cellData[timeField].(string); ok && timeVal != "" { + specialListVerification = append(specialListVerification, map[string]interface{}{ + "mz": field.reason + "(手机号)距今时间", + "jg": timeVal + "年", + }) + } + } + } + } + + if len(specialListVerification) > 0 { + overdueRiskProduct["specialListVerification"] = specialListVerification + overdueRiskProduct["tsmdyzFlag"] = 1 // 高风险 + } else { + overdueRiskProduct["tsmdyzFlag"] = 2 // 低风险 + } + } + } + + return overdueRiskProduct +} + +// buildMultCourtInfo 构建司法风险核验产品 +func buildMultCourtInfo(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + multCourtInfo := make(map[string]interface{}) + + // 初始化默认值 + multCourtInfo["legalCases"] = []interface{}{} + multCourtInfo["executionCases"] = []interface{}{} + multCourtInfo["disinCases"] = []interface{}{} + multCourtInfo["limitCases"] = []interface{}{} + multCourtInfo["legalCasesFlag"] = 0 + multCourtInfo["executionCasesFlag"] = 0 + multCourtInfo["disinCasesFlag"] = 0 + multCourtInfo["limitCasesFlag"] = 0 + + // 从个人司法涉诉查询获取数据 + flxg7e8fData := getMapValue(apiData, "FLXG7E8F") + if flxg7e8fData != nil { + if flxg7e8fMap, ok := flxg7e8fData.(map[string]interface{}); ok { + // 获取judicial_data + if judicialData, ok := flxg7e8fMap["judicial_data"].(map[string]interface{}); ok { + // 处理lawsuitStat + if lawsuitStat, ok := judicialData["lawsuitStat"].(map[string]interface{}); ok { + // 收集所有涉案公告案件(民事、刑事、行政、保全、破产)- 都归到legalCases + legalCases := make([]interface{}, 0) + + // 处理民事案件(civil)- 结构:civil.cases[] + if civilVal, exists := lawsuitStat["civil"]; exists && civilVal != nil { + if civil, ok := civilVal.(map[string]interface{}); ok && len(civil) > 0 { + if cases, ok := civil["cases"].([]interface{}); ok && len(cases) > 0 { + for _, caseItem := range cases { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + legalCase := convertCivilCase(caseMap) + if legalCase != nil { + legalCases = append(legalCases, legalCase) + } + } + } + } + } + } + + // 处理刑事案件(criminal)- 结构:criminal.cases[] + if criminalVal, exists := lawsuitStat["criminal"]; exists && criminalVal != nil { + if criminal, ok := criminalVal.(map[string]interface{}); ok && len(criminal) > 0 { + if cases, ok := criminal["cases"].([]interface{}); ok && len(cases) > 0 { + for _, caseItem := range cases { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + legalCase := convertCriminalCase(caseMap) + if legalCase != nil { + legalCases = append(legalCases, legalCase) + } + } + } + } + } + } + + // 处理行政案件(administrative)- 结构:administrative.cases[] + if administrativeVal, exists := lawsuitStat["administrative"]; exists && administrativeVal != nil { + if administrative, ok := administrativeVal.(map[string]interface{}); ok && len(administrative) > 0 { + if cases, ok := administrative["cases"].([]interface{}); ok && len(cases) > 0 { + for _, caseItem := range cases { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + legalCase := convertAdministrativeCase(caseMap) + if legalCase != nil { + legalCases = append(legalCases, legalCase) + } + } + } + } + } + } + + // 处理保全审查案件(preservation)- 结构:preservation.cases[] + if preservationVal, exists := lawsuitStat["preservation"]; exists && preservationVal != nil { + if preservation, ok := preservationVal.(map[string]interface{}); ok && len(preservation) > 0 { + if cases, ok := preservation["cases"].([]interface{}); ok && len(cases) > 0 { + for _, caseItem := range cases { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + legalCase := convertPreservationCase(caseMap) + if legalCase != nil { + legalCases = append(legalCases, legalCase) + } + } + } + } + } + } + + // 处理破产清算案件(bankrupt)- 结构:bankrupt.cases[] + if bankruptVal, exists := lawsuitStat["bankrupt"]; exists && bankruptVal != nil { + if bankrupt, ok := bankruptVal.(map[string]interface{}); ok && len(bankrupt) > 0 { + if cases, ok := bankrupt["cases"].([]interface{}); ok && len(cases) > 0 { + for _, caseItem := range cases { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + legalCase := convertBankruptCase(caseMap) + if legalCase != nil { + legalCases = append(legalCases, legalCase) + } + } + } + } + } + } + + // 如果有涉案公告案件,设置legalCases和legalCasesFlag + if len(legalCases) > 0 { + multCourtInfo["legalCases"] = legalCases + multCourtInfo["legalCasesFlag"] = 1 // 高风险 + } + + // 处理执行案件(executionCases)- 单独处理 + if implement, ok := lawsuitStat["implement"].(map[string]interface{}); ok { + if cases, ok := implement["cases"].([]interface{}); ok { + executionCases := make([]interface{}, 0) + for _, caseItem := range cases { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + executionCase := convertExecutionCase(caseMap) + if executionCase != nil { + executionCases = append(executionCases, executionCase) + } + } + } + if len(executionCases) > 0 { + multCourtInfo["executionCases"] = executionCases + multCourtInfo["executionCasesFlag"] = 1 // 高风险 + } + } + } + } + + // 处理失信案件(disinCases) + if breachCaseList, ok := judicialData["breachCaseList"].([]interface{}); ok { + disinCases := make([]interface{}, 0) + for _, caseItem := range breachCaseList { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + disinCase := convertBreachCase(caseMap) + if disinCase != nil { + disinCases = append(disinCases, disinCase) + } + } + } + if len(disinCases) > 0 { + multCourtInfo["disinCases"] = disinCases + multCourtInfo["disinCasesFlag"] = 1 // 高风险 + } + } + + // 处理限高案件(limitCases) + if consumptionRestrictionList, ok := judicialData["consumptionRestrictionList"].([]interface{}); ok { + limitCases := make([]interface{}, 0) + for _, caseItem := range consumptionRestrictionList { + if caseMap, ok := caseItem.(map[string]interface{}); ok { + limitCase := convertLimitCase(caseMap) + if limitCase != nil { + limitCases = append(limitCases, limitCase) + } + } + } + if len(limitCases) > 0 { + multCourtInfo["limitCases"] = limitCases + multCourtInfo["limitCasesFlag"] = 1 // 高风险 + } + } + } + } + } + + return multCourtInfo +} + +// convertCivilCase 转换民事案件 +func convertCivilCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if cAh, ok := caseMap["c_ah"].(string); ok { + caseInfo["caseNumber"] = cAh + } else { + return nil // 案号是必需字段 + } + + // 案件类型 + if nAjlx, ok := caseMap["n_ajlx"].(string); ok { + caseInfo["caseType"] = nAjlx + } else { + caseInfo["caseType"] = "未知" + } + + // 法院 + if nJbfy, ok := caseMap["n_jbfy"].(string); ok { + caseInfo["court"] = nJbfy + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + if nSsdw, ok := caseMap["n_ssdw"].(string); ok { + caseInfo["litigantType"] = nSsdw + } else { + caseInfo["litigantType"] = "" + } + + // 立案时间 + if dLarq, ok := caseMap["d_larq"].(string); ok { + caseInfo["filingTime"] = dLarq + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + if dJarq, ok := caseMap["d_jarq"].(string); ok { + caseInfo["disposalTime"] = dJarq + } else { + caseInfo["disposalTime"] = "" + } + + // 案件状态 + if nAjjzjd, ok := caseMap["n_ajjzjd"].(string); ok { + caseInfo["caseStatus"] = nAjjzjd + } else { + caseInfo["caseStatus"] = "" + } + + // 执行金额(民事案件通常没有) + caseInfo["executionAmount"] = "" + caseInfo["repaidAmount"] = "" + + // 案由 + if nLaay, ok := caseMap["n_laay"].(string); ok { + caseInfo["caseReason"] = nLaay + } else { + caseInfo["caseReason"] = "未知" + } + + // 结案方式 + if nJafs, ok := caseMap["n_jafs"].(string); ok { + caseInfo["disposalMethod"] = nJafs + } else { + caseInfo["disposalMethod"] = "" + } + + // 判决结果(只使用详细的判决结果文本c_gkws_pjjg,如果没有则返回空字符串) + if cGkwsPjjg, ok := caseMap["c_gkws_pjjg"].(string); ok && cGkwsPjjg != "" { + caseInfo["judgmentResult"] = cGkwsPjjg + } else { + caseInfo["judgmentResult"] = "" + } + + return caseInfo +} + +// convertExecutionCase 转换执行案件 +func convertExecutionCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if cAh, ok := caseMap["c_ah"].(string); ok { + caseInfo["caseNumber"] = cAh + } else { + return nil + } + + // 案件类型 + if nAjlx, ok := caseMap["n_ajlx"].(string); ok { + caseInfo["caseType"] = nAjlx + } else if nJaay, ok := caseMap["n_jaay"].(string); ok { + caseInfo["caseType"] = nJaay + "执行" + } else { + caseInfo["caseType"] = "执行" + } + + // 法院 + if nJbfy, ok := caseMap["n_jbfy"].(string); ok { + caseInfo["court"] = nJbfy + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + if nSsdw, ok := caseMap["n_ssdw"].(string); ok { + caseInfo["litigantType"] = nSsdw + } else { + caseInfo["litigantType"] = "" + } + + // 立案时间 + if dLarq, ok := caseMap["d_larq"].(string); ok { + caseInfo["filingTime"] = dLarq + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + if dJarq, ok := caseMap["d_jarq"].(string); ok { + caseInfo["disposalTime"] = dJarq + } else { + caseInfo["disposalTime"] = "" + } + + // 案件状态 + if nAjjzjd, ok := caseMap["n_ajjzjd"].(string); ok { + caseInfo["caseStatus"] = nAjjzjd + } else { + caseInfo["caseStatus"] = "" + } + + // 执行金额(支持字符串、数字和interface{}类型) + var executionAmountStr string + if nSqzxbdjeVal, exists := caseMap["n_sqzxbdje"]; exists && nSqzxbdjeVal != nil { + switch v := nSqzxbdjeVal.(type) { + case string: + if v != "" { + executionAmountStr = v + } + case float64: + executionAmountStr = strconv.FormatFloat(v, 'f', -1, 64) + case int: + executionAmountStr = strconv.Itoa(v) + case int64: + executionAmountStr = strconv.FormatInt(v, 10) + } + } + // 如果n_sqzxbdje没有值,尝试使用n_jabdje + if executionAmountStr == "" { + if nJabdjeVal, exists := caseMap["n_jabdje"]; exists && nJabdjeVal != nil { + switch v := nJabdjeVal.(type) { + case string: + if v != "" { + executionAmountStr = v + } + case float64: + executionAmountStr = strconv.FormatFloat(v, 'f', -1, 64) + case int: + executionAmountStr = strconv.Itoa(v) + case int64: + executionAmountStr = strconv.FormatInt(v, 10) + } + } + } + caseInfo["executionAmount"] = executionAmountStr + + // 已还款金额(从结案金额n_jabdje获取) + var repaidAmountStr string + if nJabdjeVal, exists := caseMap["n_jabdje"]; exists && nJabdjeVal != nil { + switch v := nJabdjeVal.(type) { + case string: + if v != "" { + repaidAmountStr = v + } + case float64: + repaidAmountStr = strconv.FormatFloat(v, 'f', -1, 64) + case int: + repaidAmountStr = strconv.Itoa(v) + case int64: + repaidAmountStr = strconv.FormatInt(v, 10) + } + } + caseInfo["repaidAmount"] = repaidAmountStr + + // 案由 + if nLaay, ok := caseMap["n_laay"].(string); ok { + caseInfo["caseReason"] = nLaay + } else { + caseInfo["caseReason"] = "未知" + } + + // 结案方式 + if nJafs, ok := caseMap["n_jafs"].(string); ok { + caseInfo["disposalMethod"] = nJafs + } else { + caseInfo["disposalMethod"] = "" + } + + caseInfo["judgmentResult"] = "" + + // 原案号(从c_ah_ys提取,保留完整值包括冒号后的ID) + if cAhYs, ok := caseMap["c_ah_ys"].(string); ok && cAhYs != "" { + caseInfo["oldCaseNumber"] = cAhYs + } else { + caseInfo["oldCaseNumber"] = "" + } + + return caseInfo +} + +// convertBreachCase 转换失信案件 +func convertBreachCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if caseNumber, ok := caseMap["caseNumber"].(string); ok { + caseInfo["caseNumber"] = caseNumber + } else { + return nil + } + + // 案件类型 + caseInfo["caseType"] = "失信被执行人" + + // 法院 + if executiveCourt, ok := caseMap["executiveCourt"].(string); ok { + caseInfo["court"] = executiveCourt + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + caseInfo["litigantType"] = "被执行人" + + // 立案时间 + if fileDate, ok := caseMap["fileDate"].(string); ok { + caseInfo["filingTime"] = fileDate + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + if issueDate, ok := caseMap["issueDate"].(string); ok { + caseInfo["disposalTime"] = issueDate + } else { + caseInfo["disposalTime"] = "" + } + + // 案件状态 + if fulfillStatus, ok := caseMap["fulfillStatus"].(string); ok { + if fulfillStatus == "全部未履行" { + caseInfo["caseStatus"] = "未结案" + } else { + caseInfo["caseStatus"] = "已结案" + } + } else { + caseInfo["caseStatus"] = "" + } + + // 执行金额 + if estimatedJudgementAmount, ok := caseMap["estimatedJudgementAmount"].(float64); ok { + caseInfo["executionAmount"] = strconv.FormatFloat(estimatedJudgementAmount, 'f', -1, 64) + } else { + caseInfo["executionAmount"] = "" + } + + caseInfo["repaidAmount"] = "" + + // 案由 + if obligation, ok := caseMap["obligation"].(string); ok { + caseInfo["caseReason"] = obligation + } else { + caseInfo["caseReason"] = "未知" + } + + // 结案方式 + if concreteDetails, ok := caseMap["concreteDetails"].(string); ok { + caseInfo["disposalMethod"] = concreteDetails + } else { + caseInfo["disposalMethod"] = "" + } + + caseInfo["judgmentResult"] = "" + + return caseInfo +} + +// convertLimitCase 转换限高案件 +func convertLimitCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if caseNumber, ok := caseMap["caseNumber"].(string); ok { + caseInfo["caseNumber"] = caseNumber + } else { + return nil + } + + // 案件类型 + caseInfo["caseType"] = "限制消费令" + + // 法院 + if executiveCourt, ok := caseMap["executiveCourt"].(string); ok { + caseInfo["court"] = executiveCourt + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + caseInfo["litigantType"] = "被限制消费人" + + // 立案时间 + if issueDate, ok := caseMap["issueDate"].(string); ok { + caseInfo["filingTime"] = issueDate + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + caseInfo["disposalTime"] = "" + caseInfo["caseStatus"] = "未结案" + caseInfo["executionAmount"] = "" + caseInfo["repaidAmount"] = "" + caseInfo["caseReason"] = "未知" + caseInfo["disposalMethod"] = "" + caseInfo["judgmentResult"] = "" + + return caseInfo +} + +// convertPreservationCase 转换保全审查案件 +func convertPreservationCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if cAh, ok := caseMap["c_ah"].(string); ok { + caseInfo["caseNumber"] = cAh + } else { + return nil // 案号是必需字段 + } + + // 案件类型 + caseInfo["caseType"] = "保全审查" + + // 法院 + if nJbfy, ok := caseMap["n_jbfy"].(string); ok { + caseInfo["court"] = nJbfy + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + if nSsdw, ok := caseMap["n_ssdw"].(string); ok { + caseInfo["litigantType"] = nSsdw + } else { + caseInfo["litigantType"] = "" + } + + // 立案时间 + if dLarq, ok := caseMap["d_larq"].(string); ok { + caseInfo["filingTime"] = dLarq + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + if dJarq, ok := caseMap["d_jarq"].(string); ok { + caseInfo["disposalTime"] = dJarq + } else { + caseInfo["disposalTime"] = "" + } + + // 案件状态 + if nAjjzjd, ok := caseMap["n_ajjzjd"].(string); ok { + caseInfo["caseStatus"] = nAjjzjd + } else { + caseInfo["caseStatus"] = "" + } + + // 执行金额(申请保全数额) + var executionAmountStr string + if nSqbqseVal, exists := caseMap["n_sqbqse"]; exists && nSqbqseVal != nil { + switch v := nSqbqseVal.(type) { + case string: + if v != "" { + executionAmountStr = v + } + case float64: + executionAmountStr = strconv.FormatFloat(v, 'f', -1, 64) + case int: + executionAmountStr = strconv.Itoa(v) + case int64: + executionAmountStr = strconv.FormatInt(v, 10) + } + } + caseInfo["executionAmount"] = executionAmountStr + + // 已还款金额(保全案件通常没有) + caseInfo["repaidAmount"] = "" + + // 案由 + if nLaay, ok := caseMap["n_laay"].(string); ok { + caseInfo["caseReason"] = nLaay + } else { + caseInfo["caseReason"] = "未知" + } + + // 结案方式 + if nJafs, ok := caseMap["n_jafs"].(string); ok { + caseInfo["disposalMethod"] = nJafs + } else { + caseInfo["disposalMethod"] = "" + } + + // 判决结果 + if cGkwsPjjg, ok := caseMap["c_gkws_pjjg"].(string); ok && cGkwsPjjg != "" { + caseInfo["judgmentResult"] = cGkwsPjjg + } else { + caseInfo["judgmentResult"] = "" + } + + return caseInfo +} + +// convertCriminalCase 转换刑事案件 +func convertCriminalCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if cAh, ok := caseMap["c_ah"].(string); ok { + caseInfo["caseNumber"] = cAh + } else { + return nil // 案号是必需字段 + } + + // 案件类型 + if nAjlx, ok := caseMap["n_ajlx"].(string); ok { + caseInfo["caseType"] = nAjlx + } else { + caseInfo["caseType"] = "刑事案件" + } + + // 法院 + if nJbfy, ok := caseMap["n_jbfy"].(string); ok { + caseInfo["court"] = nJbfy + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + if nSsdw, ok := caseMap["n_ssdw"].(string); ok { + caseInfo["litigantType"] = nSsdw + } else { + caseInfo["litigantType"] = "" + } + + // 立案时间 + if dLarq, ok := caseMap["d_larq"].(string); ok { + caseInfo["filingTime"] = dLarq + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + if dJarq, ok := caseMap["d_jarq"].(string); ok { + caseInfo["disposalTime"] = dJarq + } else { + caseInfo["disposalTime"] = "" + } + + // 案件状态 + if nAjjzjd, ok := caseMap["n_ajjzjd"].(string); ok { + caseInfo["caseStatus"] = nAjjzjd + } else { + caseInfo["caseStatus"] = "" + } + + // 执行金额(刑事案件通常没有) + caseInfo["executionAmount"] = "" + caseInfo["repaidAmount"] = "" + + // 案由 + if nLaay, ok := caseMap["n_laay"].(string); ok { + caseInfo["caseReason"] = nLaay + } else { + caseInfo["caseReason"] = "未知" + } + + // 结案方式 + if nJafs, ok := caseMap["n_jafs"].(string); ok { + caseInfo["disposalMethod"] = nJafs + } else { + caseInfo["disposalMethod"] = "" + } + + // 判决结果(只使用详细的判决结果文本c_gkws_pjjg,如果没有则返回空字符串) + if cGkwsPjjg, ok := caseMap["c_gkws_pjjg"].(string); ok && cGkwsPjjg != "" { + caseInfo["judgmentResult"] = cGkwsPjjg + } else { + caseInfo["judgmentResult"] = "" + } + + return caseInfo +} + +// convertAdministrativeCase 转换行政案件 +func convertAdministrativeCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if cAh, ok := caseMap["c_ah"].(string); ok { + caseInfo["caseNumber"] = cAh + } else { + return nil // 案号是必需字段 + } + + // 案件类型 + if nAjlx, ok := caseMap["n_ajlx"].(string); ok { + caseInfo["caseType"] = nAjlx + } else { + caseInfo["caseType"] = "行政案件" + } + + // 法院 + if nJbfy, ok := caseMap["n_jbfy"].(string); ok { + caseInfo["court"] = nJbfy + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + if nSsdw, ok := caseMap["n_ssdw"].(string); ok { + caseInfo["litigantType"] = nSsdw + } else { + caseInfo["litigantType"] = "" + } + + // 立案时间 + if dLarq, ok := caseMap["d_larq"].(string); ok { + caseInfo["filingTime"] = dLarq + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + if dJarq, ok := caseMap["d_jarq"].(string); ok { + caseInfo["disposalTime"] = dJarq + } else { + caseInfo["disposalTime"] = "" + } + + // 案件状态 + if nAjjzjd, ok := caseMap["n_ajjzjd"].(string); ok { + caseInfo["caseStatus"] = nAjjzjd + } else { + caseInfo["caseStatus"] = "" + } + + // 执行金额(行政案件通常没有) + caseInfo["executionAmount"] = "" + caseInfo["repaidAmount"] = "" + + // 案由 + if nLaay, ok := caseMap["n_laay"].(string); ok { + caseInfo["caseReason"] = nLaay + } else { + caseInfo["caseReason"] = "未知" + } + + // 结案方式 + if nJafs, ok := caseMap["n_jafs"].(string); ok { + caseInfo["disposalMethod"] = nJafs + } else { + caseInfo["disposalMethod"] = "" + } + + // 判决结果(只使用详细的判决结果文本c_gkws_pjjg,如果没有则返回空字符串) + if cGkwsPjjg, ok := caseMap["c_gkws_pjjg"].(string); ok && cGkwsPjjg != "" { + caseInfo["judgmentResult"] = cGkwsPjjg + } else { + caseInfo["judgmentResult"] = "" + } + + return caseInfo +} + +// convertBankruptCase 转换破产清算案件 +func convertBankruptCase(caseMap map[string]interface{}) map[string]interface{} { + caseInfo := make(map[string]interface{}) + + // 案号 + if cAh, ok := caseMap["c_ah"].(string); ok { + caseInfo["caseNumber"] = cAh + } else { + return nil // 案号是必需字段 + } + + // 案件类型 + if nAjlx, ok := caseMap["n_ajlx"].(string); ok { + caseInfo["caseType"] = nAjlx + } else { + caseInfo["caseType"] = "破产清算" + } + + // 法院 + if nJbfy, ok := caseMap["n_jbfy"].(string); ok { + caseInfo["court"] = nJbfy + } else { + caseInfo["court"] = "" + } + + // 诉讼地位 + if nSsdw, ok := caseMap["n_ssdw"].(string); ok { + caseInfo["litigantType"] = nSsdw + } else { + caseInfo["litigantType"] = "" + } + + // 立案时间 + if dLarq, ok := caseMap["d_larq"].(string); ok { + caseInfo["filingTime"] = dLarq + } else { + caseInfo["filingTime"] = "" + } + + // 结案时间 + if dJarq, ok := caseMap["d_jarq"].(string); ok { + caseInfo["disposalTime"] = dJarq + } else { + caseInfo["disposalTime"] = "" + } + + // 案件状态 + if nAjjzjd, ok := caseMap["n_ajjzjd"].(string); ok { + caseInfo["caseStatus"] = nAjjzjd + } else { + caseInfo["caseStatus"] = "" + } + + // 执行金额(破产案件通常没有) + caseInfo["executionAmount"] = "" + caseInfo["repaidAmount"] = "" + + // 案由 + if nLaay, ok := caseMap["n_laay"].(string); ok { + caseInfo["caseReason"] = nLaay + } else { + caseInfo["caseReason"] = "未知" + } + + // 结案方式 + if nJafs, ok := caseMap["n_jafs"].(string); ok { + caseInfo["disposalMethod"] = nJafs + } else { + caseInfo["disposalMethod"] = "" + } + + // 判决结果(只使用详细的判决结果文本c_gkws_pjjg,如果没有则返回空字符串) + if cGkwsPjjg, ok := caseMap["c_gkws_pjjg"].(string); ok && cGkwsPjjg != "" { + caseInfo["judgmentResult"] = cGkwsPjjg + } else { + caseInfo["judgmentResult"] = "" + } + + return caseInfo +} + +// buildLoanEvaluationVerificationDetail 构建借贷评估产品 +func buildLoanEvaluationVerificationDetail(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + detail := make(map[string]interface{}) + + // 初始化默认值 + detail["riskFlag"] = 0 + detail["organLoanPerformances"] = []interface{}{} + detail["customerLoanPerformances"] = []interface{}{} + detail["businessLoanPerformances"] = []interface{}{} + detail["timeLoanPerformances"] = []interface{}{} + + // 从借贷意向验证A获取数据 + jrzq6f2aData := getMapValue(apiData, "JRZQ6F2A") + if jrzq6f2aData != nil { + if jrzq6f2aMap, ok := jrzq6f2aData.(map[string]interface{}); ok { + // 获取risk_screen_v2 + if riskScreenV2, ok := jrzq6f2aMap["risk_screen_v2"].(map[string]interface{}); ok { + // 获取variables + if variables, ok := riskScreenV2["variables"].([]interface{}); ok && len(variables) > 0 { + if variable, ok := variables[0].(map[string]interface{}); ok { + if variableValue, ok := variable["variableValue"].(map[string]interface{}); ok { + // 构建organLoanPerformances(本人在本机构借贷意向表现) + organLoanPerformances := buildOrganLoanPerformances(variableValue) + if len(organLoanPerformances) > 0 { + detail["organLoanPerformances"] = organLoanPerformances + } + + // 构建customerLoanPerformances(本人在各个客户类型借贷意向表现) + customerLoanPerformances := buildCustomerLoanPerformances(variableValue) + if len(customerLoanPerformances) > 0 { + detail["customerLoanPerformances"] = customerLoanPerformances + } + + // 构建businessLoanPerformances(本人在各个业务类型借贷意向表现) + businessLoanPerformances := buildBusinessLoanPerformances(variableValue) + if len(businessLoanPerformances) > 0 { + detail["businessLoanPerformances"] = businessLoanPerformances + } + + // 构建timeLoanPerformances(本人在异常时间段借贷意向表现) + timeLoanPerformances := buildTimeLoanPerformances(variableValue) + if len(timeLoanPerformances) > 0 { + detail["timeLoanPerformances"] = timeLoanPerformances + } + + // 判断风险标识:根据申请频率判断 + hasHighRisk := checkLoanRisk(variableValue) + if hasHighRisk { + detail["riskFlag"] = 1 // 高风险 + } else { + detail["riskFlag"] = 2 // 低风险 + } + } + } + } + } + } + } + + return detail +} + +// buildOrganLoanPerformances 构建本人在本机构借贷意向表现 +func buildOrganLoanPerformances(variableValue map[string]interface{}) []interface{} { + performances := make([]interface{}, 0) + + // 时间周期映射 + periodMap := map[string]string{ + "last7Day": "d7", + "last15Day": "d15", + "last1Month": "m1", + "last3Month": "m3", + "last6Month": "m6", + "last12Month": "m12", + } + + // 银行(格式:身份证/手机号) + bankPerf := make(map[string]interface{}) + bankPerf["applyCount"] = "银行" + for period, apiPeriod := range periodMap { + idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_bank_allnum", apiPeriod)) + cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_bank_allnum", apiPeriod)) + if idAllnum == "" { + idAllnum = "0" + } + if cellAllnum == "" { + cellAllnum = "0" + } + bankPerf[period] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum) + } + performances = append(performances, bankPerf) + + // 非银(格式:身份证/手机号) + nbankPerf := make(map[string]interface{}) + nbankPerf["applyCount"] = "非银" + for period, apiPeriod := range periodMap { + idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_nbank_allnum", apiPeriod)) + cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_nbank_allnum", apiPeriod)) + if idAllnum == "" { + idAllnum = "0" + } + if cellAllnum == "" { + cellAllnum = "0" + } + nbankPerf[period] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum) + } + performances = append(performances, nbankPerf) + + return performances +} + +// buildCustomerLoanPerformances 构建本人在各个客户类型借贷意向表现 +func buildCustomerLoanPerformances(variableValue map[string]interface{}) []interface{} { + performances := make([]interface{}, 0) + + // 客户类型列表 + customerTypes := []struct { + typeName string + prefix string + }{ + {"银行汇总", "bank"}, + {"传统银行", "bank_tra"}, + {"网络零售银行", "bank_ret"}, + {"非银汇总", "nbank"}, + {"持牌网络小贷", "nbank_nsloan"}, + {"持牌消费金融", "nbank_cons"}, + {"持牌融资租赁机构", "nbank_finlea"}, + {"持牌汽车金融", "nbank_autofin"}, + {"其他", "nbank_oth"}, + } + + periodMap := map[string]string{ + "last7Day": "d7", + "last15Day": "d15", + "last1Month": "m1", + "last3Month": "m3", + "last6Month": "m6", + "last12Month": "m12", + } + + for _, customerType := range customerTypes { + perf := make(map[string]interface{}) + perf["type"] = customerType.typeName + + for period, apiPeriod := range periodMap { + // 格式:身份证/手机号 + idOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_orgnum", apiPeriod, customerType.prefix)) + cellOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_orgnum", apiPeriod, customerType.prefix)) + idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_allnum", apiPeriod, customerType.prefix)) + cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_allnum", apiPeriod, customerType.prefix)) + + if idOrgnum == "" { + idOrgnum = "0" + } + if cellOrgnum == "" { + cellOrgnum = "0" + } + if idAllnum == "" { + idAllnum = "0" + } + if cellAllnum == "" { + cellAllnum = "0" + } + + perf[period] = fmt.Sprintf("%s/%s", idOrgnum, cellOrgnum) + perf[period+"Count"] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum) + } + + performances = append(performances, perf) + } + + return performances +} + +// buildBusinessLoanPerformances 构建本人在各个业务类型借贷意向表现 +func buildBusinessLoanPerformances(variableValue map[string]interface{}) []interface{} { + performances := make([]interface{}, 0) + + // 业务类型列表 + businessTypes := []struct { + typeName string + prefix string + }{ + {"信用卡(类信用卡)", "rel"}, + {"线上小额现金贷", "pdl"}, + {"汽车金融", "af"}, + {"线上消费分期", "coon"}, + {"线下消费分期", "cooff"}, + {"其他", "nbank_oth"}, // 修复:应该是nbank_oth而不是oth + } + + periodMap := map[string]string{ + "last7Day": "d7", + "last15Day": "d15", + "last1Month": "m1", + "last3Month": "m3", + "last6Month": "m6", + "last12Month": "m12", + } + + for _, businessType := range businessTypes { + perf := make(map[string]interface{}) + perf["type"] = businessType.typeName + + for period, apiPeriod := range periodMap { + // 格式:身份证/手机号 + idOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_orgnum", apiPeriod, businessType.prefix)) + cellOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_orgnum", apiPeriod, businessType.prefix)) + idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_allnum", apiPeriod, businessType.prefix)) + cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_allnum", apiPeriod, businessType.prefix)) + + if idOrgnum == "" { + idOrgnum = "0" + } + if cellOrgnum == "" { + cellOrgnum = "0" + } + if idAllnum == "" { + idAllnum = "0" + } + if cellAllnum == "" { + cellAllnum = "0" + } + + perf[period] = fmt.Sprintf("%s/%s", idOrgnum, cellOrgnum) + perf[period+"Count"] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum) + } + + performances = append(performances, perf) + } + + return performances +} + +// buildTimeLoanPerformances 构建本人在异常时间段借贷意向表现 +func buildTimeLoanPerformances(variableValue map[string]interface{}) []interface{} { + performances := make([]interface{}, 0) + + // 异常时间段类型 + timeTypes := []struct { + typeName string + timeType string + orgType string + }{ + {"夜间-银行", "night", "bank"}, + {"夜间-非银", "night", "nbank"}, + {"周末-银行", "week", "bank"}, + {"周末-非银", "week", "nbank"}, + } + + periodMap := map[string]string{ + "last7Day": "d7", + "last15Day": "d15", + "last1Month": "m1", + "last3Month": "m3", + "last6Month": "m6", + "last12Month": "m12", + } + + for _, timeType := range timeTypes { + perf := make(map[string]interface{}) + perf["type"] = timeType.typeName + + for period, apiPeriod := range periodMap { + // 格式:身份证/手机号 + idOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_%s_orgnum", apiPeriod, timeType.orgType, timeType.timeType)) + cellOrgnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_%s_orgnum", apiPeriod, timeType.orgType, timeType.timeType)) + idAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_%s_%s_allnum", apiPeriod, timeType.orgType, timeType.timeType)) + cellAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_cell_%s_%s_allnum", apiPeriod, timeType.orgType, timeType.timeType)) + + if idOrgnum == "" { + idOrgnum = "0" + } + if cellOrgnum == "" { + cellOrgnum = "0" + } + if idAllnum == "" { + idAllnum = "0" + } + if cellAllnum == "" { + cellAllnum = "0" + } + + perf[period] = fmt.Sprintf("%s/%s", idOrgnum, cellOrgnum) + perf[period+"Count"] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum) + } + + performances = append(performances, perf) + } + + return performances +} + +// checkLoanRisk 检查借贷风险 +func checkLoanRisk(variableValue map[string]interface{}) bool { + // 检查近期申请频率是否过高 + periods := []string{"d7", "d15", "m1", "m3"} + for _, period := range periods { + bankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_bank_allnum", period)) + nbankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_nbank_allnum", period)) + + if bankAllnum != "" && bankAllnum != "0" { + if count, err := strconv.Atoi(bankAllnum); err == nil && count >= 5 { + return true + } + } + if nbankAllnum != "" && nbankAllnum != "0" { + if count, err := strconv.Atoi(nbankAllnum); err == nil && count >= 5 { + return true + } + } + } + return false +} + +// getStringValue 安全获取字符串值 +func getStringValue(data map[string]interface{}, key string) string { + if val, ok := data[key]; ok { + if str, ok := val.(string); ok { + return str + } + } + return "" +} + +// convertXypT01aazzzcToInterval 将xyp_t01aazzzc的值转换为区间格式 +// 映射规则:1->(0,1000), 2->[1000,4800), 3->[4800,Inf) +func convertXypT01aazzzcToInterval(value string) string { + switch value { + case "1": + return "(0,1000)" + case "2": + return "[1000,4800)" + case "3": + return "[4800,+)" + default: + return "0" + } +} + +// convertXypCpl0068ToInterval 将xyp_cpl0068的值转换为区间格式 +// 映射规则:1->(0,5), 2->[5,50), 3->[50,160), 4->[160,Inf) +func convertXypCpl0068ToInterval(value string) string { + switch value { + case "1": + return "(0,5)" + case "2": + return "[5,50)" + case "3": + return "[50,160)" + case "4": + return "160+" + default: + return "0" + } +} + +// convertXypCpl0001ToInterval 将xyp_cpl0001的值转换为区间格式 +// 映射规则:1->(0,9), 2->[9,14), 3->[14,Inf) +func convertXypCpl0001ToInterval(value string) string { + switch value { + case "1": + return "(0,9)" + case "2": + return "[9,14)" + case "3": + return "[14,+)" + default: + return "0" + } +} + +// convertXypCpl0002ToInterval 将xyp_cpl0002的值转换为区间格式 +// 映射规则:1->(0,5), 2->[5,9), 3->[9,14), 4->[14,17), 5->[17,Inf) +func convertXypCpl0002ToInterval(value string) string { + switch value { + case "1": + return "(0,5)" + case "2": + return "[5,9)" + case "3": + return "[9,14)" + case "4": + return "[14,17)" + case "5": + return "[17,+)" + default: + return "0" + } +} + +// convertXypCpl0071ToInterval 将xyp_cpl0071的值转换为区间格式 +// 映射规则:1->[1,2), 2->[2,4), 3->[4,Inf) +func convertXypCpl0071ToInterval(value string) string { + switch value { + case "1": + return "[1,2)" + case "2": + return "[2,4)" + case "3": + return "[4,+)" + default: + return "0" + } +} + +// convertXypCpl0072ToInterval 将xyp_cpl0072的值转换为区间格式 +// 映射规则:1->(0,1000), 2->[1000,2000), 3->[2000,3000), 4->[3000,5000), 5->[5000,7000), 6->[7000,11000), 7->[11000,Inf) +func convertXypCpl0072ToInterval(value string) string { + switch value { + case "1": + return "(0,1000)" + case "2": + return "[1000,2000)" + case "3": + return "[2000,3000)" + case "4": + return "[3000,5000)" + case "5": + return "[5000,7000)" + case "6": + return "[7000,11000)" + case "7": + return "[11000,+)" + default: + return "0" + } +} + +// convertXypCpl0018ToInterval 将xyp_cpl0018的值转换为区间格式 +// 映射规则:1->(0,3), 2->[3,5), 3->[5,7), 4->[7,Inf) +func convertXypCpl0018ToInterval(value string) string { + switch value { + case "1": + return "(0,3)" + case "2": + return "[3,5)" + case "3": + return "[5,7)" + case "4": + return "[7,+)" + default: + return "0" + } +} + +// convertXypCpl0019ToInterval 将xyp_cpl0019的值转换为区间格式 +// 映射规则:1->(0,3), 2->[3,13), 3->[13,Inf) +func convertXypCpl0019ToInterval(value string) string { + switch value { + case "1": + return "(0,3)" + case "2": + return "[3,13)" + case "3": + return "[13,+)" + default: + return "0" + } +} + +// convertXypCpl0020ToInterval 将xyp_cpl0020的值转换为区间格式 +// 映射规则:1->(0,3), 2->[3,5), 3->[5,15), 4->[15,Inf) +func convertXypCpl0020ToInterval(value string) string { + switch value { + case "1": + return "(0,3)" + case "2": + return "[3,5)" + case "3": + return "[5,15)" + case "4": + return "[15,+)" + default: + return "0" + } +} + +// convertXypCpl0021ToInterval 将xyp_cpl0021的值转换为区间格式 +// 映射规则:1->(0,5), 2->[5,21), 3->[21,Inf) +func convertXypCpl0021ToInterval(value string) string { + switch value { + case "1": + return "(0,5)" + case "2": + return "[5,21)" + case "3": + return "[21,+)" + default: + return "0" + } +} + +// convertXypCpl0022ToInterval 将xyp_cpl0022的值转换为区间格式 +// 映射规则:1->(0,3), 2->[3,5), 3->[5,34), 4->[34,Inf) +func convertXypCpl0022ToInterval(value string) string { + switch value { + case "1": + return "(0,3)" + case "2": + return "[3,5)" + case "3": + return "[5,34)" + case "4": + return "[34,+)" + default: + return "0" + } +} + +// convertXypCpl0023ToInterval 将xyp_cpl0023的值转换为区间格式 +// 映射规则:1->(0,7), 2->[7,34), 3->[34,Inf) +func convertXypCpl0023ToInterval(value string) string { + switch value { + case "1": + return "(0,7)" + case "2": + return "[7,34)" + case "3": + return "[34,+)" + default: + return "0" + } +} + +// convertXypCpl0024ToInterval 将xyp_cpl0024的值转换为区间格式 +// 映射规则:1->(0,6), 2->[6,22), 3->[22,56), 4->[56,Inf) +func convertXypCpl0024ToInterval(value string) string { + switch value { + case "1": + return "(0,6)" + case "2": + return "[6,22)" + case "3": + return "[22,56)" + case "4": + return "[56,+)" + default: + return "0" + } +} + +// convertXypCpl0025ToInterval 将xyp_cpl0025的值转换为区间格式 +// 映射规则:1->(0,2), 2->[2,31), 3->[31,Inf) +func convertXypCpl0025ToInterval(value string) string { + switch value { + case "1": + return "(0,2)" + case "2": + return "[2,31)" + case "3": + return "[31,+)" + default: + return "0" + } +} + +// convertXypCpl0026ToInterval 将xyp_cpl0026的值转换为区间格式 +// 映射规则:1->(0,3), 2->[3,25), 3->[25,30), 4->[30,70), 5->[70,Inf) +func convertXypCpl0026ToInterval(value string) string { + switch value { + case "1": + return "(0,3)" + case "2": + return "[3,25)" + case "3": + return "[25,30)" + case "4": + return "[30,70)" + case "5": + return "[70,+)" + default: + return "0" + } +} + +// convertXypCpl0034ToInterval 将xyp_cpl0034的值转换为区间格式 +// 映射规则:1->(0,2000), 2->[2000,10000), 3->[10000,17000), 4->[17000,26000), 5->[26000,Inf) +func convertXypCpl0034ToInterval(value string) string { + switch value { + case "1": + return "(0,2000)" + case "2": + return "[2000,10000)" + case "3": + return "[10000,17000)" + case "4": + return "[17000,26000)" + case "5": + return "[26000,+)" + default: + return "0" + } +} + +// convertXypCpl0035ToInterval 将xyp_cpl0035的值转换为区间格式 +// 映射规则:1->(0,2000), 2->[2000,17000), 3->[17000,Inf) +func convertXypCpl0035ToInterval(value string) string { + switch value { + case "1": + return "(0,2000)" + case "2": + return "[2000,17000)" + case "3": + return "[17000,+)" + default: + return "0" + } +} + +// convertXypCpl0036ToInterval 将xyp_cpl0036的值转换为区间格式 +// 映射规则:1->(0,1000), 2->[1000,4000), 3->[4000,9000), 4->[9000,30000), 5->[30000,Inf) +func convertXypCpl0036ToInterval(value string) string { + switch value { + case "1": + return "(0,1000)" + case "2": + return "[1000,4000)" + case "3": + return "[4000,9000)" + case "4": + return "[9000,30000)" + case "5": + return "[30000,+)" + default: + return "0" + } +} + +// convertXypCpl0037ToInterval 将xyp_cpl0037的值转换为区间格式 +// 映射规则:1->(0,9000), 2->[9000,31000), 3->[31000,Inf) +func convertXypCpl0037ToInterval(value string) string { + switch value { + case "1": + return "(0,9000)" + case "2": + return "[9000,31000)" + case "3": + return "[31000,+)" + default: + return "0" + } +} + +// convertXypCpl0038ToInterval 将xyp_cpl0038的值转换为区间格式 +// 映射规则:1->(0,6000), 2->[6000,10000), 3->[10000,50000), 4->[50000,Inf) +func convertXypCpl0038ToInterval(value string) string { + switch value { + case "1": + return "(0,6000)" + case "2": + return "[6000,10000)" + case "3": + return "[10000,50000)" + case "4": + return "[50000,+)" + default: + return "0" + } +} + +// convertXypCpl0039ToInterval 将xyp_cpl0039的值转换为区间格式 +// 映射规则:1->(0,10000), 2->[10000,36000), 3->[36000,Inf) +func convertXypCpl0039ToInterval(value string) string { + switch value { + case "1": + return "(0,10000)" + case "2": + return "[10000,36000)" + case "3": + return "[36000,+)" + default: + return "0" + } +} + +// convertXypCpl0040ToInterval 将xyp_cpl0040的值转换为区间格式 +// 映射规则:1->(0,10000), 2->[10000,50000), 3->[50000,80000), 4->[80000,Inf) +func convertXypCpl0040ToInterval(value string) string { + switch value { + case "1": + return "(0,10000)" + case "2": + return "[10000,50000)" + case "3": + return "[50000,80000)" + case "4": + return "[80000,+)" + default: + return "0" + } +} + +// convertXypCpl0041ToInterval 将xyp_cpl0041的值转换为区间格式 +// 映射规则:1->(0,13000), 2->[13000,49000), 3->[49000,Inf) +func convertXypCpl0041ToInterval(value string) string { + switch value { + case "1": + return "(0,13000)" + case "2": + return "[13000,49000)" + case "3": + return "[49000,+)" + default: + return "0" + } +} + +// convertXypCpl0042ToInterval 将xyp_cpl0042的值转换为区间格式 +// 映射规则:1->(0,2000), 2->[2000,30000), 3->[30000,60000), 4->[60000,90000), 5->[90000,Inf) +func convertXypCpl0042ToInterval(value string) string { + switch value { + case "1": + return "(0,2000)" + case "2": + return "[2000,30000)" + case "3": + return "[30000,60000)" + case "4": + return "[60000,90000)" + case "5": + return "[90000,+)" + default: + return "0" + } +} + +// checkRecentApplicationFrequency 检查近期申请频率 +func checkRecentApplicationFrequency(variableValue map[string]interface{}, riskWarning *map[string]interface{}) { + // 检查近7天、近15天、近1个月的申请次数 + periods := []struct { + apiPeriod string + field string + }{ + {"d7", "frequentApplicationRecent"}, + {"d15", "frequentApplicationRecent"}, + {"m1", "frequentApplicationRecent"}, + } + + for _, period := range periods { + bankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_bank_allnum", period.apiPeriod)) + nbankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_nbank_allnum", period.apiPeriod)) + + // 如果近7天申请次数>=5,认为是极为频繁 + if bankAllnum != "" && bankAllnum != "0" { + if count, err := strconv.Atoi(bankAllnum); err == nil && count >= 5 { + (*riskWarning)[period.field] = 1 + break + } + } + if nbankAllnum != "" && nbankAllnum != "0" { + if count, err := strconv.Atoi(nbankAllnum); err == nil && count >= 5 { + (*riskWarning)[period.field] = 1 + break + } + } + } +} + +// checkBankApplicationFrequency 检查银行/非银申请次数 +func checkBankApplicationFrequency(variableValue map[string]interface{}, riskWarning *map[string]interface{}) { + // 检查近6个月和近12个月的申请次数 + periods := []struct { + apiPeriod string + }{ + {"m6"}, + {"m12"}, + } + + bankTotal := 0 + nbankTotal := 0 + + for _, period := range periods { + bankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_bank_allnum", period.apiPeriod)) + nbankAllnum := getStringValue(variableValue, fmt.Sprintf("als_%s_id_nbank_allnum", period.apiPeriod)) + + if bankAllnum != "" && bankAllnum != "0" { + if count, err := strconv.Atoi(bankAllnum); err == nil { + bankTotal += count + } + } + if nbankAllnum != "" && nbankAllnum != "0" { + if count, err := strconv.Atoi(nbankAllnum); err == nil { + nbankTotal += count + } + } + } + + // 银行申请次数极多(>=10) + if bankTotal >= 10 { + (*riskWarning)["frequentBankApplications"] = 1 + } else if bankTotal >= 5 { + (*riskWarning)["moreFrequentBankApplications"] = 1 + } + + // 非银申请次数极多(>=10) + if nbankTotal >= 10 { + (*riskWarning)["frequentNonBankApplications"] = 1 + } else if nbankTotal >= 5 { + (*riskWarning)["moreFrequentNonBankApplications"] = 1 + } +} + +// checkDebtPressure 检查偿债压力 +func checkDebtPressure(apiData map[string]interface{}, riskWarning *map[string]interface{}) { + // 从借选指数评估获取数据 + jrzq5e9fData := getMapValue(apiData, "JRZQ5E9F") + if jrzq5e9fData != nil { + if jrzq5e9fMap, ok := jrzq5e9fData.(map[string]interface{}); ok { + // 检查当前逾期金额和机构数 + currentOverdueAmount := getStringValue(jrzq5e9fMap, "xyp_cpl0072") + currentOverdueInstitutionCount := getStringValue(jrzq5e9fMap, "xyp_cpl0071") + + // 如果当前逾期金额较大或机构数较多,认为偿债压力极高 + if currentOverdueAmount != "" && currentOverdueAmount != "0" && currentOverdueAmount != "1" { + (*riskWarning)["highDebtPressure"] = 1 + } + if currentOverdueInstitutionCount != "" && currentOverdueInstitutionCount != "0" && currentOverdueInstitutionCount != "1" { + (*riskWarning)["highDebtPressure"] = 1 + } + } + } +} + +// calculateLoanRiskCounts 计算借贷评估风险计数 +func calculateLoanRiskCounts(variableValue map[string]interface{}, riskWarning *map[string]interface{}) { + // 计算jdpgRiskCounts(借贷评估风险计数) + jdpgRiskCount := 0 + if val, ok := (*riskWarning)["frequentApplicationRecent"].(int); ok && val == 1 { + jdpgRiskCount++ + } + if val, ok := (*riskWarning)["frequentBankApplications"].(int); ok && val == 1 { + jdpgRiskCount++ + } + if val, ok := (*riskWarning)["frequentNonBankApplications"].(int); ok && val == 1 { + jdpgRiskCount++ + } + if val, ok := (*riskWarning)["highDebtPressure"].(int); ok && val == 1 { + jdpgRiskCount++ + } + + if jdpgRiskCount > 0 { + (*riskWarning)["jdpgRiskCounts"] = jdpgRiskCount + if jdpgRiskCount >= 3 { + (*riskWarning)["jdpgRiskHighCounts"] = 1 + } else { + (*riskWarning)["jdpgRiskMiddleCounts"] = 1 + } + } +} + +// checkRentalApplicationFrequency 检查租赁申请频率 +func checkRentalApplicationFrequency(jrzq1d09Map map[string]interface{}, riskWarning *map[string]interface{}) { + // 检查近3个月和近6个月的申请次数 + periods := []struct { + apiPeriod string + }{ + {"m3"}, + {"m6"}, + {"m12"}, + } + + totalCount := 0 + + for _, period := range periods { + idAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_allnum", period.apiPeriod)) + cellAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_allnum", period.apiPeriod)) + + // 取较大的值 + idCount := 0 + cellCount := 0 + if idAllnum != "" && idAllnum != "0" { + if count, err := strconv.Atoi(idAllnum); err == nil { + idCount = count + } + } + if cellAllnum != "" && cellAllnum != "0" { + if count, err := strconv.Atoi(cellAllnum); err == nil { + cellCount = count + } + } + + if idCount > cellCount { + totalCount += idCount + } else { + totalCount += cellCount + } + } + + // 租赁申请次数极多(>=10) + if totalCount >= 10 { + (*riskWarning)["veryFrequentRentalApplications"] = 1 + (*riskWarning)["frequentRentalApplications"] = 1 + } else if totalCount >= 5 { + (*riskWarning)["frequentRentalApplications"] = 1 + } +} + +// buildLeasingRiskAssessment 构建租赁风险评估产品 +func buildLeasingRiskAssessment(apiData map[string]interface{}, log *zap.Logger) map[string]interface{} { + assessment := make(map[string]interface{}) + + // 设置默认值 + assessment["riskFlag"] = 0 + // 设置所有3C相关的字段为默认值 + timePeriods := []string{"3Days", "7Days", "14Days", "Month", "3Months", "6Months", "12Months"} + timeTypes := []string{"Night", "Weekend"} + queryTypes := []string{"Platform", "Institution"} + + for _, period := range timePeriods { + for _, queryType := range queryTypes { + fieldName := fmt.Sprintf("threeC%sApplicationCountLast%s", queryType, period) + assessment[fieldName] = "0/0" + for _, timeType := range timeTypes { + fieldName := fmt.Sprintf("threeC%sApplicationCountLast%s%s", queryType, period, timeType) + assessment[fieldName] = "0/0" + } + } + } + + // 从3C租赁申请意向获取数据 + jrzq1d09Data := getMapValue(apiData, "JRZQ1D09") + if jrzq1d09Data != nil { + if jrzq1d09Map, ok := jrzq1d09Data.(map[string]interface{}); ok { + // 映射规则: + // - Institution(3C机构): 使用id(身份证)查询的数据 + // - Platform(3C平台): 使用cell(手机号)查询的数据 + // 格式: "身份证/手机号",如果没有匹配的就是0 + + // 时间周期映射:3Days->d3, 7Days->d7, 14Days->d14, Month->m1, 3Months->m3, 6Months->m6, 12Months->m12 + periodMap := map[string]string{ + "3Days": "d3", + "7Days": "d7", + "14Days": "d14", + "Month": "m1", + "3Months": "m3", + "6Months": "m6", + "12Months": "m12", + } + + // 填充Institution字段(3C机构,格式:身份证/手机号) + for period, apiPeriod := range periodMap { + // 基础字段:threeCInstitutionApplicationCountLast{period} + fieldName := fmt.Sprintf("threeCInstitutionApplicationCountLast%s", period) + idAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_allnum", apiPeriod)) + cellAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_allnum", apiPeriod)) + if idAllnum == "" { + idAllnum = "0" + } + if cellAllnum == "" { + cellAllnum = "0" + } + assessment[fieldName] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum) + + // Weekend字段 + fieldNameWeekend := fmt.Sprintf("threeCInstitutionApplicationCountLast%sWeekend", period) + idWeekendAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_weekend_allnum", apiPeriod)) + cellWeekendAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_weekend_allnum", apiPeriod)) + if idWeekendAllnum == "" { + idWeekendAllnum = "0" + } + if cellWeekendAllnum == "" { + cellWeekendAllnum = "0" + } + assessment[fieldNameWeekend] = fmt.Sprintf("%s/%s", idWeekendAllnum, cellWeekendAllnum) + + // Night字段 + fieldNameNight := fmt.Sprintf("threeCInstitutionApplicationCountLast%sNight", period) + idNightAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_night_allnum", apiPeriod)) + cellNightAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_night_allnum", apiPeriod)) + if idNightAllnum == "" { + idNightAllnum = "0" + } + if cellNightAllnum == "" { + cellNightAllnum = "0" + } + assessment[fieldNameNight] = fmt.Sprintf("%s/%s", idNightAllnum, cellNightAllnum) + } + + // 填充Platform字段(3C平台,格式:身份证/手机号) + for period, apiPeriod := range periodMap { + // 基础字段:threeCPlatformApplicationCountLast{period} + fieldName := fmt.Sprintf("threeCPlatformApplicationCountLast%s", period) + idAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_allnum", apiPeriod)) + cellAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_allnum", apiPeriod)) + if idAllnum == "" { + idAllnum = "0" + } + if cellAllnum == "" { + cellAllnum = "0" + } + assessment[fieldName] = fmt.Sprintf("%s/%s", idAllnum, cellAllnum) + + // Weekend字段 + fieldNameWeekend := fmt.Sprintf("threeCPlatformApplicationCountLast%sWeekend", period) + idWeekendAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_weekend_allnum", apiPeriod)) + cellWeekendAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_weekend_allnum", apiPeriod)) + if idWeekendAllnum == "" { + idWeekendAllnum = "0" + } + if cellWeekendAllnum == "" { + cellWeekendAllnum = "0" + } + assessment[fieldNameWeekend] = fmt.Sprintf("%s/%s", idWeekendAllnum, cellWeekendAllnum) + + // Night字段 + fieldNameNight := fmt.Sprintf("threeCPlatformApplicationCountLast%sNight", period) + idNightAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_id_night_allnum", apiPeriod)) + cellNightAllnum := getStringValue(jrzq1d09Map, fmt.Sprintf("alc_%s_cell_night_allnum", apiPeriod)) + if idNightAllnum == "" { + idNightAllnum = "0" + } + if cellNightAllnum == "" { + cellNightAllnum = "0" + } + assessment[fieldNameNight] = fmt.Sprintf("%s/%s", idNightAllnum, cellNightAllnum) + } + + // 判断风险标识:如果有申请记录,设置为高风险 + hasApplication := false + for _, apiPeriod := range periodMap { + fieldName := fmt.Sprintf("alc_%s_id_allnum", apiPeriod) + // 尝试多种类型:string、int、float64 + if allnum, ok := jrzq1d09Map[fieldName].(string); ok && allnum != "" && allnum != "0" { + hasApplication = true + break + } else if allnum, ok := jrzq1d09Map[fieldName].(int); ok && allnum > 0 { + hasApplication = true + break + } else if allnum, ok := jrzq1d09Map[fieldName].(float64); ok && allnum > 0 { + hasApplication = true + break + } + // 也检查cell字段 + fieldNameCell := fmt.Sprintf("alc_%s_cell_allnum", apiPeriod) + if allnum, ok := jrzq1d09Map[fieldNameCell].(string); ok && allnum != "" && allnum != "0" { + hasApplication = true + break + } else if allnum, ok := jrzq1d09Map[fieldNameCell].(int); ok && allnum > 0 { + hasApplication = true + break + } else if allnum, ok := jrzq1d09Map[fieldNameCell].(float64); ok && allnum > 0 { + hasApplication = true + break + } + } + // 风险标识:如果有申请记录,风险较高;如果没有申请记录,风险较低 + // 注意:riskFlag的值:0=未查得,1=低风险,2=高风险 + if hasApplication { + assessment["riskFlag"] = 2 // 有申请记录,风险较高 + } else { + assessment["riskFlag"] = 1 // 无申请记录,风险较低 + } + } + } + + return assessment +} + +// 辅助函数 + +func getMapValue(data map[string]interface{}, key string) interface{} { + if val, ok := data[key]; ok { + return val + } + return nil +} + +func maskName(name string) string { + if name == "" { + return "" + } + // 使用rune处理Unicode字符,支持中文等多字节字符 + runes := []rune(name) + if len(runes) <= 1 { + return name + } + if len(runes) == 2 { + // 两个字符:第一个字符 + "*" + return string(runes[0]) + "*" + } + // 三个或以上字符:第一个字符 + "*" + 最后一个字符 + return string(runes[0]) + "*" + string(runes[len(runes)-1]) +} + +func maskIDCard(idCard string) string { + if len(idCard) < 8 { + return idCard + } + return idCard[:4] + "**" + idCard[len(idCard)-6:] +} + +func maskMobile(mobile string) string { + if len(mobile) < 7 { + return mobile + } + return mobile[:3] + "****" + mobile[len(mobile)-4:] +} + +func calculateAgeAndSex(idCard string) (int, string) { + if len(idCard) != 18 { + return 0, "" + } + + // 提取出生年份 + yearStr := idCard[6:10] + year, err := strconv.Atoi(yearStr) + if err != nil { + return 0, "" + } + + // 计算年龄(简化处理,使用当前年份) + age := 2024 - year + + // 提取性别(第17位,奇数为男,偶数为女) + sexCode := idCard[16:17] + sexCodeInt, err := strconv.Atoi(sexCode) + if err != nil { + return age, "" + } + + sex := "女" + if sexCodeInt%2 == 1 { + sex = "男" + } + + return age, sex +} + +func getLocationFromIDCard(idCard string) string { + if len(idCard) < 6 { + return "" + } + // 这里需要根据身份证前6位查询地区代码表 + // 简化处理,返回空字符串 + return "" +} + +func getPhoneArea(mobile string) string { + // 这里需要根据手机号前7位查询归属地 + // 简化处理,返回空字符串 + return "" +} + +func convertChannel(channel string) string { + channelMap := map[string]string{ + "cmcc": "中国移动", + "cucc": "中国联通", + "ctcc": "中国电信", + "gdcc": "中国广电", + } + if converted, ok := channelMap[strings.ToLower(channel)]; ok { + return converted + } + return channel +} + +// convertChannelName 转换运营商名称(处理中文名称) +func convertChannelName(channel string) string { + // 如果已经是中文名称,直接返回 + if strings.Contains(channel, "移动") || strings.Contains(channel, "联通") || + strings.Contains(channel, "电信") || strings.Contains(channel, "广电") { + return channel + } + // 否则使用convertChannel转换 + return convertChannel(channel) +} + +// convertStatusFromOnlineStatus 根据手机在网状态V即时版的响应转换为status +// status: -1:未查得; 0:空号; 1:实号; 2:停机; 3:库无; 4:沉默号; 5:风险号 +func convertStatusFromOnlineStatus(data map[string]interface{}) int { + // 获取status字段(0-在网,1-不在网) + var statusVal interface{} + var ok bool + + // status可能是int或float64类型 + if statusVal, ok = data["status"]; !ok { + return -1 // 未查得 + } + + var statusInt int + switch v := statusVal.(type) { + case int: + statusInt = v + case float64: + statusInt = int(v) + case string: + if parsed, err := strconv.Atoi(v); err == nil { + statusInt = parsed + } else { + return -1 + } + default: + return -1 + } + + // status=0表示在网,直接返回实号 + if statusInt == 0 { + return 1 // 实号 + } + + // status=1表示不在网,需要根据desc字段判断具体原因 + if statusInt == 1 { + desc, ok := data["desc"].(string) + if !ok { + return 4 // 沉默号(默认) + } + + // 根据desc中的关键词判断状态 + if strings.Contains(desc, "停机") { + return 2 // 停机 + } else if strings.Contains(desc, "销号") || strings.Contains(desc, "空号") { + return 0 // 空号 + } else if strings.Contains(desc, "库无") { + return 3 // 库无 + } else if strings.Contains(desc, "风险") { + return 5 // 风险号 + } else { + return 4 // 沉默号(其他不在网情况) + } + } + + return -1 // 未查得 +} + +func convertStatus(result string) int { + // 根据运营商三要素的result字段转换为status(备用方法) + // result: "0" - 一致, "1" - 不一致, "2" - 无记录 + // status: -1:未查得; 0:空号; 1:实号; 2:停机; 3:库无; 4:沉默号; 5:风险号 + if result == "0" { + return 1 // 实号 + } else if result == "1" { + return 5 // 风险号 + } else if result == "2" { + return 3 // 库无 + } + return -1 // 未查得 +} + +func formatOnlineTime(inTime string) string { + // 格式化在网时长 + // 0: [0,3), 3: [3,6), 6: [6,12), 12: [12,24), 24: [24,+) + if inTime == "0" { + return "0,3(个月)" + } else if inTime == "3" { + return "3,6(个月)" + } else if inTime == "6" { + return "6,12(个月)" + } else if inTime == "12" { + return "12,24(个月)" + } else if inTime == "24" { + return "24,+(个月)" + } + return inTime + "(个月)" +} + +func getFraudScore(apiData map[string]interface{}) int { + // 从涉赌涉诈风险评估获取基础反欺诈评分 + flxgData := getMapValue(apiData, "FLXG8B4D") + baseScore := -1 + + if flxgData != nil { + flxgMap, ok := flxgData.(map[string]interface{}) + if ok { + // 风险等级到分数的映射 + riskLevelToScore := map[string]int{ + "0": 0, // 无风险 + "A": 35, // 较低风险(取区间中值) + "B": 60, // 低风险(取区间中值) + "C": 80, // 中风险(取区间中值) + "D": 95, // 高风险(取区间中值) + } + + // 尝试从data字段获取(兼容旧格式) + var data map[string]interface{} + if dataVal, ok := flxgMap["data"].(map[string]interface{}); ok { + data = dataVal + } else { + // 如果没有data字段,直接使用flxgMap(新格式) + data = flxgMap + } + + // 检查是否有riskScore字段且为0(表示无风险) + if riskScore, ok := data["riskScore"].(string); ok && riskScore == "0" { + // 检查所有风险字段是否都为0 + allZero := true + riskFields := []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} + for _, field := range riskFields { + if val, ok := data[field].(string); ok && val != "" && val != "0" { + allZero = false + break + } + } + if allZero { + baseScore = 0 // 无风险 + } + } + + // 遍历所有风险字段,取最高风险等级对应的分数 + if baseScore == -1 { + maxScore := 0 + riskFields := []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} + for _, field := range riskFields { + if val, ok := data[field].(string); ok && val != "" { + if score, exists := riskLevelToScore[val]; exists && score > maxScore { + maxScore = score + } + } + } + if maxScore > 0 { + baseScore = maxScore + } + } + } + } + + // 考虑特殊名单风险(JRZQ8A2D) + specialListScore := 0 + jrzq8a2dData := getMapValue(apiData, "JRZQ8A2D") + if jrzq8a2dData != nil { + if jrzq8a2dMap, ok := jrzq8a2dData.(map[string]interface{}); ok { + // 检查Rule_final_decision和Rule_final_weight + if decision, ok := jrzq8a2dMap["Rule_final_decision"].(string); ok && decision == "Reject" { + if weight, ok := jrzq8a2dMap["Rule_final_weight"].(string); ok && weight != "" { + if weightInt, err := strconv.Atoi(weight); err == nil { + // 权重越高,风险越大,转换为分数(权重0-100映射到分数0-100) + specialListScore = weightInt + } + } + } + } + } + + // 考虑借选指数风险(JRZQ5E9F) + creditRiskScore := 0 + jrzq5e9fData := getMapValue(apiData, "JRZQ5E9F") + if jrzq5e9fData != nil { + if jrzq5e9fMap, ok := jrzq5e9fData.(map[string]interface{}); ok { + // 从xyp_cpl0081获取信用评分,如果评分很低(<500),增加风险分数 + if xypCpl0081, ok := jrzq5e9fMap["xyp_cpl0081"].(string); ok && xypCpl0081 != "" { + if score, err := strconv.ParseFloat(xypCpl0081, 64); err == nil { + // 信用评分越低,风险越高 + // 如果信用评分<500,增加风险分数(500-900映射到0-40分) + if score < 500 { + creditRiskScore = int((500 - score) / 10) // 每10分信用评分差异对应1分风险分数 + if creditRiskScore > 40 { + creditRiskScore = 40 // 最高40分 + } + } + } + } + } + } + + // 综合评分:取baseScore、specialListScore、creditRiskScore的最大值 + finalScore := baseScore + if specialListScore > finalScore { + finalScore = specialListScore + } + if creditRiskScore > finalScore { + finalScore = creditRiskScore + } + + // 如果所有评分都是-1或0,返回-1 + if finalScore == -1 { + return -1 + } + + return finalScore +} + +func getCreditScore(apiData map[string]interface{}) int { + // 从借选指数评估获取信用评分 + jrzq5e9fData := getMapValue(apiData, "JRZQ5E9F") + if jrzq5e9fData == nil { + return -1 + } + + jrzq5e9fMap, ok := jrzq5e9fData.(map[string]interface{}) + if !ok { + return -1 + } + + // 获取xyp_cpl0081字段(信用风险评分,范围[0,1],分数越高信用越低) + xypCpl0081Val, ok := jrzq5e9fMap["xyp_cpl0081"] + if !ok { + // 如果字段不存在,返回900(默认信用较好) + return 900 + } + + // 转换为float64 + var xypCpl0081 float64 + var isValid bool + switch v := xypCpl0081Val.(type) { + case float64: + xypCpl0081 = v + isValid = true + case string: + if v == "" || v == "-1" { + // 如果为空或-1,返回900(默认信用较好) + return 900 + } + parsed, err := strconv.ParseFloat(v, 64) + if err != nil { + // 解析失败,返回900(默认信用较好) + return 900 + } + xypCpl0081 = parsed + isValid = true + case int: + xypCpl0081 = float64(v) + isValid = true + default: + // 类型不匹配,返回900(默认信用较好) + return 900 + } + + // 验证范围[0,1] + if !isValid || xypCpl0081 < 0 || xypCpl0081 > 1 { + // 如果不在有效范围内,返回900(默认信用较好) + return 900 + } + + // 映射公式:creditScore = 1000 - (xyp_cpl0081 * 700) + // 当xyp_cpl0081 = 0时,creditScore = 1000(信用最好) + // 当xyp_cpl0081 = 1时,creditScore = 300(信用最差) + creditScore := 1000 - (xypCpl0081 * 700) + + // 确保在[300,1000]范围内 + if creditScore < 300 { + creditScore = 300 + } else if creditScore > 1000 { + creditScore = 1000 + } + + return int(creditScore) +} + +// isDevelopmentMode 检查是否为开发模式 +func isDevelopmentMode() bool { + // 检查环境变量,优先级:DWBG_USE_MOCK_DATA > ENV > CONFIG_ENV > APP_ENV + if useMock := os.Getenv("DWBG_USE_MOCK_DATA"); useMock != "" { + return useMock == "1" || useMock == "true" || useMock == "yes" + } + + env := os.Getenv("ENV") + if env == "" { + env = os.Getenv("CONFIG_ENV") + } + if env == "" { + env = os.Getenv("APP_ENV") + } + + // 默认开发环境 + if env == "" { + return true + } + + return env == "development" +} + +// loadMockAPIDataFromFile 从本地JSON文件加载模拟API数据 +func loadMockAPIDataFromFile(log *zap.Logger) map[string]interface{} { + // 优先使用指定的文件路径 + mockDataPath := os.Getenv("DWBG_MOCK_DATA_PATH") + if mockDataPath == "" { + // 默认使用最新的导出文件 + mockDataPath = "api_data_export/api_data_4522_20260214_152339.json" + } + + // 检查文件是否存在 + if _, err := os.Stat(mockDataPath); os.IsNotExist(err) { + log.Warn("开发模式:模拟数据文件不存在", zap.String("path", mockDataPath)) + return nil + } + + // 读取文件内容 + fileData, err := os.ReadFile(mockDataPath) + if err != nil { + log.Warn("开发模式:读取模拟数据文件失败", zap.String("path", mockDataPath), zap.Error(err)) + return nil + } + + // 解析JSON + var apiData map[string]interface{} + if err := json.Unmarshal(fileData, &apiData); err != nil { + log.Warn("开发模式:解析模拟数据JSON失败", zap.String("path", mockDataPath), zap.Error(err)) + return nil + } + + log.Info("开发模式:成功加载模拟数据", + zap.String("path", mockDataPath), + zap.Int("api_count", len(apiData)), + ) + + return apiData +} + +func getTotalRiskCounts(apiData map[string]interface{}) int { + // 计算总风险点数量 + // 通过构建riskWarning来获取totalRiskCounts + riskWarning := buildRiskWarning(apiData, zap.NewNop()) + if totalRiskCounts, ok := riskWarning["totalRiskCounts"].(int); ok { + return totalRiskCounts + } + return 0 +} + +func getRiskLevel(apiData map[string]interface{}) string { + // 从公安不良人员名单获取风险等级 + flxgdea9Data := getMapValue(apiData, "FLXGDEA9") + if flxgdea9Data != nil { + // 根据实际API响应结构提取风险等级 + } + return "" } diff --git a/internal/domains/api/services/processors/dwbg/dwbg8b4d_processor_test.go b/internal/domains/api/services/processors/dwbg/dwbg8b4d_processor_test.go new file mode 100644 index 0000000..e3dc22c --- /dev/null +++ b/internal/domains/api/services/processors/dwbg/dwbg8b4d_processor_test.go @@ -0,0 +1,1328 @@ +package dwbg + +import ( + "encoding/json" + "strings" + "testing" + + "tyapi-server/internal/domains/api/dto" + + "go.uber.org/zap" +) + +// TestTransformToDitingReport 测试数据转换逻辑 +func TestTransformToDitingReport(t *testing.T) { + // 准备测试参数 + params := dto.DWBG8B4DReq{ + Name: "封伟", + IDCard: "320321199102011152", + MobileNo: "15812342970", + } + + // 准备模拟的API响应数据 + apiData := prepareMockAPIData() + + // 创建logger + log := zap.NewNop() + + // 执行转换 + report := transformToDitingReport(apiData, params, log) + + // 验证报告结构 + validateReportStructure(t, report) + + // 验证baseInfo + validateBaseInfo(t, report, params) + + // 验证riskWarning + validateRiskWarning(t, report) + + // 验证overdueRiskProduct + validateOverdueRiskProduct(t, report) + + // 输出完整的报告JSON用于调试 + reportJSON, _ := json.MarshalIndent(report, "", " ") + t.Logf("转换后的报告:\n%s", string(reportJSON)) +} + +// prepareMockAPIData 准备模拟的API响应数据 +func prepareMockAPIData() map[string]interface{} { + apiData := make(map[string]interface{}) + + // 1. 运营商三要素简版V政务版 (YYSYH6D2) + apiData["YYSYH6D2"] = map[string]interface{}{ + "result": "0", // 一致 + "channel": "cmcc", + "address": "江苏省徐州市沛县", + } + + // 2. 手机在网时长B (YYSY8B1C) + apiData["YYSY8B1C"] = map[string]interface{}{ + "inTime": "6", // 6-12个月 + "operators": "移动", + } + + // 3. 公安二要素认证即时版 (IVYZ9K7F) + apiData["IVYZ9K7F"] = map[string]interface{}{ + "data": map[string]interface{}{ + "status": "一致", + }, + } + + // 4. 手机号归属地核验 (YYSY9E4A) + apiData["YYSY9E4A"] = map[string]interface{}{ + "provinceName": "江苏", + "cityName": "徐州", + "channel": "cmcc", + } + + // 5. 手机在网状态V即时版 (YYSYE7V5) + apiData["YYSYE7V5"] = map[string]interface{}{ + "status": 0, // 在网 + "desc": "在网", + "channel": "cmcc", + } + + // 6. 涉赌涉诈风险评估 (FLXG8B4D) + apiData["FLXG8B4D"] = map[string]interface{}{ + "data": map[string]interface{}{ + "moneyLaundering": "0", + "deceiver": "0", + "gamblerPlayer": "0", + "gamblerBanker": "0", + "riskScore": "0", + }, + } + + // 7. 公安不良人员名单(加强版) (FLXGDEA9) + apiData["FLXGDEA9"] = map[string]interface{}{ + "level": "C2,C5", // 妨害社会管理秩序 + } + + // 8. 特殊名单验证B (JRZQ8A2D) + apiData["JRZQ8A2D"] = map[string]interface{}{ + "id": map[string]interface{}{ + "id_bank_lost": "1", + "id_nbank_lost": "1", + "id_bank_overdue": "1", + "id_nbank_overdue": "1", + }, + "cell": map[string]interface{}{ + "cell_bank_lost": "1", + "cell_nbank_lost": "1", + "cell_bank_overdue": "1", + "cell_nbank_overdue": "1", + }, + } + + // 9. 借选指数评估 (JRZQ5E9F) + apiData["JRZQ5E9F"] = map[string]interface{}{ + "xyp_cpl0001": "14", // 贷款总机构数 + "xyp_cpl0002": "17", // 已结清机构数 + "xyp_cpl0044": "1", // 当前是否存在逾期未结清 + "xyp_cpl0071": "1", // 当前逾期机构数 + "xyp_cpl0072": "1", // 当前逾期金额 + "xyp_cpl0028": "0", // 最近1天是否发生过逾期 + "xyp_cpl0029": "0", // 最近7天是否发生过逾期 + "xyp_cpl0030": "1", // 最近14天是否发生过逾期 + "xyp_cpl0031": "1", // 最近30天是否发生过逾期 + "xyp_cpl0068": "4", // 最近一次还款成功距离当前天数 (160+) + "xyp_cpl0018": "0", // 最近7天交易失败笔数 + "xyp_cpl0034": "0", // 最近7天还款失败金额 + "xyp_cpl0019": "0", // 最近7天还款成功次数 + "xyp_cpl0035": "0", // 最近7天还款成功金额 + "xyp_cpl0020": "2", // 最近14天交易失败笔数 + "xyp_cpl0036": "1", // 最近14天还款失败金额 + "xyp_cpl0021": "0", // 最近14天还款成功次数 + "xyp_cpl0037": "0", // 最近14天还款成功金额 + "xyp_cpl0022": "3", // 最近1个月交易失败笔数 + "xyp_cpl0038": "2", // 最近1个月还款失败金额 + "xyp_cpl0023": "0", // 最近1个月还款成功次数 + "xyp_cpl0039": "0", // 最近1个月还款成功金额 + "xyp_cpl0024": "4", // 最近3个月交易失败笔数 + "xyp_cpl0040": "3", // 最近3个月还款失败金额 + "xyp_cpl0025": "0", // 最近3个月还款成功次数 + "xyp_cpl0041": "0", // 最近3个月还款成功金额 + "xyp_cpl0026": "5", // 最近6个月交易失败笔数 + "xyp_cpl0042": "4", // 最近6个月还款失败金额 + "xyp_cpl0027": "0", // 最近6个月还款成功次数 + "xyp_cpl0043": "0", // 最近6个月还款成功金额 + "xyp_t01aazzzc": "3", // 还款成功_还款金额_最大值 (区间化: [4800,Inf)) + "xyp_cpl0081": "0.5", // 信用风险评分 (0-1之间,分数越高信用越低) -> 应该映射到650分 + } + + // 10. 个人司法涉诉查询 (FLXG7E8F) + apiData["FLXG7E8F"] = map[string]interface{}{ + "judicial_data": map[string]interface{}{ + "lawsuitStat": map[string]interface{}{ + "civil": map[string]interface{}{ + "cases": []interface{}{}, + }, + "criminal": map[string]interface{}{ + "cases": []interface{}{}, + }, + "implement": map[string]interface{}{ + "cases": []interface{}{ + map[string]interface{}{ + "c_ah": "(2023)赣1102执保608号", + "n_ajlx": "财产保全执行", + "n_jbfy": "上饶市信州区人民法院", + "n_ssdw": "被申请人", + "d_larq": "2023-05-12", + "d_jarq": "2023-05-17", + "n_ajjzjd": "已结案", + "n_sqzxbdje": 0.0, + "n_jabdje": 0.0, + "n_laay": "未知", + "n_jafs": "部分保全", + }, + }, + }, + }, + "breachCaseList": []interface{}{}, + "consumptionRestrictionList": []interface{}{}, + }, + } + + // 11. 借贷意向验证A (JRZQ6F2A) + apiData["JRZQ6F2A"] = map[string]interface{}{ + "risk_screen_v2": map[string]interface{}{ + "variables": []interface{}{ + map[string]interface{}{ + "variableValue": map[string]interface{}{ + // 银行数据 + "als_d7_id_bank_allnum": "0", + "als_d7_id_bank_orgnum": "0", + "als_d7_cell_bank_allnum": "0", + "als_d15_id_bank_allnum": "0", + "als_d15_id_bank_orgnum": "0", + "als_d15_cell_bank_allnum": "0", + "als_m1_id_bank_allnum": "0", + "als_m1_id_bank_orgnum": "0", + "als_m1_cell_bank_allnum": "0", + "als_m3_id_bank_allnum": "0", + "als_m3_id_bank_orgnum": "0", + "als_m3_cell_bank_allnum": "0", + "als_m6_id_bank_allnum": "2", + "als_m6_id_bank_orgnum": "2", + "als_m6_cell_bank_allnum": "0", + "als_m12_id_bank_allnum": "2", + "als_m12_id_bank_orgnum": "2", + "als_m12_cell_bank_allnum": "0", + // 非银数据 + "als_d7_id_nbank_allnum": "0", + "als_d7_id_nbank_orgnum": "0", + "als_d7_cell_nbank_allnum": "0", + "als_d15_id_nbank_allnum": "0", + "als_d15_id_nbank_orgnum": "0", + "als_d15_cell_nbank_allnum": "0", + "als_m1_id_nbank_allnum": "0", + "als_m1_id_nbank_orgnum": "0", + "als_m1_cell_nbank_allnum": "0", + "als_m3_id_nbank_allnum": "0", + "als_m3_id_nbank_orgnum": "0", + "als_m3_cell_nbank_allnum": "0", + "als_m6_id_nbank_allnum": "2", + "als_m6_id_nbank_orgnum": "2", + "als_m6_cell_nbank_allnum": "2", + "als_m12_id_nbank_allnum": "2", + "als_m12_id_nbank_orgnum": "2", + "als_m12_cell_nbank_allnum": "2", + // 网络零售银行 + "als_m6_id_bank_ret_allnum": "2", + "als_m6_id_bank_ret_orgnum": "2", + "als_m6_cell_bank_ret_allnum": "0", + "als_m12_id_bank_ret_allnum": "2", + "als_m12_id_bank_ret_orgnum": "2", + "als_m12_cell_bank_ret_allnum": "0", + // 持牌消费金融 + "als_m6_id_nbank_cons_allnum": "2", + "als_m6_id_nbank_cons_orgnum": "2", + "als_m6_cell_nbank_cons_allnum": "2", + "als_m12_id_nbank_cons_allnum": "2", + "als_m12_id_nbank_cons_orgnum": "2", + "als_m12_cell_nbank_cons_allnum": "2", + // 夜间-非银 + "als_m6_id_nbank_night_allnum": "1", + "als_m6_id_nbank_night_orgnum": "1", + "als_m6_cell_nbank_night_allnum": "1", + "als_m12_id_nbank_night_allnum": "1", + "als_m12_id_nbank_night_orgnum": "1", + "als_m12_cell_nbank_night_allnum": "1", + }, + }, + }, + }, + } + + // 12. 3C租赁申请意向 (JRZQ1D09) + apiData["JRZQ1D09"] = map[string]interface{}{ + "phone_relation_idard": "0", // 同一身份证关联手机号数 + "idcard_relation_phone": "0", // 同一手机号关联身份证数 + "least_application_time": "2025-06-02", + // 3个月数据 + "alc_m3_id_allnum": "2", + "alc_m3_cell_allnum": "1", + "alc_m3_id_weekend_allnum": "0", + "alc_m3_cell_weekend_allnum": "0", + "alc_m3_id_night_allnum": "0", + "alc_m3_cell_night_allnum": "0", + // 6个月数据 + "alc_m6_id_allnum": "2", + "alc_m6_cell_allnum": "1", + "alc_m6_id_weekend_allnum": "0", + "alc_m6_cell_weekend_allnum": "0", + "alc_m6_id_night_allnum": "0", + "alc_m6_cell_night_allnum": "0", + // 12个月数据 + "alc_m12_id_allnum": "3", + "alc_m12_cell_allnum": "2", + "alc_m12_id_weekend_allnum": "0", + "alc_m12_cell_weekend_allnum": "0", + "alc_m12_id_night_allnum": "0", + "alc_m12_cell_night_allnum": "0", + } + + return apiData +} + +// validateReportStructure 验证报告的基本结构 +func validateReportStructure(t *testing.T, report map[string]interface{}) { + requiredFields := []string{ + "baseInfo", + "standLiveInfo", + "checkSuggest", + "fraudScore", + "creditScore", + "verifyRule", + "fraudRule", + "riskWarning", + "elementVerificationDetail", + "riskSupervision", + "overdueRiskProduct", + "multCourtInfo", + "loanEvaluationVerificationDetail", + "leasingRiskAssessment", + } + + for _, field := range requiredFields { + if _, exists := report[field]; !exists { + t.Errorf("报告缺少必需字段: %s", field) + } + } +} + +// validateBaseInfo 验证基本信息 +func validateBaseInfo(t *testing.T, report map[string]interface{}, params dto.DWBG8B4DReq) { + baseInfo, ok := report["baseInfo"].(map[string]interface{}) + if !ok { + t.Fatal("baseInfo不是map类型") + } + + // 验证姓名脱敏 + if name, ok := baseInfo["name"].(string); ok { + if name == "" { + t.Error("name字段为空") + } + // 验证脱敏格式(应该包含*) + if !strings.Contains(name, "*") { + t.Errorf("name字段未脱敏: %s", name) + } + } else { + t.Error("name字段不存在或类型错误") + } + + // 验证身份证脱敏 + if idCard, ok := baseInfo["idCard"].(string); ok { + if idCard == "" { + t.Error("idCard字段为空") + } + // 验证脱敏格式(应该包含*) + if !strings.Contains(idCard, "*") { + t.Errorf("idCard字段未脱敏: %s", idCard) + } + } else { + t.Error("idCard字段不存在或类型错误") + } + + // 验证手机号脱敏 + if phone, ok := baseInfo["phone"].(string); ok { + if phone == "" { + t.Error("phone字段为空") + } + // 验证脱敏格式(应该包含*) + if !strings.Contains(phone, "*") { + t.Errorf("phone字段未脱敏: %s", phone) + } + } else { + t.Error("phone字段不存在或类型错误") + } + + // 验证年龄 + if age, ok := baseInfo["age"].(int); ok { + if age <= 0 || age > 150 { + t.Errorf("age字段值异常: %d", age) + } + } else { + t.Error("age字段不存在或类型错误") + } + + // 验证性别 + if sex, ok := baseInfo["sex"].(string); ok { + if sex != "男" && sex != "女" { + t.Errorf("sex字段值异常: %s", sex) + } + } else { + t.Error("sex字段不存在或类型错误") + } + + // 验证location + if location, ok := baseInfo["location"].(string); ok { + if location == "" { + t.Error("location字段为空") + } + } else { + t.Error("location字段不存在或类型错误") + } + + // 验证phoneArea + if phoneArea, ok := baseInfo["phoneArea"].(string); ok { + if phoneArea == "" { + t.Error("phoneArea字段为空") + } + // 验证格式(应该包含"-") + if !strings.Contains(phoneArea, "-") { + t.Errorf("phoneArea格式异常: %s", phoneArea) + } + } else { + t.Error("phoneArea字段不存在或类型错误") + } + + // 验证channel + if channel, ok := baseInfo["channel"].(string); ok { + if channel == "" { + t.Error("channel字段为空") + } + } else { + t.Error("channel字段不存在或类型错误") + } + + // 验证status + if status, ok := baseInfo["status"].(int); ok { + if status < -1 || status > 5 { + t.Errorf("status字段值异常: %d", status) + } + } else { + t.Error("status字段不存在或类型错误") + } +} + +// validateRiskWarning 验证风险警告 +func validateRiskWarning(t *testing.T, report map[string]interface{}) { + riskWarning, ok := report["riskWarning"].(map[string]interface{}) + if !ok { + t.Fatal("riskWarning不是map类型") + } + + // 验证totalRiskCounts + if totalRiskCounts, ok := riskWarning["totalRiskCounts"].(int); ok { + if totalRiskCounts < 0 { + t.Errorf("totalRiskCounts值异常: %d", totalRiskCounts) + } + } else { + t.Error("totalRiskCounts字段不存在或类型错误") + } + + // 验证level字段(应该是string类型) + if level, ok := riskWarning["level"].(string); ok { + // level可以是空字符串或包含风险等级代码 + _ = level + } else { + // level字段可能不存在,这是允许的 + } + + // 验证一些关键风险字段 + riskFields := []string{ + "idCardTwoElementMismatch", + "phoneThreeElementMismatch", + "shortPhoneDuration", + "noPhoneDuration", + "hasCriminalRecord", + "isDisrupSocial", + "hitExecutionCase", + } + + for _, field := range riskFields { + if val, ok := riskWarning[field].(int); ok { + if val != 0 && val != 1 { + t.Errorf("风险字段%s值异常: %d", field, val) + } + } + } +} + +// validateOverdueRiskProduct 验证逾期风险产品 +func validateOverdueRiskProduct(t *testing.T, report map[string]interface{}) { + overdueRiskProduct, ok := report["overdueRiskProduct"].(map[string]interface{}) + if !ok { + t.Fatal("overdueRiskProduct不是map类型") + } + + // 验证风险标识 + flags := []string{"lyjlhyFlag", "dkzhktjFlag", "tsmdyzFlag"} + for _, flag := range flags { + if val, ok := overdueRiskProduct[flag].(int); ok { + if val < 0 || val > 2 { + t.Errorf("风险标识%s值异常: %d", flag, val) + } + } + } + + // 验证hasUnsettledOverdue + if hasOverdue, ok := overdueRiskProduct["hasUnsettledOverdue"].(string); ok { + if hasOverdue != "逾期" && hasOverdue != "未逾期" { + t.Errorf("hasUnsettledOverdue值异常: %s", hasOverdue) + } + } + + // 验证totalLoanRepaymentAmount(应该是区间化格式) + if amount, ok := overdueRiskProduct["totalLoanRepaymentAmount"].(string); ok { + // 验证区间化格式(应该包含括号或方括号) + if amount != "0" && !strings.ContainsAny(amount, "()[]+") { + t.Errorf("totalLoanRepaymentAmount格式异常: %s", amount) + } + } + + // 验证daysSinceLastSuccessfulRepayment(应该是区间化格式) + if days, ok := overdueRiskProduct["daysSinceLastSuccessfulRepayment"].(string); ok { + // 验证区间化格式 + if days != "0" && !strings.ContainsAny(days, "()[]+") { + t.Errorf("daysSinceLastSuccessfulRepayment格式异常: %s", days) + } + } + + // 验证repaymentSuccessCount和repaymentSuccessAmount应该是"-"或数字 + successFields := []string{ + "repaymentSuccessCountLast7Days", + "repaymentSuccessAmountLast7Days", + "repaymentSuccessCountLast14Days", + "repaymentSuccessAmountLast14Days", + "repaymentSuccessCountLastMonth", + "repaymentSuccessAmountLastMonth", + "repaymentSuccessCountLast3Months", + "repaymentSuccessAmountLast3Months", + "repaymentSuccessCountLast6Months", + "repaymentSuccessAmountLast6Months", + } + + for _, field := range successFields { + if val, ok := overdueRiskProduct[field].(string); ok { + if val != "-" && val != "0" { + // 如果不是"-"或"0",应该是有效的数字或区间 + _ = val + } + } + } +} + +// TestBuildBaseInfo 测试基本信息构建 +func TestBuildBaseInfo(t *testing.T) { + params := dto.DWBG8B4DReq{ + Name: "张三", + IDCard: "110101199001011234", + MobileNo: "13800138000", + } + + apiData := map[string]interface{}{ + "YYSYH6D2": map[string]interface{}{ + "result": "0", + "channel": "cmcc", + "address": "北京市东城区", + }, + "YYSY9E4A": map[string]interface{}{ + "provinceName": "北京", + "cityName": "北京", + }, + "YYSYE7V5": map[string]interface{}{ + "status": 0, + "desc": "在网", + }, + } + + log := zap.NewNop() + baseInfo := buildBaseInfo(apiData, params, log) + + // 验证脱敏 + if name, ok := baseInfo["name"].(string); ok { + if !strings.Contains(name, "*") { + t.Errorf("姓名未脱敏: %s", name) + } + } + + if idCard, ok := baseInfo["idCard"].(string); ok { + if !strings.Contains(idCard, "*") { + t.Errorf("身份证未脱敏: %s", idCard) + } + } + + if phone, ok := baseInfo["phone"].(string); ok { + if !strings.Contains(phone, "*") { + t.Errorf("手机号未脱敏: %s", phone) + } + } + + // 验证年龄和性别 + if age, ok := baseInfo["age"].(int); ok { + // 根据身份证号110101199001011234,应该是1990年出生,2024年应该是34岁 + if age < 20 || age > 100 { + t.Errorf("年龄计算异常: %d", age) + } + } + + if sex, ok := baseInfo["sex"].(string); ok { + if sex != "男" && sex != "女" { + t.Errorf("性别计算异常: %s", sex) + } + } +} + +// TestBuildRiskWarning 测试风险警告构建 +func TestBuildRiskWarning(t *testing.T) { + apiData := map[string]interface{}{ + "IVYZ9K7F": map[string]interface{}{ + "data": map[string]interface{}{ + "status": "不一致", // 应该触发idCardTwoElementMismatch + }, + }, + "YYSYH6D2": map[string]interface{}{ + "result": "1", // 应该触发phoneThreeElementMismatch + }, + "YYSY8B1C": map[string]interface{}{ + "inTime": "0", // 应该触发shortPhoneDuration + }, + "FLXGDEA9": map[string]interface{}{ + "level": "C2,C5", // 应该触发isDisrupSocial + }, + } + + log := zap.NewNop() + riskWarning := buildRiskWarning(apiData, log) + + // 验证风险字段 + if idCardMismatch, ok := riskWarning["idCardTwoElementMismatch"].(int); ok { + if idCardMismatch != 1 { + t.Errorf("idCardTwoElementMismatch应该为1,实际为%d", idCardMismatch) + } + } + + if phoneMismatch, ok := riskWarning["phoneThreeElementMismatch"].(int); ok { + if phoneMismatch != 1 { + t.Errorf("phoneThreeElementMismatch应该为1,实际为%d", phoneMismatch) + } + } + + if shortDuration, ok := riskWarning["shortPhoneDuration"].(int); ok { + if shortDuration != 1 { + t.Errorf("shortPhoneDuration应该为1,实际为%d", shortDuration) + } + } + + if isDisrupSocial, ok := riskWarning["isDisrupSocial"].(int); ok { + if isDisrupSocial != 1 { + t.Errorf("isDisrupSocial应该为1,实际为%d", isDisrupSocial) + } + } + + // 验证totalRiskCounts + if totalRiskCounts, ok := riskWarning["totalRiskCounts"].(int); ok { + if totalRiskCounts <= 0 { + t.Errorf("totalRiskCounts应该大于0,实际为%d", totalRiskCounts) + } + } +} + +// TestBuildOverdueRiskProduct 测试逾期风险产品构建 +func TestBuildOverdueRiskProduct(t *testing.T) { + apiData := map[string]interface{}{ + "JRZQ5E9F": map[string]interface{}{ + "xyp_cpl0044": "1", // 有逾期 + "xyp_cpl0071": "1", // 当前逾期机构数 + "xyp_cpl0072": "1", // 当前逾期金额 + "xyp_cpl0029": "1", // 最近7天逾期 + "xyp_cpl0068": "4", // 最近一次还款成功距离当前天数 (160+) + "xyp_t01aazzzc": "3", // 还款成功_还款金额_最大值 ([4800,Inf)) + }, + "JRZQ8A2D": map[string]interface{}{ + "id": map[string]interface{}{ + "id_bank_overdue": "0", // 命中当前逾期 + }, + }, + } + + log := zap.NewNop() + overdueRiskProduct := buildOverdueRiskProduct(apiData, log) + + // 验证hasUnsettledOverdue + if hasOverdue, ok := overdueRiskProduct["hasUnsettledOverdue"].(string); ok { + if hasOverdue != "逾期" { + t.Errorf("hasUnsettledOverdue应该为'逾期',实际为%s", hasOverdue) + } + } + + // 验证overdueLast7Days + if overdue7Days, ok := overdueRiskProduct["overdueLast7Days"].(string); ok { + if overdue7Days != "逾期" { + t.Errorf("overdueLast7Days应该为'逾期',实际为%s", overdue7Days) + } + } + + // 验证daysSinceLastSuccessfulRepayment + if days, ok := overdueRiskProduct["daysSinceLastSuccessfulRepayment"].(string); ok { + if days != "160+" { + t.Errorf("daysSinceLastSuccessfulRepayment应该为'160+',实际为%s", days) + } + } + + // 验证totalLoanRepaymentAmount + if amount, ok := overdueRiskProduct["totalLoanRepaymentAmount"].(string); ok { + if amount != "[4800,Inf)" { + t.Errorf("totalLoanRepaymentAmount应该为'[4800,Inf)',实际为%s", amount) + } + } +} + +// TestBuildLeasingRiskAssessment 测试租赁风险评估构建 +func TestBuildLeasingRiskAssessment(t *testing.T) { + apiData := map[string]interface{}{ + "JRZQ1D09": map[string]interface{}{ + "alc_m3_id_allnum": "2", + "alc_m3_cell_allnum": "1", + "alc_m6_id_allnum": "2", + "alc_m6_cell_allnum": "1", + "alc_m12_id_allnum": "3", + "alc_m12_cell_allnum": "2", + }, + } + + log := zap.NewNop() + assessment := buildLeasingRiskAssessment(apiData, log) + + // 验证格式为"身份证/手机号" + if platform3Months, ok := assessment["threeCPlatformApplicationCountLast3Months"].(string); ok { + if !strings.Contains(platform3Months, "/") { + t.Errorf("threeCPlatformApplicationCountLast3Months格式异常: %s", platform3Months) + } + // 应该是"2/1" + if platform3Months != "2/1" { + t.Errorf("threeCPlatformApplicationCountLast3Months应该为'2/1',实际为%s", platform3Months) + } + } + + if institution12Months, ok := assessment["threeCInstitutionApplicationCountLast12Months"].(string); ok { + if !strings.Contains(institution12Months, "/") { + t.Errorf("threeCInstitutionApplicationCountLast12Months格式异常: %s", institution12Months) + } + // 应该是"3/2" + if institution12Months != "3/2" { + t.Errorf("threeCInstitutionApplicationCountLast12Months应该为'3/2',实际为%s", institution12Months) + } + } +} + +// TestBuildLoanEvaluationVerificationDetail 测试借贷评估产品构建 +func TestBuildLoanEvaluationVerificationDetail(t *testing.T) { + apiData := map[string]interface{}{ + "JRZQ6F2A": map[string]interface{}{ + "risk_screen_v2": map[string]interface{}{ + "variables": []interface{}{ + map[string]interface{}{ + "variableValue": map[string]interface{}{ + "als_m6_id_bank_allnum": "2", + "als_m6_cell_bank_allnum": "0", + "als_m6_id_nbank_allnum": "2", + "als_m6_cell_nbank_allnum": "2", + }, + }, + }, + }, + }, + } + + log := zap.NewNop() + detail := buildLoanEvaluationVerificationDetail(apiData, log) + + // 验证organLoanPerformances + if organLoanPerformances, ok := detail["organLoanPerformances"].([]interface{}); ok { + if len(organLoanPerformances) == 0 { + t.Error("organLoanPerformances为空") + } + + // 验证银行数据格式 + if bankPerf, ok := organLoanPerformances[0].(map[string]interface{}); ok { + if last6Month, ok := bankPerf["last6Month"].(string); ok { + if !strings.Contains(last6Month, "/") { + t.Errorf("last6Month格式异常: %s", last6Month) + } + // 应该是"2/0" + if last6Month != "2/0" { + t.Errorf("last6Month应该为'2/0',实际为%s", last6Month) + } + } + } + } +} + +// TestConvertXypT01aazzzcToInterval 测试区间化转换 +func TestConvertXypT01aazzzcToInterval(t *testing.T) { + testCases := []struct { + input string + expected string + }{ + {"1", "(0,1000)"}, + {"2", "[1000,4800)"}, + {"3", "[4800,Inf)"}, + {"0", "0"}, + {"", "0"}, + } + + for _, tc := range testCases { + result := convertXypT01aazzzcToInterval(tc.input) + if result != tc.expected { + t.Errorf("convertXypT01aazzzcToInterval(%s) = %s, 期望 %s", tc.input, result, tc.expected) + } + } +} + +// TestConvertXypCpl0068ToInterval 测试daysSinceLastSuccessfulRepayment区间化转换 +func TestConvertXypCpl0068ToInterval(t *testing.T) { + testCases := []struct { + input string + expected string + }{ + {"1", "(0,5)"}, + {"2", "[5,50)"}, + {"3", "[50,160)"}, + {"4", "160+"}, + {"0", "0"}, + {"", "0"}, + } + + for _, tc := range testCases { + result := convertXypCpl0068ToInterval(tc.input) + if result != tc.expected { + t.Errorf("convertXypCpl0068ToInterval(%s) = %s, 期望 %s", tc.input, result, tc.expected) + } + } +} + +// TestGetCreditScore 测试信用评分计算 +func TestGetCreditScore(t *testing.T) { + testCases := []struct { + name string + xypCpl0081 interface{} + expected int + }{ + {"有效值0", "0", 1000}, + {"有效值0.5", "0.5", 650}, + {"有效值1", "1", 300}, + {"有效值0.2", "0.2", 860}, + {"有效值0.8", "0.8", 440}, + {"字段不存在", nil, 900}, + {"字段为空字符串", "", 900}, + {"字段为-1", "-1", 900}, + {"float64类型", 0.5, 650}, + {"int类型", 0, 1000}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + apiData := map[string]interface{}{} + if tc.xypCpl0081 != nil { + apiData["JRZQ5E9F"] = map[string]interface{}{ + "xyp_cpl0081": tc.xypCpl0081, + } + } else { + apiData["JRZQ5E9F"] = map[string]interface{}{} + } + + result := getCreditScore(apiData) + if result != tc.expected { + t.Errorf("getCreditScore(%v) = %d, 期望 %d", tc.xypCpl0081, result, tc.expected) + } + }) + } +} + +// TestGetFraudScore 测试反欺诈评分计算 +func TestGetFraudScore(t *testing.T) { + testCases := []struct { + name string + data map[string]interface{} + expected int + }{ + {"无风险", map[string]interface{}{ + "moneyLaundering": "0", + "deceiver": "0", + "gamblerPlayer": "0", + "gamblerBanker": "0", + "riskScore": "0", + }, 0}, + {"较低风险A", map[string]interface{}{ + "moneyLaundering": "A", + "deceiver": "0", + "gamblerPlayer": "0", + "gamblerBanker": "0", + }, 35}, + {"低风险B", map[string]interface{}{ + "moneyLaundering": "B", + "deceiver": "0", + "gamblerPlayer": "0", + "gamblerBanker": "0", + }, 60}, + {"中风险C", map[string]interface{}{ + "moneyLaundering": "C", + "deceiver": "0", + "gamblerPlayer": "0", + "gamblerBanker": "0", + }, 80}, + {"高风险D", map[string]interface{}{ + "moneyLaundering": "D", + "deceiver": "0", + "gamblerPlayer": "0", + "gamblerBanker": "0", + }, 95}, + {"多个风险取最高", map[string]interface{}{ + "moneyLaundering": "A", + "deceiver": "C", + "gamblerPlayer": "B", + "gamblerBanker": "0", + }, 80}, + {"数据不存在", nil, -1}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + apiData := map[string]interface{}{} + if tc.data != nil { + apiData["FLXG8B4D"] = map[string]interface{}{ + "data": tc.data, + } + } + + result := getFraudScore(apiData) + if result != tc.expected { + t.Errorf("getFraudScore() = %d, 期望 %d", result, tc.expected) + } + }) + } +} + +// TestBuildMultCourtInfo 测试司法风险核验产品构建 +func TestBuildMultCourtInfo(t *testing.T) { + apiData := map[string]interface{}{ + "FLXG7E8F": map[string]interface{}{ + "judicial_data": map[string]interface{}{ + "lawsuitStat": map[string]interface{}{ + "civil": map[string]interface{}{ + "cases": []interface{}{}, + }, + "criminal": map[string]interface{}{ + "cases": []interface{}{}, + }, + "implement": map[string]interface{}{ + "cases": []interface{}{ + map[string]interface{}{ + "c_ah": "(2023)赣1102执保608号", + "n_ajlx": "财产保全执行", + "n_jbfy": "上饶市信州区人民法院", + "n_ssdw": "被申请人", + "d_larq": "2023-05-12", + "d_jarq": "2023-05-17", + "n_ajjzjd": "已结案", + "n_sqzxbdje": 0.0, + "n_jabdje": 0.0, + "n_laay": "未知", + "n_jafs": "部分保全", + }, + }, + }, + }, + "breachCaseList": []interface{}{}, + "consumptionRestrictionList": []interface{}{}, + }, + }, + } + + log := zap.NewNop() + multCourtInfo := buildMultCourtInfo(apiData, log) + + // 验证执行案件 + if executionCases, ok := multCourtInfo["executionCases"].([]interface{}); ok { + if len(executionCases) == 0 { + t.Error("executionCases应该包含案件") + } else { + if case1, ok := executionCases[0].(map[string]interface{}); ok { + if caseNumber, ok := case1["caseNumber"].(string); ok { + if caseNumber != "(2023)赣1102执保608号" { + t.Errorf("caseNumber应该为'(2023)赣1102执保608号',实际为%s", caseNumber) + } + } + } + } + } + + // 验证风险标识 + if executionCasesFlag, ok := multCourtInfo["executionCasesFlag"].(int); ok { + if executionCasesFlag != 1 { + t.Errorf("executionCasesFlag应该为1,实际为%d", executionCasesFlag) + } + } +} + +// TestBuildElementVerificationDetail 测试要素核查详情构建 +func TestBuildElementVerificationDetail(t *testing.T) { + apiData := map[string]interface{}{ + "YYSYH6D2": map[string]interface{}{ + "result": "1", // 不一致 + "channel": "cmcc", + "address": "江苏省徐州市", + }, + "IVYZ9K7F": map[string]interface{}{ + "data": map[string]interface{}{ + "status": "一致", + }, + }, + "YYSY8B1C": map[string]interface{}{ + "inTime": "3", // 3-6个月 + "operators": "移动", + }, + "YYSYE7V5": map[string]interface{}{ + "status": 1, // 不在网 + "desc": "风险号", + "channel": "cmcc", + }, + "YYSY9E4A": map[string]interface{}{ + "provinceName": "江苏", + "cityName": "徐州", + "channel": "cmcc", + }, + "FLXGDEA9": map[string]interface{}{ + "level": "A1,B2,C3", // 多种风险类型 + }, + } + + log := zap.NewNop() + detail := buildElementVerificationDetail(apiData, log) + + // 验证sjsysFlag(手机三要素风险标识) + if sjsysFlag, ok := detail["sjsysFlag"].(int); ok { + if sjsysFlag != 1 { + t.Errorf("sjsysFlag应该为1(高风险),实际为%d", sjsysFlag) + } + } + + // 验证phoneVailRiskFlag(手机信息验证风险标识) + if phoneVailRiskFlag, ok := detail["phoneVailRiskFlag"].(int); ok { + if phoneVailRiskFlag != 1 { + t.Errorf("phoneVailRiskFlag应该为1(高风险),实际为%d", phoneVailRiskFlag) + } + } + + // 验证highRiskFlag(公安重点人员风险标识) + if highRiskFlag, ok := detail["highRiskFlag"].(int); ok { + if highRiskFlag != 1 { + t.Errorf("highRiskFlag应该为1(高风险),实际为%d", highRiskFlag) + } + } + + // 验证keyPersonCheckList + if keyPersonCheckList, ok := detail["keyPersonCheckList"].(map[string]interface{}); ok { + if fontFlag, ok := keyPersonCheckList["fontFlag"].(int); ok { + if fontFlag != 1 { + t.Errorf("fontFlag应该为1,实际为%d", fontFlag) + } + } + if jingJiFontFlag, ok := keyPersonCheckList["jingJiFontFlag"].(int); ok { + if jingJiFontFlag != 1 { + t.Errorf("jingJiFontFlag应该为1,实际为%d", jingJiFontFlag) + } + } + if fangAiFlag, ok := keyPersonCheckList["fangAiFlag"].(int); ok { + if fangAiFlag != 1 { + t.Errorf("fangAiFlag应该为1,实际为%d", fangAiFlag) + } + } + } +} + +// TestBuildRiskSupervision 测试关联风险监督构建 +func TestBuildRiskSupervision(t *testing.T) { + apiData := map[string]interface{}{ + "JRZQ1D09": map[string]interface{}{ + "phone_relation_idard": "5", // 同一身份证关联手机号数 + "idcard_relation_phone": "3", // 同一手机号关联身份证数 + "least_application_time": "2025-06-02", + }, + } + + log := zap.NewNop() + riskSupervision := buildRiskSupervision(apiData, log) + + // 验证rentalRiskListIdCardRelationsPhones + if idCardRelationsPhones, ok := riskSupervision["rentalRiskListIdCardRelationsPhones"].(int); ok { + if idCardRelationsPhones != 5 { + t.Errorf("rentalRiskListIdCardRelationsPhones应该为5,实际为%d", idCardRelationsPhones) + } + } + + // 验证rentalRiskListPhoneRelationsIdCards + if phoneRelationsIdCards, ok := riskSupervision["rentalRiskListPhoneRelationsIdCards"].(int); ok { + if phoneRelationsIdCards != 3 { + t.Errorf("rentalRiskListPhoneRelationsIdCards应该为3,实际为%d", phoneRelationsIdCards) + } + } + + // 验证leastApplicationTime + if leastApplicationTime, ok := riskSupervision["leastApplicationTime"].(string); ok { + if leastApplicationTime != "2025-06-02" { + t.Errorf("leastApplicationTime应该为'2025-06-02',实际为%s", leastApplicationTime) + } + } +} + +// TestRiskCountsCalculation 测试风险计数计算 +func TestRiskCountsCalculation(t *testing.T) { + apiData := map[string]interface{}{ + "FLXGDEA9": map[string]interface{}{ + "level": "A1,B2,C3,D4,E5", // 多种风险类型 + }, + "FLXG8B4D": map[string]interface{}{ + "data": map[string]interface{}{ + "moneyLaundering": "A", // 有风险 + }, + }, + "JRZQ8A2D": map[string]interface{}{ + "id": map[string]interface{}{ + "id_bank_lost": "0", // 命中 + "id_nbank_lost": "0", // 命中 + "id_bank_overdue": "0", // 命中 + }, + }, + "JRZQ6F2A": map[string]interface{}{ + "risk_screen_v2": map[string]interface{}{ + "variables": []interface{}{ + map[string]interface{}{ + "variableValue": map[string]interface{}{ + "als_d7_id_bank_allnum": "10", // 频繁申请 + "als_d7_id_nbank_allnum": "10", // 频繁申请 + "als_m6_id_bank_allnum": "10", + "als_m6_id_nbank_allnum": "10", + "als_m12_id_bank_allnum": "10", + "als_m12_id_nbank_allnum": "10", + }, + }, + }, + }, + }, + "JRZQ1D09": map[string]interface{}{ + "alc_m3_id_allnum": "10", + "alc_m3_cell_allnum": "10", + "alc_m6_id_allnum": "10", + "alc_m6_cell_allnum": "10", + "alc_m12_id_allnum": "10", + "alc_m12_cell_allnum": "10", + }, + "JRZQ5E9F": map[string]interface{}{ + "xyp_cpl0072": "5", // 当前逾期金额较大 + "xyp_cpl0071": "5", // 当前逾期机构数较多 + }, + } + + log := zap.NewNop() + riskWarning := buildRiskWarning(apiData, log) + + // 验证gazdyrhyRiskCounts(公安重点人员风险计数) + if gazdyrhyRiskCounts, ok := riskWarning["gazdyrhyRiskCounts"].(int); ok { + if gazdyrhyRiskCounts < 5 { + t.Errorf("gazdyrhyRiskCounts应该至少为5,实际为%d", gazdyrhyRiskCounts) + } + } + + // 验证sfhyfxRiskCounts(涉赌涉诈风险计数) + if sfhyfxRiskCounts, ok := riskWarning["sfhyfxRiskCounts"].(int); ok { + if sfhyfxRiskCounts != 1 { + t.Errorf("sfhyfxRiskCounts应该为1,实际为%d", sfhyfxRiskCounts) + } + } + + // 验证yqfxRiskCounts(逾期风险计数) + if yqfxRiskCounts, ok := riskWarning["yqfxRiskCounts"].(int); ok { + if yqfxRiskCounts < 3 { + t.Errorf("yqfxRiskCounts应该至少为3,实际为%d", yqfxRiskCounts) + } + } + + // 验证frequentBankApplications + if frequentBankApplications, ok := riskWarning["frequentBankApplications"].(int); ok { + if frequentBankApplications != 1 { + t.Errorf("frequentBankApplications应该为1,实际为%d", frequentBankApplications) + } + } + + // 验证frequentRentalApplications + if frequentRentalApplications, ok := riskWarning["frequentRentalApplications"].(int); ok { + if frequentRentalApplications != 1 { + t.Errorf("frequentRentalApplications应该为1,实际为%d", frequentRentalApplications) + } + } + + // 验证highDebtPressure + if highDebtPressure, ok := riskWarning["highDebtPressure"].(int); ok { + if highDebtPressure != 1 { + t.Errorf("highDebtPressure应该为1,实际为%d", highDebtPressure) + } + } +} + +// TestDataFormatConversion 测试数据格式转换 +func TestDataFormatConversion(t *testing.T) { + // 测试身份证/手机号格式转换 + apiData := map[string]interface{}{ + "JRZQ6F2A": map[string]interface{}{ + "risk_screen_v2": map[string]interface{}{ + "variables": []interface{}{ + map[string]interface{}{ + "variableValue": map[string]interface{}{ + "als_m6_id_bank_allnum": "5", + "als_m6_cell_bank_allnum": "3", + }, + }, + }, + }, + }, + } + + log := zap.NewNop() + detail := buildLoanEvaluationVerificationDetail(apiData, log) + + // 验证格式为"身份证/手机号" + if organLoanPerformances, ok := detail["organLoanPerformances"].([]interface{}); ok { + if len(organLoanPerformances) > 0 { + if bankPerf, ok := organLoanPerformances[0].(map[string]interface{}); ok { + if last6Month, ok := bankPerf["last6Month"].(string); ok { + if !strings.Contains(last6Month, "/") { + t.Errorf("last6Month格式异常,应该包含'/',实际为%s", last6Month) + } + // 应该是"5/3" + if last6Month != "5/3" { + t.Errorf("last6Month应该为'5/3',实际为%s", last6Month) + } + } + } + } + } +} + +// TestDefaultValues 测试默认值设置 +func TestDefaultValues(t *testing.T) { + // 测试空数据情况 + apiData := map[string]interface{}{} + + params := dto.DWBG8B4DReq{ + Name: "测试", + IDCard: "110101199001011234", + MobileNo: "13800138000", + } + + log := zap.NewNop() + report := transformToDitingReport(apiData, params, log) + + // 验证baseInfo有默认值 + if baseInfo, ok := report["baseInfo"].(map[string]interface{}); ok { + if name, ok := baseInfo["name"].(string); ok { + if name == "" { + t.Error("name字段应该有默认值(脱敏后的姓名)") + } + } + if age, ok := baseInfo["age"].(int); ok { + if age < 0 { + t.Errorf("age字段默认值异常: %d", age) + } + } + } + + // 验证overdueRiskProduct有默认值 + if overdueRiskProduct, ok := report["overdueRiskProduct"].(map[string]interface{}); ok { + if hasOverdue, ok := overdueRiskProduct["hasUnsettledOverdue"].(string); ok { + if hasOverdue == "" { + t.Error("hasUnsettledOverdue应该有默认值") + } + } + // 验证repaymentSuccessCount默认值为"-" + if successCount, ok := overdueRiskProduct["repaymentSuccessCountLast7Days"].(string); ok { + if successCount != "-" { + t.Errorf("repaymentSuccessCountLast7Days默认值应该为'-',实际为%s", successCount) + } + } + } +} + +// TestEdgeCases 测试边界情况 +func TestEdgeCases(t *testing.T) { + // 测试手机在网状态的各种情况 + testCases := []struct { + name string + status interface{} + desc string + expected int + }{ + {"在网", 0, "在网", 1}, // 实号 + {"停机", 1, "停机", 2}, // 停机 + {"空号", 1, "空号", 0}, // 空号 + {"库无", 1, "库无", 3}, // 库无 + {"沉默号", 1, "其他", 4}, // 沉默号 + {"风险号", 1, "风险", 5}, // 风险号 + {"未查得", nil, "", 1}, // 未查得(会使用YYSYH6D2的result作为fallback,result="0"表示一致,status=1) + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + apiData := map[string]interface{}{ + "YYSYH6D2": map[string]interface{}{ + "result": "0", + "channel": "cmcc", + "address": "北京市东城区", + }, + "YYSY9E4A": map[string]interface{}{ + "provinceName": "北京", + "cityName": "北京", + }, + } + + // 只有当status不为nil时才添加YYSYE7V5数据 + if tc.status != nil { + apiData["YYSYE7V5"] = map[string]interface{}{ + "status": tc.status, + "desc": tc.desc, + "channel": "cmcc", + } + } + + log := zap.NewNop() + baseInfo := buildBaseInfo(apiData, dto.DWBG8B4DReq{ + Name: "测试", + IDCard: "110101199001011234", + MobileNo: "13800138000", + }, log) + + if status, ok := baseInfo["status"].(int); ok { + if status != tc.expected { + t.Errorf("status应该为%d,实际为%d", tc.expected, status) + } + } else if tc.expected != -1 { + t.Errorf("status字段不存在,期望为%d", tc.expected) + } + }) + } +} +