Skip to content

Commit

Permalink
Merge pull request #226 from theopensystemslab/oz/mortgage-fixes
Browse files Browse the repository at this point in the history
fix: Mortgage class & tests
  • Loading branch information
zz-hh-aa authored Jan 7, 2025
2 parents 447caf8 + 0315cb7 commit 13bd4de
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 47 deletions.
48 changes: 27 additions & 21 deletions app/models/Mortgage.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
import { Mortgage } from "./Mortgage";

const mortgage = new Mortgage({
propertyValue: 100000,
interestRate: 0.05,
mortgageTerm: 25,
initialDeposit: 0.1,
});

it("can be instantiated", () => {
const mortgage = new Mortgage({
propertyValue: 100,
interestRate: 0.05,
mortgageTerm: 25,
initialDeposit: 0.1,
});
expect(mortgage).toBeDefined();
});

it("correctly calculates the amount of the mortgage ", () => {
const mortgage = new Mortgage({
propertyValue: 100,
interestRate: 0.05,
mortgageTerm: 25,
initialDeposit: 0.1,
});

expect(mortgage.principal).toBeCloseTo(90);
expect(mortgage.principal).toBeCloseTo(90000);
});

it("correctly calculates the amount of monthly payment ", () => {
const mortgage = new Mortgage({
propertyValue: 100,
interestRate: 0.05,
mortgageTerm: 25,
initialDeposit: 0.1,
});
expect(mortgage.monthlyPayment).toBeCloseTo(0.53);
expect(mortgage.monthlyPayment).toBeCloseTo(526.13);
});

it("correctly calculates the total mortgage cost", () => {
expect(mortgage.totalMortgageCost).toBeCloseTo(157839.31);
})

it("correctly calculates the split between interest and principal", () => {
const breakdown = mortgage.yearlyPaymentBreakdown;

expect(breakdown[0].yearlyPayment).toBeCloseTo(16313.56); // Higher figure because it includes the deposit
expect(breakdown[0].cumulativePaid).toBeCloseTo(16313.56);
expect(breakdown[0].remainingBalance).toBeCloseTo(88144.3);

expect(breakdown.length).toBe(25);
})

it("correctly calculates the total interest paid", () => {
expect(mortgage.totalInterest).toBeCloseTo(67839.31)
})
92 changes: 66 additions & 26 deletions app/models/Mortgage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ interface MortgageParams {
}

type MortgageBreakdown = {
year: number;
yearlyPayment: number;
cumulativePaid: number;
cumulativeInterestPaid: number;
cumulativePrincipalPaid: number;
remainingBalance: number;
}[];

Expand All @@ -37,7 +40,7 @@ export class Mortgage {
*/
principal: number;
monthlyPayment: number;
/** This includes principal and interest */
/** This includes principal, monthly repayments and interest */
totalMortgageCost: number;
yearlyPaymentBreakdown: MortgageBreakdown;
totalInterest: number;
Expand Down Expand Up @@ -74,51 +77,88 @@ export class Mortgage {
monthlyInterestRate *
Math.pow(1 + monthlyInterestRate, numberOfPayments)) /
(Math.pow(1 + monthlyInterestRate, numberOfPayments) - 1);
monthlyPayment = parseFloat(monthlyPayment.toFixed(2));

const totalMortgageCost = monthlyPayment * numberOfPayments;

const totalMortgageCost = parseFloat((monthlyPayment * numberOfPayments).toFixed(2));

// Putting the rounding after the totalMortgageCost calculation for precision
monthlyPayment = parseFloat(monthlyPayment.toFixed(2));
return { monthlyPayment, totalMortgageCost };
}
private calculateYearlyPaymentBreakdown() {
let yearlyPayment =
this.initialDeposit * this.propertyValue +
this.monthlyPayment * MONTHS_PER_YEAR;
let cumulativePaid =
const monthlyRate = this.interestRate / MONTHS_PER_YEAR;

// Initial year (year 0) calculations
let yearlyPayment =
this.initialDeposit * this.propertyValue +
this.monthlyPayment * MONTHS_PER_YEAR;
let remainingBalance =
this.totalMortgageCost - this.monthlyPayment * MONTHS_PER_YEAR;
let cumulativePaid = yearlyPayment;

// Calculate first year's interest and principal
let balance = parseFloat(this.principal.toFixed(10)); // This method uses this throughout to ensure consistent precision, was hitting errors
let yearInterestPaid = 0;
let yearPrincipalPaid = 0;

// Calculate first year's monthly payments
for (let month = 0; month < MONTHS_PER_YEAR; month++) {
const monthlyInterest = parseFloat((balance * monthlyRate).toFixed(10));
const monthlyPrincipal = parseFloat((this.monthlyPayment - monthlyInterest).toFixed(10));

yearInterestPaid += monthlyInterest;
yearPrincipalPaid += monthlyPrincipal;
balance = parseFloat((balance - monthlyPrincipal).toFixed(10));
}

let cumulativeInterestPaid = yearInterestPaid;
let cumulativePrincipalPaid = yearPrincipalPaid;
let remainingBalance = balance;

const yearlyPaymentBreakdown: MortgageBreakdown = [
{
yearlyPayment: yearlyPayment,
cumulativePaid: cumulativePaid,
remainingBalance: remainingBalance,
year: 0,
yearlyPayment,
cumulativePaid,
cumulativeInterestPaid,
cumulativePrincipalPaid,
remainingBalance,
},
];
const isFinalYear = this.termYears - 2;
for (let i = 0; i < this.termYears - 1; i++) {
if (i == isFinalYear) {
yearlyPayment = remainingBalance;
continue;
}
yearlyPayment = this.monthlyPayment * MONTHS_PER_YEAR;

cumulativePaid = cumulativePaid + yearlyPayment;
remainingBalance = remainingBalance - yearlyPayment;
for (let i = 1; i < this.termYears; i++) {
yearlyPayment = this.monthlyPayment * MONTHS_PER_YEAR;
cumulativePaid += yearlyPayment;

yearInterestPaid = 0;
yearPrincipalPaid = 0;

// Calculate each month's breakdown
for (let month = 0; month < MONTHS_PER_YEAR; month++) {
const monthlyInterest = parseFloat((balance * monthlyRate).toFixed(10));
const monthlyPrincipal = parseFloat((this.monthlyPayment - monthlyInterest).toFixed(10));

yearInterestPaid += monthlyInterest;
yearPrincipalPaid += monthlyPrincipal;
balance = parseFloat((balance - monthlyPrincipal).toFixed(10));
}

cumulativeInterestPaid = parseFloat((cumulativeInterestPaid + yearInterestPaid).toFixed(10));
cumulativePrincipalPaid = parseFloat((cumulativePrincipalPaid + yearPrincipalPaid).toFixed(10));
i === (this.termYears - 1) ? remainingBalance = 0 : remainingBalance = balance;

yearlyPaymentBreakdown.push({
yearlyPayment: yearlyPayment,
cumulativePaid: cumulativePaid,
remainingBalance: remainingBalance,
year: i,
yearlyPayment,
cumulativePaid,
cumulativeInterestPaid,
cumulativePrincipalPaid,
remainingBalance,
});
}

return yearlyPaymentBreakdown;
}

private calculateTotalInterest() {
const totalInterest = parseFloat((this.principal * this.interestRate * this.termYears).toFixed(2))
const totalInterest = parseFloat((this.totalMortgageCost - this.principal).toFixed(2))
return totalInterest
}
}

0 comments on commit 13bd4de

Please sign in to comment.