Upload files to "/"
This commit is contained in:
parent
848a80fb09
commit
8f7a795573
22
block.py
22
block.py
@ -20,7 +20,7 @@ def __main__(application_id: str, creditBureau: dict) -> Dict:
|
||||
A dict containing:
|
||||
- prediction: float
|
||||
- grade: str
|
||||
- reason_description: str or None
|
||||
- reason_codes: list or None
|
||||
"""
|
||||
record = extract_model_variables(creditBureau)
|
||||
|
||||
@ -29,24 +29,27 @@ def __main__(application_id: str, creditBureau: dict) -> Dict:
|
||||
'application_id': application_id,
|
||||
'prediction': 0.99,
|
||||
'grade': 'M14',
|
||||
'reason_description': "Lack of account information",
|
||||
"reason_codes": [{
|
||||
"code": None,
|
||||
"rank": None,
|
||||
"description": "Lack of account information"
|
||||
}]
|
||||
}
|
||||
logger.info(f"final_result (early exit due to missing or empty extracted variables): {final_result}")
|
||||
logger.info(
|
||||
f"final_result (early exit due to missing or empty extracted variables): {final_result}")
|
||||
return final_result
|
||||
|
||||
processed = pre_processing(record)
|
||||
|
||||
out = processing(processed)
|
||||
|
||||
final = post_processing(out)
|
||||
final = post_processing(out, record)
|
||||
|
||||
final_result = {
|
||||
'application_id': application_id,
|
||||
'prediction': out['prediction'],
|
||||
'grade': final['grade'],
|
||||
'reason_description': final['reason_description'],
|
||||
# 'tu_credit_report': record,
|
||||
# 'pre_processed_output': processed
|
||||
'reason_codes': final['reason_codes'],
|
||||
}
|
||||
|
||||
logger.info(f"final_result: {final_result}")
|
||||
@ -55,7 +58,8 @@ def __main__(application_id: str, creditBureau: dict) -> Dict:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json, sys
|
||||
import json
|
||||
import sys
|
||||
with open(sys.argv[1]) as f:
|
||||
data = json.load(f)
|
||||
__main__(application_id=data["application_id"], creditBureau=data)
|
||||
__main__(application_id=data["application_id"], creditBureau=data)
|
||||
|
||||
@ -163,8 +163,25 @@ def extract_model_variables(creditBureau: dict) -> dict:
|
||||
|
||||
# 2b. Also capture score result if present
|
||||
score = safe_get(score_model, "score")
|
||||
if score and "results" in score:
|
||||
value_map[(code, "score")] = score["results"]
|
||||
if score:
|
||||
# Capture score result
|
||||
results = score.get("results")
|
||||
if isinstance(results, str):
|
||||
value_map[(code, "score")] = results
|
||||
|
||||
# Capture top 4 score factors as list of {code, rank}
|
||||
raw_factors = score.get("factors", {}).get("factor", [])
|
||||
if not isinstance(raw_factors, list):
|
||||
raw_factors = []
|
||||
|
||||
top_factors = [
|
||||
{"code": f.get("code"), "rank": f.get("rank")}
|
||||
for f in raw_factors[:4]
|
||||
if isinstance(f, dict) and "code" in f and "rank" in f
|
||||
]
|
||||
if top_factors:
|
||||
value_map[(code, "factors")] = top_factors
|
||||
|
||||
|
||||
# Step 3.a: Use variable_to_code_map to fetch final vars
|
||||
for var, code in variable_code_map.items():
|
||||
@ -176,6 +193,11 @@ def extract_model_variables(creditBureau: dict) -> dict:
|
||||
if value:
|
||||
extracted[key] = value.lstrip("+")
|
||||
|
||||
# Extract factor list, if available
|
||||
factor_list = value_map.get((code, "factors"))
|
||||
if factor_list:
|
||||
extracted[f"{key}_factors"] = factor_list
|
||||
|
||||
return extracted
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,7 +1,180 @@
|
||||
import math
|
||||
|
||||
def post_processing(processing_output):
|
||||
prediction = processing_output["prediction"]
|
||||
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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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:
|
||||
@ -16,51 +189,59 @@ def post_processing(processing_output):
|
||||
# if prediction ≤ 0.04, not declined
|
||||
if prediction <= 0.04:
|
||||
return {
|
||||
"grade": grade,
|
||||
"reason_description": None
|
||||
"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,
|
||||
'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,
|
||||
'trv01': lambda x: x <= 3,
|
||||
'us34s': lambda x: x > 90
|
||||
'trv01': lambda x: x <= 3,
|
||||
'us34s': lambda x: x > 90
|
||||
}
|
||||
|
||||
reason_map = {
|
||||
'evtg04': "System Generated",
|
||||
'eads66': "System Generated",
|
||||
's004s': "Length of time on file is too short",
|
||||
# 'mt34s': "Too high open mortgage credit utilization recently",
|
||||
'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': "Too high revolving credit utilization over the last 24 months",
|
||||
'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': "Too high open unsecured installment credit utilization recently"
|
||||
'us34s': "Not enough balance decreases on installment trades in the past 12 months"
|
||||
}
|
||||
|
||||
|
||||
for item in shape_reasoncode:
|
||||
feat = item["feature"]
|
||||
val = item["value"]
|
||||
feat = item.get("feature")
|
||||
val = item.get("value")
|
||||
cond = conditions.get(feat)
|
||||
if cond and cond(val):
|
||||
return {
|
||||
"grade": grade,
|
||||
"reason_description": reason_map[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_description": "No suitable Product Offerings found"
|
||||
"grade": grade,
|
||||
"reason_codes": [{
|
||||
"code": feat,
|
||||
"rank": "1",
|
||||
"description": "No suitable Product Offerings found"
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
@ -14,9 +14,35 @@
|
||||
"type": "string",
|
||||
"description": "HD Model Grade"
|
||||
},
|
||||
"reason_description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Reason for the model decision"
|
||||
"reason_codes": {
|
||||
"type": "array",
|
||||
"description": "List of reason codes explaining the model decision",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Feature or score reason code"
|
||||
},
|
||||
"rank": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Rank of importance (1 to 4)"
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Human-readable explanation for the reason"
|
||||
}
|
||||
},
|
||||
"required": ["code", "rank", "description"]
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 4
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"application_id",
|
||||
"prediction",
|
||||
"grade",
|
||||
"reason_codes"
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user