Skip to content

Commit

Permalink
Tidy up of the SocialRent class (#28)
Browse files Browse the repository at this point in the history
* tidy up the class

* fix internal setting

* Grouped constants outside of the class definition

* Typos fix. SocialRentAdjusment type also fixed.

---------

Co-authored-by: Gabriele Granello <[email protected]>
  • Loading branch information
gabrielegranello and Gabriele Granello authored Jul 31, 2024
1 parent 82705f9 commit e047b55
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 205 deletions.
9 changes: 5 additions & 4 deletions app/models/Household.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FairholdLandPurchase } from "./tenure/FairholdLandPurchase";
import { FairholdLandRent } from "./tenure/FairholdLandRent";
import { Fairhold } from "./Fairhold";
import { Property } from "./Property";
import { SocialRent } from "./tenure/SocialRent";
import { SocialRent, socialRentAdjustmentTypes } from "./tenure/SocialRent";
import { ForecastParameters } from "./ForecastParameters";

export class Household {
Expand Down Expand Up @@ -39,7 +39,7 @@ export class Household {
incomePerPersonYearly: number;
averageRentYearly: number;
socialRentAverageEarning: number;
socialRentAdjustments: any;
socialRentAdjustments: socialRentAdjustmentTypes;
housePriceIndex: number;
gasBillYearly: number;
property: Property;
Expand Down Expand Up @@ -72,7 +72,7 @@ export class Household {
calculateTenures(
averageRentYearly: number,
socialRentAverageEarning: number,
socialRentAdjustments: any,
socialRentAdjustments: socialRentAdjustmentTypes,
housePriceIndex: number
) {
if (this.incomeYearly == undefined) throw new Error("income is undefined");
Expand Down Expand Up @@ -126,7 +126,8 @@ export class Household {
socialRentAverageEarning: socialRentAverageEarning,
socialRentAdjustments: socialRentAdjustments,
housePriceIndex: housePriceIndex,
property: this.property,
landToTotalRatio: this.property.landToTotalRatio,
numberOfBedrooms: this.property.numberOfBedrooms,
});

if (this.tenure.marketPurchase.affordability == undefined)
Expand Down
25 changes: 14 additions & 11 deletions app/models/Property.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@

import * as math from "mathjs";

import { BED_WEIGHTS_AND_CAPS } from "./constants";
/**
* Number of decimal places to use when rounding numerical values
*/
const PRECISION = 2
const PRECISION = 2;
const DEPRECIATION_FACTOR = -32938;

type PropertyParams = Pick<
Expand Down Expand Up @@ -90,18 +89,22 @@ export class Property {
return depreciatedBuildPrice;
}

private calculateBedWeightedAveragePrice(
beds: number[] = [0, 1, 2, 3, 4, 5, 6],
bedWeights: number[] = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4],
) {
private calculateBedWeightedAveragePrice() {
const beds = BED_WEIGHTS_AND_CAPS.numberOfBedrooms;
const bedWeights = BED_WEIGHTS_AND_CAPS.weight;
let bedWeight;

if (this.numberOfBedrooms < beds[beds.length - 1]) {
if (
this.numberOfBedrooms <
BED_WEIGHTS_AND_CAPS.numberOfBedrooms[
BED_WEIGHTS_AND_CAPS.numberOfBedrooms.length - 1
]
) {
// assign the weight based on the number of beds
bedWeight = bedWeights[this.numberOfBedrooms];
bedWeight = BED_WEIGHTS_AND_CAPS.weight[this.numberOfBedrooms];
} else {
// assign the last value if out of scale
bedWeight = bedWeights[bedWeights.length - 1];
bedWeight = BED_WEIGHTS_AND_CAPS.weight[bedWeights.length - 1];
}

bedWeight = parseFloat(
Expand All @@ -110,4 +113,4 @@ export class Property {

return bedWeight;
}
}
}
33 changes: 32 additions & 1 deletion app/models/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
export const MONTHS_PER_YEAR = 12;
export const MONTHS_PER_YEAR = 12;
export const WEEKS_PER_MONTH = 4.2;

export type bedWeightsAndCapsType = {
numberOfBedrooms: number[];
weight: number[];
socialRentCap: number[];
};

/**
* multiplying value weight and social rent cap for a property based on number of bed rooms
*/
export const BED_WEIGHTS_AND_CAPS: bedWeightsAndCapsType = {
numberOfBedrooms: [0, 1, 2, 3, 4, 5, 6],
weight: [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4],
socialRentCap: [155.73, 155.73, 164.87, 174.03, 183.18, 192.35, 201.5],
};

export type nationalAverageType = {
socialRentWeekly: number;
propertyValue: number;
earningsWeekly: number;
};

/**
* National average values
*/
export const NATIONAL_AVERAGES: nationalAverageType = {
socialRentWeekly: 54.62,
propertyValue: 49750,
earningsWeekly: 316.4,
};
192 changes: 88 additions & 104 deletions app/models/tenure/FairholdLandRent.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,91 @@
import { Fairhold } from "../Fairhold";
import { Mortgage } from "../Mortgage";
import { MONTHS_PER_YEAR } from "../constants";

interface FairholdLandRentParams {
averageRentYearly: number;
averagePrice: number;
newBuildPrice: number;
depreciatedBuildPrice: number;
landPrice: number;
incomeYearly: number;
affordabilityThresholdIncomePercentage: number;
propertyPriceGrowthPerYear: number;
constructionPriceGrowthPerYear: number;
yearsForecast: number;
maintenanceCostPercentage: number;
incomeGrowthPerYear: number;
rentGrowthPerYear: number;
fairhold: Fairhold;
}

export class FairholdLandRent {
depreciatedHouseMortgage?: Mortgage; // mortgage on the depreciated house
discountedLandRentMonthly?: number; // discounted land rent
lifetime?: {
maintenanceCost: number;
fairholdRentLand: number;
houseMortgagePaymentYearly: number;
}[]; // lifetime object with projections
constructor({
averageRentYearly, // average rent per year
averagePrice, // average price of the property
newBuildPrice, // new build price of the property
depreciatedBuildPrice, // depreciated building price
landPrice, // land price
incomeYearly, // yearly income per household
propertyPriceGrowthPerYear, // 5% per year
constructionPriceGrowthPerYear, // 2.5% per year
yearsForecast, // 40 years
maintenanceCostPercentage,
incomeGrowthPerYear, // 4% per year income growth
rentGrowthPerYear, // rent growth per year
}: {
averageRentYearly: number;
averagePrice: number;
newBuildPrice: number;
depreciatedBuildPrice: number;
landPrice: number;
incomeYearly: number;
affordabilityThresholdIncomePercentage: number;
propertyPriceGrowthPerYear: number;
constructionPriceGrowthPerYear: number;
yearsForecast: number;
maintenanceCostPercentage: number;
incomeGrowthPerYear: number;
rentGrowthPerYear: number;
fairhold: Fairhold;
}) {
this.calculateMortgage(depreciatedBuildPrice);
this.calculateLifetime(
averagePrice,
newBuildPrice,
landPrice,
incomeYearly,
averageRentYearly,
yearsForecast,
propertyPriceGrowthPerYear,
constructionPriceGrowthPerYear,
incomeGrowthPerYear,
rentGrowthPerYear,
maintenanceCostPercentage
);
}
type Lifetime = {
maintenanceCost: number;
fairholdRentLand: number;
houseMortgagePaymentYearly: number;
}[];

calculateMortgage(depreciatedBuildPrice: number) {
export class FairholdLandRent {
/** Mortgage on the depreaciated value of the house */
depreciatedHouseMortgage: Mortgage;
/** discounted value of the monthly land rent according to fairhold */
discountedLandRentMonthly: number;
/** lifetime projections of the tenure model */
lifetime: Lifetime;

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

this.discountedLandRentMonthly =
this.calculateDiscountedLandRentMonthly(params);
this.lifetime = this.calculateLifetime(params);
}

calculateLifetime(
averagePrice: number,
newBuildPrice: number,
landPrice: number,
incomeYearly: number,
averageRentYearly: number,
yearsForecast: number,
propertyPriceGrowthPerYear: number,
constructionPriceGrowthPerYear: number,
incomeGrowthPerYear: number,
rentGrowthPerYear: number,
maintenanceCostPercentage: number
) {
private calculateDiscountedLandRentMonthly({
incomeYearly,
averageRentYearly,
landPrice,
averagePrice,
}: FairholdLandRentParams) {
const marketRentAffordability = incomeYearly / averageRentYearly;
const landToTotalRatio = landPrice / averagePrice;
const averageRentLandMonthly =
(averageRentYearly / MONTHS_PER_YEAR) * landToTotalRatio;

const fairholdLandRent = new Fairhold({
affordability: marketRentAffordability,
landPriceOrRent: averageRentLandMonthly,
});
const discountedLandRentMonthly =
fairholdLandRent.calculateDiscountedPriceOrRent();

return discountedLandRentMonthly;
}
private calculateLifetime({
averagePrice,
newBuildPrice,
landPrice,
incomeYearly,
averageRentYearly,
yearsForecast,
propertyPriceGrowthPerYear,
constructionPriceGrowthPerYear,
incomeGrowthPerYear,
rentGrowthPerYear,
maintenanceCostPercentage,
}: FairholdLandRentParams) {
// initialize the variables that are going to be iterated
let averagePriceIterative = averagePrice;
let newBuildPriceIterative = newBuildPrice;
let landPriceIterative = landPrice;
let landToTotalRatioIterative = landPrice / averagePrice;
let incomeIterative = incomeYearly; // set the current income
let averageRentYearlyIterative = averageRentYearly; // yearly rent
let incomeIterative = incomeYearly;
let averageRentYearlyIterative = averageRentYearly;
let averageRentLandYearlyIterative =
averageRentYearlyIterative * landToTotalRatioIterative; // yearly rent for land
let affordabilityIterative = averageRentYearlyIterative / incomeIterative; // affordability
averageRentYearlyIterative * landToTotalRatioIterative;
let affordabilityIterative = averageRentYearlyIterative / incomeIterative;
let maintenanceCostIterative =
maintenanceCostPercentage * newBuildPriceIterative;

Expand All @@ -92,56 +95,37 @@ export class FairholdLandRent {
}).calculateDiscountedPriceOrRent(); // calculate the discounted land rent
this.discountedLandRentMonthly = fairholdRentLandIterative;

interface mortgageBreakdownTypes {
yearlyPayment: number;
cumulativePaid: number;
remainingBalance: number;
}

if (
this.depreciatedHouseMortgage === undefined ||
this.depreciatedHouseMortgage.yearlyPaymentBreakdown === undefined
) {
throw new Error("depreciatedHouseMortgage is undefined");
}

const houseMortgagePaymentYearly = this.depreciatedHouseMortgage
.yearlyPaymentBreakdown as mortgageBreakdownTypes[];
const houseMortgagePaymentYearly =
this.depreciatedHouseMortgage.yearlyPaymentBreakdown;
let houseMortgagePaymentYearlyIterative =
houseMortgagePaymentYearly[0].yearlyPayment; // find the first year

interface lifetimeTypes {
maintenanceCost: number;
fairholdRentLand: number;
houseMortgagePaymentYearly: number;
}

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

for (let i = 0; i < yearsForecast - 1; i++) {
averagePriceIterative =
averagePriceIterative * (1 + propertyPriceGrowthPerYear); // calculate the average price at a given year
averagePriceIterative * (1 + propertyPriceGrowthPerYear);
newBuildPriceIterative =
newBuildPriceIterative * (1 + constructionPriceGrowthPerYear); // calculate the new build price at a given year
landPriceIterative = averagePriceIterative - newBuildPriceIterative; // calculate the land price at agiven year
landToTotalRatioIterative = landPriceIterative / averagePriceIterative; // calculate the land to total ratio
incomeIterative = incomeIterative * (1 + incomeGrowthPerYear); // calculate the current income
newBuildPriceIterative * (1 + constructionPriceGrowthPerYear);
landPriceIterative = averagePriceIterative - newBuildPriceIterative;
landToTotalRatioIterative = landPriceIterative / averagePriceIterative;
incomeIterative = incomeIterative * (1 + incomeGrowthPerYear);
maintenanceCostIterative =
maintenanceCostPercentage * newBuildPriceIterative; // calculate the curretn maintenance cost
maintenanceCostPercentage * newBuildPriceIterative;

averageRentYearlyIterative =
averageRentYearlyIterative * (1 + rentGrowthPerYear); // calculate the current rent
averageRentYearlyIterative * (1 + rentGrowthPerYear);

averageRentLandYearlyIterative =
averageRentYearlyIterative * landToTotalRatioIterative; // yearly rent for land
averageRentYearlyIterative * landToTotalRatioIterative;

let affordabilityIterative = averageRentYearlyIterative / incomeIterative; // affordability
let affordabilityIterative = averageRentYearlyIterative / incomeIterative;

let fairholdRentLandIterative = new Fairhold({
affordability: affordabilityIterative,
Expand All @@ -161,6 +145,6 @@ export class FairholdLandRent {
houseMortgagePaymentYearly: houseMortgagePaymentYearlyIterative,
}); // add the current price to the new build price forecast
}
this.lifetime = lifetime; // save the object
return lifetime; // save the object
}
}
Loading

0 comments on commit e047b55

Please sign in to comment.