Skip to content

Commit

Permalink
feat: Improve types in Mortgage and FairholdLandPurchase
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr committed Jul 26, 2024
1 parent bb3cbaf commit 4798881
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 133 deletions.
6 changes: 3 additions & 3 deletions app/models/Mortgage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ it("can be instantiated", () => {
const mortgage = new Mortgage({
propertyValue: 100,
interestRate: 0.05,
termOfTheMortgage: 25,
mortgageTerm: 25,
initialDeposit: 0.1,
});
expect(mortgage).toBeDefined();
Expand All @@ -14,7 +14,7 @@ it("correctly calculates the amount of the mortgage ", () => {
const mortgage = new Mortgage({
propertyValue: 100,
interestRate: 0.05,
termOfTheMortgage: 25,
mortgageTerm: 25,
initialDeposit: 0.1,
});

Expand All @@ -25,7 +25,7 @@ it("correctly calculates the amount of monthly payment ", () => {
const mortgage = new Mortgage({
propertyValue: 100,
interestRate: 0.05,
termOfTheMortgage: 25,
mortgageTerm: 25,
initialDeposit: 0.1,
});
expect(mortgage.monthlyPayment).toBeCloseTo(0.53);
Expand Down
138 changes: 79 additions & 59 deletions app/models/Mortgage.ts
Original file line number Diff line number Diff line change
@@ -1,95 +1,115 @@
import { MONTHS_PER_YEAR } from "./constants";

const DEFAULT_INTEREST_RATE = 0.06;
const DEFAULT_MORTGAGE_TERM = 30;
const DEFAULT_INITIAL_DEPOSIT = 0.15;

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

export class Mortgage {
propertyValue: number; //value of the property for the mortgage
interestRate: number; // interest rate of the mortgage in percentage e.r, 0.05=5%
termYears: number; // number of years of the mortgage
initialDeposit: number; // initial deposit of the value of the mortgage in percentage e.g. 0.15 =15% deposit
principal?: number; // amount of the morgage requested
monthlyPayment?: number; // monthly rate of the mortgage
totalMortgageCost?: number; // total cost of the mortgage
yearlyPaymentBreakdown?: {
yearlyPayment: number;
cumulativePaid: number;
remainingBalance: number;
}[]; // yearly breakdown of the mortgage
propertyValue: number;
/**
* This value is given as a percentage. For example, 0.05 represents a 5% rate
*/
interestRate: number;
termYears: number;
/**
* This value is given as a percentage. For example, 0.15 represents a 15% deposit
*/
initialDeposit: number;
/**
* The principle is the value of the property, minus the deposit
*/
principal: number;
monthlyPayment: number;
totalMortgageCost: number;
yearlyPaymentBreakdown: MortgageBreakdown;

constructor({
propertyValue,
interestRate = 0.06,
termOfTheMortgage = 30,
initialDeposit = 0.15,
interestRate = DEFAULT_INTEREST_RATE,
mortgageTerm = DEFAULT_MORTGAGE_TERM,
initialDeposit = DEFAULT_INITIAL_DEPOSIT,
}: {
propertyValue: number;
interestRate?: number;
termOfTheMortgage?: number;
mortgageTerm?: number;
initialDeposit?: number;
}) {
this.propertyValue = propertyValue;
this.initialDeposit = initialDeposit;
this.interestRate = interestRate;
this.termYears = termOfTheMortgage;
this.calculateAmountOfTheMortgage(); // calculate the amount of the mortgage
this.calculateMonthlyMortgagePayment(); // calculate the montly payment
this.calculateYearlyPaymentBreakdown(); // calculate the yearly breakdown;
}
this.termYears = mortgageTerm;
this.principal = this.calculateMortgagePrinciple();

const { monthlyPayment, totalMortgageCost } =
this.calculateMonthlyMortgagePayment();
this.monthlyPayment = monthlyPayment;
this.totalMortgageCost = totalMortgageCost;

calculateAmountOfTheMortgage() {
this.principal = this.propertyValue * (1 - this.initialDeposit); // calculate the amount of the mortgage by removing the deposit
return this.principal;
this.yearlyPaymentBreakdown = this.calculateYearlyPaymentBreakdown();
}

calculateMonthlyMortgagePayment() {
const monthlyInterestRate = this.interestRate / 12; // Convert annual interest rate to monthly rate
const numberOfPayments = this.termYears * 12; // Convert term in years to total number of payments
if (this.principal !== undefined) {
const monthlyPayment =
(this.principal *
monthlyInterestRate *
Math.pow(1 + monthlyInterestRate, numberOfPayments)) /
(Math.pow(1 + monthlyInterestRate, numberOfPayments) - 1); // Calculate the monthly payment
this.monthlyPayment = parseFloat(monthlyPayment.toFixed(2)); // Store monthly payment rounded to 2 decimal places in class property
this.totalMortgageCost = this.monthlyPayment * numberOfPayments; // total cost of the mortgage
return this.monthlyPayment;
} else {
throw new Error("amountOfTheMortgage is undefined");
}
private calculateMortgagePrinciple() {
const principal = this.propertyValue * (1 - this.initialDeposit);
return principal;
}
calculateYearlyPaymentBreakdown() {
if (this.monthlyPayment == undefined || this.totalMortgageCost == undefined)
throw new Error("monthlyPayment or totalMortgageCost is undefined");

private calculateMonthlyMortgagePayment() {
const monthlyInterestRate = this.interestRate / MONTHS_PER_YEAR;
const numberOfPayments = this.termYears * MONTHS_PER_YEAR;

let monthlyPayment =
(this.principal *
monthlyInterestRate *
Math.pow(1 + monthlyInterestRate, numberOfPayments)) /
(Math.pow(1 + monthlyInterestRate, numberOfPayments) - 1);
monthlyPayment = parseFloat(monthlyPayment.toFixed(2));

const totalMortgageCost = monthlyPayment * numberOfPayments;

return { monthlyPayment, totalMortgageCost };
}
private calculateYearlyPaymentBreakdown() {
let yearlyPayment =
this.initialDeposit * this.propertyValue + this.monthlyPayment * 12;
this.initialDeposit * this.propertyValue +
this.monthlyPayment * MONTHS_PER_YEAR;
let cumulativePaid =
this.initialDeposit * this.propertyValue + this.monthlyPayment * 12;
let remainingBalance = this.totalMortgageCost - this.monthlyPayment * 12;
this.initialDeposit * this.propertyValue +
this.monthlyPayment * MONTHS_PER_YEAR;
let remainingBalance =
this.totalMortgageCost - this.monthlyPayment * MONTHS_PER_YEAR;

interface mortgageBreakdownTypes {
yearlyPayment: number;
cumulativePaid: number;
remainingBalance: number;
}
let yearlyPaymentBreakdown: mortgageBreakdownTypes[] = [
let yearlyPaymentBreakdown: MortgageBreakdown = [
{
yearlyPayment: yearlyPayment,
cumulativePaid: cumulativePaid,
remainingBalance: remainingBalance,
},
]; // initialize the yearlyPaymentBreakdown
];

for (let i = 0; i < this.termYears - 1; i++) {
if (i != this.termYears - 1) {
yearlyPayment = this.monthlyPayment * 12; // calculate the yearly payment
yearlyPayment = this.monthlyPayment * MONTHS_PER_YEAR;
} else {
yearlyPayment = remainingBalance; // last year just pay the remaining balance
// last year just pay the remaining balance
yearlyPayment = remainingBalance;
}

cumulativePaid = cumulativePaid + yearlyPayment; // calculate the updated cumulative paid
remainingBalance = remainingBalance - yearlyPayment; // calculate the updated remaining balance
cumulativePaid = cumulativePaid + yearlyPayment;
remainingBalance = remainingBalance - yearlyPayment;

yearlyPaymentBreakdown.push({
yearlyPayment: yearlyPayment,
cumulativePaid: cumulativePaid,
remainingBalance: remainingBalance,
}); // add the current yearly payment to the yearlyPaymentBreakdown
});
}
this.yearlyPaymentBreakdown = yearlyPaymentBreakdown; // set the yearlyPaymentBreakdown

return yearlyPaymentBreakdown;
}
}
}
1 change: 1 addition & 0 deletions app/models/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MONTHS_PER_YEAR = 12;
119 changes: 48 additions & 71 deletions app/models/tenure/FairholdLandPurchase.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { Fairhold } from "../Fairhold";
import { Mortgage } from "../Mortgage";

type Lifetime = {
maintenanceCost: number;
landMortgagePaymentYearly: number;
houseMortgagePaymentYearly: number;
}[];

export class FairholdLandPurchase {
discountedLandPrice?: number;
discountedLandMortgage?: Mortgage;
depreciatedHouseMortgage?: Mortgage;
lifetime?: {
maintenanceCost: number;
landMortgagePaymentYearly: number;
houseMortgagePaymentYearly: number;
}[];
discountedLandPrice: number;
discountedLandMortgage: Mortgage;
depreciatedHouseMortgage: Mortgage;
lifetime: Lifetime;

constructor({
newBuildPrice, // new build price of the property
depreciatedBuildPrice, // depreciated building price
constructionPriceGrowthPerYear, // construction price growth per year
yearsForecast, // years forecast
maintenanceCostPercentage, // maintenance cost percentage
newBuildPrice,
depreciatedBuildPrice,
constructionPriceGrowthPerYear,
yearsForecast,
maintenanceCostPercentage,
fairhold,
}: {
newBuildPrice: number;
Expand All @@ -27,97 +30,70 @@ export class FairholdLandPurchase {
affordability: number;
fairhold: Fairhold;
}) {
this.calculateFairholdDiscount(fairhold); // calculate the fairhold discountLand
this.calculateMortgage(depreciatedBuildPrice); // calculate the mortgage
this.calculateLifetime(
newBuildPrice,
maintenanceCostPercentage,
yearsForecast,
constructionPriceGrowthPerYear
); // calculate the lifetime
}

calculateFairholdDiscount(fairhold: Fairhold) {
let discountedLandPrice = fairhold.calculateDiscountedPriceOrRent(); // calculate the discounted land price
this.discountedLandPrice = discountedLandPrice; // discounted land price
}
this.discountedLandPrice = fairhold.calculateDiscountedPriceOrRent();

calculateMortgage(depreciatedBuildPrice: number) {
if (this.discountedLandPrice == undefined) {
throw new Error("discountedLandPrice is not defined");
}
this.discountedLandMortgage = new Mortgage({
propertyValue: this.discountedLandPrice,
});

this.depreciatedHouseMortgage = new Mortgage({
propertyValue: depreciatedBuildPrice,
});

this.lifetime = this.calculateLifetime(
newBuildPrice,
maintenanceCostPercentage,
yearsForecast,
constructionPriceGrowthPerYear
);
}

calculateLifetime(
private calculateLifetime(
newBuildPrice: number,
maintenanceCostPercentage: number,
yearsForecast: number,
constructionPriceGrowthPerYear: number
) {
let newBuildPriceIterative = newBuildPrice;
let maintenanceCostIterative = maintenanceCostPercentage * newBuildPrice;
// retrieve the mortgage payments for the first year
if (
this.depreciatedHouseMortgage === undefined ||
this.depreciatedHouseMortgage.yearlyPaymentBreakdown === undefined
) {
throw new Error("depreciatedHouseMortgage is undefined");
}

interface mortgageBreakdownTypes {
yearlyPayment: number;
cumulativePaid: number;
remainingBalance: number;
}
const houseMortgagePaymentYearly = this.depreciatedHouseMortgage
.yearlyPaymentBreakdown as mortgageBreakdownTypes[];
let houseMortgagePaymentYearlyIterative =
houseMortgagePaymentYearly[0].yearlyPayment; // find the first year
const houseMortgagePaymentYearly =
this.depreciatedHouseMortgage.yearlyPaymentBreakdown;

if (
this.discountedLandMortgage === undefined ||
this.discountedLandMortgage.yearlyPaymentBreakdown === undefined
) {
throw new Error("depreciatedHouseMortgage is undefined");
}
// find the first year
let houseMortgagePaymentYearlyIterative =
houseMortgagePaymentYearly[0].yearlyPayment;

const landMortgagePaymentYearly = this.discountedLandMortgage
.yearlyPaymentBreakdown as mortgageBreakdownTypes[];
const landMortgagePaymentYearly =
this.discountedLandMortgage.yearlyPaymentBreakdown;

// find the first year
let landMortgagePaymentYearlyIterative =
landMortgagePaymentYearly[0].yearlyPayment; // find the first year

interface lifetimeTypes {
maintenanceCost: number;
landMortgagePaymentYearly: number;
houseMortgagePaymentYearly: number;
}
landMortgagePaymentYearly[0].yearlyPayment;

let lifetime: lifetimeTypes[] = [
let lifetime: Lifetime = [
{
maintenanceCost: maintenanceCostIterative,
landMortgagePaymentYearly: landMortgagePaymentYearlyIterative,
houseMortgagePaymentYearly: houseMortgagePaymentYearlyIterative,
},
]; // initialize the forecast
];

for (let i = 0; i < yearsForecast - 1; i++) {
// calculate the new build price at a given year
newBuildPriceIterative =
newBuildPriceIterative * (1 + constructionPriceGrowthPerYear); // calculate the new build price at a given year
newBuildPriceIterative * (1 + constructionPriceGrowthPerYear);
// set the current maintenance cost
maintenanceCostIterative =
newBuildPriceIterative * maintenanceCostPercentage; // set the current maintenance cost
newBuildPriceIterative * maintenanceCostPercentage;

if (i < houseMortgagePaymentYearly.length - 1) {
// find the first year
houseMortgagePaymentYearlyIterative =
houseMortgagePaymentYearly[i + 1].yearlyPayment; // find the first year
houseMortgagePaymentYearly[i + 1].yearlyPayment;
// find the first year
landMortgagePaymentYearlyIterative =
landMortgagePaymentYearly[i + 1].yearlyPayment; // find the first year
landMortgagePaymentYearly[i + 1].yearlyPayment;
} else {
houseMortgagePaymentYearlyIterative = 0;
landMortgagePaymentYearlyIterative = 0;
Expand All @@ -127,8 +103,9 @@ export class FairholdLandPurchase {
maintenanceCost: maintenanceCostIterative,
landMortgagePaymentYearly: landMortgagePaymentYearlyIterative,
houseMortgagePaymentYearly: houseMortgagePaymentYearlyIterative,
}); // add the current price to the new build price forecast
});
}
this.lifetime = lifetime; // save the object

return lifetime;
}
}

0 comments on commit 4798881

Please sign in to comment.