f
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package dwbg
|
||||
|
||||
// calcSinanRisk 基于当前 report 与原始 apiData 计算 riskList / riskPoint。
|
||||
func calcSinanRisk(report map[string]interface{}, apiData map[string]interface{}) {
|
||||
riskList := asMap(report["riskList"])
|
||||
if riskList == nil {
|
||||
riskList = emptyRiskList()
|
||||
report["riskList"] = riskList
|
||||
}
|
||||
|
||||
jrzq := jrzqMap(apiData)
|
||||
jrzq1d09 := jrzq1d09Map(apiData)
|
||||
baseInfo := asMap(report["baseInfo"])
|
||||
|
||||
if jrzq1d09 != nil {
|
||||
if rentalHasApplication(jrzq1d09) {
|
||||
riskList["creditLeaseRisk"] = 1
|
||||
}
|
||||
if maxTotMons(jrzq1d09) >= 15 {
|
||||
riskList["vehicleLeaseViolation"] = 1
|
||||
}
|
||||
relationHit := hitRelationRisk(jrzq1d09)
|
||||
riskList["mediumRelationRisk"] = relationHit
|
||||
}
|
||||
|
||||
if jrzq != nil {
|
||||
riskList["bankOverdueRecord"] = hitBankOverdueRecord(jrzq)
|
||||
if xypString(jrzq, "xyp_cpl0044") == "1" {
|
||||
riskList["creditOverdueRecord"] = 1
|
||||
}
|
||||
}
|
||||
|
||||
if hasCourtViolator(apiData) {
|
||||
riskList["courtViolator"] = 1
|
||||
}
|
||||
|
||||
if baseInfo != nil {
|
||||
status := parseIntValue(baseInfo["status"])
|
||||
if status != 1 {
|
||||
riskList["phoneNumberStatus"] = 1
|
||||
}
|
||||
if status == 5 {
|
||||
riskList["riskPhoneNumber"] = 1
|
||||
}
|
||||
}
|
||||
|
||||
if yysyh6f3 := asMap(getMapValue(apiData, "YYSYH6F3")); yysyh6f3 != nil {
|
||||
if xypString(yysyh6f3, "result") == "1" {
|
||||
riskList["identityFake"] = 1
|
||||
}
|
||||
}
|
||||
|
||||
if groupFraudHit(asMap(getMapValue(apiData, "FLXG8B4D"))) {
|
||||
riskList["groupFraud"] = 1
|
||||
}
|
||||
|
||||
if flxgdea9 := asMap(getMapValue(apiData, "FLXGDEA9")); flxgdea9 != nil {
|
||||
if industryBlacklistHit(xypString(flxgdea9, "level")) {
|
||||
riskList["industryBlacklist"] = 1
|
||||
}
|
||||
}
|
||||
|
||||
riskList["highRiskArea"] = 0
|
||||
riskList["taxDebt"] = 0
|
||||
|
||||
riskPoint := asMap(report["riskPoint"])
|
||||
if riskPoint == nil {
|
||||
riskPoint = emptyRiskPoint()
|
||||
report["riskPoint"] = riskPoint
|
||||
}
|
||||
|
||||
if jrzq != nil {
|
||||
riskPoint["multiQuery"] = calcMultiQuery(jrzq)
|
||||
riskPoint["deductFail"] = calcDeductFail(jrzq)
|
||||
riskPoint["newRiskFeature"] = hitNewRiskFeature(jrzq)
|
||||
}
|
||||
|
||||
riskPoint["riskList"] = 0
|
||||
for _, v := range riskList {
|
||||
if parseIntValue(v) == 1 {
|
||||
riskPoint["riskList"] = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasJudicialCases(apiData) {
|
||||
riskPoint["judicialRisk"] = 1
|
||||
}
|
||||
if hasPreservationCases(apiData) {
|
||||
riskPoint["hitPreservationReview"] = 1
|
||||
}
|
||||
if hasLegalCases(apiData) {
|
||||
riskPoint["legalCasesFlag"] = 1
|
||||
}
|
||||
if hasExecutionCases(apiData) {
|
||||
riskPoint["executionCasesFlag"] = 1
|
||||
}
|
||||
if hasDisinCases(apiData) {
|
||||
riskPoint["disinCasesFlag"] = 1
|
||||
}
|
||||
if hasLimitCases(apiData) {
|
||||
riskPoint["limitCasesFlag"] = 1
|
||||
}
|
||||
|
||||
if flxgdea9 := asMap(getMapValue(apiData, "FLXGDEA9")); flxgdea9 != nil {
|
||||
if securityLevelHit(xypString(flxgdea9, "level")) {
|
||||
riskPoint["securityRisk"] = 1
|
||||
}
|
||||
}
|
||||
|
||||
if antiFraudHit(asMap(getMapValue(apiData, "FLXG8B4D"))) {
|
||||
riskPoint["antiFraudRisk"] = 1
|
||||
}
|
||||
|
||||
relationHit := 0
|
||||
if jrzq1d09 != nil {
|
||||
relationHit = hitRelationRisk(jrzq1d09)
|
||||
}
|
||||
riskPoint["relationRisk"] = relationHit
|
||||
riskPoint["registerRisk"] = 0
|
||||
riskPoint["judicialCase"] = 0
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type sinanAPICallInfo struct {
|
||||
apiCode string
|
||||
params map[string]interface{}
|
||||
}
|
||||
|
||||
type sinanProcessorResult struct {
|
||||
apiCode string
|
||||
data interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
// collectSinanAPIData 并发调用 9 个子接口;单接口失败仅记日志,对应 key 为 nil。
|
||||
func collectSinanAPIData(ctx context.Context, params dto.DWBG6A2CReq, deps *processors.ProcessorDependencies, log *zap.Logger) map[string]interface{} {
|
||||
apiCalls := []sinanAPICallInfo{
|
||||
{apiCode: "YYSY35TA", params: map[string]interface{}{"mobile_no": params.MobileNo}},
|
||||
{apiCode: "YYSYE7V5", params: map[string]interface{}{"mobile_no": params.MobileNo}},
|
||||
{
|
||||
apiCode: "YYSYH6F3",
|
||||
params: map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
},
|
||||
},
|
||||
{apiCode: "YYSYP0T4", params: map[string]interface{}{"mobile_no": params.MobileNo}},
|
||||
{
|
||||
apiCode: "FLXGDEA9",
|
||||
params: map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "FLXG8B4D",
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "FLXG7E8F",
|
||||
params: map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQ5E9F",
|
||||
params: map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQ1D09",
|
||||
params: map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
apiData := make(map[string]interface{}, len(apiCalls))
|
||||
results := make(chan sinanProcessorResult, len(apiCalls))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, apiCall := range apiCalls {
|
||||
wg.Add(1)
|
||||
go func(ac sinanAPICallInfo) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Error("调用司南子接口时发生panic",
|
||||
zap.String("api_code", ac.apiCode),
|
||||
zap.Any("panic", r),
|
||||
)
|
||||
results <- sinanProcessorResult{apiCode: ac.apiCode, err: fmt.Errorf("处理器panic: %v", r)}
|
||||
}
|
||||
}()
|
||||
|
||||
paramsBytes, err := json.Marshal(ac.params)
|
||||
if err != nil {
|
||||
log.Warn("序列化司南子接口参数失败",
|
||||
zap.String("api_code", ac.apiCode),
|
||||
zap.Error(err),
|
||||
)
|
||||
results <- sinanProcessorResult{apiCode: ac.apiCode, err: err}
|
||||
return
|
||||
}
|
||||
|
||||
data, err := callProcessor(ctx, ac.apiCode, paramsBytes, deps)
|
||||
results <- sinanProcessorResult{apiCode: ac.apiCode, data: data, err: err}
|
||||
}(apiCall)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
successCount := 0
|
||||
for result := range results {
|
||||
if result.err != nil {
|
||||
log.Warn("调用司南子接口失败,将使用默认值",
|
||||
zap.String("api_code", result.apiCode),
|
||||
zap.Error(result.err),
|
||||
)
|
||||
apiData[result.apiCode] = nil
|
||||
continue
|
||||
}
|
||||
apiData[result.apiCode] = result.data
|
||||
successCount++
|
||||
}
|
||||
|
||||
log.Info("司南子接口调用完成",
|
||||
zap.Int("total", len(apiCalls)),
|
||||
zap.Int("success", successCount),
|
||||
zap.Int("failed", len(apiCalls)-successCount),
|
||||
)
|
||||
|
||||
return apiData
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package dwbg
|
||||
|
||||
import "tyapi-server/internal/domains/api/dto"
|
||||
|
||||
var rentalBehaviorFieldNames = []string{
|
||||
"rentalApplicationInstitutionsLast3Days",
|
||||
"rentalApplicationCountLast3Days",
|
||||
"rentalApplicationInstitutionsLast3DaysWeekend",
|
||||
"rentalApplicationCountLast3DaysWeekend",
|
||||
"rentalApplicationInstitutionsLast3DaysNight",
|
||||
"rentalApplicationCountLast3DaysNight",
|
||||
"rentalApplicationInstitutionsLast7Days",
|
||||
"rentalApplicationCountLast7Days",
|
||||
"rentalApplicationInstitutionsLast7DaysWeekend",
|
||||
"rentalApplicationCountLast7DaysWeekend",
|
||||
"rentalApplicationInstitutionsLast7DaysNight",
|
||||
"rentalApplicationCountLast7DaysNight",
|
||||
"rentalApplicationInstitutionsLast14Days",
|
||||
"rentalApplicationCountLast14Days",
|
||||
"rentalApplicationInstitutionsLast14DaysWeekend",
|
||||
"rentalApplicationCountLast14DaysWeekend",
|
||||
"rentalApplicationInstitutionsLast14DaysNight",
|
||||
"rentalApplicationCountLast14DaysNight",
|
||||
"rentalApplicationInstitutionsLast1Month",
|
||||
"rentalApplicationCountLast1Month",
|
||||
"rentalApplicationInstitutionsLast1MonthWeekend",
|
||||
"rentalApplicationCountLast1MonthWeekend",
|
||||
"rentalApplicationInstitutionsLast1MonthNight",
|
||||
"rentalApplicationCountLast1MonthNight",
|
||||
"rentalApplicationInstitutionsLast3Months",
|
||||
"rentalApplicationCountLast3Months",
|
||||
"rentalApplicationInstitutionsLast3MonthsWeekend",
|
||||
"rentalApplicationCountLast3MonthsWeekend",
|
||||
"rentalApplicationInstitutionsLast3MonthsNight",
|
||||
"rentalApplicationCountLast3MonthsNight",
|
||||
"rentalApplicationInstitutionsLast6Months",
|
||||
"rentalApplicationCountLast6Months",
|
||||
"rentalApplicationInstitutionsLast6MonthsWeekend",
|
||||
"rentalApplicationCountLast6MonthsWeekend",
|
||||
"rentalApplicationInstitutionsLast6MonthsNight",
|
||||
"rentalApplicationCountLast6MonthsNight",
|
||||
"rentalApplicationInstitutionsLast12Months",
|
||||
"rentalApplicationCountLast12Months",
|
||||
"rentalApplicationInstitutionsLast12MonthsWeekend",
|
||||
"rentalApplicationCountLast12MonthsWeekend",
|
||||
"rentalApplicationInstitutionsLast12MonthsNight",
|
||||
"rentalApplicationCountLast12MonthsNight",
|
||||
}
|
||||
|
||||
var judicialCaseCountsKeys = []string{
|
||||
"caseCounts",
|
||||
"civilCaseCounts",
|
||||
"criminalCaseCounts",
|
||||
"administrativeCaseCounts",
|
||||
"preservationCaseCounts",
|
||||
"enforcementCaseCounts",
|
||||
"supervisionCaseCounts",
|
||||
"compensationCaseCounts",
|
||||
"bankruptcyCaseCounts",
|
||||
}
|
||||
|
||||
// emptyCaseCounts 返回司法统计 object 默认骨架(值均为 String)。
|
||||
func emptyCaseCounts() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"beigaoTotalCaseAmounts": "0",
|
||||
"beigaoTotalCasesCounts": "0",
|
||||
"beigaoTotalJieAmountCases": "0",
|
||||
"beigaoTotalJieCaseCounts": "0",
|
||||
"beigaoTotalWeiAmountCases": "0",
|
||||
"beigaoTotalWeiCaseCounts": "0",
|
||||
"caseActionDistribution": "-",
|
||||
"caseCloselDistribution": "-",
|
||||
"localDistribution": "-",
|
||||
"otherTotalCaseAmounts": "0",
|
||||
"otherTotalCasesCounts": "0",
|
||||
"otherTotalJieAmountCases": "0",
|
||||
"otherTotalJieCaseCounts": "0",
|
||||
"otherTotalWeiAmountCases": "0",
|
||||
"otherTotalWeiCaseCounts": "0",
|
||||
"timeDistribution": "-",
|
||||
"totalCaseAmounts": "0",
|
||||
"totalCasesCounts": "0",
|
||||
"totalJieAmountCases": "0",
|
||||
"totalJieCaseCounts": "0",
|
||||
"totalWeiAmountCases": "0",
|
||||
"totalWeiCaseCounts": "0",
|
||||
"yuangaoTotalCaseAmounts": "0",
|
||||
"yuangaoTotalCasesCounts": "0",
|
||||
"yuangaoTotalJieAmountCases": "0",
|
||||
"yuangaoTotalJieCaseCounts": "0",
|
||||
"yuangaoTotalWeiAmountCases": "0",
|
||||
"yuangaoTotalWeiCaseCounts": "0",
|
||||
}
|
||||
}
|
||||
|
||||
func emptyNewMultCourtInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, len(judicialCaseCountsKeys))
|
||||
for _, key := range judicialCaseCountsKeys {
|
||||
info[key] = emptyCaseCounts()
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func emptyCaseOverviewInfo() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"disinCaseCounts": 0,
|
||||
"limitCaseCounts": 0,
|
||||
"beigaoTotalCasesCounts": 0,
|
||||
"beigaoTotalWeiCaseCounts": 0,
|
||||
"executionCaseCounts": 0,
|
||||
"beigaoTotalCaseAmounts": 0,
|
||||
"affiliateCompany": 0,
|
||||
"leastCaseTime": "",
|
||||
}
|
||||
}
|
||||
|
||||
// emptyJudicialLeaseReport 返回司法模块默认骨架。
|
||||
func emptyJudicialLeaseReport() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"courtInfo": map[string]interface{}{
|
||||
"civilCasesCount": 0,
|
||||
"criminalCasesCount": 0,
|
||||
"administrativeCasesCount": 0,
|
||||
"preservationCasesCount": 0,
|
||||
"enforcementCasesCount": 0,
|
||||
"supervisionCasesCount": 0,
|
||||
"compensationCasesCount": 0,
|
||||
"bankruptcyCasesCount": 0,
|
||||
"newMultCourtInfo": emptyNewMultCourtInfo(),
|
||||
},
|
||||
"caseOverviewInfo": emptyCaseOverviewInfo(),
|
||||
}
|
||||
}
|
||||
|
||||
func emptyRentalBehavior() map[string]interface{} {
|
||||
behavior := make(map[string]interface{}, len(rentalBehaviorFieldNames))
|
||||
for _, field := range rentalBehaviorFieldNames {
|
||||
behavior[field] = "0/0"
|
||||
}
|
||||
return behavior
|
||||
}
|
||||
|
||||
func emptyBaseInfo(params dto.DWBG6A2CReq) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"name": maskName(params.Name),
|
||||
"idCard": maskIDCard(params.IDCard),
|
||||
"phone": maskMobile(params.MobileNo),
|
||||
"age": 0,
|
||||
"sex": "",
|
||||
"location": "",
|
||||
"phoneArea": "",
|
||||
"channel": "",
|
||||
"status": -1,
|
||||
}
|
||||
}
|
||||
|
||||
func emptyStandLiveInfo() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"finalAuthResult": "1",
|
||||
"verification": "-1",
|
||||
"inTime": "-1",
|
||||
}
|
||||
}
|
||||
|
||||
func emptySecurityInfo() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"front": 0,
|
||||
"drug": 0,
|
||||
"takeDrug": 0,
|
||||
"escape": 0,
|
||||
"icase": 0,
|
||||
"ikey": 0,
|
||||
"itrancase": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func emptyAntiFraudInfo() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"moneyLaundering": "0",
|
||||
"deceiver": "0",
|
||||
"gamblerPlayer": "0",
|
||||
"gamblerBanker": "0",
|
||||
}
|
||||
}
|
||||
|
||||
func emptyRiskList() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"creditLeaseRisk": 0,
|
||||
"bankOverdueRecord": 0,
|
||||
"riskPhoneNumber": 0,
|
||||
"highRiskArea": 0,
|
||||
"creditOverdueRecord": 0,
|
||||
"vehicleLeaseViolation": 0,
|
||||
"courtViolator": 0,
|
||||
"industryBlacklist": 0,
|
||||
"identityFake": 0,
|
||||
"groupFraud": 0,
|
||||
"taxDebt": 0,
|
||||
"mediumRelationRisk": 0,
|
||||
"phoneNumberStatus": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func emptyRiskPoint() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"multiQuery": 0,
|
||||
"riskList": 0,
|
||||
"newRiskFeature": 0,
|
||||
"deductFail": 0,
|
||||
"judicialRisk": 0,
|
||||
"hitPreservationReview": 0,
|
||||
"legalCasesFlag": 0,
|
||||
"executionCasesFlag": 0,
|
||||
"disinCasesFlag": 0,
|
||||
"limitCasesFlag": 0,
|
||||
"securityRisk": 0,
|
||||
"antiFraudRisk": 0,
|
||||
"registerRisk": 0,
|
||||
"relationRisk": 0,
|
||||
"judicialCase": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func emptyApplicationStatistics() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"totalApplicationCount": 0,
|
||||
"consumptionInstallmentApplicationCount": 0,
|
||||
"onlineLoanApplicationCount": 0,
|
||||
"otherApplicationCount": 0,
|
||||
"lastApplicationDate": "-",
|
||||
"daysSinceLastApplication": "-",
|
||||
"applicationCountLastMonth": 0,
|
||||
"applicationCountLast3Months": 0,
|
||||
"applicationCountLast6Months": 0,
|
||||
"applicationCountLast12Months": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func emptyLendingStatistics() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"totalLendingInstitutionCount": 0,
|
||||
"installmentLendingInstitutionCount": 0,
|
||||
"onlineLendingInstitutionCount": 0,
|
||||
"lastLendingDate": "-",
|
||||
"daysSinceLastLending": "-",
|
||||
"lendingCountLastMonth": 0,
|
||||
"lendingAmountLastMonth": "0",
|
||||
"lendingCountLast3Months": 0,
|
||||
"lendingAmountLast3Months": "0",
|
||||
"lendingCountLast6Months": 0,
|
||||
"lendingAmountLast6Months": "0",
|
||||
"lendingCountLast12Months": 0,
|
||||
"lendingAmountLast12Months": "0",
|
||||
"lendingCountLast24Months": 0,
|
||||
"lendingAmountLast24Months": "0",
|
||||
}
|
||||
}
|
||||
|
||||
func emptyPerformanceStatistics() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"normalRepaymentRatio": "0",
|
||||
"creditLoanDuration": 0,
|
||||
"settledLoanOrderCount": 0,
|
||||
"daysSinceLastPerformance": "-",
|
||||
"performanceCountLastMonth": 0,
|
||||
"performanceAmountLastMonth": "0",
|
||||
"repaymentExceptionCountLastMonth": 0,
|
||||
"performanceCountLast3Months": 0,
|
||||
"performanceAmountLast3Months": "0",
|
||||
"repaymentExceptionCountLast3Months": 0,
|
||||
"performanceCountLast6Months": 0,
|
||||
"performanceAmountLast6Months": "0",
|
||||
"repaymentExceptionCountLast6Months": 0,
|
||||
"performanceCountLast12Months": 0,
|
||||
"performanceAmountLast12Months": "0",
|
||||
"repaymentExceptionCountLast12Months": 0,
|
||||
"performanceCountLast24Months": 0,
|
||||
"performanceAmountLast24Months": "0",
|
||||
"repaymentExceptionCountLast24Months": 0,
|
||||
"serialVersionUID": 1,
|
||||
}
|
||||
}
|
||||
|
||||
func emptyOverdueRecord() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"currentOverdueInstitution": "-",
|
||||
"currentOverdueCount": 0,
|
||||
"totalOverdueAmount": "0",
|
||||
"lastOverdueDate": "-",
|
||||
"m0PlusCountLast6Months": 0,
|
||||
"m1PlusCountLast6Months": 0,
|
||||
"totalAmountLast6Months": "0",
|
||||
"m0PlusCountLast12Months": 0,
|
||||
"m1PlusCountLast12Months": 0,
|
||||
"totalAmountLast12Months": "0",
|
||||
"m0PlusCountLast24Months": 0,
|
||||
"m1PlusCountLast24Months": 0,
|
||||
"totalAmountLast24Months": "0",
|
||||
}
|
||||
}
|
||||
|
||||
func emptyCreditDetail() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"maxOnlineLoanCredit": "0",
|
||||
"avgOnlineLoanCredit": "0",
|
||||
"maxConsumptionInstallmentCredit": "0",
|
||||
"avgConsumptionInstallmentCredit": "0",
|
||||
}
|
||||
}
|
||||
|
||||
func emptyRiskSupervision() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"leastApplicationTime": "",
|
||||
"rentalRiskListIdCardRelationsPhones": 0,
|
||||
"rentalRiskListPhoneRelationsIdCards": 0,
|
||||
"details": "无",
|
||||
}
|
||||
}
|
||||
|
||||
// emptySinanReport 返回司南报告全字段默认骨架。
|
||||
func emptySinanReport(params dto.DWBG6A2CReq) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"baseInfo": emptyBaseInfo(params),
|
||||
"standLiveInfo": emptyStandLiveInfo(),
|
||||
"riskPoint": emptyRiskPoint(),
|
||||
"securityInfo": emptySecurityInfo(),
|
||||
"antiFraudInfo": emptyAntiFraudInfo(),
|
||||
"riskList": emptyRiskList(),
|
||||
"applicationStatistics": emptyApplicationStatistics(),
|
||||
"lendingStatistics": emptyLendingStatistics(),
|
||||
"performanceStatistics": emptyPerformanceStatistics(),
|
||||
"overdueRecord": emptyOverdueRecord(),
|
||||
"creditDetail": emptyCreditDetail(),
|
||||
"rentalBehavior": emptyRentalBehavior(),
|
||||
"riskSupervision": emptyRiskSupervision(),
|
||||
"judicialLeaseReport": emptyJudicialLeaseReport(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// buildJudicialLeaseReport maps FLXG7E8F to Sinan judicialLeaseReport.
|
||||
func buildJudicialLeaseReport(apiData map[string]interface{}) map[string]interface{} {
|
||||
report := emptyJudicialLeaseReport()
|
||||
flxg := flxg7e8fMap(apiData)
|
||||
if flxg == nil {
|
||||
return report
|
||||
}
|
||||
judicialData := asMap(flxg["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return report
|
||||
}
|
||||
|
||||
lawsuitStat := asMap(judicialData["lawsuitStat"])
|
||||
courtInfo, newMult := judicialCourtMaps(report)
|
||||
if courtInfo == nil || newMult == nil {
|
||||
return report
|
||||
}
|
||||
|
||||
if lawsuitStat != nil {
|
||||
courtInfo["civilCasesCount"] = lawsuitCasesCount(lawsuitStat, "civil")
|
||||
courtInfo["criminalCasesCount"] = lawsuitCasesCount(lawsuitStat, "criminal")
|
||||
courtInfo["administrativeCasesCount"] = lawsuitCasesCount(lawsuitStat, "administrative")
|
||||
courtInfo["preservationCasesCount"] = lawsuitPreservationCount(lawsuitStat)
|
||||
courtInfo["enforcementCasesCount"] = lawsuitCasesCount(lawsuitStat, "implement")
|
||||
courtInfo["bankruptcyCasesCount"] = lawsuitCasesCount(lawsuitStat, "bankrupt")
|
||||
|
||||
if cases := convertSinanCaseList(lawsuitStat, "civil"); len(cases) > 0 {
|
||||
newMult["civilCases"] = cases
|
||||
}
|
||||
if cases := convertSinanCaseList(lawsuitStat, "criminal"); len(cases) > 0 {
|
||||
newMult["criminalCases"] = cases
|
||||
}
|
||||
if cases := convertSinanCaseList(lawsuitStat, "administrative"); len(cases) > 0 {
|
||||
newMult["administrativeCases"] = cases
|
||||
}
|
||||
if cases := convertSinanCaseList(lawsuitStat, "preservation"); len(cases) > 0 {
|
||||
newMult["preservationCases"] = cases
|
||||
}
|
||||
if cases := convertSinanCaseList(lawsuitStat, "implement"); len(cases) > 0 {
|
||||
newMult["enforcementCases"] = cases
|
||||
}
|
||||
if cases := convertSinanCaseList(lawsuitStat, "bankrupt"); len(cases) > 0 {
|
||||
newMult["bankruptcyCases"] = cases
|
||||
}
|
||||
|
||||
if globalCount := asMap(lawsuitStat["count"]); globalCount != nil {
|
||||
newMult["caseCounts"] = convertFLXGCountToSinan(globalCount)
|
||||
}
|
||||
for _, pair := range []struct {
|
||||
flxgKey string
|
||||
sinanKey string
|
||||
}{
|
||||
{"civil", "civilCaseCounts"},
|
||||
{"criminal", "criminalCaseCounts"},
|
||||
{"administrative", "administrativeCaseCounts"},
|
||||
{"preservation", "preservationCaseCounts"},
|
||||
{"implement", "enforcementCaseCounts"},
|
||||
{"bankrupt", "bankruptcyCaseCounts"},
|
||||
} {
|
||||
if bucket := asMap(lawsuitStat[pair.flxgKey]); bucket != nil {
|
||||
if count := asMap(bucket["count"]); count != nil {
|
||||
newMult[pair.sinanKey] = convertFLXGCountToSinan(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if breachList, ok := judicialData["breachCaseList"].([]interface{}); ok && len(breachList) > 0 {
|
||||
disinCases := make([]interface{}, 0, len(breachList))
|
||||
for _, item := range breachList {
|
||||
if caseMap, ok := item.(map[string]interface{}); ok {
|
||||
if converted := convertSinanDisinCase(caseMap); converted != nil {
|
||||
disinCases = append(disinCases, converted)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(disinCases) > 0 {
|
||||
newMult["disinCases"] = disinCases
|
||||
}
|
||||
}
|
||||
|
||||
if limitList, ok := judicialData["consumptionRestrictionList"].([]interface{}); ok && len(limitList) > 0 {
|
||||
limitCases := make([]interface{}, 0, len(limitList))
|
||||
for _, item := range limitList {
|
||||
if caseMap, ok := item.(map[string]interface{}); ok {
|
||||
if converted := convertSinanLimitCase(caseMap); converted != nil {
|
||||
limitCases = append(limitCases, converted)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(limitCases) > 0 {
|
||||
newMult["limitCases"] = limitCases
|
||||
}
|
||||
}
|
||||
|
||||
report["caseOverviewInfo"] = buildCaseOverviewInfo(lawsuitStat, judicialData, courtInfo)
|
||||
return report
|
||||
}
|
||||
|
||||
func judicialCourtMaps(report map[string]interface{}) (courtInfo, newMult map[string]interface{}) {
|
||||
ci, ok := report["courtInfo"].(map[string]interface{})
|
||||
if !ok || ci == nil {
|
||||
return nil, nil
|
||||
}
|
||||
nm, ok := ci["newMultCourtInfo"].(map[string]interface{})
|
||||
if !ok || nm == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return ci, nm
|
||||
}
|
||||
|
||||
func lawsuitCasesCount(lawsuitStat map[string]interface{}, key string) int {
|
||||
bucket := asMap(lawsuitStat[key])
|
||||
if bucket == nil {
|
||||
return 0
|
||||
}
|
||||
if cases, ok := bucket["cases"].([]interface{}); ok && len(cases) > 0 {
|
||||
return len(cases)
|
||||
}
|
||||
if count := asMap(bucket["count"]); count != nil {
|
||||
return parseIntValue(count["count_total"])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func lawsuitPreservationCount(lawsuitStat map[string]interface{}) int {
|
||||
bucket := asMap(lawsuitStat["preservation"])
|
||||
if bucket == nil {
|
||||
return 0
|
||||
}
|
||||
if count := asMap(bucket["count"]); count != nil {
|
||||
if total := parseIntValue(count["count_total"]); total > 0 {
|
||||
return total
|
||||
}
|
||||
}
|
||||
return lawsuitCasesCount(lawsuitStat, "preservation")
|
||||
}
|
||||
|
||||
func convertSinanCaseList(lawsuitStat map[string]interface{}, key string) []interface{} {
|
||||
bucket := asMap(lawsuitStat[key])
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
cases, ok := bucket["cases"].([]interface{})
|
||||
if !ok || len(cases) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]interface{}, 0, len(cases))
|
||||
for _, item := range cases {
|
||||
caseMap, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if converted := convertSinanLegalCase(caseMap); converted != nil {
|
||||
out = append(out, converted)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func convertSinanLegalCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseNumber := xypString(caseMap, "c_ah")
|
||||
if caseNumber == "" {
|
||||
return nil
|
||||
}
|
||||
info := map[string]interface{}{
|
||||
"caseNumber": caseNumber,
|
||||
"nssdw": xypString(caseMap, "n_ssdw"),
|
||||
"caseStatus": xypString(caseMap, "n_ajjzjd"),
|
||||
"najlx": xypString(caseMap, "n_ajlx"),
|
||||
"cdsrxx": formatCdsrxx(caseMap["c_dsrxx"]),
|
||||
"nslcx": xypString(caseMap, "n_slcx"),
|
||||
"zxfy": xypString(caseMap, "n_jbfy"),
|
||||
"njbfy": xypString(caseMap, "n_jbfy"),
|
||||
"dlarq": xypString(caseMap, "d_larq"),
|
||||
"djarq": xypString(caseMap, "d_jarq"),
|
||||
"njafs": xypString(caseMap, "n_jafs"),
|
||||
"nqsbdje": formatNumericValue(caseMap["n_qsbdje"]),
|
||||
"njabdje": formatNumericValue(caseMap["n_jabdje"]),
|
||||
"nsqzxbdje": formatNumericValue(caseMap["n_sqzxbdje"]),
|
||||
"nsjdwje": formatNumericValue(caseMap["n_sjdwje"]),
|
||||
"nwzxje": formatNumericValue(caseMap["n_wzxje"]),
|
||||
"npjVictory": xypString(caseMap, "n_pj_victory"),
|
||||
"cgkwsDsr": xypString(caseMap, "c_gkws_dsr"),
|
||||
"cgkwsPjjg": xypString(caseMap, "c_gkws_pjjg"),
|
||||
"najbs": xypString(caseMap, "n_ajbs"),
|
||||
"nlaayTree": firstNonEmpty(xypString(caseMap, "n_laay_tree"), xypString(caseMap, "n_jaay_tree")),
|
||||
"nfzje": formatNumericValue(caseMap["n_fzje"]),
|
||||
"npcpcje": formatNumericValue(caseMap["n_pcpcje"]),
|
||||
"nccxzxje": formatNumericValue(caseMap["n_ccxzxje"]),
|
||||
"cahHx": "",
|
||||
"cahHxBs": "",
|
||||
}
|
||||
if cAhHx := xypString(caseMap, "c_ah_hx"); cAhHx != "" {
|
||||
parts := strings.SplitN(cAhHx, ":", 2)
|
||||
info["cahHx"] = strings.TrimSpace(parts[0])
|
||||
if len(parts) == 2 {
|
||||
info["cahHxBs"] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func formatCdsrxx(raw interface{}) string {
|
||||
list, ok := raw.([]interface{})
|
||||
if !ok || len(list) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, 0, len(list))
|
||||
for _, item := range list {
|
||||
m, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
role := xypString(m, "n_ssdw")
|
||||
name := xypString(m, "c_mc")
|
||||
typ := xypString(m, "n_dsrlx")
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("%s%s\u3010%s\u3011", role, name, typ))
|
||||
}
|
||||
return strings.Join(parts, "\uff1b")
|
||||
}
|
||||
|
||||
func convertFLXGCountToSinan(count map[string]interface{}) map[string]interface{} {
|
||||
out := emptyCaseCounts()
|
||||
out["totalCasesCounts"] = countToString(count["count_total"])
|
||||
out["totalJieCaseCounts"] = countToString(count["count_jie_total"])
|
||||
out["totalWeiCaseCounts"] = countToString(count["count_wei_total"])
|
||||
out["totalCaseAmounts"] = countToString(count["money_total"])
|
||||
out["totalJieAmountCases"] = countToString(count["money_jie_total"])
|
||||
out["totalWeiAmountCases"] = countToString(count["money_wei_total"])
|
||||
out["beigaoTotalCasesCounts"] = countToString(count["count_beigao"])
|
||||
out["beigaoTotalJieCaseCounts"] = countToString(count["count_jie_beigao"])
|
||||
out["beigaoTotalWeiCaseCounts"] = countToString(count["count_wei_beigao"])
|
||||
out["beigaoTotalCaseAmounts"] = countToString(count["money_beigao"])
|
||||
out["beigaoTotalJieAmountCases"] = countToString(count["money_jie_beigao"])
|
||||
out["beigaoTotalWeiAmountCases"] = countToString(count["money_wei_beigao"])
|
||||
out["yuangaoTotalCasesCounts"] = countToString(count["count_yuangao"])
|
||||
out["yuangaoTotalJieCaseCounts"] = countToString(count["count_jie_yuangao"])
|
||||
out["yuangaoTotalWeiCaseCounts"] = countToString(count["count_wei_yuangao"])
|
||||
out["yuangaoTotalCaseAmounts"] = countToString(count["money_yuangao"])
|
||||
out["yuangaoTotalJieAmountCases"] = countToString(count["money_jie_yuangao"])
|
||||
out["yuangaoTotalWeiAmountCases"] = countToString(count["money_wei_yuangao"])
|
||||
out["otherTotalCasesCounts"] = countToString(count["count_other"])
|
||||
out["otherTotalJieCaseCounts"] = countToString(count["count_jie_other"])
|
||||
out["otherTotalWeiCaseCounts"] = countToString(count["count_wei_other"])
|
||||
out["otherTotalCaseAmounts"] = countToString(count["money_other"])
|
||||
out["otherTotalJieAmountCases"] = countToString(count["money_jie_other"])
|
||||
out["otherTotalWeiAmountCases"] = countToString(count["money_wei_other"])
|
||||
out["caseActionDistribution"] = statOrDash(count["ay_stat"])
|
||||
out["localDistribution"] = statOrDash(count["area_stat"])
|
||||
out["timeDistribution"] = statOrDash(count["larq_stat"])
|
||||
out["caseCloselDistribution"] = statOrDash(count["jafs_stat"])
|
||||
return out
|
||||
}
|
||||
|
||||
func countToString(val interface{}) string {
|
||||
if val == nil {
|
||||
return "0"
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
if strings.TrimSpace(v) == "" {
|
||||
return "0"
|
||||
}
|
||||
return v
|
||||
case float64:
|
||||
return formatNumericValue(v)
|
||||
case int:
|
||||
return formatNumericValue(v)
|
||||
case int64:
|
||||
return formatNumericValue(v)
|
||||
default:
|
||||
return "0"
|
||||
}
|
||||
}
|
||||
|
||||
func statOrDash(val interface{}) string {
|
||||
s := strings.TrimSpace(fmt.Sprint(val))
|
||||
if s == "" || s == "<nil>" {
|
||||
return "-"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func buildCaseOverviewInfo(lawsuitStat map[string]interface{}, judicialData map[string]interface{}, courtInfo map[string]interface{}) map[string]interface{} {
|
||||
overview := emptyCaseOverviewInfo()
|
||||
|
||||
if lawsuitStat != nil {
|
||||
if globalCount := asMap(lawsuitStat["count"]); globalCount != nil {
|
||||
overview["beigaoTotalCasesCounts"] = parseIntValue(globalCount["count_beigao"])
|
||||
overview["beigaoTotalWeiCaseCounts"] = parseIntValue(globalCount["count_wei_beigao"])
|
||||
overview["beigaoTotalCaseAmounts"] = parseIntValue(globalCount["money_beigao"])
|
||||
overview["leastCaseTime"] = extractLatestCaseTime(statOrDash(globalCount["larq_stat"]))
|
||||
}
|
||||
}
|
||||
|
||||
overview["executionCaseCounts"] = courtInfo["enforcementCasesCount"]
|
||||
|
||||
if breachList, ok := judicialData["breachCaseList"].([]interface{}); ok && len(breachList) > 0 {
|
||||
overview["disinCaseCounts"] = 1
|
||||
}
|
||||
if limitList, ok := judicialData["consumptionRestrictionList"].([]interface{}); ok && len(limitList) > 0 {
|
||||
overview["limitCaseCounts"] = 1
|
||||
}
|
||||
|
||||
return overview
|
||||
}
|
||||
|
||||
func extractLatestCaseTime(stat string) string {
|
||||
if stat == "" || stat == "-" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(stat, ",")
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(parts[len(parts)-1])
|
||||
}
|
||||
|
||||
func convertSinanDisinCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseNumber := xypString(caseMap, "caseNumber")
|
||||
if caseNumber == "" {
|
||||
return nil
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"caseNumber": caseNumber,
|
||||
"court": xypString(caseMap, "executiveCourt"),
|
||||
"filingTime": xypString(caseMap, "fileDate"),
|
||||
"caseStatus": xypString(caseMap, "fulfillStatus"),
|
||||
}
|
||||
}
|
||||
|
||||
func convertSinanLimitCase(caseMap map[string]interface{}) map[string]interface{} {
|
||||
caseNumber := xypString(caseMap, "caseNumber")
|
||||
if caseNumber == "" {
|
||||
return nil
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"caseNumber": caseNumber,
|
||||
"court": xypString(caseMap, "executiveCourt"),
|
||||
"filingTime": xypString(caseMap, "fileDate"),
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, v := range values {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func hasJudicialCases(apiData map[string]interface{}) bool {
|
||||
flxg := flxg7e8fMap(apiData)
|
||||
if flxg == nil {
|
||||
return false
|
||||
}
|
||||
judicialData := asMap(flxg["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return false
|
||||
}
|
||||
lawsuitStat := asMap(judicialData["lawsuitStat"])
|
||||
if lawsuitStat != nil {
|
||||
for _, key := range []string{"civil", "criminal", "administrative", "preservation", "implement", "bankrupt"} {
|
||||
if lawsuitCasesCount(lawsuitStat, key) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if list, ok := judicialData["breachCaseList"].([]interface{}); ok && len(list) > 0 {
|
||||
return true
|
||||
}
|
||||
if list, ok := judicialData["consumptionRestrictionList"].([]interface{}); ok && len(list) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasPreservationCases(apiData map[string]interface{}) bool {
|
||||
flxg := flxg7e8fMap(apiData)
|
||||
if flxg == nil {
|
||||
return false
|
||||
}
|
||||
judicialData := asMap(flxg["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return false
|
||||
}
|
||||
lawsuitStat := asMap(judicialData["lawsuitStat"])
|
||||
if lawsuitStat == nil {
|
||||
return false
|
||||
}
|
||||
return lawsuitPreservationCount(lawsuitStat) > 0
|
||||
}
|
||||
|
||||
func hasLegalCases(apiData map[string]interface{}) bool {
|
||||
flxg := flxg7e8fMap(apiData)
|
||||
if flxg == nil {
|
||||
return false
|
||||
}
|
||||
judicialData := asMap(flxg["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return false
|
||||
}
|
||||
lawsuitStat := asMap(judicialData["lawsuitStat"])
|
||||
if lawsuitStat == nil {
|
||||
return false
|
||||
}
|
||||
for _, key := range []string{"civil", "criminal", "administrative"} {
|
||||
if lawsuitCasesCount(lawsuitStat, key) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasExecutionCases(apiData map[string]interface{}) bool {
|
||||
flxg := flxg7e8fMap(apiData)
|
||||
if flxg == nil {
|
||||
return false
|
||||
}
|
||||
judicialData := asMap(flxg["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return false
|
||||
}
|
||||
lawsuitStat := asMap(judicialData["lawsuitStat"])
|
||||
if lawsuitStat == nil {
|
||||
return false
|
||||
}
|
||||
return lawsuitCasesCount(lawsuitStat, "implement") > 0
|
||||
}
|
||||
|
||||
func hasDisinCases(apiData map[string]interface{}) bool {
|
||||
flxg := flxg7e8fMap(apiData)
|
||||
if flxg == nil {
|
||||
return false
|
||||
}
|
||||
judicialData := asMap(flxg["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return false
|
||||
}
|
||||
list, ok := judicialData["breachCaseList"].([]interface{})
|
||||
return ok && len(list) > 0
|
||||
}
|
||||
|
||||
func hasLimitCases(apiData map[string]interface{}) bool {
|
||||
flxg := flxg7e8fMap(apiData)
|
||||
if flxg == nil {
|
||||
return false
|
||||
}
|
||||
judicialData := asMap(flxg["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return false
|
||||
}
|
||||
list, ok := judicialData["consumptionRestrictionList"].([]interface{})
|
||||
return ok && len(list) > 0
|
||||
}
|
||||
|
||||
func hasCourtViolator(apiData map[string]interface{}) bool {
|
||||
return hasExecutionCases(apiData) || hasDisinCases(apiData)
|
||||
}
|
||||
682
internal/domains/api/services/processors/dwbg/dwbg6a2c_map.go
Normal file
682
internal/domains/api/services/processors/dwbg/dwbg6a2c_map.go
Normal file
@@ -0,0 +1,682 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// transformToSinanReport 将子接口数据组合为司南报告格式。
|
||||
func transformToSinanReport(apiData map[string]interface{}, params dto.DWBG6A2CReq, log *zap.Logger) map[string]interface{} {
|
||||
_ = log
|
||||
report := emptySinanReport(params)
|
||||
|
||||
mergeInto(report, "baseInfo", buildSinanBaseInfo(apiData, params))
|
||||
mergeInto(report, "standLiveInfo", buildSinanStandLiveInfo(apiData))
|
||||
mergeInto(report, "antiFraudInfo", buildSinanAntiFraudInfo(apiData))
|
||||
mergeInto(report, "securityInfo", buildSinanSecurityInfo(apiData))
|
||||
mergeInto(report, "applicationStatistics", buildApplicationStatistics(apiData))
|
||||
mergeInto(report, "lendingStatistics", buildLendingStatistics(apiData))
|
||||
mergeInto(report, "performanceStatistics", buildPerformanceStatistics(apiData))
|
||||
mergeInto(report, "overdueRecord", buildOverdueRecord(apiData))
|
||||
mergeInto(report, "creditDetail", buildCreditDetail(apiData))
|
||||
mergeInto(report, "rentalBehavior", buildRentalBehavior(apiData))
|
||||
mergeInto(report, "riskSupervision", buildSinanRiskSupervision(apiData))
|
||||
report["judicialLeaseReport"] = buildJudicialLeaseReport(apiData)
|
||||
|
||||
calcSinanRisk(report, apiData)
|
||||
return report
|
||||
}
|
||||
|
||||
func buildSinanBaseInfo(apiData map[string]interface{}, params dto.DWBG6A2CReq) map[string]interface{} {
|
||||
base := emptyBaseInfo(params)
|
||||
|
||||
if age, sex := calculateAgeAndSex(params.IDCard); age > 0 {
|
||||
base["age"] = age
|
||||
base["sex"] = sex
|
||||
}
|
||||
|
||||
if yysyh6f3 := asMap(getMapValue(apiData, "YYSYH6F3")); yysyh6f3 != nil {
|
||||
if address := xypString(yysyh6f3, "address"); address != "" {
|
||||
base["location"] = address
|
||||
}
|
||||
if sex := xypString(yysyh6f3, "sex"); sex != "" {
|
||||
base["sex"] = sex
|
||||
}
|
||||
if birthday := xypString(yysyh6f3, "birthday"); birthday != "" {
|
||||
if age := ageFromBirthday(birthday); age > 0 {
|
||||
base["age"] = age
|
||||
}
|
||||
}
|
||||
if channel := xypString(yysyh6f3, "channel"); channel != "" {
|
||||
base["channel"] = convertChannelName(channel)
|
||||
}
|
||||
}
|
||||
if base["location"] == "" {
|
||||
if loc := getLocationFromIDCard(params.IDCard); loc != "" {
|
||||
base["location"] = loc
|
||||
}
|
||||
}
|
||||
|
||||
if yysy35ta := asMap(getMapValue(apiData, "YYSY35TA")); yysy35ta != nil {
|
||||
prov := xypString(yysy35ta, "prov")
|
||||
city := xypString(yysy35ta, "city")
|
||||
if prov != "" && city != "" {
|
||||
base["phoneArea"] = prov + "-" + city
|
||||
} else if prov != "" {
|
||||
base["phoneArea"] = prov
|
||||
}
|
||||
if base["channel"] == "" {
|
||||
if name := xypString(yysy35ta, "name"); name != "" {
|
||||
base["channel"] = convertChannelName(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if yysye7v5 := asMap(getMapValue(apiData, "YYSYE7V5")); yysye7v5 != nil {
|
||||
base["status"] = convertStatusFromOnlineStatus(yysye7v5)
|
||||
if base["channel"] == "" {
|
||||
if ch := xypString(yysye7v5, "channel"); ch != "" {
|
||||
base["channel"] = convertChannelName(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
func ageFromBirthday(birthday string) int {
|
||||
birthday = strings.TrimSpace(birthday)
|
||||
layouts := []string{"2006-01-02", "20060102", "2006/01/02"}
|
||||
for _, layout := range layouts {
|
||||
if t, err := time.Parse(layout, birthday); err == nil {
|
||||
now := time.Now()
|
||||
age := now.Year() - t.Year()
|
||||
if now.YearDay() < t.YearDay() {
|
||||
age--
|
||||
}
|
||||
if age > 0 {
|
||||
return age
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func buildSinanStandLiveInfo(apiData map[string]interface{}) map[string]interface{} {
|
||||
info := emptyStandLiveInfo()
|
||||
|
||||
if yysyh6f3 := asMap(getMapValue(apiData, "YYSYH6F3")); yysyh6f3 != nil {
|
||||
finalAuth, verification := mapYYSYH6F3StandLive(xypString(yysyh6f3, "result"))
|
||||
info["finalAuthResult"] = finalAuth
|
||||
info["verification"] = verification
|
||||
}
|
||||
|
||||
if yysyp0t4 := asMap(getMapValue(apiData, "YYSYP0T4")); yysyp0t4 != nil {
|
||||
info["inTime"] = mapYYSYP0T4InTime(xypString(yysyp0t4, "time"), apiData)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func mapYYSYH6F3StandLive(result string) (finalAuthResult, verification string) {
|
||||
switch result {
|
||||
case "0":
|
||||
return "0", "0"
|
||||
case "1":
|
||||
return "1", "1"
|
||||
case "2":
|
||||
return "1", "-1"
|
||||
default:
|
||||
return "1", "-1"
|
||||
}
|
||||
}
|
||||
|
||||
func mapYYSYP0T4InTime(timeRange string, apiData map[string]interface{}) string {
|
||||
timeRange = strings.TrimSpace(timeRange)
|
||||
if timeRange == "" {
|
||||
if yysye7v5 := asMap(getMapValue(apiData, "YYSYE7V5")); yysye7v5 != nil {
|
||||
status := convertStatusFromOnlineStatus(yysye7v5)
|
||||
if status != 1 {
|
||||
return "99"
|
||||
}
|
||||
}
|
||||
return "-1"
|
||||
}
|
||||
switch timeRange {
|
||||
case "[0,3)":
|
||||
return "0"
|
||||
case "[3,6)":
|
||||
return "3"
|
||||
case "[6,12)":
|
||||
return "6"
|
||||
case "[12,24)":
|
||||
return "12"
|
||||
case "[24,-1)", "[24,+)", "[24,Inf)":
|
||||
return "24"
|
||||
default:
|
||||
return "-1"
|
||||
}
|
||||
}
|
||||
|
||||
func buildSinanAntiFraudInfo(apiData map[string]interface{}) map[string]interface{} {
|
||||
info := emptyAntiFraudInfo()
|
||||
flxg := asMap(getMapValue(apiData, "FLXG8B4D"))
|
||||
if flxg == nil {
|
||||
return info
|
||||
}
|
||||
data := flxg
|
||||
if nested, ok := flxg["data"].(map[string]interface{}); ok {
|
||||
data = nested
|
||||
}
|
||||
for _, field := range []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} {
|
||||
if val := xypString(data, field); val != "" {
|
||||
info[field] = val
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func buildSinanSecurityInfo(apiData map[string]interface{}) map[string]interface{} {
|
||||
info := emptySecurityInfo()
|
||||
flxg := asMap(getMapValue(apiData, "FLXGDEA9"))
|
||||
if flxg == nil {
|
||||
return info
|
||||
}
|
||||
level := xypString(flxg, "level")
|
||||
if level == "" || level == "0" {
|
||||
return info
|
||||
}
|
||||
for k, v := range parseSecurityInfoLevel(level) {
|
||||
info[k] = v
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func parseSecurityInfoLevel(level string) map[string]interface{} {
|
||||
out := emptySecurityInfo()
|
||||
for _, raw := range strings.Split(level, ",") {
|
||||
t := strings.TrimSpace(raw)
|
||||
if t == "" {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case t == "A":
|
||||
out["escape"] = 1
|
||||
out["icase"] = 1
|
||||
case t == "A1", t == "A2", t == "A3", t == "A4", t == "A5":
|
||||
out["front"] = 1
|
||||
out["icase"] = 1
|
||||
case t == "C3":
|
||||
out["drug"] = 1
|
||||
out["takeDrug"] = 1
|
||||
out["icase"] = 1
|
||||
case t == "D", t == "D1", t == "D2", t == "D3", t == "D4", t == "D5":
|
||||
out["icase"] = 1
|
||||
out["ikey"] = 1
|
||||
case strings.HasPrefix(t, "B") || strings.HasPrefix(t, "C"):
|
||||
out["icase"] = 1
|
||||
case t == "E":
|
||||
out["itrancase"] = 1
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildApplicationStatistics(apiData map[string]interface{}) map[string]interface{} {
|
||||
stats := emptyApplicationStatistics()
|
||||
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
|
||||
if m == nil {
|
||||
return stats
|
||||
}
|
||||
|
||||
total := decodeXypInt(m, "xyp_cpl0001")
|
||||
consumption := decodeXypInt(m, "xyp_cpl0007")
|
||||
online := decodeXypInt(m, "xyp_cpl0008")
|
||||
other := total - consumption - online
|
||||
if other < 0 {
|
||||
other = 0
|
||||
}
|
||||
|
||||
v180 := decodeXypInt(m, "xyp_cpl0013")
|
||||
v360 := decodeXypInt(m, "xyp_t01dezhbc") + decodeXypInt(m, "xyp_t01dezhba")
|
||||
last12 := v180
|
||||
if v360 > last12 {
|
||||
last12 = v360
|
||||
}
|
||||
|
||||
stats["totalApplicationCount"] = total
|
||||
stats["consumptionInstallmentApplicationCount"] = consumption
|
||||
stats["onlineLoanApplicationCount"] = online
|
||||
stats["otherApplicationCount"] = other
|
||||
stats["applicationCountLastMonth"] = decodeXypInt(m, "xyp_cpl0011")
|
||||
stats["applicationCountLast3Months"] = decodeXypInt(m, "xyp_cpl0012")
|
||||
stats["applicationCountLast6Months"] = v180
|
||||
stats["applicationCountLast12Months"] = last12
|
||||
stats["lastApplicationDate"] = reverseYYYYMMFromCpl0046(m)
|
||||
stats["daysSinceLastApplication"] = formatXypInterval(m, "xyp_cpl0046")
|
||||
if stats["daysSinceLastApplication"] == "0" {
|
||||
stats["daysSinceLastApplication"] = "-"
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func buildLendingStatistics(apiData map[string]interface{}) map[string]interface{} {
|
||||
stats := emptyLendingStatistics()
|
||||
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
|
||||
if m == nil {
|
||||
return stats
|
||||
}
|
||||
|
||||
v360Count := decodeXypInt(m, "xyp_t01cczhzz")
|
||||
v360Amount := formatXypInterval(m, "xyp_t01achzbz")
|
||||
|
||||
stats["totalLendingInstitutionCount"] = decodeXypInt(m, "xyp_cpl0001")
|
||||
stats["installmentLendingInstitutionCount"] = decodeXypInt(m, "xyp_cpl0007")
|
||||
stats["onlineLendingInstitutionCount"] = decodeXypInt(m, "xyp_cpl0008")
|
||||
stats["lastLendingDate"] = reverseYYYYMMFromCpl0046(m)
|
||||
stats["daysSinceLastLending"] = formatXypInterval(m, "xyp_cpl0046")
|
||||
if stats["daysSinceLastLending"] == "0" {
|
||||
stats["daysSinceLastLending"] = "-"
|
||||
}
|
||||
stats["lendingCountLastMonth"] = decodeXypInt(m, "xyp_t01ccezzz")
|
||||
stats["lendingAmountLastMonth"] = formatXypInterval(m, "xyp_t01acezzz")
|
||||
stats["lendingCountLast3Months"] = decodeXypInt(m, "xyp_t01ccfzzz")
|
||||
stats["lendingAmountLast3Months"] = formatXypInterval(m, "xyp_t01acfzzz")
|
||||
stats["lendingCountLast6Months"] = decodeXypInt(m, "xyp_t01ccgzzz")
|
||||
stats["lendingAmountLast6Months"] = formatXypInterval(m, "xyp_t01acgzzz")
|
||||
stats["lendingCountLast12Months"] = v360Count
|
||||
stats["lendingAmountLast12Months"] = v360Amount
|
||||
stats["lendingCountLast24Months"] = v360Count
|
||||
stats["lendingAmountLast24Months"] = v360Amount
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func buildPerformanceStatistics(apiData map[string]interface{}) map[string]interface{} {
|
||||
stats := emptyPerformanceStatistics()
|
||||
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
|
||||
if m == nil {
|
||||
return stats
|
||||
}
|
||||
|
||||
v360Count := decodeXypInt(m, "xyp_t01cchzzc")
|
||||
v360Amount := formatXypInterval(m, "xyp_t01achzzc")
|
||||
v180Exception := decodeXypInt(m, "xyp_cpl0026")
|
||||
v360Exception := decodeXypInt(m, "xyp_t01cczhza")
|
||||
exception12 := v180Exception
|
||||
if v360Exception > exception12 {
|
||||
exception12 = v360Exception
|
||||
}
|
||||
|
||||
stats["creditLoanDuration"] = decodeXypInt(m, "xyp_cpl0045")
|
||||
stats["settledLoanOrderCount"] = decodeXypInt(m, "xyp_cpl0002")
|
||||
stats["daysSinceLastPerformance"] = formatXypInterval(m, "xyp_cpl0068")
|
||||
if stats["daysSinceLastPerformance"] == "0" {
|
||||
stats["daysSinceLastPerformance"] = "-"
|
||||
}
|
||||
stats["normalRepaymentRatio"] = formatNormalRepaymentRatio(m)
|
||||
stats["performanceCountLastMonth"] = decodeXypInt(m, "xyp_cpl0023")
|
||||
stats["performanceAmountLastMonth"] = formatXypInterval(m, "xyp_cpl0039")
|
||||
stats["repaymentExceptionCountLastMonth"] = decodeXypInt(m, "xyp_cpl0022")
|
||||
stats["performanceCountLast3Months"] = decodeXypInt(m, "xyp_cpl0025")
|
||||
stats["performanceAmountLast3Months"] = formatXypInterval(m, "xyp_cpl0041")
|
||||
stats["repaymentExceptionCountLast3Months"] = decodeXypInt(m, "xyp_cpl0024")
|
||||
stats["performanceCountLast6Months"] = decodeXypInt(m, "xyp_cpl0027")
|
||||
stats["performanceAmountLast6Months"] = formatXypInterval(m, "xyp_cpl0043")
|
||||
stats["repaymentExceptionCountLast6Months"] = v180Exception
|
||||
stats["performanceCountLast12Months"] = v360Count
|
||||
stats["performanceAmountLast12Months"] = v360Amount
|
||||
stats["repaymentExceptionCountLast12Months"] = exception12
|
||||
stats["performanceCountLast24Months"] = v360Count
|
||||
stats["performanceAmountLast24Months"] = v360Amount
|
||||
stats["repaymentExceptionCountLast24Months"] = exception12
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func buildOverdueRecord(apiData map[string]interface{}) map[string]interface{} {
|
||||
record := emptyOverdueRecord()
|
||||
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
|
||||
if m == nil {
|
||||
return record
|
||||
}
|
||||
|
||||
currentOverdue := decodeXypInt(m, "xyp_cpl0071")
|
||||
if xypString(m, "xyp_cpl0044") == "1" {
|
||||
if currentOverdue > 0 {
|
||||
record["currentOverdueInstitution"] = fmt.Sprintf("%d(未结清)", currentOverdue)
|
||||
} else {
|
||||
record["currentOverdueInstitution"] = "1(未结清)"
|
||||
}
|
||||
} else if currentOverdue > 0 {
|
||||
record["currentOverdueInstitution"] = formatXypInterval(m, "xyp_cpl0071")
|
||||
}
|
||||
|
||||
record["currentOverdueCount"] = currentOverdue
|
||||
record["totalOverdueAmount"] = formatOverdueAmountHyphen(m, "xyp_cpl0072")
|
||||
record["lastOverdueDate"] = "-"
|
||||
|
||||
m1Signal := 0
|
||||
for _, key := range []string{"xyp_cpl0029", "xyp_cpl0030", "xyp_cpl0031"} {
|
||||
if xypString(m, key) == "1" {
|
||||
m1Signal++
|
||||
}
|
||||
}
|
||||
m1Plus6 := m1Signal
|
||||
if m1Plus6 > 6 {
|
||||
m1Plus6 = 6
|
||||
}
|
||||
m1Plus12 := m1Signal
|
||||
if m1Plus12 > 12 {
|
||||
m1Plus12 = 12
|
||||
}
|
||||
record["m1PlusCountLast6Months"] = m1Plus6
|
||||
record["m1PlusCountLast12Months"] = m1Plus12
|
||||
record["m1PlusCountLast24Months"] = m1Plus12
|
||||
|
||||
repay360 := decodeXypInt(m, "xyp_t01cchzzc")
|
||||
if xypString(m, "xyp_cpl0044") != "1" && repay360 > 0 {
|
||||
record["m0PlusCountLast6Months"] = decodeXypInt(m, "xyp_cpl0027")
|
||||
record["m0PlusCountLast12Months"] = repay360
|
||||
record["m0PlusCountLast24Months"] = repay360
|
||||
}
|
||||
|
||||
record["totalAmountLast6Months"] = formatXypInterval(m, "xyp_cpl0042")
|
||||
record["totalAmountLast12Months"] = formatXypInterval(m, "xyp_t01aahzza")
|
||||
record["totalAmountLast24Months"] = record["totalAmountLast12Months"]
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func buildCreditDetail(apiData map[string]interface{}) map[string]interface{} {
|
||||
detail := emptyCreditDetail()
|
||||
m := asMap(getMapValue(apiData, "JRZQ5E9F"))
|
||||
if m == nil {
|
||||
return detail
|
||||
}
|
||||
detail["maxOnlineLoanCredit"] = formatXypNumericString(m, "xyp_t01aahzbz")
|
||||
detail["avgOnlineLoanCredit"] = formatXypNumericString(m, "xyp_t01adhzbc")
|
||||
detail["maxConsumptionInstallmentCredit"] = formatXypNumericString(m, "xyp_t01aazhaz")
|
||||
detail["avgConsumptionInstallmentCredit"] = formatXypNumericString(m, "xyp_t01adgzzc")
|
||||
return detail
|
||||
}
|
||||
|
||||
var rentalPeriodMap = map[string]string{
|
||||
"3Days": "d3",
|
||||
"7Days": "d7",
|
||||
"14Days": "d14",
|
||||
"1Month": "m1",
|
||||
"3Months": "m3",
|
||||
"6Months": "m6",
|
||||
"12Months": "m12",
|
||||
}
|
||||
|
||||
func buildRentalBehavior(apiData map[string]interface{}) map[string]interface{} {
|
||||
behavior := emptyRentalBehavior()
|
||||
jrzq := asMap(getMapValue(apiData, "JRZQ1D09"))
|
||||
if jrzq == nil {
|
||||
return behavior
|
||||
}
|
||||
|
||||
for periodLabel, apiPeriod := range rentalPeriodMap {
|
||||
behavior[fmt.Sprintf("rentalApplicationCountLast%s", periodLabel)] = formatRentalPair(
|
||||
rentalNestedValue(jrzq, apiPeriod, "id", "allnum"),
|
||||
rentalNestedValue(jrzq, apiPeriod, "cell", "allnum"),
|
||||
)
|
||||
behavior[fmt.Sprintf("rentalApplicationInstitutionsLast%s", periodLabel)] = formatRentalPair(
|
||||
rentalNestedValue(jrzq, apiPeriod, "id", "orgnum"),
|
||||
rentalNestedValue(jrzq, apiPeriod, "cell", "orgnum"),
|
||||
)
|
||||
behavior[fmt.Sprintf("rentalApplicationCountLast%sWeekend", periodLabel)] = formatRentalPair(
|
||||
rentalNestedValue(jrzq, apiPeriod, "id", "weekend_allnum"),
|
||||
rentalNestedValue(jrzq, apiPeriod, "cell", "weekend_allnum"),
|
||||
)
|
||||
behavior[fmt.Sprintf("rentalApplicationInstitutionsLast%sWeekend", periodLabel)] = formatRentalPair(
|
||||
rentalNestedValue(jrzq, apiPeriod, "id", "weekend_orgnum"),
|
||||
rentalNestedValue(jrzq, apiPeriod, "cell", "weekend_orgnum"),
|
||||
)
|
||||
behavior[fmt.Sprintf("rentalApplicationCountLast%sNight", periodLabel)] = formatRentalPair(
|
||||
rentalNestedValue(jrzq, apiPeriod, "id", "night_allnum"),
|
||||
rentalNestedValue(jrzq, apiPeriod, "cell", "night_allnum"),
|
||||
)
|
||||
behavior[fmt.Sprintf("rentalApplicationInstitutionsLast%sNight", periodLabel)] = formatRentalPair(
|
||||
rentalNestedValue(jrzq, apiPeriod, "id", "night_orgnum"),
|
||||
rentalNestedValue(jrzq, apiPeriod, "cell", "night_orgnum"),
|
||||
)
|
||||
}
|
||||
|
||||
return behavior
|
||||
}
|
||||
|
||||
func rentalNestedValue(jrzq map[string]interface{}, period, dim, field string) string {
|
||||
if periodData := asMap(jrzq[period]); periodData != nil {
|
||||
if dimData := asMap(periodData[dim]); dimData != nil {
|
||||
if val := xypString(dimData, field); val != "" {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
flatKey := fmt.Sprintf("alc_%s_%s_%s", period, dim, field)
|
||||
if val := getStringValue(jrzq, flatKey); val != "" {
|
||||
return val
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func formatRentalPair(idVal, cellVal string) string {
|
||||
if idVal == "" {
|
||||
idVal = "0"
|
||||
}
|
||||
if cellVal == "" {
|
||||
cellVal = "0"
|
||||
}
|
||||
return idVal + "/" + cellVal
|
||||
}
|
||||
|
||||
func buildSinanRiskSupervision(apiData map[string]interface{}) map[string]interface{} {
|
||||
supervision := buildRiskSupervision(apiData, zap.NewNop())
|
||||
idCount := parseIntValue(supervision["rentalRiskListIdCardRelationsPhones"])
|
||||
phoneCount := parseIntValue(supervision["rentalRiskListPhoneRelationsIdCards"])
|
||||
if idCount > 1 || phoneCount > 1 {
|
||||
supervision["details"] = "存在中介关联风险"
|
||||
} else {
|
||||
supervision["details"] = "无"
|
||||
}
|
||||
return supervision
|
||||
}
|
||||
|
||||
func asMap(val interface{}) map[string]interface{} {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
m, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func jrzqMap(apiData map[string]interface{}) map[string]interface{} {
|
||||
return asMap(getMapValue(apiData, "JRZQ5E9F"))
|
||||
}
|
||||
|
||||
func jrzq1d09Map(apiData map[string]interface{}) map[string]interface{} {
|
||||
return asMap(getMapValue(apiData, "JRZQ1D09"))
|
||||
}
|
||||
|
||||
func flxg7e8fMap(apiData map[string]interface{}) map[string]interface{} {
|
||||
return asMap(getMapValue(apiData, "FLXG7E8F"))
|
||||
}
|
||||
|
||||
func maxTotMons(jrzq map[string]interface{}) int {
|
||||
maxVal := 0
|
||||
for _, period := range []string{"m6", "m12"} {
|
||||
for _, dim := range []string{"id", "cell"} {
|
||||
if periodData := asMap(jrzq[period]); periodData != nil {
|
||||
if dimData := asMap(periodData[dim]); dimData != nil {
|
||||
maxVal = maxInt(maxVal, parseIntValue(dimData["tot_mons"]))
|
||||
}
|
||||
}
|
||||
flatKey := fmt.Sprintf("alc_%s_%s_tot_mons", period, dim)
|
||||
maxVal = maxInt(maxVal, parseIntValue(jrzq[flatKey]))
|
||||
}
|
||||
}
|
||||
return maxVal
|
||||
}
|
||||
|
||||
func maxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func rentalHasApplication(jrzq map[string]interface{}) bool {
|
||||
for _, period := range []string{"m12"} {
|
||||
idVal := parseIntValue(rentalNestedValue(jrzq, period, "id", "allnum"))
|
||||
cellVal := parseIntValue(rentalNestedValue(jrzq, period, "cell", "allnum"))
|
||||
if idVal > 0 || cellVal > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hitBankOverdueRecord(m map[string]interface{}) int {
|
||||
if xypString(m, "xyp_cpl0044") == "1" {
|
||||
return 1
|
||||
}
|
||||
if decodeXypInt(m, "xyp_cpl0071") > 0 {
|
||||
return 1
|
||||
}
|
||||
if decodeXypInt(m, "xyp_cpl0072") > 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func hitRelationRisk(jrzq map[string]interface{}) int {
|
||||
if jrzq == nil {
|
||||
return 0
|
||||
}
|
||||
idCount := parseIntValue(jrzq["idcard_relation_phone"])
|
||||
phoneCount := parseIntValue(jrzq["phone_relation_idard"])
|
||||
if idCount > 1 || phoneCount > 1 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func hitNewRiskFeature(m map[string]interface{}) int {
|
||||
raw := xypString(m, "xyp_model_score_mid")
|
||||
if raw == "" || raw == "-1" {
|
||||
return 0
|
||||
}
|
||||
score, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
const threshold = 350.0 + (950.0-350.0)/3.0
|
||||
if score < threshold {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func calcMultiQuery(m map[string]interface{}) int {
|
||||
windows := []struct {
|
||||
months int
|
||||
key string
|
||||
}{
|
||||
{6, "xyp_cpl0013"},
|
||||
{3, "xyp_cpl0012"},
|
||||
{1, "xyp_cpl0011"},
|
||||
}
|
||||
for _, w := range windows {
|
||||
if decodeXypInt(m, w.key) > 0 {
|
||||
return w.months
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func calcDeductFail(m map[string]interface{}) int {
|
||||
windows := []struct {
|
||||
months int
|
||||
key string
|
||||
}{
|
||||
{6, "xyp_cpl0026"},
|
||||
{3, "xyp_cpl0024"},
|
||||
{1, "xyp_cpl0022"},
|
||||
}
|
||||
for _, w := range windows {
|
||||
if decodeXypInt(m, w.key) > 0 {
|
||||
return w.months
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func antiFraudHit(data map[string]interface{}) bool {
|
||||
if data == nil {
|
||||
return false
|
||||
}
|
||||
nested := data
|
||||
if d, ok := data["data"].(map[string]interface{}); ok {
|
||||
nested = d
|
||||
}
|
||||
for _, field := range []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} {
|
||||
val := xypString(nested, field)
|
||||
if val != "" && val != "0" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func securityLevelHit(level string) bool {
|
||||
level = strings.TrimSpace(level)
|
||||
return level != "" && level != "0"
|
||||
}
|
||||
|
||||
func industryBlacklistHit(level string) bool {
|
||||
for _, raw := range strings.Split(level, ",") {
|
||||
if isLevelDClass(strings.TrimSpace(raw)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isLevelDClass(token string) bool {
|
||||
switch token {
|
||||
case "D", "D1", "D2", "D3", "D4", "D5":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func groupFraudHit(data map[string]interface{}) bool {
|
||||
if data == nil {
|
||||
return false
|
||||
}
|
||||
nested := data
|
||||
if d, ok := data["data"].(map[string]interface{}); ok {
|
||||
nested = d
|
||||
}
|
||||
for _, field := range []string{"deceiver", "gamblerBanker"} {
|
||||
val := strings.ToUpper(xypString(nested, field))
|
||||
if val == "B" || val == "C" || val == "D" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// ProcessDWBG6A2CRequest DWBG6A2C API处理方法 - 司南报告
|
||||
@@ -21,47 +23,25 @@ func ProcessDWBG6A2CRequest(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
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,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI102", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤响应数据,删除指定字段
|
||||
if respMap, ok := respData.(map[string]interface{}); ok {
|
||||
delete(respMap, "reportUrl")
|
||||
delete(respMap, "multCourtInfo")
|
||||
// delete(respMap, "judiciaRiskInfos")
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Info("开始处理司南报告请求",
|
||||
zap.String("name", paramsDto.Name),
|
||||
zap.String("id_card", maskIDCard(paramsDto.IDCard)),
|
||||
zap.String("mobile_no", maskMobile(paramsDto.MobileNo)),
|
||||
)
|
||||
|
||||
apiData := collectSinanAPIData(ctx, paramsDto, deps, log)
|
||||
report := transformToSinanReport(apiData, paramsDto, log)
|
||||
if cleaned, ok := stripNullValues(report).(map[string]interface{}); ok {
|
||||
report = cleaned
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
log.Error("序列化司南报告响应失败", zap.Error(err))
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
log.Info("司南报告处理完成")
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
345
internal/domains/api/services/processors/dwbg/dwbg6a2c_test.go
Normal file
345
internal/domains/api/services/processors/dwbg/dwbg6a2c_test.go
Normal file
@@ -0,0 +1,345 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestEmptySinanReportMarshal(t *testing.T) {
|
||||
params := dto.DWBG6A2CReq{
|
||||
Name: "张三",
|
||||
IDCard: "320321199102011152",
|
||||
MobileNo: "13812345678",
|
||||
}
|
||||
|
||||
report := emptySinanReport(params)
|
||||
data, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
t.Fatalf("序列化失败: %v", err)
|
||||
}
|
||||
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||||
t.Fatalf("反序列化失败: %v", err)
|
||||
}
|
||||
|
||||
requiredModules := []string{
|
||||
"baseInfo", "standLiveInfo", "riskPoint", "securityInfo", "antiFraudInfo",
|
||||
"riskList", "applicationStatistics", "lendingStatistics", "performanceStatistics",
|
||||
"overdueRecord", "creditDetail", "rentalBehavior", "riskSupervision", "judicialLeaseReport",
|
||||
}
|
||||
for _, module := range requiredModules {
|
||||
if _, ok := parsed[module]; !ok {
|
||||
t.Errorf("缺少顶层模块: %s", module)
|
||||
}
|
||||
}
|
||||
|
||||
antiFraud, ok := parsed["antiFraudInfo"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("antiFraudInfo 类型错误")
|
||||
}
|
||||
for _, key := range []string{"moneyLaundering", "deceiver", "gamblerPlayer", "gamblerBanker"} {
|
||||
if _, exists := antiFraud[key]; !exists {
|
||||
t.Errorf("antiFraudInfo 缺少键: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
rental, ok := parsed["rentalBehavior"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("rentalBehavior 类型错误")
|
||||
}
|
||||
if len(rental) != len(rentalBehaviorFieldNames) {
|
||||
t.Errorf("rentalBehavior 字段数 = %d, want %d", len(rental), len(rentalBehaviorFieldNames))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransformToSinanReportAllNil(t *testing.T) {
|
||||
params := dto.DWBG6A2CReq{
|
||||
Name: "张三",
|
||||
IDCard: "320321199102011152",
|
||||
MobileNo: "13812345678",
|
||||
}
|
||||
|
||||
apiData := map[string]interface{}{
|
||||
"YYSY35TA": nil,
|
||||
"YYSYE7V5": nil,
|
||||
"YYSYH6F3": nil,
|
||||
"YYSYP0T4": nil,
|
||||
"FLXGDEA9": nil,
|
||||
"FLXG8B4D": nil,
|
||||
"FLXG7E8F": nil,
|
||||
"JRZQ5E9F": nil,
|
||||
"JRZQ1D09": nil,
|
||||
}
|
||||
|
||||
report := transformToSinanReport(apiData, params, zap.NewNop())
|
||||
if _, err := json.Marshal(report); err != nil {
|
||||
t.Fatalf("全 nil 数据源仍应可序列化: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransformToSinanReportWithMockData(t *testing.T) {
|
||||
params := dto.DWBG6A2CReq{
|
||||
Name: "黄春丽",
|
||||
IDCard: "452621197903185522",
|
||||
MobileNo: "13907764404",
|
||||
}
|
||||
|
||||
apiData := map[string]interface{}{
|
||||
"YYSYH6F3": map[string]interface{}{
|
||||
"result": "0",
|
||||
"sex": "女",
|
||||
"address": "广西壮族自治区百色地区百色市",
|
||||
"channel": "cmcc",
|
||||
},
|
||||
"YYSYE7V5": map[string]interface{}{
|
||||
"status": 0,
|
||||
"channel": "cmcc",
|
||||
},
|
||||
"YYSY35TA": map[string]interface{}{
|
||||
"prov": "广西壮族自治区",
|
||||
"city": "南宁",
|
||||
"name": "中国移动",
|
||||
},
|
||||
"YYSYP0T4": map[string]interface{}{
|
||||
"time": "[24,-1)",
|
||||
},
|
||||
"FLXG8B4D": map[string]interface{}{
|
||||
"gamblerBanker": "A",
|
||||
},
|
||||
"FLXGDEA9": map[string]interface{}{
|
||||
"level": "A1,C3",
|
||||
},
|
||||
"JRZQ5E9F": map[string]interface{}{
|
||||
"xyp_cpl0001": "2",
|
||||
"xyp_cpl0007": "2",
|
||||
"xyp_cpl0008": "1",
|
||||
"xyp_cpl0011": "2",
|
||||
"xyp_cpl0012": "2",
|
||||
"xyp_cpl0013": "3",
|
||||
"xyp_cpl0044": "1",
|
||||
"xyp_cpl0074": "0.56",
|
||||
"xyp_model_score_mid": "500",
|
||||
},
|
||||
"JRZQ1D09": map[string]interface{}{
|
||||
"m12": map[string]interface{}{
|
||||
"id": map[string]interface{}{"allnum": "7", "orgnum": "1", "tot_mons": "16"},
|
||||
"cell": map[string]interface{}{"allnum": "7", "orgnum": "1", "tot_mons": "16"},
|
||||
},
|
||||
"idcard_relation_phone": 1,
|
||||
"phone_relation_idard": 0,
|
||||
},
|
||||
}
|
||||
|
||||
report := transformToSinanReport(apiData, params, zap.NewNop())
|
||||
|
||||
standLive := report["standLiveInfo"].(map[string]interface{})
|
||||
if standLive["finalAuthResult"] != "0" || standLive["verification"] != "0" {
|
||||
t.Errorf("standLiveInfo 三要素映射错误: %+v", standLive)
|
||||
}
|
||||
if standLive["inTime"] != "24" {
|
||||
t.Errorf("inTime = %v, want 24", standLive["inTime"])
|
||||
}
|
||||
|
||||
security := report["securityInfo"].(map[string]interface{})
|
||||
if security["front"] != 1 || security["escape"] != 0 || security["drug"] != 1 {
|
||||
t.Errorf("securityInfo 解析错误: %+v", security)
|
||||
}
|
||||
|
||||
antiFraud := report["antiFraudInfo"].(map[string]interface{})
|
||||
if antiFraud["gamblerBanker"] != "A" {
|
||||
t.Errorf("antiFraudInfo.gamblerBanker = %v", antiFraud["gamblerBanker"])
|
||||
}
|
||||
|
||||
riskList := report["riskList"].(map[string]interface{})
|
||||
if riskList["creditLeaseRisk"] != 1 {
|
||||
t.Errorf("creditLeaseRisk = %v, want 1", riskList["creditLeaseRisk"])
|
||||
}
|
||||
if riskList["vehicleLeaseViolation"] != 1 {
|
||||
t.Errorf("vehicleLeaseViolation = %v, want 1", riskList["vehicleLeaseViolation"])
|
||||
}
|
||||
if riskList["highRiskArea"] != 0 {
|
||||
t.Errorf("highRiskArea = %v, want 0", riskList["highRiskArea"])
|
||||
}
|
||||
|
||||
riskPoint := report["riskPoint"].(map[string]interface{})
|
||||
if riskPoint["antiFraudRisk"] != 1 {
|
||||
t.Errorf("antiFraudRisk = %v, want 1", riskPoint["antiFraudRisk"])
|
||||
}
|
||||
if riskPoint["judicialCase"] != 0 {
|
||||
t.Errorf("judicialCase = %v, want 0", riskPoint["judicialCase"])
|
||||
}
|
||||
|
||||
perf := report["performanceStatistics"].(map[string]interface{})
|
||||
if perf["serialVersionUID"] != 1 {
|
||||
t.Errorf("serialVersionUID = %v, want 1", perf["serialVersionUID"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapYYSYH6F3StandLive(t *testing.T) {
|
||||
cases := []struct {
|
||||
result, wantFinal, wantVerify string
|
||||
}{
|
||||
{"0", "0", "0"},
|
||||
{"1", "1", "1"},
|
||||
{"2", "1", "-1"},
|
||||
{"", "1", "-1"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
final, verify := mapYYSYH6F3StandLive(tc.result)
|
||||
if final != tc.wantFinal || verify != tc.wantVerify {
|
||||
t.Errorf("result=%q got (%s,%s) want (%s,%s)", tc.result, final, verify, tc.wantFinal, tc.wantVerify)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSecurityInfoLevel(t *testing.T) {
|
||||
info := parseSecurityInfoLevel("A,C3")
|
||||
if info["escape"] != 1 || info["front"] != 0 || info["drug"] != 1 {
|
||||
t.Fatalf("A,C3 => %+v", info)
|
||||
}
|
||||
info = parseSecurityInfoLevel("A1")
|
||||
if info["front"] != 1 || info["escape"] != 0 {
|
||||
t.Fatalf("A1 => %+v", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndustryBlacklistHit(t *testing.T) {
|
||||
if !industryBlacklistHit("D1,B") {
|
||||
t.Fatal("D1 should hit industry blacklist")
|
||||
}
|
||||
if industryBlacklistHit("A1,C3") {
|
||||
t.Fatal("A1,C3 should not hit industry blacklist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReportNoNullValues(t *testing.T) {
|
||||
params := dto.DWBG6A2CReq{
|
||||
Name: "张三",
|
||||
IDCard: "320321199102011152",
|
||||
MobileNo: "13812345678",
|
||||
}
|
||||
apiData := map[string]interface{}{
|
||||
"FLXG7E8F": map[string]interface{}{
|
||||
"judicial_data": map[string]interface{}{
|
||||
"breachCaseList": []interface{}{},
|
||||
"consumptionRestrictionList": []interface{}{},
|
||||
"lawsuitStat": map[string]interface{}{
|
||||
"count": map[string]interface{}{
|
||||
"count_beigao": 0,
|
||||
"larq_stat": "",
|
||||
},
|
||||
"civil": map[string]interface{}{
|
||||
"cases": []interface{}{},
|
||||
"count": map[string]interface{}{"count_total": 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
report := transformToSinanReport(apiData, params, zap.NewNop())
|
||||
cleaned, ok := stripNullValues(report).(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("stripNullValues failed")
|
||||
}
|
||||
if reportContainsNull(cleaned) {
|
||||
t.Fatal("report still contains null after strip")
|
||||
}
|
||||
data, err := json.Marshal(cleaned)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal failed: %v", err)
|
||||
}
|
||||
if strings.Contains(string(data), "null") {
|
||||
t.Fatalf("JSON contains null: %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildJudicialLeaseReportWithCases(t *testing.T) {
|
||||
apiData := map[string]interface{}{
|
||||
"FLXG7E8F": map[string]interface{}{
|
||||
"judicial_data": map[string]interface{}{
|
||||
"breachCaseList": []interface{}{},
|
||||
"consumptionRestrictionList": []interface{}{},
|
||||
"lawsuitStat": map[string]interface{}{
|
||||
"count": map[string]interface{}{
|
||||
"count_total": 1,
|
||||
"count_beigao": 1,
|
||||
"larq_stat": "2026(1)",
|
||||
"ay_stat": "民事(1)",
|
||||
},
|
||||
"civil": map[string]interface{}{
|
||||
"cases": []interface{}{
|
||||
map[string]interface{}{
|
||||
"c_ah": "(2019)桂0103民初5129号",
|
||||
"n_ssdw": "原告",
|
||||
"n_ajjzjd": "已结案",
|
||||
"n_ajlx": "民事一审",
|
||||
"c_dsrxx": []interface{}{
|
||||
map[string]interface{}{
|
||||
"c_mc": "黄春丽",
|
||||
"n_ssdw": "原告",
|
||||
"n_dsrlx": "自然人",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"count": map[string]interface{}{
|
||||
"count_total": 1,
|
||||
"count_beigao": 1,
|
||||
},
|
||||
},
|
||||
"preservation": map[string]interface{}{
|
||||
"cases": []interface{}{},
|
||||
"count": map[string]interface{}{"count_total": 1},
|
||||
},
|
||||
"implement": map[string]interface{}{
|
||||
"cases": []interface{}{},
|
||||
"count": map[string]interface{}{"count_total": 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
jlr := buildJudicialLeaseReport(apiData)
|
||||
courtInfo := jlr["courtInfo"].(map[string]interface{})
|
||||
if courtInfo["civilCasesCount"] != 1 {
|
||||
t.Fatalf("civilCasesCount = %v", courtInfo["civilCasesCount"])
|
||||
}
|
||||
if courtInfo["preservationCasesCount"] != 1 {
|
||||
t.Fatalf("preservationCasesCount = %v", courtInfo["preservationCasesCount"])
|
||||
}
|
||||
newMult := courtInfo["newMultCourtInfo"].(map[string]interface{})
|
||||
civilCases, ok := newMult["civilCases"].([]interface{})
|
||||
if !ok || len(civilCases) != 1 {
|
||||
t.Fatalf("civilCases missing or empty: %+v", newMult["civilCases"])
|
||||
}
|
||||
if _, has := newMult["preservationCases"]; has {
|
||||
t.Fatal("preservationCases list should be omitted when empty")
|
||||
}
|
||||
overview := jlr["caseOverviewInfo"].(map[string]interface{})
|
||||
if overview["beigaoTotalCasesCounts"] != 1 {
|
||||
t.Fatalf("beigaoTotalCasesCounts = %v", overview["beigaoTotalCasesCounts"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestM1PlusCountCaps(t *testing.T) {
|
||||
apiData := map[string]interface{}{
|
||||
"JRZQ5E9F": map[string]interface{}{
|
||||
"xyp_cpl0029": "1",
|
||||
"xyp_cpl0030": "1",
|
||||
"xyp_cpl0031": "1",
|
||||
},
|
||||
}
|
||||
record := buildOverdueRecord(apiData)
|
||||
if record["m1PlusCountLast6Months"] != 3 {
|
||||
t.Fatalf("6m cap = %v", record["m1PlusCountLast6Months"])
|
||||
}
|
||||
if record["m1PlusCountLast12Months"] != 3 {
|
||||
t.Fatalf("12m cap = %v", record["m1PlusCountLast12Months"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package dwbg
|
||||
|
||||
// stripNullValues 递归移除 map/slice 中的 nil,避免 JSON 输出 null。
|
||||
func stripNullValues(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
out := make(map[string]interface{}, len(val))
|
||||
for k, item := range val {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
out[k] = stripNullValues(item)
|
||||
}
|
||||
return out
|
||||
case []interface{}:
|
||||
out := make([]interface{}, 0, len(val))
|
||||
for _, item := range val {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, stripNullValues(item))
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func reportContainsNull(v interface{}) bool {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
for _, item := range val {
|
||||
if item == nil || reportContainsNull(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range val {
|
||||
if item == nil || reportContainsNull(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
233
internal/domains/api/services/processors/dwbg/dwbg6a2c_xyp.go
Normal file
233
internal/domains/api/services/processors/dwbg/dwbg6a2c_xyp.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/api/services/processors/jrzq"
|
||||
)
|
||||
|
||||
func xypString(m map[string]interface{}, key string) string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
val, ok := m[key]
|
||||
if !ok || val == nil {
|
||||
return ""
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
case float64:
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||
return ""
|
||||
}
|
||||
if v == float64(int64(v)) {
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
}
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
default:
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeXypInt(m map[string]interface{}, key string) int {
|
||||
code := xypString(m, key)
|
||||
if code == "" || code == "0" {
|
||||
return 0
|
||||
}
|
||||
return jrzq.DecodeIntervalMidpoint(key, code)
|
||||
}
|
||||
|
||||
func formatXypInterval(m map[string]interface{}, key string) string {
|
||||
code := xypString(m, key)
|
||||
if code == "" || code == "0" {
|
||||
return "0"
|
||||
}
|
||||
if conv := xypIntervalConverter(key); conv != nil {
|
||||
return conv(code)
|
||||
}
|
||||
return jrzq.FormatLoanRiskInterval(key, code)
|
||||
}
|
||||
|
||||
func xypIntervalConverter(key string) func(string) string {
|
||||
switch key {
|
||||
case "xyp_t01aazzzc":
|
||||
return convertXypT01aazzzcToInterval
|
||||
case "xyp_cpl0068":
|
||||
return convertXypCpl0068ToInterval
|
||||
case "xyp_cpl0001":
|
||||
return convertXypCpl0001ToInterval
|
||||
case "xyp_cpl0002":
|
||||
return convertXypCpl0002ToInterval
|
||||
case "xyp_cpl0071":
|
||||
return convertXypCpl0071ToInterval
|
||||
case "xyp_cpl0072":
|
||||
return convertXypCpl0072ToInterval
|
||||
case "xyp_cpl0018", "xyp_cpl0019", "xyp_cpl0020", "xyp_cpl0021", "xyp_cpl0022", "xyp_cpl0023", "xyp_cpl0024", "xyp_cpl0025", "xyp_cpl0026":
|
||||
return func(code string) string {
|
||||
return callCplCountConverter(key, code)
|
||||
}
|
||||
case "xyp_cpl0034", "xyp_cpl0035", "xyp_cpl0036", "xyp_cpl0037", "xyp_cpl0038", "xyp_cpl0039", "xyp_cpl0040", "xyp_cpl0041", "xyp_cpl0042":
|
||||
return func(code string) string {
|
||||
return callCplAmountConverter(key, code)
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func callCplCountConverter(key, code string) string {
|
||||
switch key {
|
||||
case "xyp_cpl0018":
|
||||
return convertXypCpl0018ToInterval(code)
|
||||
case "xyp_cpl0019":
|
||||
return convertXypCpl0019ToInterval(code)
|
||||
case "xyp_cpl0020":
|
||||
return convertXypCpl0020ToInterval(code)
|
||||
case "xyp_cpl0021":
|
||||
return convertXypCpl0021ToInterval(code)
|
||||
case "xyp_cpl0022":
|
||||
return convertXypCpl0022ToInterval(code)
|
||||
case "xyp_cpl0023":
|
||||
return convertXypCpl0023ToInterval(code)
|
||||
case "xyp_cpl0024":
|
||||
return convertXypCpl0024ToInterval(code)
|
||||
case "xyp_cpl0025":
|
||||
return convertXypCpl0025ToInterval(code)
|
||||
case "xyp_cpl0026":
|
||||
return convertXypCpl0026ToInterval(code)
|
||||
default:
|
||||
return jrzq.FormatLoanRiskInterval(key, code)
|
||||
}
|
||||
}
|
||||
|
||||
func callCplAmountConverter(key, code string) string {
|
||||
switch key {
|
||||
case "xyp_cpl0034":
|
||||
return convertXypCpl0034ToInterval(code)
|
||||
case "xyp_cpl0035":
|
||||
return convertXypCpl0035ToInterval(code)
|
||||
case "xyp_cpl0036":
|
||||
return convertXypCpl0036ToInterval(code)
|
||||
case "xyp_cpl0037":
|
||||
return convertXypCpl0037ToInterval(code)
|
||||
case "xyp_cpl0038":
|
||||
return convertXypCpl0038ToInterval(code)
|
||||
case "xyp_cpl0039":
|
||||
return convertXypCpl0039ToInterval(code)
|
||||
case "xyp_cpl0040":
|
||||
return convertXypCpl0040ToInterval(code)
|
||||
case "xyp_cpl0041":
|
||||
return convertXypCpl0041ToInterval(code)
|
||||
case "xyp_cpl0042":
|
||||
return convertXypCpl0042ToInterval(code)
|
||||
default:
|
||||
return jrzq.FormatLoanRiskInterval(key, code)
|
||||
}
|
||||
}
|
||||
|
||||
func formatXypNumericString(m map[string]interface{}, key string) string {
|
||||
mid := decodeXypInt(m, key)
|
||||
if mid <= 0 {
|
||||
return "0"
|
||||
}
|
||||
return strconv.Itoa(mid)
|
||||
}
|
||||
|
||||
func formatOverdueAmountHyphen(m map[string]interface{}, key string) string {
|
||||
interval := formatXypInterval(m, key)
|
||||
if interval == "0" || interval == "-" {
|
||||
return "0"
|
||||
}
|
||||
return intervalToHyphen(interval)
|
||||
}
|
||||
|
||||
func intervalToHyphen(interval string) string {
|
||||
interval = strings.TrimSpace(interval)
|
||||
if interval == "0" || interval == "-" {
|
||||
return interval
|
||||
}
|
||||
inner := strings.TrimPrefix(strings.TrimPrefix(interval, "("), "[")
|
||||
inner = strings.TrimSuffix(strings.TrimSuffix(inner, ")"), "]")
|
||||
parts := strings.SplitN(inner, ",", 2)
|
||||
if len(parts) != 2 {
|
||||
return interval
|
||||
}
|
||||
left := strings.TrimSpace(parts[0])
|
||||
right := strings.TrimSpace(parts[1])
|
||||
right = strings.TrimSuffix(right, "+)")
|
||||
right = strings.TrimSuffix(right, "+")
|
||||
if left == "" || right == "" {
|
||||
return interval
|
||||
}
|
||||
return left + "-" + right
|
||||
}
|
||||
|
||||
func reverseYYYYMMFromCpl0046(m map[string]interface{}) string {
|
||||
days := decodeXypInt(m, "xyp_cpl0046")
|
||||
if days <= 0 {
|
||||
return "-"
|
||||
}
|
||||
return time.Now().AddDate(0, 0, -days).Format("2006-01")
|
||||
}
|
||||
|
||||
func formatNormalRepaymentRatio(m map[string]interface{}) string {
|
||||
raw := xypString(m, "xyp_cpl0074")
|
||||
if raw == "" || raw == "0" || raw == "-1" {
|
||||
return "0"
|
||||
}
|
||||
if val, err := strconv.ParseFloat(raw, 64); err == nil {
|
||||
if val <= 1 && val >= 0 {
|
||||
return fmt.Sprintf("%.0f%%", val*100)
|
||||
}
|
||||
if val > 1 && val <= 100 {
|
||||
return fmt.Sprintf("%.0f%%", val)
|
||||
}
|
||||
}
|
||||
mid := decodeXypInt(m, "xyp_cpl0074")
|
||||
if mid <= 0 {
|
||||
return "0"
|
||||
}
|
||||
return fmt.Sprintf("%d%%", mid)
|
||||
}
|
||||
|
||||
func parseIntValue(val interface{}) int {
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
return v
|
||||
case int64:
|
||||
return int(v)
|
||||
case float64:
|
||||
return int(v)
|
||||
case string:
|
||||
if v == "" {
|
||||
return 0
|
||||
}
|
||||
if n, err := strconv.Atoi(v); err == nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func mergeInto(base map[string]interface{}, key string, patch map[string]interface{}) {
|
||||
if patch == nil {
|
||||
return
|
||||
}
|
||||
existing, ok := base[key].(map[string]interface{})
|
||||
if !ok {
|
||||
base[key] = patch
|
||||
return
|
||||
}
|
||||
for k, v := range patch {
|
||||
existing[k] = v
|
||||
}
|
||||
}
|
||||
@@ -53,3 +53,6 @@ func ProcessIVYZ5E3FRequest(ctx context.Context, params []byte, deps *processors
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// DecodeIntervalMidpoint 将 xyp 区间码解码为区间中位整数。
|
||||
func DecodeIntervalMidpoint(field, code string) int {
|
||||
if code == "" || code == "0" {
|
||||
return 0
|
||||
}
|
||||
rules, ok := loanRiskIntervalMappings[field]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if rule.output != code {
|
||||
continue
|
||||
}
|
||||
min := rule.min
|
||||
max := rule.max
|
||||
if max >= math.MaxFloat64/4 {
|
||||
if rule.minInclusive {
|
||||
return int(math.Round(min + 1))
|
||||
}
|
||||
return int(math.Round(min + 2))
|
||||
}
|
||||
lo := min
|
||||
hi := max
|
||||
if !rule.minInclusive {
|
||||
lo += 0.5
|
||||
}
|
||||
if !rule.maxInclusive {
|
||||
hi -= 0.5
|
||||
}
|
||||
return int(math.Round((lo + hi) / 2))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// FormatLoanRiskInterval 将 xyp 区间码格式化为可读区间字符串。
|
||||
func FormatLoanRiskInterval(field, code string) string {
|
||||
if code == "" || code == "0" {
|
||||
return "0"
|
||||
}
|
||||
rules, ok := loanRiskIntervalMappings[field]
|
||||
if !ok {
|
||||
return "0"
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if rule.output != code {
|
||||
continue
|
||||
}
|
||||
return formatIntervalBounds(rule.min, rule.max, rule.minInclusive, rule.maxInclusive)
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func formatIntervalBounds(min, max float64, minInclusive, maxInclusive bool) string {
|
||||
if max >= math.MaxFloat64/4 {
|
||||
left := "("
|
||||
if minInclusive {
|
||||
left = "["
|
||||
}
|
||||
return fmt.Sprintf("%s%s,+)", left, formatBound(min))
|
||||
}
|
||||
left := "("
|
||||
if minInclusive {
|
||||
left = "["
|
||||
}
|
||||
right := ")"
|
||||
if maxInclusive {
|
||||
right = "]"
|
||||
}
|
||||
return fmt.Sprintf("%s%s,%s%s", left, formatBound(min), formatBound(max), right)
|
||||
}
|
||||
|
||||
func formatBound(v float64) string {
|
||||
if v == float64(int64(v)) {
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
}
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package jrzq
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDecodeIntervalMidpoint(t *testing.T) {
|
||||
if got := DecodeIntervalMidpoint("xyp_cpl0011", "2"); got <= 0 {
|
||||
t.Fatalf("xyp_cpl0011 code 2 midpoint = %d, want > 0", got)
|
||||
}
|
||||
if got := DecodeIntervalMidpoint("xyp_cpl0011", "0"); got != 0 {
|
||||
t.Fatalf("empty code midpoint = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatLoanRiskInterval(t *testing.T) {
|
||||
got := FormatLoanRiskInterval("xyp_cpl0046", "2")
|
||||
if got == "0" || got == "" {
|
||||
t.Fatalf("xyp_cpl0046 code 2 interval = %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user