-
Notifications
You must be signed in to change notification settings - Fork 1
/
LoanUnderwriter.cs
68 lines (54 loc) · 3.01 KB
/
LoanUnderwriter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using Codat.Demos.Underwriting.Api.Exceptions;
using Codat.Demos.Underwriting.Api.Models;
using Microsoft.Extensions.Options;
namespace Codat.Demos.Underwriting.Api.Services;
public interface ILoanUnderwriter
{
ApplicationStatus Process(decimal loanAmount, int loanTerm, FinancialStatement profitAndLoss, FinancialStatement balanceSheet);
}
public class LoanUnderwriter : ILoanUnderwriter
{
private readonly UnderwritingParameters _parameters;
public LoanUnderwriter(IOptions<UnderwritingParameters> options)
{
_parameters = options.Value;
}
public ApplicationStatus Process(decimal loanAmount, int loanTerm, FinancialStatement profitAndLoss, FinancialStatement balanceSheet)
{
try
{
var profitMarginAcceptable = IsGrossProfitMarginThresholdPassed(profitAndLoss);
var revenueAssessmentPassed = IsRevenueThresholdPassed(profitAndLoss, loanAmount, loanTerm);
var gearingPassed = IsGearingRatioBelowThreshold(balanceSheet);
return profitMarginAcceptable && revenueAssessmentPassed && gearingPassed ? ApplicationStatus.Accepted : ApplicationStatus.Rejected;
}
catch (Exception ex) when (ex is LoanUnderwriterException or InvalidOperationException or ArgumentNullException)
{
return ApplicationStatus.UnderwritingFailure;
}
}
private bool IsGrossProfitMarginThresholdPassed(FinancialStatement profitAndLoss)
{
var netSales = profitAndLoss.Lines.Where(x => x.AccountCategorization.StartsWith("Income.Operating")).Sum(x => x.Balance);
var costOfSales = profitAndLoss.Lines.Where(x => x.AccountCategorization.StartsWith("Expense.CostOfSales")).Sum(x => x.Balance);
var grossProfit = netSales - costOfSales;
var grossProfitMargin = netSales == 0 ? 0 : grossProfit / netSales;
return _parameters.MinGrossProfitMargin < grossProfitMargin;
}
private bool IsRevenueThresholdPassed(FinancialStatement profitAndLoss, decimal loanAmount, int loanTerm)
{
var operatingIncome = profitAndLoss.Lines.Where(x => x.AccountCategorization.StartsWith("Income.Operating")).Sum(x => x.Balance);
var totalLoanAmount = loanAmount * (1 + _parameters.LoanCommissionPercentage);
var monthlyRevenue = operatingIncome / 12;
var monthlyLoanPayment = totalLoanAmount / loanTerm;
var revenuePercentage = monthlyRevenue == 0 ? 0 : monthlyLoanPayment / monthlyRevenue;
return revenuePercentage < _parameters.RevenueThreshold;
}
private bool IsGearingRatioBelowThreshold(FinancialStatement balanceSheet)
{
var totalAssets = balanceSheet.Lines.Where(x => x.AccountCategorization.StartsWith("Asset")).Sum(x => x.Balance);
var totalDebt = balanceSheet.Lines.Where(x => x.AccountCategorization.StartsWith("Liability.NonCurrent.LoansPayable")).Sum(x => x.Balance);
var gearingRatio = totalAssets == 0 ? 0m : totalDebt/totalAssets;
return gearingRatio <= _parameters.MaxGearingRatio;
}
}