2025-11-27 13:19:45 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 信贷表现工具函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 结果编码映射
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const RESULT_CODE_MAP = {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
1: {
|
|
|
|
|
|
text: "用户最近一笔订单未结清",
|
|
|
|
|
|
description:
|
|
|
|
|
|
"通过模型计算展现该用户行为画像,用户最近一笔订单未结清,建议谨慎评估其信用状况和还款能力。",
|
|
|
|
|
|
detail: "用户最近一笔订单未结清",
|
|
|
|
|
|
color: "text-red-500",
|
|
|
|
|
|
level: "high",
|
|
|
|
|
|
},
|
|
|
|
|
|
2: {
|
|
|
|
|
|
text: "用户订单结清情况良好",
|
|
|
|
|
|
description:
|
|
|
|
|
|
"通过模型计算展现该用户行为画像,多笔订单用户最近一笔订单已结清且历史所有订单均已结清,或单笔订单用户该订单发生时间在3个月之内且该订单结清,信贷表现良好。",
|
|
|
|
|
|
detail: "多笔订单用户最近一笔订单已结清且历史所有订单均已结清,或单笔订单用户该订单发生时间在3个月之内且该订单结清",
|
|
|
|
|
|
color: "text-green-500",
|
|
|
|
|
|
level: "low",
|
|
|
|
|
|
},
|
|
|
|
|
|
3: {
|
|
|
|
|
|
text: "用户最近订单已结清,历史存在未结清订单",
|
|
|
|
|
|
description:
|
|
|
|
|
|
"通过模型计算展现该用户行为画像,用户最近一笔订单已结清,但历史存在未结清订单,建议关注其历史还款记录。",
|
|
|
|
|
|
detail: "用户最近一笔订单已结清,但历史存在未结清订单",
|
|
|
|
|
|
color: "text-yellow-500",
|
|
|
|
|
|
level: "medium",
|
|
|
|
|
|
},
|
|
|
|
|
|
4: {
|
|
|
|
|
|
text: "用户数据不充分",
|
|
|
|
|
|
description:
|
|
|
|
|
|
"用户数据不充分无法展现该用户行为画像。建议通过其他征信渠道补充信息进行综合评估。",
|
|
|
|
|
|
detail: "数据不充分",
|
|
|
|
|
|
color: "text-gray-500",
|
|
|
|
|
|
level: "unknown",
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 金额区间映射(根据文档提供的区间)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const AMOUNT_RANGES = [
|
2026-02-08 16:56:41 +08:00
|
|
|
|
"(1~1000]",
|
|
|
|
|
|
"(1000~2000]",
|
|
|
|
|
|
"(2000~3000]",
|
|
|
|
|
|
"(3000~4000]",
|
|
|
|
|
|
"(4000~6000]",
|
|
|
|
|
|
"(6000~8000]",
|
|
|
|
|
|
"(8000~10000]",
|
|
|
|
|
|
"(10000~20000]",
|
|
|
|
|
|
"(20000~40000]",
|
|
|
|
|
|
"(40000~60000]",
|
|
|
|
|
|
"(60000~80000]",
|
|
|
|
|
|
"(80000~100000]",
|
|
|
|
|
|
"(100000~150000]",
|
|
|
|
|
|
"(150000~200000]",
|
|
|
|
|
|
"(200000~250000]",
|
|
|
|
|
|
"(250000~300000]",
|
|
|
|
|
|
"(300000~350000]",
|
|
|
|
|
|
"(350000~400000]",
|
|
|
|
|
|
"(400000~450000]",
|
|
|
|
|
|
"(450000~500000]",
|
|
|
|
|
|
"(500000~550000]",
|
|
|
|
|
|
"(550000~600000]",
|
|
|
|
|
|
"(600000~650000]",
|
|
|
|
|
|
"(650000~700000]",
|
|
|
|
|
|
"(700000~750000]",
|
|
|
|
|
|
"(750000~800000]",
|
|
|
|
|
|
"(800000~850000]",
|
|
|
|
|
|
"(850000~900000]",
|
|
|
|
|
|
"(900000~950000]",
|
|
|
|
|
|
"(950000~1000000]",
|
|
|
|
|
|
">1000000",
|
|
|
|
|
|
];
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取结果编码信息
|
|
|
|
|
|
* @param {string} code - 结果编码
|
|
|
|
|
|
* @returns {object} 结果编码信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const getResultCodeInfo = (code) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
return (
|
|
|
|
|
|
RESULT_CODE_MAP[code] || {
|
|
|
|
|
|
text: "未知状态",
|
|
|
|
|
|
description:
|
|
|
|
|
|
"信贷表现主要为企业在背景调查过程中探查用户近期信贷表现时提供参考,帮助企业对其内部员工、外部业务进行个人风险过滤。",
|
|
|
|
|
|
color: "text-gray-500",
|
|
|
|
|
|
level: "unknown",
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化金额区间
|
|
|
|
|
|
* @param {string} amount - 金额区间字符串
|
|
|
|
|
|
* @returns {string} 格式化后的金额区间
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatAmountRange = (amount) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
if (!amount || amount === "0") {
|
|
|
|
|
|
return "无记录";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理标准区间格式,如 "1000-2000"
|
|
|
|
|
|
if (amount.includes("-")) {
|
|
|
|
|
|
const [min, max] = amount.split("-");
|
|
|
|
|
|
return `${formatNumber(min)}元 - ${formatNumber(max)}元`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理带括号的区间格式,如 "(1000~2000]"
|
|
|
|
|
|
if (amount.includes("~")) {
|
|
|
|
|
|
const cleanAmount = amount.replace(/[()[\]]/g, "");
|
|
|
|
|
|
const [min, max] = cleanAmount.split("~");
|
|
|
|
|
|
return `${formatNumber(min)}元 - ${formatNumber(max)}元`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理大于某个值的格式,如 ">1000000"
|
|
|
|
|
|
if (amount.startsWith(">")) {
|
|
|
|
|
|
const value = amount.substring(1);
|
|
|
|
|
|
return `大于 ${formatNumber(value)}元`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 其他格式直接返回
|
|
|
|
|
|
return amount;
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化天数区间
|
|
|
|
|
|
* @param {string} days - 天数区间字符串
|
|
|
|
|
|
* @returns {string} 格式化后的天数区间
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatDaysRange = (days) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
if (!days || days === "0") {
|
|
|
|
|
|
return "无记录";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理区间格式,如 "1-15"
|
|
|
|
|
|
if (days.includes("-")) {
|
|
|
|
|
|
const [min, max] = days.split("-");
|
|
|
|
|
|
return `${min}天 - ${max}天`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理带括号的区间格式,如 "[1~15]"
|
|
|
|
|
|
if (days.includes("~")) {
|
|
|
|
|
|
const cleanDays = days.replace(/[()[\]]/g, "");
|
|
|
|
|
|
const [min, max] = cleanDays.split("~");
|
|
|
|
|
|
return `${min}天 - ${max}天`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return days + "天";
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化时间
|
|
|
|
|
|
* @param {string} time - 时间字符串
|
|
|
|
|
|
* @returns {string} 格式化后的时间
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatTime = (time) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
if (!time) {
|
|
|
|
|
|
return "无记录";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理 YYYY-MM 格式
|
|
|
|
|
|
if (time.match(/^\d{4}-\d{2}$/)) {
|
|
|
|
|
|
return time.replace("-", "年") + "月";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理 YYYY-MM-DD 格式
|
|
|
|
|
|
if (time.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
|
|
|
|
|
const [year, month, day] = time.split("-");
|
|
|
|
|
|
return `${year}年${month}月${day}日`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return time;
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化数字,添加千分位分隔符
|
|
|
|
|
|
* @param {string|number} num - 数字
|
|
|
|
|
|
* @returns {string} 格式化后的数字
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatNumber = (num) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
if (!num) return "0";
|
|
|
|
|
|
|
|
|
|
|
|
const number = typeof num === "string" ? parseFloat(num) : num;
|
|
|
|
|
|
|
|
|
|
|
|
if (isNaN(number)) return num;
|
|
|
|
|
|
|
|
|
|
|
|
return number.toLocaleString("zh-CN");
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取风险等级颜色类名
|
|
|
|
|
|
* @param {string} level - 风险等级
|
|
|
|
|
|
* @returns {string} CSS类名
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const getRiskLevelClass = (level) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
const levelMap = {
|
|
|
|
|
|
high: "text-red-500",
|
|
|
|
|
|
medium: "text-yellow-500",
|
|
|
|
|
|
low: "text-green-500",
|
|
|
|
|
|
unknown: "text-gray-500",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return levelMap[level] || "text-gray-500";
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取风险等级背景色类名
|
|
|
|
|
|
* @param {string} level - 风险等级
|
|
|
|
|
|
* @returns {string} CSS类名
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const getRiskLevelBgClass = (level) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
const levelMap = {
|
|
|
|
|
|
high: "bg-red-50 border-red-200",
|
|
|
|
|
|
medium: "bg-yellow-50 border-yellow-200",
|
|
|
|
|
|
low: "bg-green-50 border-green-200",
|
|
|
|
|
|
unknown: "bg-gray-50 border-gray-200",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return levelMap[level] || "bg-gray-50 border-gray-200";
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成信贷表现总结
|
|
|
|
|
|
* @param {object} data - 信贷表现数据
|
|
|
|
|
|
* @returns {object} 风险评估总结
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const generateRiskSummary = (data) => {
|
2026-02-08 16:56:41 +08:00
|
|
|
|
if (!data) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
level: "unknown",
|
|
|
|
|
|
text: "无法获取数据进行风险评估",
|
|
|
|
|
|
recommendations: ["建议通过其他渠道获取更多信息"],
|
|
|
|
|
|
};
|
2025-11-27 13:19:45 +08:00
|
|
|
|
}
|
2026-02-08 16:56:41 +08:00
|
|
|
|
|
|
|
|
|
|
const resultCode = data.result_code;
|
|
|
|
|
|
const codeInfo = getResultCodeInfo(resultCode);
|
|
|
|
|
|
|
|
|
|
|
|
const currentlyOverdue = parseInt(data.currently_overdue) || 0;
|
|
|
|
|
|
const accExc = parseInt(data.acc_exc) || 0;
|
|
|
|
|
|
const accSleep = parseInt(data.acc_sleep) || 0;
|
|
|
|
|
|
|
|
|
|
|
|
let riskLevel = codeInfo.level;
|
|
|
|
|
|
let recommendations = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 基于结果编码的建议
|
|
|
|
|
|
switch (resultCode) {
|
|
|
|
|
|
case "1": // 用户最近一笔订单未结清
|
|
|
|
|
|
recommendations.push("用户最近一笔订单未结清,存在风险");
|
|
|
|
|
|
recommendations.push("建议进一步核实原因和当前还款能力");
|
|
|
|
|
|
recommendations.push("考虑要求提供担保或抵押措施");
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "2": // 用户订单结清情况良好
|
|
|
|
|
|
recommendations.push("用户订单结清情况良好,信贷表现正常");
|
|
|
|
|
|
recommendations.push("可以正常开展业务合作");
|
|
|
|
|
|
recommendations.push("建议定期跟踪信用状况变化");
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "3": // 用户最近订单已结清,历史存在未结清订单
|
|
|
|
|
|
recommendations.push("用户最近订单已结清,但历史存在未结清订单");
|
|
|
|
|
|
recommendations.push("建议关注历史还款记录和还款意愿");
|
|
|
|
|
|
recommendations.push("可考虑适当降低授信额度或增加风控措施");
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "4": // 用户数据不充分
|
|
|
|
|
|
recommendations.push("用户数据不充分,无法完整评估行为画像");
|
|
|
|
|
|
recommendations.push("建议通过其他征信渠道补充信息");
|
|
|
|
|
|
recommendations.push("谨慎开展高风险业务,建议人工审核");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 基于其他指标的额外风险评估
|
|
|
|
|
|
if (currentlyOverdue > 0) {
|
|
|
|
|
|
riskLevel = "high";
|
|
|
|
|
|
recommendations.push(`当前有${currentlyOverdue}个机构逾期,风险较高`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (accExc > 2) {
|
|
|
|
|
|
if (riskLevel !== "high") riskLevel = "medium";
|
|
|
|
|
|
recommendations.push(`存在${accExc}个异常还款机构,需要关注`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (accSleep > 10) {
|
|
|
|
|
|
recommendations.push(`有${accSleep}个睡眠机构,信贷活跃度偏低`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
level: riskLevel,
|
|
|
|
|
|
text: codeInfo.description,
|
|
|
|
|
|
recommendations: recommendations,
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|