@wealthx/borrow-capacity-lib
Version:
Borrow capacity calculation library for WealthX
651 lines (642 loc) • 30.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
(function (AccountType) {
AccountType["Transaction"] = "transaction";
AccountType["Savings"] = "savings";
AccountType["CreditCard"] = "credit-card";
AccountType["Mortgage"] = "mortgage";
AccountType["Loan"] = "loan";
AccountType["Investment"] = "investment";
AccountType["Share"] = "share";
AccountType["TermDeposit"] = "term-deposit";
AccountType["Insurance"] = "insurance";
AccountType["Other"] = "other";
AccountType["Unknown"] = "unknown";
AccountType["Superannuation"] = "superannuation";
})(exports.AccountType || (exports.AccountType = {}));
(function (PeriodType) {
PeriodType["Monthly"] = "monthly";
PeriodType["Weekly"] = "weekly";
})(exports.PeriodType || (exports.PeriodType = {}));
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
/**
* Process scenario finance history to calculate aggregated monthly data
* This function processes expense and income data for all participants across multiple months
*
* @param scenarioFinanceHistory - Array of scenario finance history items
* @param scenarioFinanceHistory.incomeAndExpensesSummary.avgExpenseByLastXMonths - Negative value represent a deduction, Example: { "2025-09": -1000 }
* @param scenarioFinanceHistory.incomeAndExpensesSummary.avgRepaymentExpenseByLastXMonths - Positive value represent a repayment, Example: { "2025-09": 1000 }
* @param scenarioFinanceHistory.incomeAndExpensesSummary.incomesByLastXMonths - Positive value represent a credit action, Example: { "2025-09": { "regular": [{ "avgAmount": 1000, "isRentalIncome": true }] } }
* @param userId - Main applicant user ID
* @returns Array of processed history data sorted by month
*/
function processScenarioHistoryData(scenarioFinanceHistory, userId) {
var allMonthsData = {};
// Process each scenario's participant finance history
(scenarioFinanceHistory || []).forEach(function (item) {
var isMainApplicant = item.memberId === userId;
var summary = item == null ? void 0 : item.incomeAndExpensesSummary;
if (!summary) return;
// Process expense data for all months
var expenseByMonths = summary.avgExpenseByLastXMonths || {};
var repaymentExpenseByMonths = summary.avgRepaymentExpenseByLastXMonths || {};
Object.keys(expenseByMonths).forEach(function (monthKey) {
if (!allMonthsData[monthKey]) {
allMonthsData[monthKey] = {
month: monthKey,
mainApplicantMonthlyIncome: 0,
coApplicantMonthlyIncome: 0,
mainApplicantMonthlyRentalIncome: 0,
coApplicantMonthlyRentalIncome: 0,
monthlyExpense: 0,
monthlyDebtRepayment: 0
};
}
var monthNonRepaymentExpenseValues = Math.abs(Number(expenseByMonths[monthKey]) || 0);
// Repayment transaction amount is positive (user add credit to loan account => positive amount)
var monthDebtRepaymentValues = Number(repaymentExpenseByMonths[monthKey]) || 0;
allMonthsData[monthKey].monthlyExpense += monthNonRepaymentExpenseValues;
allMonthsData[monthKey].monthlyDebtRepayment += monthDebtRepaymentValues;
});
// Process income data for all months
var incomesByMonths = summary.incomesByLastXMonths || {};
Object.keys(incomesByMonths).forEach(function (monthKey) {
var _incomesByMonths$mont;
if (!allMonthsData[monthKey]) {
allMonthsData[monthKey] = {
month: monthKey,
mainApplicantMonthlyIncome: 0,
coApplicantMonthlyIncome: 0,
mainApplicantMonthlyRentalIncome: 0,
coApplicantMonthlyRentalIncome: 0,
monthlyExpense: 0,
monthlyDebtRepayment: 0
};
}
var monthIncomeValues = ((_incomesByMonths$mont = incomesByMonths[monthKey]) == null ? void 0 : _incomesByMonths$mont.regular) || [];
var incomeData = monthIncomeValues.reduce(function (applicantAcc, curr) {
var updatedAcc = _extends({}, applicantAcc);
var fieldToUpdate;
if (curr.isRentalIncome) {
fieldToUpdate = isMainApplicant ? "mainApplicantMonthlyRentalIncome" : "coApplicantMonthlyRentalIncome";
} else {
fieldToUpdate = isMainApplicant ? "mainApplicantMonthlyIncome" : "coApplicantMonthlyIncome";
}
updatedAcc[fieldToUpdate] += Number(curr.avgAmount) || 0;
return updatedAcc;
}, {
mainApplicantMonthlyIncome: 0,
coApplicantMonthlyIncome: 0,
mainApplicantMonthlyRentalIncome: 0,
coApplicantMonthlyRentalIncome: 0
});
allMonthsData[monthKey].mainApplicantMonthlyIncome += incomeData.mainApplicantMonthlyIncome;
allMonthsData[monthKey].coApplicantMonthlyIncome += incomeData.coApplicantMonthlyIncome;
allMonthsData[monthKey].mainApplicantMonthlyRentalIncome += incomeData.mainApplicantMonthlyRentalIncome;
allMonthsData[monthKey].coApplicantMonthlyRentalIncome += incomeData.coApplicantMonthlyRentalIncome;
});
});
// Convert to array and round values
var historyData = Object.values(allMonthsData).map(function (monthData) {
return _extends({}, monthData, {
mainApplicantMonthlyIncome: Math.round(monthData.mainApplicantMonthlyIncome),
coApplicantMonthlyIncome: Math.round(monthData.coApplicantMonthlyIncome),
mainApplicantMonthlyRentalIncome: Math.round(monthData.mainApplicantMonthlyRentalIncome),
coApplicantMonthlyRentalIncome: Math.round(monthData.coApplicantMonthlyRentalIncome),
monthlyExpense: Math.round(monthData.monthlyExpense),
monthlyDebtRepayment: Math.round(monthData.monthlyDebtRepayment)
});
}).sort(function (a, b) {
return a.month.localeCompare(b.month);
});
return historyData;
}
var DEFAULT_INTEREST_RATE = 0.0524;
var DEFAULT_SURPLUS_AVAILABLE_FOR_LOAN_RATE = 1;
var DEFAULT_LOAN_TERM = 30 * 12;
var DEFAULT_ASSESSMENT_BUFFER_RATE = 0.03;
var DEFAULT_HEM_BUFFER_RATE = 0.05;
var DEFAULT_SHADED_RENTAL_INCOME_RATIO = 0.8;
var accountTypes = /*#__PURE__*/Object.values(exports.AccountType);
/**
* Credit card, mortgage, loan
*/
var liabilityAccountTypes = [exports.AccountType.CreditCard, exports.AccountType.Mortgage, exports.AccountType.Loan];
/**
* Loan, mortgage
*/
var loanAccountTypes = [exports.AccountType.Loan, exports.AccountType.Mortgage];
/**
* Not credit card, mortgage, loan
*/
var nonLiabilityAccountTypes = /*#__PURE__*/accountTypes.filter(function (type) {
return !liabilityAccountTypes.includes(type);
});
var nonSuperannuationAccountTypes = /*#__PURE__*/accountTypes.filter(function (type) {
return type !== exports.AccountType.Superannuation;
});
// Income bracket upper bounds for lookup
var HEM_INCOME_BRACKETS = [26000, 39000, 52000, 66000, 79000, 105000, 131000, 157000, 184000, 210000, 262000, 328000, 394000, 656000];
// HEM (Household Expenditure Measure) data structure
// Map<PeriodType, Map<number of applicant, Map<number of dependants, money[]>>>
var HEM_DATA = /*#__PURE__*/new Map([[exports.PeriodType.Weekly, /*#__PURE__*/new Map([[2, /*#__PURE__*/new Map([
// Couple households
[0, [599, 599, 613, 638, 679, 742, 829, 903, 985, 1034, 1100, 1216, 1389, 1403]], [1, [599, 599, 704, 728, 769, 833, 920, 995, 1077, 1126, 1192, 1309, 1482, 1497]], [2, [599, 599, 769, 794, 835, 897, 984, 1058, 1140, 1189, 1255, 1371, 1543, 1558]], [3, [599, 599, 769, 859, 900, 962, 1049, 1123, 1205, 1254, 1319, 1436, 1607, 1622]]])], [1, /*#__PURE__*/new Map([
// Single households
[0, [311, 326, 341, 366, 407, 470, 557, 631, 714, 762, 828, 945, 1118, 1133]], [1, [311, 425, 440, 464, 505, 567, 654, 728, 810, 858, 924, 1040, 1211, 1226]], [2, [311, 536, 551, 575, 616, 678, 765, 839, 920, 969, 1034, 1150, 1321, 1336]], [3, [311, 536, 662, 687, 727, 789, 876, 949, 1031, 1079, 1145, 1260, 1431, 1446]]])]])], [exports.PeriodType.Monthly, /*#__PURE__*/new Map([[2, /*#__PURE__*/new Map([
// Couple households
[0, [2394, 2394, 2453, 2553, 2715, 2967, 3316, 3612, 3942, 4135, 4399, 4865, 5555, 5614]], [1, [2394, 2394, 2814, 2914, 3077, 3330, 3681, 3978, 4309, 4503, 4768, 5236, 5929, 5988]], [2, [2394, 2394, 3077, 3176, 3338, 3590, 3938, 4233, 4562, 4755, 5018, 5483, 6171, 6230]], [3, [2394, 2394, 3077, 3436, 3598, 3849, 4197, 4493, 4821, 5014, 5277, 5742, 6430, 6489]]])], [1, /*#__PURE__*/new Map([
// Single households
[0, [1244, 1305, 1364, 1464, 1627, 1879, 2229, 2525, 2855, 3049, 3313, 3780, 4471, 4530]], [1, [1244, 1700, 1758, 1857, 2019, 2270, 2617, 2912, 3239, 3432, 3695, 4158, 4844, 4903]], [2, [1244, 2144, 2203, 2302, 2463, 2714, 3061, 3355, 3682, 3874, 4136, 4600, 5284, 5343]], [3, [1244, 2144, 2648, 2746, 2908, 3158, 3504, 3798, 4124, 4316, 4578, 5041, 5725, 5784]]])]])]]);
// Australian Tax Rates for 2025-26 (for reference)
var AUSTRALIAN_TAX_BRACKETS = [{
min: 0,
max: 18200,
rate: 0.0,
baseTax: 0
}, {
min: 18201,
max: 45000,
rate: 0.16,
baseTax: 0
}, {
min: 45001,
max: 135000,
rate: 0.3,
baseTax: 4288
}, {
min: 135001,
max: 190000,
rate: 0.37,
baseTax: 31288
}, {
min: 190001,
max: Number.MAX_SAFE_INTEGER,
rate: 0.45,
baseTax: 51638
}];
// Medicare Levy rate (2% on entire taxable income)
var MEDICARE_LEVY_RATE = 0.02;
var getReverseBrackets = function getReverseBrackets(includeMedicareLevy) {
var brackets = [];
for (var _i = 0, _AUSTRALIAN_TAX_BRACK = AUSTRALIAN_TAX_BRACKETS; _i < _AUSTRALIAN_TAX_BRACK.length; _i++) {
var _AUSTRALIAN_TAX_BRACK2 = _AUSTRALIAN_TAX_BRACK[_i],
min = _AUSTRALIAN_TAX_BRACK2.min,
max = _AUSTRALIAN_TAX_BRACK2.max,
rate = _AUSTRALIAN_TAX_BRACK2.rate,
baseTax = _AUSTRALIAN_TAX_BRACK2.baseTax;
var gRatio = 1 - rate - (includeMedicareLevy ? MEDICARE_LEVY_RATE : 0);
var base = (min - 1) * rate - baseTax;
var minValue = Math.round(min * gRatio + base);
var maxValue = max === Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : Math.round(max * gRatio + base);
brackets.push({
min: minValue,
max: maxValue,
gRatio: gRatio,
base: base
});
}
return brackets;
};
var AUSTRALIAN_REVERSE_TAX_BRACKETS = /*#__PURE__*/getReverseBrackets(true);
var AUSTRALIAN_REVERSE_TAX_BRACKETS_WITHOUT_MEDICARE_LEVY = /*#__PURE__*/getReverseBrackets(false);
/**
* Calculate borrowing capacity based on income, expenses, and interest rate
* Formula based on: https://wealthx.atlassian.net/wiki/spaces/WD/pages/86343838/Finance+Borrow+capacity+formula
*
* @param params.monthlyIncome - NET Monthly income not including rental income
* @param params.monthlyExpense - NET Monthly expense
* @param params.interestRate - Annual interest rate (as decimal, e.g., 0.05 for 5%)
* @param params.bufferRate - Buffer of assessment rate (default 0.03)
* @param params.surplusAvailableForLoanRate - Actual portion of surplus available for loan (default 1.0)
* @param params.loanTerm - Default 30 years (360 months)
* @param params.numberOfApplicants - Number of applicants (default 1 for single, 2 for couple)
* @param params.numberOfDependants - Number of dependants (default 0)
* @param params.monthlyRentalIncome - NET Monthly rental income
* @returns Maximum borrowing capacity
*/
function calculateBorrowCapacity(params) {
var _params$monthlyIncome = params.monthlyIncome,
monthlyIncome = _params$monthlyIncome === void 0 ? 0 : _params$monthlyIncome,
_params$monthlyExpens = params.monthlyExpense,
monthlyExpense = _params$monthlyExpens === void 0 ? 0 : _params$monthlyExpens,
_params$interestRate = params.interestRate,
interestRate = _params$interestRate === void 0 ? DEFAULT_INTEREST_RATE : _params$interestRate,
_params$loanTerm = params.loanTerm,
loanTerm = _params$loanTerm === void 0 ? DEFAULT_LOAN_TERM : _params$loanTerm,
_params$bufferRate = params.bufferRate,
bufferRate = _params$bufferRate === void 0 ? DEFAULT_ASSESSMENT_BUFFER_RATE : _params$bufferRate,
_params$surplusAvaila = params.surplusAvailableForLoanRate,
surplusAvailableForLoanRate = _params$surplusAvaila === void 0 ? DEFAULT_SURPLUS_AVAILABLE_FOR_LOAN_RATE : _params$surplusAvaila,
_params$numberOfAppli = params.numberOfApplicants,
numberOfApplicants = _params$numberOfAppli === void 0 ? 1 : _params$numberOfAppli,
_params$numberOfDepen = params.numberOfDependants,
numberOfDependants = _params$numberOfDepen === void 0 ? 0 : _params$numberOfDepen,
_params$monthlyRental = params.monthlyRentalIncome,
monthlyRentalIncome = _params$monthlyRental === void 0 ? 0 : _params$monthlyRental;
var originalAnnualTotalIncome = (monthlyIncome + monthlyRentalIncome) * 12;
var grossAnnualTotalIncome = netToGross({
netIncome: originalAnnualTotalIncome,
includeMedicareLevy: true
});
var hemExpense = calculateHEMExpense({
annualIncome: grossAnnualTotalIncome,
numberOfApplicants: numberOfApplicants,
numberOfDependants: numberOfDependants,
period: exports.PeriodType.Monthly
});
var expenseForSurplusCalculation = monthlyExpense < hemExpense ? hemExpense : monthlyExpense;
// Calculate surplus available for loan (100% of net income)
var shadedRentalIncome = monthlyRentalIncome * DEFAULT_SHADED_RENTAL_INCOME_RATIO;
var monthlyTotalIncomeForSurplusCalculation = monthlyIncome + shadedRentalIncome;
var surplusForLoan = Math.max(monthlyTotalIncomeForSurplusCalculation - expenseForSurplusCalculation, 0) * surplusAvailableForLoanRate;
// Assessment rate includes a 3% buffer
var assessmentRate = (interestRate + bufferRate) / 12;
if (assessmentRate === 0) {
// If no interest rate, simple calculation
return surplusForLoan * loanTerm;
}
// Present value of annuity formula
var borrowCapacity = surplusForLoan * (Math.pow(1 + assessmentRate, loanTerm) - 1) / (assessmentRate * Math.pow(1 + assessmentRate, loanTerm));
return borrowCapacity;
}
/**
* Calculate monthly repayment amount for a loan
* Formula based on: https://wealthx.atlassian.net/wiki/spaces/WD/pages/86343838/Finance+Borrow+capacity+formula
*
* @param params.interestRate - Annual interest rate (as decimal, e.g., 0.05 for 5%)
* @param params.loanTerm - Loan term in months (default 30 years)
* @returns Monthly repayment amount
*/
function calculateLoanRepayment(params) {
var principal = params.principal,
_params$interestRate2 = params.interestRate,
interestRate = _params$interestRate2 === void 0 ? DEFAULT_INTEREST_RATE : _params$interestRate2,
_params$loanTerm2 = params.loanTerm,
loanTerm = _params$loanTerm2 === void 0 ? DEFAULT_LOAN_TERM : _params$loanTerm2;
var monthlyRate = interestRate / 12;
if (monthlyRate === 0) {
// If no interest rate, simple division
return principal / loanTerm;
}
// Monthly payment formula for fixed-rate loan
var monthlyPayment = principal * (monthlyRate * Math.pow(1 + monthlyRate, loanTerm)) / (Math.pow(1 + monthlyRate, loanTerm) - 1);
return monthlyPayment;
}
/**
* Calculate HEM (Household Expenditure Measure) expense based on income, household composition, and period
*
* @param params.totalIncome - GROSS Total household ANNUAL income
* @param params.numberOfApplicants - Number of applicants (2 for couple, 1 for single)
* @param params.numberOfDependants - Number of dependants (0-3, capped at 3)
* @param params.period - Period type (weekly or monthly)
* @param params.bufferRate - Buffer rate to add on top (default 0.05 for 5%)
* @returns HEM expense amount
*/
function calculateHEMExpense(params) {
var annualIncome = params.annualIncome,
numberOfApplicants = params.numberOfApplicants,
numberOfDependants = params.numberOfDependants,
period = params.period,
_params$bufferRate2 = params.bufferRate,
bufferRate = _params$bufferRate2 === void 0 ? DEFAULT_HEM_BUFFER_RATE : _params$bufferRate2;
// Cap dependants at 3
var cappedDependants = Math.min(Math.max(numberOfDependants, 0), 3);
// Cap income at 656000
var cappedIncome = Math.min(Math.max(annualIncome, 0), HEM_INCOME_BRACKETS[HEM_INCOME_BRACKETS.length - 1]);
// Get the data for the specified period
var periodData = HEM_DATA.get(period);
if (!periodData) {
throw new Error("Invalid period type: " + period);
}
// Get the data for the number of applicants
var applicantData = periodData.get(numberOfApplicants);
if (!applicantData) {
throw new Error("Invalid number of applicants: " + numberOfApplicants + ". Must be 1 (single) or 2 (couple)");
}
// Get the data for the number of dependants
var dependantData = applicantData.get(cappedDependants);
if (!dependantData) {
throw new Error("Invalid number of dependants: " + cappedDependants);
}
// Find the appropriate income bracket using binary search
var left = 0;
var right = HEM_INCOME_BRACKETS.length - 1;
var bracketIndex = -1;
while (left <= right) {
var mid = Math.floor((left + right) / 2);
if (cappedIncome <= HEM_INCOME_BRACKETS[mid]) {
bracketIndex = mid;
right = mid - 1; // Look for the first occurrence
} else {
left = mid + 1;
}
}
if (bracketIndex === -1) {
throw new Error("Something went wrong");
}
// Get the expense value for the income bracket
var expenseValue = dependantData[bracketIndex];
if (expenseValue === undefined) {
throw new Error("No HEM data available for income " + cappedIncome + " with " + numberOfApplicants + " applicants and " + cappedDependants + " dependants");
}
// Apply the 5% buffer rate
return expenseValue * (1 + bufferRate);
}
/**
* Calculate tax payable on gross income using Australian tax brackets
* Based on 2025-26 Australian Resident Tax Rates
* @param grossIncome - Annual gross income
* @returns Tax payable amount
*/
function calculateTaxPayable(grossIncome) {
var _grossIncome = Math.floor(grossIncome);
if (_grossIncome <= 0) return 0;
var braket = AUSTRALIAN_TAX_BRACKETS.find(function (bracket) {
return _grossIncome >= bracket.min && _grossIncome <= bracket.max;
});
if (!braket) return 0;
return braket.baseTax + (_grossIncome - braket.min) * braket.rate;
}
/**
* @param params.grossIncome - Annual gross income
* @param params.includeMedicareLevy - Whether to include 2% Medicare levy (default: true)
* @returns Net income after tax and Medicare levy
*/
function grossToNet(params) {
var grossIncome = params.grossIncome,
_params$includeMedica = params.includeMedicareLevy,
includeMedicareLevy = _params$includeMedica === void 0 ? true : _params$includeMedica;
if (grossIncome <= 0) return 0;
var taxPayable = calculateTaxPayable(grossIncome);
var medicareLevy = includeMedicareLevy ? grossIncome * MEDICARE_LEVY_RATE : 0;
return grossIncome - taxPayable - medicareLevy;
}
/**
* Convert net income to gross income (reverse calculation)
* @param params.netIncome - Target net income after tax and Medicare levy
* @param params.includeMedicareLevy - Whether to include 2% Medicare levy (default: true)
* @returns Gross income required to achieve the target net income
*/
function netToGross(params) {
var netIncome = params.netIncome,
_params$includeMedica2 = params.includeMedicareLevy,
includeMedicareLevy = _params$includeMedica2 === void 0 ? true : _params$includeMedica2;
var brackets = includeMedicareLevy ? AUSTRALIAN_REVERSE_TAX_BRACKETS : AUSTRALIAN_REVERSE_TAX_BRACKETS_WITHOUT_MEDICARE_LEVY;
var bracket = brackets.find(function (bracket) {
return netIncome >= bracket.min && netIncome <= bracket.max;
});
if (!bracket) return 0;
return Math.round((netIncome - bracket.base) / bracket.gRatio);
}
function calculateCashAvailable(accounts) {
var filteredAccounts = accounts == null ? void 0 : accounts.filter(function (account) {
var _account$class;
var accountType = ((_account$class = account["class"]) == null ? void 0 : _account$class.type) || exports.AccountType.Unknown;
return accountType !== exports.AccountType.Superannuation && nonLiabilityAccountTypes.includes(accountType);
});
return filteredAccounts == null ? void 0 : filteredAccounts.reduce(function (totalBalance, _ref) {
var _ref$balance = _ref.balance,
balance = _ref$balance === void 0 ? 0 : _ref$balance;
return totalBalance + (balance > 0 ? balance : 0);
}, 0);
}
function calculateLiability(accounts) {
var filteredLiabilityAccount = accounts == null ? void 0 : accounts.filter(function (account) {
var _account$class2;
return liabilityAccountTypes.includes((account == null || (_account$class2 = account["class"]) == null ? void 0 : _account$class2.type) || exports.AccountType.Unknown);
});
return filteredLiabilityAccount == null ? void 0 : filteredLiabilityAccount.reduce(function (sum, _ref2) {
var _ref2$balance = _ref2.balance,
balance = _ref2$balance === void 0 ? 0 : _ref2$balance;
return sum + (balance < 0 ? balance : 0);
}, 0);
}
/**
* Calculate Loan-to-Value Ratio (LVR) as a percentage
* LVR = (Mortgage Loan / Properties Estimated Value) * 100
*
* @param params.mortgageLoan - Total mortgage loan amount (positive number)
* @param params.propertiesEstimatedValue - Total estimated value of properties
* @returns LVR as percentage (0-100), returns 0 if no properties, 100 if mortgage >= property value
*/
function calculateLVR(params) {
var mortgageLoan = params.mortgageLoan,
propertiesEstimatedValue = params.propertiesEstimatedValue;
if (propertiesEstimatedValue <= 0) {
return 0;
}
if (mortgageLoan >= propertiesEstimatedValue) {
return 100;
}
return mortgageLoan / propertiesEstimatedValue * 100 || 0;
}
/**
* Calculate property equity amount
* Equity = Properties Estimated Value - Mortgage Loan Amount
*
* @param params.propertiesEstimatedValue - Total estimated value of properties
* @param params.mortgageLoan - Total mortgage loan amount (positive number)
* @returns Equity amount (minimum 0)
*/
function calculateEquity(params) {
var propertiesEstimatedValue = params.propertiesEstimatedValue,
mortgageLoan = params.mortgageLoan;
return Math.max(propertiesEstimatedValue - mortgageLoan, 0);
}
/**
* Calculate excess monthly surplus
* Excess Monthly Surplus = max(Total Income + Total Expense, 0)
* Note: totalExpense is expected to be negative in the backend
*
* @param params.totalIncome - Total monthly income (positive)
* @param params.totalExpense - Total monthly expense (negative value)
* @returns Excess monthly surplus (minimum 0)
*/
function calculateExcessMonthlySurplus(params) {
var totalIncome = params.totalIncome,
totalExpense = params.totalExpense;
// totalExpense is negative, so we use + to subtract
return Math.max(totalIncome + totalExpense, 0);
}
/**
* Calculate total estimated value of properties
*
* @param params.properties - Array of properties with estimate values
* @returns Total estimated value of all properties
*/
function calculatePropertiesEstimatedValue(params) {
var properties = params.properties;
return properties.reduce(function (acc, property) {
return acc + ((property == null ? void 0 : property.estimate) || 0);
}, 0);
}
/**
* Calculate total mortgage loan amount from accounts linked to properties
*
* @param params.accounts - Array of accounts with balance and type information
* @param params.propertyLinkedAccountIds - Set of account IDs linked to properties
* @returns Total mortgage loan amount (positive number)
*/
function calculateMortgageLoan(params) {
var accounts = params.accounts,
propertyLinkedAccountIds = params.propertyLinkedAccountIds;
var totalMortgageLoan = accounts.filter(function (account) {
return propertyLinkedAccountIds.has(account.id);
}).reduce(function (acc, account) {
var newLocal = Number(account.balance);
// Only take negative balance (loan amount)
return acc + (newLocal < 0 ? newLocal : 0);
}, 0);
// Return absolute value as mortgage loan amount should be positive
return Math.abs(totalMortgageLoan);
}
/**
* Extract lender information from mortgage accounts
*
* @param accounts - Array of account data
* @returns Object containing lender names, short names, and interest rates
*/
function getLenderInfo(accounts) {
var mortgageAccounts = accounts.filter(function (account) {
var _account$class3;
return ((_account$class3 = account["class"]) == null ? void 0 : _account$class3.type) === exports.AccountType.Mortgage;
});
return mortgageAccounts.reduce(function (acc, account) {
var _account$meta;
acc.lenderNames.push(account.institutionName || "Unknown");
acc.lenderShortNames.push(account.institutionShortName || null);
var rawLendingRate = account.lendingRate || ((_account$meta = account.meta) == null || (_account$meta = _account$meta.lendingRates) == null || (_account$meta = _account$meta[0]) == null ? void 0 : _account$meta.rate);
acc.debtInterestRates.push(rawLendingRate ? parseFloat(rawLendingRate) : null);
return acc;
}, {
lenderNames: [],
lenderShortNames: [],
debtInterestRates: []
});
}
/**
* Get linked property account IDs from properties
*
* @param properties - Array of properties with accountIds
* @returns Set of account IDs linked to properties
*/
function getLinkedPropertyAccountIds(properties) {
return properties.reduce(function (acc, property) {
var _property$accountIds;
var accountIds = ((_property$accountIds = property.accountIds) == null ? void 0 : _property$accountIds.split(",")) || [];
accountIds.forEach(function (accountId) {
if (accountId.trim()) {
acc.add(accountId.trim());
}
});
return acc;
}, new Set());
}
/**
* Calculate comprehensive finance summary including equity, properties value, and mortgage loan
* This combines property data with account data to calculate key financial metrics
*
* @param params.properties - Array of properties with estimates and linked account IDs
* @param params.accounts - Array of accounts with balances and types
* @returns Object containing equity, properties estimated value, and mortgage loan amount
*/
function calculateEquityData(params) {
var properties = params.properties,
accounts = params.accounts;
// Calculate total estimated value of properties
var propertiesEstimatedValue = calculatePropertiesEstimatedValue({
properties: properties
});
// Get account IDs linked to properties
var propertyLinkedAccountIds = getLinkedPropertyAccountIds(properties);
// Calculate mortgage loan amount from linked accounts
var mortgageLoan = calculateMortgageLoan({
accounts: accounts,
propertyLinkedAccountIds: propertyLinkedAccountIds
});
// Calculate equity
var equity = calculateEquity({
propertiesEstimatedValue: propertiesEstimatedValue,
mortgageLoan: mortgageLoan
});
return {
equity: equity,
propertiesEstimatedValue: propertiesEstimatedValue,
mortgageLoan: mortgageLoan
};
}
/**
* Calculate buying goal
* Buying Goal = Cash Available + Equity + Desired Loan Amount
*
* @param params.cashAvailable - Cash available
* @param params.equity - Equity
* @param params.desiredLoanAmount - Desired loan amount
* @returns Buying goal
*/
function calculateBuyingGoal(params) {
var cashAvailable = params.cashAvailable,
equity = params.equity,
desiredLoanAmount = params.desiredLoanAmount;
return Math.max(cashAvailable, 0) + Math.max(equity, 0) + Math.max(desiredLoanAmount, 0);
}
/**
* Calculate buying power
* Buying Power = Cash Available + Equity + Max Loan Amount.
*
* @param params.cashAvailable - Cash available
* @param params.equity - Equity
* @param params.maxLoanAmount - Max loan amount
* @returns Buying power
*/
function calculateBuyingPower(params) {
var cashAvailable = params.cashAvailable,
equity = params.equity,
maxLoanAmount = params.maxLoanAmount;
return Math.max(cashAvailable, 0) + Math.max(equity, 0) + Math.max(maxLoanAmount, 0);
}
exports.AUSTRALIAN_REVERSE_TAX_BRACKETS = AUSTRALIAN_REVERSE_TAX_BRACKETS;
exports.AUSTRALIAN_REVERSE_TAX_BRACKETS_WITHOUT_MEDICARE_LEVY = AUSTRALIAN_REVERSE_TAX_BRACKETS_WITHOUT_MEDICARE_LEVY;
exports.AUSTRALIAN_TAX_BRACKETS = AUSTRALIAN_TAX_BRACKETS;
exports.DEFAULT_ASSESSMENT_BUFFER_RATE = DEFAULT_ASSESSMENT_BUFFER_RATE;
exports.DEFAULT_HEM_BUFFER_RATE = DEFAULT_HEM_BUFFER_RATE;
exports.DEFAULT_INTEREST_RATE = DEFAULT_INTEREST_RATE;
exports.DEFAULT_LOAN_TERM = DEFAULT_LOAN_TERM;
exports.DEFAULT_SHADED_RENTAL_INCOME_RATIO = DEFAULT_SHADED_RENTAL_INCOME_RATIO;
exports.DEFAULT_SURPLUS_AVAILABLE_FOR_LOAN_RATE = DEFAULT_SURPLUS_AVAILABLE_FOR_LOAN_RATE;
exports.HEM_DATA = HEM_DATA;
exports.HEM_INCOME_BRACKETS = HEM_INCOME_BRACKETS;
exports.MEDICARE_LEVY_RATE = MEDICARE_LEVY_RATE;
exports.accountTypes = accountTypes;
exports.calculateBorrowCapacity = calculateBorrowCapacity;
exports.calculateBuyingGoal = calculateBuyingGoal;
exports.calculateBuyingPower = calculateBuyingPower;
exports.calculateCashAvailable = calculateCashAvailable;
exports.calculateEquity = calculateEquity;
exports.calculateEquityData = calculateEquityData;
exports.calculateExcessMonthlySurplus = calculateExcessMonthlySurplus;
exports.calculateHEMExpense = calculateHEMExpense;
exports.calculateLVR = calculateLVR;
exports.calculateLiability = calculateLiability;
exports.calculateLoanRepayment = calculateLoanRepayment;
exports.calculateMortgageLoan = calculateMortgageLoan;
exports.calculatePropertiesEstimatedValue = calculatePropertiesEstimatedValue;
exports.getLenderInfo = getLenderInfo;
exports.getLinkedPropertyAccountIds = getLinkedPropertyAccountIds;
exports.grossToNet = grossToNet;
exports.liabilityAccountTypes = liabilityAccountTypes;
exports.loanAccountTypes = loanAccountTypes;
exports.netToGross = netToGross;
exports.nonLiabilityAccountTypes = nonLiabilityAccountTypes;
exports.nonSuperannuationAccountTypes = nonSuperannuationAccountTypes;
exports.processScenarioHistoryData = processScenarioHistoryData;
//# sourceMappingURL=borrow-capacity-lib.cjs.development.js.map