From 808b99a1e2c2015ac38f1512585858f945e34468 Mon Sep 17 00:00:00 2001 From: zzhhaa Date: Wed, 27 Nov 2024 15:01:30 +0100 Subject: [PATCH] docs: improve tsdoc comments for documentation clarity --- app/models/Fairhold.ts | 21 +++++++++++++++++++++ app/models/ForecastParameters.ts | 23 ++++++++++++++--------- app/models/Household.ts | 12 +++++++----- app/models/Lifetime.ts | 12 ++++++++---- app/models/Mortgage.ts | 4 ++++ app/models/Property.ts | 5 +++-- app/models/constants.ts | 8 ++++++-- app/models/tenure/FairholdLandPurchase.ts | 6 +++++- app/models/tenure/FairholdLandRent.ts | 10 +++++++--- app/models/tenure/MarketPurchase.ts | 3 +++ app/models/tenure/MarketRent.ts | 1 + app/models/tenure/SocialRent.ts | 6 +++--- 12 files changed, 82 insertions(+), 29 deletions(-) diff --git a/app/models/Fairhold.ts b/app/models/Fairhold.ts index e137edab..66401b20 100644 --- a/app/models/Fairhold.ts +++ b/app/models/Fairhold.ts @@ -7,10 +7,25 @@ const PLATEAU = 0.15 type ConstructorParams = Pick; +/** The Fairhold class is a generic class + * (meaning it will calculate a discount regardless if it is given a purchase price or monthly rent). + * It is instantiated twice, + * once each for `FairholdLandPurchase` and `FairholdLandRent` + */ export class Fairhold { + /** Affordability is calculated as monthly housing cost / GDHI */ public affordability: number; + /** Depending on whether calculating `FairholdLandPurchase` or `FairholdLandRent`, + * pass the relevant figure (open market residual land value or estimated portion of monthly market rent + * that goes towards location) + */ public landPriceOrRent: number; + /** The Fairhold discount is a multiplier, so Fairhold prices are `discountLand * open market value` + */ public discountLand: number; + /** When the class is instantiated for `FairholdLandPurchase`, this is the discounted up-front land purchase price; + * when instantiated for `FairholdLandPurchase`, this is the discounted monthly community ground rent + */ public discountedLandPriceOrRent: number; constructor(params: ConstructorParams) { @@ -20,11 +35,17 @@ export class Fairhold { this.discountedLandPriceOrRent = this.calculateDiscountedPriceOrRent(); } + /** Our formula is linear; the more expensive an area is, the lower the generated price multiplier is; + * it plateaus at .15 of market rate + */ private calculateFairholdDiscount() { const discountLand = math.max(MULTIPLIER * this.affordability + OFFSET, PLATEAU) return discountLand; } + /** Multiplies market land price or rent by the discountLand multiplier; + * in the event that land values are effectively negative, land price will be £1 + */ private calculateDiscountedPriceOrRent() { if (this.landPriceOrRent < 0) { // TODO: Set a nominal value (Check with Ollie) diff --git a/app/models/ForecastParameters.ts b/app/models/ForecastParameters.ts index d21a2c60..ea4a2061 100644 --- a/app/models/ForecastParameters.ts +++ b/app/models/ForecastParameters.ts @@ -8,20 +8,25 @@ export interface ForecastParameters { affordabilityThresholdIncomePercentage: number; } +/** Parameters for forecasting changing costs over time, + * all values except years are percentages represented in decimal form + */ export const DEFAULT_FORECAST_PARAMETERS: ForecastParameters = { - maintenancePercentage: 0.02, // percentage maintenance cost - incomeGrowthPerYear: 0.04, // 4% income growth per year - constructionPriceGrowthPerYear: 0.025, // 2.5% - rentGrowthPerYear: 0.03, // 3% - propertyPriceGrowthPerYear: 0.05, // 5% - yearsForecast: 40, // 40 years - affordabilityThresholdIncomePercentage: 0.35, // percentage of income to afford rent or purchase + maintenancePercentage: 0.02, + incomeGrowthPerYear: 0.04, + constructionPriceGrowthPerYear: 0.025, + rentGrowthPerYear: 0.03, + propertyPriceGrowthPerYear: 0.05, + /** The number of years to forecast values over */ + yearsForecast: 40, + /** The threshold of GDHI at which housing is no longer considered affordable */ + affordabilityThresholdIncomePercentage: 0.35, } as const; /** * Creates forecast parameters - * @param maintenancePercentage - Maintenance spend value from form (0.015 | 0.02 | 0.0375) - * @returns ForecastParameters with updated maintenance cost + * @param maintenancePercentage - Maintenance spend value, user input from form + * @returns ForecastParameters with updated maintenance spend (overwrites default) */ export function createForecastParameters(maintenancePercentage: number): ForecastParameters { diff --git a/app/models/Household.ts b/app/models/Household.ts index 592e0227..5d1e90e5 100644 --- a/app/models/Household.ts +++ b/app/models/Household.ts @@ -9,6 +9,7 @@ import { ForecastParameters } from "./ForecastParameters"; import { socialRentAdjustmentTypes } from "../data/socialRentAdjustmentsRepo"; import { Lifetime, LifetimeParams } from "./Lifetime"; +/** Assumed number of heads per-house */ const HOUSE_MULTIPLIER = 2.4; type ConstructorParams = Pick< @@ -21,6 +22,7 @@ type ConstructorParams = Pick< housePriceIndex: number; }; +/** The 'parent' class; when instantiated, it instantiates all other relevant classes, including `Property` */ export class Household { public incomePerPersonYearly: number; public gasBillYearly: number; @@ -93,17 +95,17 @@ export class Household { const fairholdLandRent = new FairholdLandRent({ averageRentYearly: averageRentYearly, - averagePrice: this.property.averageMarketPrice, // average price of the property + averagePrice: this.property.averageMarketPrice, newBuildPrice: this.property.newBuildPrice, - depreciatedBuildPrice: this.property.depreciatedBuildPrice, // depreciated building price - landPrice: this.property.landPrice, // land price - incomeYearly: this.incomeYearly, // income + depreciatedBuildPrice: this.property.depreciatedBuildPrice, + landPrice: this.property.landPrice, + incomeYearly: this.incomeYearly, forecastParameters: this.forecastParameters, fairhold: new Fairhold({ affordability: marketRent.affordability, landPriceOrRent: averageRentYearly, - }), // fairhold object + }), marketPurchase: marketPurchase }); diff --git a/app/models/Lifetime.ts b/app/models/Lifetime.ts index e187217a..523050f8 100644 --- a/app/models/Lifetime.ts +++ b/app/models/Lifetime.ts @@ -1,4 +1,3 @@ -// imports (eg constants) import { MarketPurchase } from "./tenure/MarketPurchase"; import { MarketRent } from "./tenure/MarketRent"; import { FairholdLandPurchase } from "./tenure/FairholdLandPurchase"; @@ -7,7 +6,6 @@ import { Fairhold } from "./Fairhold"; import { Property } from "./Property"; import { MONTHS_PER_YEAR } from "./constants"; -// interfaces and types export interface LifetimeParams { marketPurchase: MarketPurchase; marketRent: MarketRent; @@ -39,14 +37,20 @@ export interface LifetimeData { // gasBillYearly: number; [key: number]: number; } - +/** The `Lifetime` class calculates yearly spend on housing over a lifetime (set by `yearsForecast`). + * Instead of storing lifetime data within each tenure class itself, + * `Lifetime` is stored in its own class (to prevent excess duplication of properties like `incomeYearly`). + */ export class Lifetime { public lifetimeData: LifetimeData[]; constructor(params: LifetimeParams) { this.lifetimeData = this.calculateLifetime(params); } - + + /** The function loops through and calculates all values for period set by yearsForecast, + * pushing the results to the lifetime array (one object per-year) + */ private calculateLifetime(params: LifetimeParams): LifetimeData[] { const lifetime: LifetimeData[] = []; diff --git a/app/models/Mortgage.ts b/app/models/Mortgage.ts index a16abbe2..3effa7d6 100644 --- a/app/models/Mortgage.ts +++ b/app/models/Mortgage.ts @@ -17,6 +17,9 @@ type MortgageBreakdown = { remainingBalance: number; }[]; +/** The `Mortgage` class is instantiated each time a mortgage needs to be calculated, + * meaning per-type of property, eg land or house, per-tenure + */ export class Mortgage { propertyValue: number; /** @@ -33,6 +36,7 @@ export class Mortgage { */ principal: number; monthlyPayment: number; + /** This includes principal and interest */ totalMortgageCost: number; yearlyPaymentBreakdown: MortgageBreakdown; totalInterest: number; diff --git a/app/models/Property.ts b/app/models/Property.ts index 7d926f0c..4a5a5dfb 100644 --- a/app/models/Property.ts +++ b/app/models/Property.ts @@ -31,7 +31,7 @@ export class Property { numberOfBedrooms: number; age: number; /** - * Size of the house in squares meters + * Size of the house in square meters */ size: number; maintenancePercentage: MaintenancePercentage; @@ -42,7 +42,8 @@ export class Property { averageMarketPrice: number; itl3: string; /** - * Price of the house if it was new + * Price of the house if it was new, used for residual land value calculations + * and for valuing the house alone in marketPurchase */ newBuildPrice: number; /** diff --git a/app/models/constants.ts b/app/models/constants.ts index 62f3ffab..8e0966d4 100644 --- a/app/models/constants.ts +++ b/app/models/constants.ts @@ -8,7 +8,7 @@ export type bedWeightsAndCapsType = { }; /** - * multiplying value weight and social rent cap for a property based on number of bed rooms + * This is used to weight social rent values by property size based on number of bedrooms */ export const BED_WEIGHTS_AND_CAPS: bedWeightsAndCapsType = { numberOfBedrooms: [0, 1, 2, 3, 4, 5, 6], @@ -23,7 +23,7 @@ export type nationalAverageType = { }; /** - * National average values + * National averages from 1999 and 2000 (from MHCLG), used as inputs for calculating social rent */ export const NATIONAL_AVERAGES: nationalAverageType = { socialRentWeekly: 54.62, @@ -31,4 +31,8 @@ export const NATIONAL_AVERAGES: nationalAverageType = { earningsWeekly: 316.4, }; +/** + * Maintenance levels are percentages (represented as decimals), + * figures from our own model + */ export const MAINTENANCE_LEVELS = [0.015, 0.02, 0.0375] as const; diff --git a/app/models/tenure/FairholdLandPurchase.ts b/app/models/tenure/FairholdLandPurchase.ts index b6e41005..93c905b5 100644 --- a/app/models/tenure/FairholdLandPurchase.ts +++ b/app/models/tenure/FairholdLandPurchase.ts @@ -12,13 +12,17 @@ interface FairholdLandPurchaseParams { marketPurchase: MarketPurchase; } +/** `FairholdLandPurchase` needs different params to FairholdLandRent, + * which is why they are separate classes that both instantiate an instance of Fairhold. + * Where `FairholdLandRent` uses other classes (eg `MarketPurchase`, `ForecastParameters`), they are passed in*/ export class FairholdLandPurchase { params: FairholdLandPurchaseParams; discountedLandPrice: number; discountedLandMortgage: Mortgage; depreciatedHouseMortgage: Mortgage; + /** All tenure classes have an `interestPaid` property, summed from `Mortgage.totalInterest` (if more than one `Mortgage` class) */ interestPaid: number; - /** interest saved relative to market purchase, pounds */ + /** Interest saved relative to `MarketPurchase`, pounds */ interestSaved: number; constructor(params: FairholdLandPurchaseParams) { diff --git a/app/models/tenure/FairholdLandRent.ts b/app/models/tenure/FairholdLandRent.ts index 985ce515..3b18bd04 100644 --- a/app/models/tenure/FairholdLandRent.ts +++ b/app/models/tenure/FairholdLandRent.ts @@ -16,14 +16,16 @@ interface FairholdLandRentParams { marketPurchase: MarketPurchase; } +/** `FairholdLandRent` needs different params to `FairholdLandPurchase`, + * which is why they are separate classes that both instantiate an instance of `Fairhold`. + * Where `FairholdLandRent` uses other classes (eg `MarketPurchase`, `ForecastParameters`), they are passed in*/ export class FairholdLandRent { params: FairholdLandRentParams; - /** Mortgage on the depreciated value of the house */ depreciatedHouseMortgage: Mortgage; - /** discounted value of the monthly land rent according to fairhold */ discountedLandRentMonthly: number; + /** All tenure classes have an `interestPaid` property, summed from `Mortgage.totalInterest` (if more than one `Mortgage` class) */ interestPaid: number; - /** interest saved relative to market purchase, pounds */ + /** Interest saved relative to `MarketPurchase`, pounds */ interestSaved: number; constructor(params: FairholdLandRentParams) { @@ -49,6 +51,8 @@ export class FairholdLandRent { averagePrice, }: FairholdLandRentParams) { const marketRentAffordability = incomeYearly / averageRentYearly; + /*TODO: landToTotalRatio is calculated elsewhere too, eg when instantiating socialRent in Property... + can we just calculate it once?*/ const landToTotalRatio = landPrice / averagePrice; const averageRentLandMonthly = (averageRentYearly / MONTHS_PER_YEAR) * landToTotalRatio; diff --git a/app/models/tenure/MarketPurchase.ts b/app/models/tenure/MarketPurchase.ts index 828c4414..e1f0df93 100644 --- a/app/models/tenure/MarketPurchase.ts +++ b/app/models/tenure/MarketPurchase.ts @@ -11,11 +11,14 @@ interface MarketPurchaseParams { forecastParameters: ForecastParameters; } +// TODO: decide on language to use (eg freeholdPurchase?) export class MarketPurchase { params: MarketPurchaseParams; + /** Uses the summed mortgages to calculate percentage of GDHI spent on housing monthly */ public affordability: number; public houseMortgage: Mortgage; public landMortgage: Mortgage; + /** All tenure classes have an `interestPaid` property, summed from `Mortgage.totalInterest` (if more than one Mortgage class) */ public interestPaid: number; constructor(params: MarketPurchaseParams) { diff --git a/app/models/tenure/MarketRent.ts b/app/models/tenure/MarketRent.ts index 64e963d1..2a546ae9 100644 --- a/app/models/tenure/MarketRent.ts +++ b/app/models/tenure/MarketRent.ts @@ -43,6 +43,7 @@ export class MarketRent { averageRentYearly, }: MarketRentParams) { const averageRentMonthly = averageRentYearly / MONTHS_PER_YEAR; + // TODO: landToTotalRatio is calculated multiple times in multiple places, can we just do it once? const landToTotalRatio = landPrice / averagePrice; const averageRentLandMonthly = averageRentMonthly * landToTotalRatio; const averageRentHouseMonthly = averageRentMonthly - averageRentLandMonthly; diff --git a/app/models/tenure/SocialRent.ts b/app/models/tenure/SocialRent.ts index 1bf2d57e..b37a22ce 100644 --- a/app/models/tenure/SocialRent.ts +++ b/app/models/tenure/SocialRent.ts @@ -15,9 +15,9 @@ export class SocialRent { /** adjustment factors that take into account the increase of living cost */ socialRentAdjustments; housePriceIndex; - adjustedSocialRentMonthly: number; //adjusted social rent monthly - socialRentMonthlyLand: number; // social rent to pay the land - socialRentMonthlyHouse: number; // social rent monthly House + adjustedSocialRentMonthly: number; + socialRentMonthlyLand: number; + socialRentMonthlyHouse: number; constructor(params: SocialRentParams) { this.socialRentAverageEarning = params.socialRentAverageEarning; this.socialRentAdjustments = params.socialRentAdjustments;