Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: improve tsdoc comments for documentation clarity #148

Merged
merged 5 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app/models/Fairhold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,26 @@ const PLATEAU = 0.15

type ConstructorParams = Pick<Fairhold, "affordability" | "landPriceOrRent">;

/** The Fairhold class is a generic class
* (meaning it will calculate a discount regardless if it is given a purchase price or monthly rent).
Comment on lines +10 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** The Fairhold class is a generic class
* (meaning it will calculate a discount regardless if it is given a purchase price or monthly rent).
/**
* The Fairhold class is a generic class
* (meaning it will calculate a discount regardless if it is given a purchase price or monthly rent).

nit: JSDocs in blocks should have just /** as the first line. It's not broken and it does work, but as it's breaking conventions it feels a bit harder to read / unexpected. This applies multiple times throughout the PR below also.

/** This is the right notation for a single line though 👍 */

Docs: https://jsdoc.app/about-getting-started

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for letting me know, super useful for future reference! Have now fixed this, I believe :)

* 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) {
Expand All @@ -20,11 +36,19 @@ 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)
Expand Down
24 changes: 15 additions & 9 deletions app/models/ForecastParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,26 @@ 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 {

Expand Down
16 changes: 9 additions & 7 deletions app/models/Household.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ForecastParameters } from "./ForecastParameters";
import { socialRentAdjustmentTypes } from "../data/socialRentAdjustmentsRepo";
import { Lifetime, LifetimeParams } from "./Lifetime";

const HOUSE_MULTIPLIER = 2.4;
const HEADS_PER_HOUSEHOLD = 2.4;

type ConstructorParams = Pick<
Household,
Expand All @@ -18,9 +18,11 @@ type ConstructorParams = Pick<
averageRentYearly: number;
socialRentAverageEarning: number;
socialRentAdjustments: socialRentAdjustmentTypes;
/** The housePriceIndex shown here is used to calculate relative property values and should be from 1999. */
housePriceIndex: number;
};

/** The 'parent' class; when instantiated, it instantiates all other relevant classes, including `Property` */
export class Household {
public incomePerPersonYearly: number;
public gasBillYearly: number;
Expand All @@ -41,7 +43,7 @@ export class Household {
this.gasBillYearly = params.gasBillYearly;
this.property = params.property;
this.forecastParameters = params.forecastParameters;
this.incomeYearly = HOUSE_MULTIPLIER * params.incomePerPersonYearly;
this.incomeYearly = HEADS_PER_HOUSEHOLD * params.incomePerPersonYearly;
this.tenure = this.calculateTenures(params);
this.lifetime = this.calculateLifetime(params);
}
Expand Down Expand Up @@ -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
});
Expand Down
16 changes: 12 additions & 4 deletions app/models/Lifetime.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// imports (eg constants)
import { MarketPurchase } from "./tenure/MarketPurchase";
import { MarketRent } from "./tenure/MarketRent";
import { FairholdLandPurchase } from "./tenure/FairholdLandPurchase";
Expand All @@ -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;
Expand Down Expand Up @@ -39,14 +37,22 @@ 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[] = [];

Expand Down Expand Up @@ -112,11 +118,13 @@ export class Lifetime {
maintenanceCostIterative =
newBuildPriceIterative * params.maintenancePercentage;

// If the mortgage term ongoing (if `i` is less than the term), calculate yearly mortgage payments
if (i < params.marketPurchase.houseMortgage.termYears - 1) {
newbuildHouseMortgageYearlyIterative = params.marketPurchase.houseMortgage.yearlyPaymentBreakdown[i].yearlyPayment;
depreciatedHouseMortgageYearlyIterative = params.fairholdLandPurchase.depreciatedHouseMortgage.yearlyPaymentBreakdown[i].yearlyPayment;
fairholdLandMortgageYearlyIterative = params.fairholdLandPurchase.discountedLandMortgage.yearlyPaymentBreakdown[i].yearlyPayment
marketLandMortgageYearlyIterative = params.marketPurchase.landMortgage.yearlyPaymentBreakdown[i].yearlyPayment;
// If the mortgage term has ended, yearly payment is 0
} else {
newbuildHouseMortgageYearlyIterative = 0;
depreciatedHouseMortgageYearlyIterative = 0;
Expand Down
5 changes: 5 additions & 0 deletions app/models/Mortgage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ 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;
/**
Expand All @@ -33,6 +37,7 @@ export class Mortgage {
*/
principal: number;
monthlyPayment: number;
/** This includes principal and interest */
totalMortgageCost: number;
yearlyPaymentBreakdown: MortgageBreakdown;
totalInterest: number;
Expand Down
5 changes: 3 additions & 2 deletions app/models/Property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
/**
Expand Down
8 changes: 6 additions & 2 deletions app/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -23,12 +23,16 @@ 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,
propertyValue: 49750,
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;
7 changes: 6 additions & 1 deletion app/models/tenure/FairholdLandPurchase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ 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) {
Expand Down
11 changes: 8 additions & 3 deletions app/models/tenure/FairholdLandRent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ 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) {
Expand All @@ -49,6 +52,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;
Expand Down
3 changes: 3 additions & 0 deletions app/models/tenure/MarketPurchase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions app/models/tenure/MarketRent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great comment 👍 Worth spinning out into a GH issue maybe?

const landToTotalRatio = landPrice / averagePrice;
const averageRentLandMonthly = averageRentMonthly * landToTotalRatio;
const averageRentHouseMonthly = averageRentMonthly - averageRentLandMonthly;
Expand Down
6 changes: 3 additions & 3 deletions app/models/tenure/SocialRent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down