v2.0

GoRules Version 2 is here - redesigned, now with managed cloud.GoRules Version 2 is here!

Watch the launch videoWatch

Loan 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:

  1. Credit Assessment: Analyzes credit scores and assigns ratings (excellent to very poor) with corresponding risk points.
  2. Income Verification: Categorizes annual income into levels (high to very low) and assigns qualification points.
  3. Debt-to-Income Calculation: Computes the percentage of monthly debt payments relative to monthly income.
  4. Employment Evaluation: Assesses stability based on employment status (full-time, part-time, self-employed) and years of employment.
  5. Rejection Analysis: Identifies specific rejection reasons when applicants fail to meet minimum requirements.
  6. Approval Decision: Determines if the application should be approved based on collected rejection reasons.
  7. 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.

Decision graph9 nodes · read-only
input loanApplicationtable creditScoreEvaluationtable incomeVerificationexpression debtToIncomeRatiotable employmentHistorytable determineLoanRejectionReasonswitch loanApprovalDecisionexpression interestRateCalculationexpression rejection
01

Loan Application

input

Nine 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
}
02

Credit Score Evaluation

table

A 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.

Decision tablefirst hit policy
Credit ScorecreditScoreCredit RatingcreditRatingScore PointscreditScorePoints
>= 750"excellent"30
[700..749]"good"25
[650..699]"fair"15
[600..649]"poor"5
< 600"very_poor"0
03

Income Verification

table

Annual 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.

Decision tablefirst hit policy
Annual IncomeannualIncomeIncome LevelincomeLevelIncome PointsincomePoints
>= 100000"high"25
[60000..99999]"medium"20
[30000..59999]"low"10
< 30000"very_low"0
04

Debt To Income Ratio

expression

Dividing 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.

Expressions1 fields
dtiRatio(monthlyDebt / monthlyIncome) * 100
05

Employment History

table

Stability 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.

Decision tablefirst hit policy
Employment StatusStabilityemploymentStabilityEmployment 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

06

Determine Loan Rejection Reason

table

Every 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.

Decision tablecollect hit policy
Rejection ConditionsMessagemessage
creditScorePoints < 15"Poor credit history"
incomePoints < 10"Insufficient income"
dtiRatio > 40"High debt-to-income ratio"
employmentPoints < 10"Unstable employment history"
07

Loan Approval Decision

switch

Applications 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.

Branches2 paths
len(rejectionReasons) == 0 Interest Rate Calculation
otherwise Rejection
08

Interest Rate Calculation

expression

Approved 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.

Expressions5 fields
totalRiskScorecreditScorePoints + incomePoints + employmentPoints + dtiRatio
approvaltrue
baseRate5.5
riskAdjustment100 - $.totalRiskScore
interestRate$.baseRate + ($.riskAdjustment * 0.05)
09

Rejection

expression

On 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.

Expressions2 fields
rejectionReasonsrejectionReasons
approvalfalse

Make this template
your own.

Load Loan Approval into GoRules, adjust the rules to your policy, and ship it behind your own API.