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

feat: Tighten types on Property class #24

Merged
merged 2 commits into from
Jul 31, 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
2 changes: 1 addition & 1 deletion app/models/Household.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ beforeEach(() => {
age: 10,
size: 88,
newBuildPricePerMetre: 2120,
averagePrice: 218091.58,
averageMarketPrice: 218091.58,
itl3: "TLG24",
});

Expand Down
6 changes: 3 additions & 3 deletions app/models/Household.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class Household {
// calculate tenure market purchase
this.tenure.marketPurchase = new MarketPurchase({
incomeYearly: this.incomeYearly,
averagePrice: this.property.averagePrice,
averagePrice: this.property.averageMarketPrice,
newBuildPrice: this.property.newBuildPrice,
depreciatedBuildPrice: this.property.depreciatedBuildPrice,
landPrice: this.property.landPrice,
Expand All @@ -104,7 +104,7 @@ export class Household {
//calculate tenure market rent
this.tenure.marketRent = new MarketRent({
averageRentYearly: averageRentYearly,
averagePrice: this.property.averagePrice,
averagePrice: this.property.averageMarketPrice,
newBuildPrice: this.property.newBuildPrice,
depreciatedBuildPrice: this.property.depreciatedBuildPrice,
landPrice: this.property.landPrice,
Expand Down Expand Up @@ -158,7 +158,7 @@ export class Household {

this.tenure.fairholdLandRent = new FairholdLandRent({
averageRentYearly: averageRentYearly,
averagePrice: this.property.averagePrice, // average price of the property
averagePrice: this.property.averageMarketPrice, // average price of the property
newBuildPrice: this.property.newBuildPrice,
depreciatedBuildPrice: this.property.depreciatedBuildPrice, // depreciated building price
landPrice: this.property.landPrice, // land price
Expand Down
2 changes: 1 addition & 1 deletion app/models/Property.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ beforeEach(() => {
age: 10,
size: 88,
newBuildPricePerMetre: 2120,
averagePrice: 218091.58,
averageMarketPrice: 218091.58,
itl3: "TLG24",
});
});
Expand Down
189 changes: 89 additions & 100 deletions app/models/Property.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,113 @@

import * as math from "mathjs";

export class Property {
postcode; // postcode of the property
houseType; // type of the house: D--> detached, S--> semidetached, T--> Terrace, F--> Flats
numberOfBedrooms; // number of bedrooms in the house
age; // age of the house
size; // size of the house in metre squares
newBuildPricePerMetre: number; // average build price per metre of a new house
averagePrice: number; // average market price
itl3: string; // ITL code
newBuildPrice?: number; // price of the house if it was new
depreciatedBuildPrice?: number; // price of the house according to the depreciation regression
bedWeightedAveragePrice?: number; // price of the house weigheted by the number of bedrooms
landPrice?: number; // price of the land
landToTotalRatio?: number; // ratio of the land price over the total
/**
* Number of decimal places to use when rounding numerical values
*/
const PRECISION = 2
const DEPRECIATION_FACTOR = -32938;

constructor({
postcode,
houseType,
numberOfBedrooms,
age,
size,
newBuildPricePerMetre,
averagePrice,
itl3,
}: {
postcode: any;
houseType: string;
numberOfBedrooms: number;
age: number;
size: number;
newBuildPricePerMetre: number;
averagePrice: number;
itl3: string;
}) {
this.postcode = postcode;
this.houseType = houseType;
this.numberOfBedrooms = numberOfBedrooms;
this.age = age;
this.size = size;
this.newBuildPricePerMetre = newBuildPricePerMetre;
this.averagePrice = averagePrice;
this.itl3 = itl3;
type PropertyParams = Pick<
Property,
| "postcode"
| "houseType"
| "numberOfBedrooms"
| "age"
| "size"
| "newBuildPricePerMetre"
| "averageMarketPrice"
| "itl3"
>;

this.calculateNewBuildPrice(); // calculate new building price
this.calculateDepreciatedBuildPrice(); // calculated the depreciated building price
this.calculateBedWeightedAveragePrice(); // calculate the bed weighted building price
this.calculateLandPrice(); // calculate the price of the land
if (
this.landPrice !== undefined &&
this.bedWeightedAveragePrice !== undefined
) {
this.landToTotalRatio = this.landPrice / this.bedWeightedAveragePrice; // define the land to total ratio
}
}
// TODO: Reuse HouseType enum
type HouseType = "D" | "S" | "T" | "F";

// calculate new building price
calculateNewBuildPrice(precisionRounding: number = 2) {
if (!this.newBuildPricePerMetre) {
throw new Error(
"The Build Price cannot be calculated because pricePerMetre has not been set"
);
}
const newBuildPrice = this.newBuildPricePerMetre * this.size; // calculate the price of the new build
this.newBuildPrice = parseFloat(newBuildPrice.toFixed(precisionRounding)); // round the number
export class Property {
postcode: string;
houseType: HouseType;
numberOfBedrooms: number;
age: number;
/**
* Size of the house in squares meters
*/
size: number;
/**
* Average build price per metre of a new house
*/
newBuildPricePerMetre: number;
averageMarketPrice: number;
itl3: string;
/**
* Price of the house if it was new
*/
newBuildPrice: number;
/**
* Price of the house according to the depreciation regression
*/
depreciatedBuildPrice: number;
/**
* Price of the house weighted by the number of bedrooms
*/
bedWeightedAveragePrice: number;
landPrice: number;
/**
* Ratio of the land price to the total price
*/
landToTotalRatio: number;

constructor(params: PropertyParams) {
this.postcode = params.postcode;
this.houseType = params.houseType;
this.numberOfBedrooms = params.numberOfBedrooms;
this.age = params.age;
this.size = params.size;
this.newBuildPricePerMetre = params.newBuildPricePerMetre;
this.averageMarketPrice = params.averageMarketPrice;
this.itl3 = params.itl3;

return this.newBuildPrice;
// Computed properties, order is significant
this.newBuildPrice = this.calculateNewBuildPrice();
this.depreciatedBuildPrice = this.calculateDepreciatedBuildPrice();
this.bedWeightedAveragePrice = this.calculateBedWeightedAveragePrice();
this.landPrice = this.averageMarketPrice - this.newBuildPrice;
this.landToTotalRatio = this.landPrice / this.bedWeightedAveragePrice;
}

// calculate nthe depraciated building price
calculateDepreciatedBuildPrice(
depreciationFactor: number = -32938,
precisionRounding: number = 2
) {
if (!this.newBuildPrice) {
throw new Error(
"The Depreciated Price cannot be calculated because newBuildPrice has not been set"
);
}
private calculateNewBuildPrice() {
let newBuildPrice = this.newBuildPricePerMetre * this.size;
newBuildPrice = parseFloat(newBuildPrice.toFixed(PRECISION));

const depreciatedBuildPrice =
this.newBuildPrice + depreciationFactor * math.log(this.age); // depreciated building price
this.depreciatedBuildPrice = parseFloat(
depreciatedBuildPrice.toFixed(precisionRounding)
); // round the number
return newBuildPrice;
}

if (this.depreciatedBuildPrice == undefined) {
throw new Error("depreciatedBuildPrice is undefined");
}
private calculateDepreciatedBuildPrice() {
let depreciatedBuildPrice =
this.newBuildPrice + DEPRECIATION_FACTOR * math.log(this.age);
depreciatedBuildPrice = parseFloat(
depreciatedBuildPrice.toFixed(PRECISION)
);

return this.depreciatedBuildPrice;
return depreciatedBuildPrice;
}

// calculate the average property price based on the number of bedrooms
calculateBedWeightedAveragePrice(
numberOfBeds: number = this.numberOfBedrooms,
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],
precisionRounding: number = 2
) {
let bedWeight; // initialize the variable
let bedWeight;

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

if (bedWeight == undefined) {
throw new Error("bedWeight is undefined.");
}
bedWeight = parseFloat(
(bedWeight * this.averageMarketPrice).toFixed(PRECISION)
);

return (this.bedWeightedAveragePrice = parseFloat(
(bedWeight * this.averagePrice).toFixed(precisionRounding)
)); // calculate the bed weighted average price
}
calculateLandPrice() {
if (this.newBuildPrice == undefined)
throw new Error("newBuildPrice is undefined");
return (this.landPrice = this.averagePrice - this.newBuildPrice); // calculate the price of the land
return bedWeight;
}
}
2 changes: 1 addition & 1 deletion app/models/tenure/FairholdLandPurchase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ beforeEach(() => {
age: 10,
size: 88,
newBuildPricePerMetre: 2120,
averagePrice: 218091.58,
averageMarketPrice: 218091.58,
itl3: "TLG24",
});

Expand Down
2 changes: 1 addition & 1 deletion app/models/tenure/FairholdLandRent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ beforeEach(() => {
age: 10,
size: 88,
newBuildPricePerMetre: 2120,
averagePrice: 218091.58,
averageMarketPrice: 218091.58,
itl3: "TLG24",
});

Expand Down
2 changes: 1 addition & 1 deletion app/models/tenure/MarketPurchase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ beforeEach(() => {
age: 10,
size: 88,
newBuildPricePerMetre: 2120,
averagePrice: 218091.58,
averageMarketPrice: 218091.58,
itl3: "TLG24",
});

Expand Down
2 changes: 1 addition & 1 deletion app/models/tenure/MarketRent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ beforeEach(() => {
age: 10,
size: 88,
newBuildPricePerMetre: 2120,
averagePrice: 218091.58,
averageMarketPrice: 218091.58,
itl3: "TLG24",
});

Expand Down
2 changes: 1 addition & 1 deletion app/models/tenure/SocialRent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ beforeEach(() => {
age: 10,
size: 88,
newBuildPricePerMetre: 2120,
averagePrice: 218091.58,
averageMarketPrice: 218091.58,
itl3: "TLG24",
});

Expand Down
2 changes: 1 addition & 1 deletion app/models/testClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function calculateFairhold(responseData: any) {
age: responseData.houseAge,
size: responseData.houseSize,
newBuildPricePerMetre: responseData.buildPrice,
averagePrice: responseData.averagePrice,
averageMarketPrice: responseData.averagePrice,
itl3: responseData.itl3,
});

Expand Down