SCORECARDAPIFORSTP / seed_rules.py
zombee11's picture
Upload 28 files
f14009c verified
Raw
History Blame Contribute Delete
13.7 kB
from decimal import Decimal
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.enums import DecisionAction, RuleType
from app.models.rule import RuleMaster
DEFAULT_RULES = [
{
"rule_code": "CIBIL_HIGH",
"rule_name": "High CIBIL score",
"rule_type": RuleType.SCORE,
"field_name": "cibilScore",
"operator": ">=",
"field_value": "750",
"score_value": Decimal("20"),
"score_component": "cibil",
"decision_action": DecisionAction.SCORE,
"reason_code": "HIGH_CIBIL",
"priority": 100,
},
{
"rule_code": "CIBIL_MEDIUM",
"rule_name": "Medium CIBIL score",
"rule_type": RuleType.SCORE,
"field_name": "cibilScore",
"operator": "BETWEEN",
"field_value": "700,749",
"score_value": Decimal("14"),
"score_component": "cibil",
"decision_action": DecisionAction.SCORE,
"reason_code": "MEDIUM_CIBIL",
"priority": 110,
},
{
"rule_code": "BUREAU_CLEAN",
"rule_name": "Clean repayment behaviour",
"rule_type": RuleType.SCORE,
"field_name": "noDpd",
"operator": "=",
"field_value": "true",
"score_value": Decimal("25"),
"score_component": "bureau",
"decision_action": DecisionAction.SCORE,
"reason_code": "NO_DELINQUENCY",
"priority": 120,
},
{
"rule_code": "FOIR_GOOD",
"rule_name": "Low FOIR",
"rule_type": RuleType.SCORE,
"field_name": "foir",
"operator": "<=",
"field_value": "35",
"score_value": Decimal("15"),
"score_component": "foir",
"decision_action": DecisionAction.SCORE,
"reason_code": "LOW_FOIR",
"priority": 130,
},
{
"rule_code": "FOIR_ACCEPTABLE",
"rule_name": "Acceptable FOIR",
"rule_type": RuleType.SCORE,
"field_name": "foir",
"operator": "BETWEEN",
"field_value": "36,50",
"score_value": Decimal("8"),
"score_component": "foir",
"decision_action": DecisionAction.SCORE,
"reason_code": "ACCEPTABLE_FOIR",
"priority": 131,
},
{
"rule_code": "LTV_GOOD",
"rule_name": "Low LTV",
"rule_type": RuleType.SCORE,
"field_name": "ltv",
"operator": "<=",
"field_value": "75",
"score_value": Decimal("10"),
"score_component": "ltv",
"decision_action": DecisionAction.SCORE,
"reason_code": "LOW_LTV",
"priority": 140,
},
{
"rule_code": "LTV_ACCEPTABLE",
"rule_name": "Acceptable LTV",
"rule_type": RuleType.SCORE,
"field_name": "ltv",
"operator": "BETWEEN",
"field_value": "76,85",
"score_value": Decimal("8"),
"score_component": "ltv",
"decision_action": DecisionAction.SCORE,
"reason_code": "ACCEPTABLE_LTV",
"priority": 141,
},
{
"rule_code": "NO_FRAUD",
"rule_name": "No fraud indicators",
"rule_type": RuleType.SCORE,
"field_name": "noFraud",
"operator": "=",
"field_value": "true",
"score_value": Decimal("10"),
"score_component": "fraud",
"decision_action": DecisionAction.SCORE,
"reason_code": "NO_FRAUD",
"priority": 150,
},
{
"rule_code": "STABLE_EMPLOYMENT",
"rule_name": "Stable employment type",
"rule_type": RuleType.SCORE,
"field_name": "stableEmployment",
"operator": "=",
"field_value": "true",
"score_value": Decimal("10"),
"score_component": "employment",
"decision_action": DecisionAction.SCORE,
"reason_code": "STABLE_EMPLOYMENT",
"priority": 160,
},
{
"rule_code": "BANKING_SURROGATE_OK",
"rule_name": "Banking surrogate available",
"rule_type": RuleType.SCORE,
"field_name": "bankingSurrogate",
"operator": "=",
"field_value": "true",
"score_value": Decimal("5"),
"score_component": "banking",
"decision_action": DecisionAction.SCORE,
"reason_code": "BANKING_SURROGATE",
"priority": 170,
},
{
"rule_code": "KYC_VERIFIED",
"rule_name": "PAN and Aadhaar verified",
"rule_type": RuleType.SCORE,
"field_name": "kycVerified",
"operator": "=",
"field_value": "true",
"score_value": Decimal("5"),
"score_component": "kyc",
"decision_action": DecisionAction.SCORE,
"reason_code": "KYC_VERIFIED",
"priority": 180,
},
{
"rule_code": "WRITTEN_OFF_REJECT",
"rule_name": "Written off account reject",
"rule_type": RuleType.HARD_REJECT,
"field_name": "writtenOffAccounts",
"operator": ">",
"field_value": "0",
"decision_action": DecisionAction.REJECT,
"reason_code": "WRITTEN_OFF_ACCOUNT",
"priority": 10,
},
{
"rule_code": "SETTLED_REJECT",
"rule_name": "Settled account reject",
"rule_type": RuleType.HARD_REJECT,
"field_name": "settledAccounts",
"operator": ">",
"field_value": "0",
"decision_action": DecisionAction.REJECT,
"reason_code": "SETTLED_ACCOUNT",
"priority": 11,
},
{
"rule_code": "OVERDUE_REJECT",
"rule_name": "Current overdue above threshold",
"rule_type": RuleType.HARD_REJECT,
"field_name": "overdueAmount",
"operator": ">",
"field_value": "5000",
"decision_action": DecisionAction.REJECT,
"reason_code": "CURRENT_OVERDUE",
"priority": 12,
},
{
"rule_code": "FRAUD_REJECT",
"rule_name": "Fraud flag reject",
"rule_type": RuleType.HARD_REJECT,
"field_name": "fraudFlag",
"operator": "=",
"field_value": "true",
"decision_action": DecisionAction.REJECT,
"reason_code": "FRAUD_FLAG",
"priority": 13,
},
{
"rule_code": "PAN_MISMATCH_REJECT",
"rule_name": "PAN mismatch reject",
"rule_type": RuleType.HARD_REJECT,
"field_name": "panMismatch",
"operator": "=",
"field_value": "true",
"decision_action": DecisionAction.REJECT,
"reason_code": "PAN_MISMATCH",
"priority": 14,
},
{
"rule_code": "DPD_90_REJECT",
"rule_name": "90 plus DPD reject",
"rule_type": RuleType.HARD_REJECT,
"field_name": "active90PlusDpd",
"operator": "=",
"field_value": "true",
"decision_action": DecisionAction.REJECT,
"reason_code": "ACTIVE_90_PLUS_DPD",
"priority": 15,
},
{
"rule_code": "MAX_DPD_UW",
"rule_name": "Max DPD above STP threshold",
"rule_type": RuleType.NON_STP,
"field_name": "maxDPD",
"operator": ">",
"field_value": "30",
"decision_action": DecisionAction.UW,
"reason_code": "MAX_DPD_ABOVE_30",
"priority": 200,
},
{
"rule_code": "DPD_60_UW",
"rule_name": "60 DPD history",
"rule_type": RuleType.NON_STP,
"field_name": "dpd60Count",
"operator": ">",
"field_value": "0",
"decision_action": DecisionAction.UW,
"reason_code": "DPD_60_HISTORY",
"priority": 201,
},
{
"rule_code": "DPD_90_UW",
"rule_name": "90 DPD history",
"rule_type": RuleType.NON_STP,
"field_name": "dpd90Count",
"operator": ">",
"field_value": "0",
"decision_action": DecisionAction.UW,
"reason_code": "DPD_90_HISTORY",
"priority": 202,
},
{
"rule_code": "ENQUIRY_HIGH_UW",
"rule_name": "High enquiry burst",
"rule_type": RuleType.NON_STP,
"field_name": "creditEnquiries6M",
"operator": ">",
"field_value": "5",
"decision_action": DecisionAction.UW,
"reason_code": "HIGH_ENQUIRY_BURST",
"priority": 203,
},
{
"rule_code": "FOIR_HIGH_UW",
"rule_name": "High FOIR underwriting",
"rule_type": RuleType.NON_STP,
"field_name": "foir",
"operator": ">",
"field_value": "50",
"decision_action": DecisionAction.UW,
"reason_code": "HIGH_FOIR",
"priority": 204,
},
{
"rule_code": "UNSECURED_LOANS_UW",
"rule_name": "Multiple active unsecured loans",
"rule_type": RuleType.NON_STP,
"field_name": "activeUnsecuredLoans",
"operator": ">",
"field_value": "1",
"decision_action": DecisionAction.UW,
"reason_code": "MULTIPLE_UNSECURED_LOANS",
"priority": 205,
},
{
"rule_code": "FACE_MATCH_LOW_UW",
"rule_name": "Low face match",
"rule_type": RuleType.NON_STP,
"field_name": "faceMatchScore",
"operator": "<",
"field_value": "75",
"decision_action": DecisionAction.UW,
"reason_code": "LOW_FACE_MATCH",
"priority": 206,
},
{
"rule_code": "STP_CIBIL",
"rule_name": "STP CIBIL threshold",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "cibilScore",
"operator": ">=",
"field_value": "750",
"decision_action": DecisionAction.STP,
"reason_code": "STP_CIBIL_OK",
"priority": 300,
},
{
"rule_code": "STP_NO_WRITEOFF",
"rule_name": "STP no written off",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "writtenOffAccounts",
"operator": "=",
"field_value": "0",
"decision_action": DecisionAction.STP,
"reason_code": "STP_NO_WRITEOFF",
"priority": 301,
},
{
"rule_code": "STP_NO_SETTLEMENT",
"rule_name": "STP no settlement",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "settledAccounts",
"operator": "=",
"field_value": "0",
"decision_action": DecisionAction.STP,
"reason_code": "STP_NO_SETTLEMENT",
"priority": 302,
},
{
"rule_code": "STP_NO_OVERDUE",
"rule_name": "STP no overdue",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "overdueAmount",
"operator": "=",
"field_value": "0",
"decision_action": DecisionAction.STP,
"reason_code": "STP_NO_OVERDUE",
"priority": 303,
},
{
"rule_code": "STP_MAX_DPD",
"rule_name": "STP max DPD threshold",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "maxDPD",
"operator": "<=",
"field_value": "30",
"decision_action": DecisionAction.STP,
"reason_code": "STP_DPD_OK",
"priority": 304,
},
{
"rule_code": "STP_FOIR",
"rule_name": "STP FOIR threshold",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "foir",
"operator": "<=",
"field_value": "40",
"decision_action": DecisionAction.STP,
"reason_code": "STP_FOIR_OK",
"priority": 305,
},
{
"rule_code": "STP_ENQUIRY",
"rule_name": "STP enquiry threshold",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "creditEnquiries6M",
"operator": "<=",
"field_value": "3",
"decision_action": DecisionAction.STP,
"reason_code": "STP_ENQUIRY_OK",
"priority": 306,
},
{
"rule_code": "STP_FACE_MATCH",
"rule_name": "STP face match threshold",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "faceMatchScore",
"operator": ">=",
"field_value": "85",
"decision_action": DecisionAction.STP,
"reason_code": "STP_FACE_MATCH_OK",
"priority": 307,
},
{
"rule_code": "STP_KYC",
"rule_name": "STP KYC verified",
"rule_type": RuleType.STP_ELIGIBILITY,
"field_name": "kycVerified",
"operator": "=",
"field_value": "true",
"decision_action": DecisionAction.STP,
"reason_code": "STP_KYC_OK",
"priority": 308,
},
]
SUPPORTED_PRODUCT_TYPES = [
"TWO_WHEELER_LOAN",
"NEW_CAR_LOAN",
"USED_CAR_LOAN",
"NEW_COMMERCIAL_VEHICLE_LOAN",
"USED_COMMERCIAL_VEHICLE_LOAN",
"NEW_TRACTOR_LOAN",
"USED_TRACTOR_LOAN",
]
def seed_default_rules(
db: Session, tenant_id: str = "default", product_type: str = "TWO_WHEELER_LOAN"
) -> int:
inserted = 0
for item in DEFAULT_RULES:
exists = db.scalar(
select(RuleMaster.id).where(
RuleMaster.tenant_id == tenant_id,
RuleMaster.product_type == product_type,
RuleMaster.rule_code == item["rule_code"],
RuleMaster.version == 1,
RuleMaster.dealer_id.is_(None),
)
)
if exists:
continue
db.add(
RuleMaster(
tenant_id=tenant_id,
product_type=product_type,
dealer_id=None,
version=1,
is_active=True,
created_by="seed",
**item,
)
)
inserted += 1
return inserted
def seed_all_product_rules(db: Session, tenant_id: str = "default") -> int:
inserted = 0
for product_type in SUPPORTED_PRODUCT_TYPES:
inserted += seed_default_rules(db, tenant_id=tenant_id, product_type=product_type)
return inserted