GoRules Version 2 is here - redesigned, now with managed cloud.GoRules Version 2 is here!
Watch the launch videoWatchLoan Approval
Automated system that evaluates credit scores, income, debt ratios, and employment history to determine mortgage eligibility and personalized interest rates.
Solution
This intelligent loan approval system streamlines the mortgage application process by analyzing key financial factors to make consistent, data-driven decisions. The engine evaluates credit scores against defined thresholds, categorizing them from excellent to very poor while assigning appropriate risk points. It verifies annual income, classifying applicants into income level brackets with corresponding qualification points.
The system calculates precise debt-to-income ratios by comparing monthly obligations to income. Employment stability is assessed through a combination of employment type and tenure, with full-time long-term employees receiving the highest stability ratings. Based on these evaluations, the engine generates specific rejection reasons when necessary or calculates personalized interest rates for approved applications by factoring in the applicant's total risk score. This standardized approach ensures fair, transparent lending decisions while reducing processing time.
How it works
The decision graph follows a systematic evaluation process:
- Credit Assessment: Analyzes credit scores and assigns ratings (excellent to very poor) with corresponding risk points.
- Income Verification: Categorizes annual income into levels (high to very low) and assigns qualification points.
- Debt-to-Income Calculation: Computes the percentage of monthly debt payments relative to monthly income.
- Employment Evaluation: Assesses stability based on employment status (full-time, part-time, self-employed) and years of employment.
- Rejection Analysis: Identifies specific rejection reasons when applicants fail to meet minimum requirements.
- Approval Decision: Determines if the application should be approved based on collected rejection reasons.
- Interest Rate Calculation: For approved loans, computes personalized interest rates based on the total risk score.
Where teams use it
- Mortgage lending institutions
- Online mortgage application platforms
- Credit unions processing home loans
- Financial technology companies
- Bank loan departments
- Mortgage broker services
Inside the decision model
Loan Approval ships as a JDM decision graph with 9 nodes, 4 decision tables and 21 rules. Download it, load it into GoRules, and run it as-is on Zen Engine.
Loan Application
inputNine flat fields drive the decision: creditScore, annualIncome, monthlyDebt and monthlyIncome, employmentStatus with employmentYears, plus applicantId, loanAmount, and loanTerm for context. The four scoring stages downstream each read a different slice of this request.
Sample requestJSON
{
"applicantId": "APL-12345",
"creditScore": 685,
"annualIncome": 72000,
"monthlyDebt": 1800,
"monthlyIncome": 6000,
"employmentYears": 4,
"employmentStatus": "full_time",
"loanAmount": 250000,
"loanTerm": 30
}Credit Score Evaluation
tableA first hit policy walks the creditScore bands from the top: '>= 750' returns "excellent" with 30 creditScorePoints, [700..749] "good" with 25, [650..699] "fair" with 15, [600..649] "poor" with 5, and anything under 600 "very_poor" with 0. Ordering the rows from best to worst means each application lands in exactly one band.
The cutoffs track the credit-score tiers most US lenders work with, where scores in the mid-700s and above price as prime and anything under 600 reads as subprime. Weighting the top band at 30 points, the largest single contribution in this graph, reflects how heavily mortgage underwriting relies on credit history as a default predictor.
| Credit ScorecreditScore | Credit RatingcreditRating | Score PointscreditScorePoints |
|---|---|---|
| >= 750 | "excellent" | 30 |
| [700..749] | "good" | 25 |
| [650..699] | "fair" | 15 |
| [600..649] | "poor" | 5 |
| < 600 | "very_poor" | 0 |
Income Verification
tableAnnual income falls into one of four brackets under a first hit policy: '>= 100000' maps to "high" with 25 incomePoints, [60000..99999] to "medium" with 20, [30000..59999] to "low" with 10, and below 30000 to "very_low" with 0.
These breakpoints are a business calibration rather than a regulatory requirement: 60000 sits near a typical household income for a mid-size mortgage, and the 0-point floor under 30000 stops low earners from accumulating qualification points on income alone. The incomePoints feed both the rejection check and the risk-priced rate later in the graph.
| Annual IncomeannualIncome | Income LevelincomeLevel | Income PointsincomePoints |
|---|---|---|
| >= 100000 | "high" | 25 |
| [60000..99999] | "medium" | 20 |
| [30000..59999] | "low" | 10 |
| < 30000 | "very_low" | 0 |
Debt To Income Ratio
expressionDividing monthlyDebt by monthlyIncome and multiplying by 100 produces dtiRatio, the percentage of gross monthly income already committed to debt service. Debt-to-income is the standard affordability measure in mortgage underwriting, and the value computed here is tested against the 40 threshold in the rejection table and folded into the risk score that prices the loan.
(monthlyDebt / monthlyIncome) * 100Employment History
tableStability is scored from paired conditions on employmentStatus and employmentYears with a first hit policy: full_time with 5 or more years earns "very_stable" and 20 employmentPoints, full_time at 2 or more years "stable" and 15, self_employed needs 3 years for the same "stable" 15, part_time tops out at "moderate" with 5, and unemployed is always "unstable" with 0.
Requiring a longer track record from self_employed applicants than from salaried ones mirrors standard mortgage practice, where lenders typically want around two years of documented self-employment income before treating it as stable. The part_time ceiling and the zero for unemployed reflect income continuity risk rather than any regulatory rule.
| Employment Status | StabilityemploymentStability | Employment PointsemploymentPoints |
|---|---|---|
| employmentStatus == "full_time" and employmentYears >= 5 | "very_stable" | 20 |
| employmentStatus == "full_time" and employmentYears >= 2 | "stable" | 15 |
| employmentStatus == "full_time" and employmentYears < 2 | "new" | 10 |
| employmentStatus == "part_time" and employmentYears >= 2 | "moderate" | 5 |
| employmentStatus == "part_time" and employmentYears < 2 | "unstable" | 0 |
| employmentStatus == "self_employed" and employmentYears >= 3 | "stable" | 15 |
+2 more rows in the downloadable template
Determine Loan Rejection Reason
tableEvery failing check contributes here because the hit policy is collect: creditScorePoints < 15 adds "Poor credit history", incomePoints < 10 adds "Insufficient income", dtiRatio > 40 adds "High debt-to-income ratio", and employmentPoints < 10 adds "Unstable employment history". The messages accumulate into the rejectionReasons array via the table's outputPath, so an applicant can fail on several grounds at once.
Returning specific reasons instead of a bare decline matches US adverse action practice, where ECOA and Regulation B require lenders to state the principal reasons for a denial. The 40 percent DTI cutoff sits just under the 43 percent benchmark long associated with qualified mortgage rules, while the point floors simply restate the weaker bands of the upstream scoring tables.
| Rejection Conditions | Messagemessage |
|---|---|
| creditScorePoints < 15 | "Poor credit history" |
| incomePoints < 10 | "Insufficient income" |
| dtiRatio > 40 | "High debt-to-income ratio" |
| employmentPoints < 10 | "Unstable employment history" |
Loan Approval Decision
switchApplications split on a single test: len(rejectionReasons) == 0 sends the request down the approval branch to interest rate pricing, and the default branch catches everything else as a rejection. Keeping the branch condition tied to the collected reasons list means the approval logic never restates the underwriting thresholds themselves.
Interest Rate Calculation RejectionInterest Rate Calculation
expressionApproved applicants get risk-based pricing: totalRiskScore sums creditScorePoints, incomePoints, employmentPoints, and dtiRatio, then riskAdjustment is computed as 100 - $.totalRiskScore and the final interestRate adds 0.05 points per adjustment unit to a 5.5 baseRate. Stronger applicants therefore pay closer to the base rate, which is how lenders translate a scorecard into a personalized offer.
creditScorePoints + incomePoints + employmentPoints + dtiRatiotrue5.5100 - $.totalRiskScore$.baseRate + ($.riskAdjustment * 0.05)Rejection
expressionOn the declined branch the response is reduced to two keys: rejectionReasons carries the collected messages forward and approval is set to false. With passThrough disabled, the caller receives a clean rejection payload instead of the full scoring state.
rejectionReasonsfalseOther Financial templates
View all templatesReal-Time Fraud Detection
Advanced transaction monitoring system that identifies suspicious financial activities using location analysis, spending patterns, and behavioral anomalies.
FinancialCustomer Onboarding KYC Verification
Automated compliance checks that validate identity documents, screen against watchlists, and apply risk-based due diligence during onboarding.
FinancialPortfolio Risk Monitor
Dynamic portfolio management system that continuously evaluates investment holdings against market conditions to implement appropriate risk mitigation actions.
Make this template
your own.
Load Loan Approval into GoRules, adjust the rules to your policy, and ship it behind your own API.