This commit is contained in:
2026-07-08 21:28:08 +08:00
parent 352ee9eaf2
commit 9f985ea8c7
12 changed files with 2504 additions and 39 deletions

View File

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

View File

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

View File

@@ -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(),
}
}

View File

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

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

View File

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

View 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"])
}
}

View File

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

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

View File

@@ -53,3 +53,6 @@ func ProcessIVYZ5E3FRequest(ctx context.Context, params []byte, deps *processors
return respBytes, nil
}

View File

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

View File

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