254 lines
12 KiB
Python
254 lines
12 KiB
Python
import math
|
|
|
|
EPD_REASON_MAP = {
|
|
"000": "No Adverse Factors",
|
|
"001": "Available Credit On Bankcard Accounts Is Too Low",
|
|
"006": "Bankcard Account Balances Are Too High In Proportion To Credit Limits",
|
|
"010": "Too Many Delinquencies",
|
|
"016": "Too Few Satisfactory Accounts",
|
|
"020": "Length Of Time Revolving Accounts Have Been Established Is Too Short",
|
|
"022": "Too many inquiries",
|
|
"023": "Months Since Most Recent Delinquency Is Too Short",
|
|
"024": "Too Many Serious Delinquencies",
|
|
"026": "Number Of Delinquent Accounts Is Too High In Proportion To Total Number Of Accounts",
|
|
"029": "Retail Account Balances Are Too High In Proportion To Credit Limits",
|
|
"030": "Not Enough Retail Debt Experience",
|
|
"031": "Revolving Account Balances Are Too High In Proportion To Credit Limits",
|
|
"035": "Length Of Time Accounts Have Been Established Is Too Short",
|
|
"037": "Too Few Bankcard Accounts",
|
|
"043": "Too Few Open Revolving Accounts",
|
|
"061": "Too Many Recently Opened Accounts",
|
|
"066": "Too Many Serious Derogatory Items",
|
|
"069": "Not Enough Debt Experience",
|
|
"070": "Length Of Time Since Most Recent Bankcard Account Has Been Established Is Too Short",
|
|
"074": "Too Few Satisfactory Revolving Accounts",
|
|
"076": "Total Amount Past Due Is Too High",
|
|
"103": "Not Enough Available Credit",
|
|
"105": "Too Few Revolving Accounts",
|
|
"117": "Length Of Time Since Most Seriously Delinquent Account Has Been Established Is Too Short",
|
|
"132": "Too Few Open Accounts",
|
|
"142": "Not Enough Balance Decreases On Active Non-Mortgage Accounts",
|
|
"146": "Recency Of A Balance Overlimit On A Bankcard Account",
|
|
"154": "Insufficient Payment Activity Over The Last Year",
|
|
"155": "Recency Of Max Aggregate Bankcard Balance Over The Last Year",
|
|
"158": "Too Few Open Retail Accounts",
|
|
"174": "Too Few Open Bankcard Accounts",
|
|
"181": "High Recent Balance Range Relative To Previous Balance Range",
|
|
"192": "Not Enough Available Credit On Revolving Accounts",
|
|
"201": "Length Of Time Since Oldest Auto Account Has Been Established Is Too Short"
|
|
}
|
|
|
|
VANTAGE_REASON_MAP = {
|
|
"10": "Too few accounts paid as agreed",
|
|
"11": "Oldest account was opened too recently",
|
|
"12": "Delinquent or derogatory status on accounts is too recent",
|
|
"13": "Balances on delinquent or derogatory accounts are too high",
|
|
"14": "Too high proportion of accounts recently opened",
|
|
"15": "Lack of recently reported accounts",
|
|
"16": "Total of credit limits and loan amounts is too low",
|
|
"17": "No open accounts in your credit file",
|
|
"18": "Lack of account information",
|
|
"19": "No negative reason code",
|
|
"20": "Delinquent or derogatory bankcard",
|
|
"21": "Too many bankcards with a high balance",
|
|
"22": "Too few bankcards with high credit limit",
|
|
"23": "Too high proportion of bankcards recently opened",
|
|
"24": "Too many bankcards with high balance compared to credit limit",
|
|
"25": "Too high proportion of balances from bankcards",
|
|
"26": "Balances on bankcards are too high",
|
|
"27": "Delinquent or derogatory status on revolving accounts is too recent",
|
|
"28": "Average credit limit on open bankcards is too low",
|
|
"29": "Balances on bankcards are too high compared with credit limits",
|
|
"30": "Too few open revolving accounts",
|
|
"31": "Not enough available credit on revolving accounts",
|
|
"32": "Oldest bankcard was opened too recently",
|
|
"33": "Not enough balance paid down over time on bankcards",
|
|
"34": "Most recently opened revolving account is too new",
|
|
"35": "Lack of revolving account information",
|
|
"36": "Lack of recently reported revolving accounts",
|
|
"37": "No open bankcards in your credit file",
|
|
"38": "Lack of bankcard account information",
|
|
"39": "Balances on delinquent or derogatory bankcards are too high",
|
|
"4": "Balances on accts too high compared to credit limits and loan amounts",
|
|
"04": "Balances on accts too high compared to credit limits and loan amounts",
|
|
"40": "Too many delinquent or derogatory revolving accounts",
|
|
"41": "Average time since revolving accounts opened is too recent",
|
|
"42": "Total credit limits on open revolving accounts are too low",
|
|
"43": "Too many revolving accounts with high balance compared to credit limit",
|
|
"44": "Balances on revolving accts are too high compared with credit limits",
|
|
"45": "Not enough balance paid down over time on retail accounts",
|
|
"46": "Oldest revolving account was opened too recently",
|
|
"47": "No open retail accounts in your credit file",
|
|
"48": "Lack of retail account information",
|
|
"49": "Not enough balance paid down over time on revolving accounts",
|
|
"5": "Too many recent delinquencies",
|
|
"05": "Too many recent delinquencies",
|
|
"50": "Balances on personal installment accts too high compared to loan amts",
|
|
"51": "Too few installment accounts recently paid as agreed",
|
|
"52": "Delinquent or derogatory installment account",
|
|
"53": "Not enough balance paid down over time on installment accounts",
|
|
"54": "Delinquent or derogatory status on installment accounts is too recent",
|
|
"55": "Lack of recently reported auto accounts",
|
|
"56": "Lack of recently reported installment accounts",
|
|
"57": "No open installment accounts in your credit file",
|
|
"58": "Lack of installment account information",
|
|
"59": "Balances on retail cards are too high compared with credit limits",
|
|
"6": "Too many accounts recently opened",
|
|
"06": "Too many accounts recently opened",
|
|
"60": "Total delinquent or derogatory balances on real estate loans too high",
|
|
"61": "No open first mortgage accounts in your credit file",
|
|
"62": "Lack of first mortgage account information",
|
|
"63": "Delinquent or derogatory real estate secured loan",
|
|
"64": "Not enough balance paid down over time on real estate secured loans",
|
|
"65": "Oldest real estate secured loan was opened too recently",
|
|
"66": "Delinquent or derogatory status on real estate loans is too recent",
|
|
"67": "No open real estate secured loans in your credit file",
|
|
"68": "Lack of real estate secured loan information",
|
|
"69": "Too high proportion of balances from loans not secured by real estate",
|
|
"7": "You have too many delinquent or derogatory accounts",
|
|
"07": "You have too many delinquent or derogatory accounts",
|
|
"70": "Too high proportion of auto accounts are delinquent or derogatory",
|
|
"71": "Not enough balance paid down over time on auto accounts",
|
|
"72": "Too few auto accounts paid as agreed",
|
|
"73": "Delinquent or derogatory auto account",
|
|
"74": "Balances on auto accounts are too high compared with loan amounts",
|
|
"75": "Payments on auto accounts less than scheduled amount",
|
|
"76": "Delinquent or derogatory status on auto accounts is too recent",
|
|
"77": "No open auto accounts in your credit file",
|
|
"78": "Lack of auto account information",
|
|
"79": "No negative reason code",
|
|
"8": "Too few accounts recently paid as agreed",
|
|
"08": "Too few accounts recently paid as agreed",
|
|
"80": "Delinquent or derogatory student loan",
|
|
"81": "Not enough balance paid down over time on student loans",
|
|
"82": "Lack of recently reported student loans",
|
|
"83": "No negative reason code",
|
|
"84": "Number of inquiries was a factor in determining the score",
|
|
"85": "Too many inquiries",
|
|
"86": "Derogatory public records",
|
|
"87": "Unpaid collections",
|
|
"88": "Bankruptcy",
|
|
"89": "No negative reason code",
|
|
"9": "Delinquent or derogatory account",
|
|
"09": "Delinquent or derogatory account",
|
|
"90": "No open revolving accounts in your credit file",
|
|
"91": "Balances on delinquent or derogatory revolving accounts are too high",
|
|
"92": "Delinquent or derogatory first mortgage",
|
|
"93": "Not enough balance paid down over time on first mortgage accounts",
|
|
"94": "No negative reason code",
|
|
"95": "No negative reason code",
|
|
"96": "Too few open accounts",
|
|
"97": "Too few accounts"
|
|
}
|
|
|
|
REASON_MAP = {
|
|
'evtg04': "System Generated",
|
|
'eads66': "System Generated",
|
|
's004s': "Length of time on file is too short",
|
|
'mt34s': "Not enough balance decreases on mortgage trades in the past 12 months",
|
|
'ct320': "Insufficient payment activity",
|
|
'us21s': "Length of time since most recent installment account has been established is too short",
|
|
'utlmag02': "Revolving account balances are too high in proportion to credit limits over the last 24 months",
|
|
'trv01': "Recency of a balance overlimit on a bankcard account",
|
|
'us34s': "Not enough balance decreases on installment trades in the past 12 months"
|
|
}
|
|
|
|
|
|
def generate_reason_codes(score_key, factors):
|
|
# fallback to 4 null rows if no factors found
|
|
if not isinstance(factors, list) or len(factors) == 0:
|
|
return [{"code": None, "rank": None, "description": None} for _ in range(4)]
|
|
|
|
reason_map = VANTAGE_REASON_MAP if score_key == "evtg04" else EPD_REASON_MAP if score_key == "eads66" else {}
|
|
|
|
reason_codes = []
|
|
for f in factors[:4]:
|
|
code = f.get("code")
|
|
rank = f.get("rank")
|
|
description = reason_map.get(str(code), "")
|
|
reason_codes.append({
|
|
"code": code,
|
|
"rank": rank,
|
|
"description": description
|
|
})
|
|
|
|
# pad to 4
|
|
while len(reason_codes) < 4:
|
|
reason_codes.append({"code": None, "rank": None, "description": None})
|
|
|
|
return reason_codes
|
|
|
|
|
|
def post_processing(processing_output, record):
|
|
prediction = processing_output["prediction"]
|
|
shape_reasoncode = processing_output["shape_reasoncode"]
|
|
|
|
# grade mapping:
|
|
if prediction < 0:
|
|
grade = "M14"
|
|
else:
|
|
m = math.ceil(prediction / 0.01)
|
|
m = max(m, 1)
|
|
m = min(m, 14)
|
|
grade = f"M{m}"
|
|
|
|
# if prediction ≤ 0.04, not declined
|
|
if prediction <= 0.04:
|
|
return {
|
|
"grade": grade,
|
|
"reason_codes": [{
|
|
"code": None,
|
|
"rank": None,
|
|
"description": None
|
|
}]
|
|
}
|
|
|
|
conditions = {
|
|
'evtg04': lambda x: x < 700,
|
|
'eads66': lambda x: x < 700,
|
|
's004s': lambda x: x < 12,
|
|
'mt34s': lambda x: x > 95,
|
|
'ct320': lambda x: x <= 3,
|
|
'us21s': lambda x: x <= 3,
|
|
'utlmag02': lambda x: x > 300,
|
|
'trv01': lambda x: x <= 3,
|
|
'us34s': lambda x: x > 90
|
|
}
|
|
|
|
for item in shape_reasoncode:
|
|
feat = item.get("feature")
|
|
val = item.get("value")
|
|
cond = conditions.get(feat)
|
|
|
|
if cond:
|
|
try:
|
|
if cond(val):
|
|
# If score-type feature (evtg04 or eads66) → full factors-based reason
|
|
if feat in ("evtg04", "eads66"):
|
|
return {
|
|
"grade": grade,
|
|
"reason_codes": generate_reason_codes(feat, record.get(f"{feat}_factors", []))
|
|
}
|
|
else:
|
|
# Other features → only 1 reason code based on REASON_MAP
|
|
return {
|
|
"grade": grade,
|
|
"reason_codes": [{
|
|
"code": feat,
|
|
"rank": "1",
|
|
"description": REASON_MAP.get(feat, "Reason not mapped")
|
|
}]
|
|
}
|
|
except Exception:
|
|
continue
|
|
|
|
# Default fallback
|
|
return {
|
|
"grade": grade,
|
|
"reason_codes": [{
|
|
"code": feat,
|
|
"rank": "1",
|
|
"description": "No suitable Product Offerings found"
|
|
}]
|
|
}
|