mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
updated the costings with new products and measurse specific contingencies
This commit is contained in:
parent
ab150c799d
commit
1579d010ee
6 changed files with 152 additions and 434 deletions
|
|
@ -57,8 +57,6 @@ INSTALLER_SOLAR_COSTS = [
|
|||
{'n_panels': 17, 'array_kwp': 17 * PANEL_SIZE, 'cost': 6637.95, 'installer': 'CEG'},
|
||||
{'n_panels': 18, 'array_kwp': 18 * PANEL_SIZE, 'cost': 6792.57, 'installer': 'CEG'}
|
||||
]
|
||||
# This is the maximum number of panels that we have a cost from the installers for
|
||||
INSTALLER_MAX_PANELS = 18
|
||||
|
||||
# CEG uses use Solshare as an inverter to provide solar PV to multiple flats. This costs £7500 for the inverter alone
|
||||
# https://midsummerwholesale.co.uk/buy/solshare
|
||||
|
|
@ -185,6 +183,20 @@ class Costs:
|
|||
# When there is less uncertainty, a lower contingency rate is used
|
||||
LOW_RISK_CONTINGENCY = 0.05
|
||||
|
||||
# Measure level contingency
|
||||
CONTINGENCIES = {
|
||||
"cavity_wall_insulation": 0.1,
|
||||
"internal_wall_insulation": 0.26,
|
||||
"external_wall_insulation": 0.26,
|
||||
"solar_pv": 0.15,
|
||||
"air_source_heat_pump": 0.2,
|
||||
"flat_roof_insulation": 0.26,
|
||||
"suspended_floor_insulation": 0.2,
|
||||
"solid_floor_insulation": 0.26,
|
||||
"low_energy_lighting": 0.26,
|
||||
"high_heat_retention_storage_heaters": 0.1,
|
||||
}
|
||||
|
||||
# Preliminaries are a percentage of the total cost of the work and covers the cost of site-specific costs
|
||||
# such as site preparation, safety measures and project management. This rate can vary but we'll assume a 10%
|
||||
# rate, on the total cost before VAT, as recommended by SPONs
|
||||
|
|
@ -258,33 +270,14 @@ class Costs:
|
|||
labour_hours = 8
|
||||
labour_days = 1
|
||||
|
||||
# if the material is based on an installer cost, we return the flat price
|
||||
if material["is_installer_quote"]:
|
||||
total_cost = material["total_cost"] * wall_area
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
total_including_vat = material["total_cost"] * wall_area
|
||||
|
||||
if is_extraction_and_refill:
|
||||
total_including_vat = CAVITY_EXTRACTION_COST * wall_area
|
||||
# Additional 2 days work
|
||||
labour_hours += + (2 * 8)
|
||||
labour_days += + 2
|
||||
|
||||
total_excluding_vat = total_including_vat / (1 + self.VAT_RATE)
|
||||
vat_cost = total_including_vat - total_excluding_vat
|
||||
total_cost = material["total_cost"] * wall_area
|
||||
|
||||
return {
|
||||
"total": total_including_vat,
|
||||
"subtotal": total_excluding_vat,
|
||||
"vat": vat_cost,
|
||||
"total": total_cost,
|
||||
"contingency": self.CONTINGENCIES["cavity_wall_insulation"] * total_cost,
|
||||
"contingency_rate": self.CONTINGENCIES["cavity_wall_insulation"],
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
def loft_and_flat_insulation(self, floor_area, material):
|
||||
|
|
@ -295,25 +288,20 @@ class Costs:
|
|||
:return: A dictionary containing detailed cost breakdown.
|
||||
"""
|
||||
|
||||
if material["is_installer_quote"]:
|
||||
total_cost = material["total_cost"] * floor_area
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"labour_hours": 8,
|
||||
"labour_days": 1,
|
||||
}
|
||||
|
||||
total_including_vat = material["total_cost"] * floor_area
|
||||
total_excluding_vat = total_including_vat / (1 + self.VAT_RATE)
|
||||
vat_cost = total_including_vat - total_excluding_vat
|
||||
total_cost = material["total_cost"] * floor_area
|
||||
if material["type"] == "loft_insulation":
|
||||
contingency_rate = self.CONTINGENCIES["loft_insulation"]
|
||||
contingency = contingency_rate * total_cost
|
||||
else:
|
||||
contingency_rate = self.CONTINGENCIES["flat_roof_insulation"]
|
||||
contingency = contingency_rate * total_cost
|
||||
|
||||
return {
|
||||
"total": total_including_vat,
|
||||
"subtotal": total_excluding_vat,
|
||||
"vat": vat_cost,
|
||||
"total": total_cost,
|
||||
"contingency": contingency,
|
||||
"contingency_rate": contingency_rate,
|
||||
"labour_hours": 8,
|
||||
"labour_days": 1
|
||||
"labour_days": 1,
|
||||
}
|
||||
|
||||
def solid_wall_insulation(self, wall_area, material):
|
||||
|
|
@ -323,469 +311,155 @@ class Costs:
|
|||
"""
|
||||
|
||||
# if the material is based on an installer cost, we return the flat price
|
||||
if material["is_installer_quote"]:
|
||||
total_cost = material["total_cost"] * wall_area
|
||||
total_cost = material["total_cost"] * wall_area
|
||||
|
||||
labour_hours = material["labour_hours_per_unit"] * wall_area
|
||||
if material["type"] == "internal_wall_insulation":
|
||||
contingency_rate = self.CONTINGENCIES["internal_wall_insulation"]
|
||||
else:
|
||||
contingency_rate = self.CONTINGENCIES["external_wall_insulation"]
|
||||
|
||||
# To install internal wall insulation, a small to medium size project might be conducted by a team of 3-5
|
||||
# people
|
||||
labour_days = (labour_hours / 8) / 4
|
||||
labour_hours = material["labour_hours_per_unit"] * wall_area
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
# Break out the individual material costs
|
||||
# Since we don't know the exact wall construction, we take an average for demolition costs, since
|
||||
# the cost will depend on the type of wall construction
|
||||
|
||||
total_including_vat = material["total_cost"] * wall_area
|
||||
total_excluding_vat = total_including_vat / (1 + self.VAT_RATE)
|
||||
vat_cost = total_including_vat - total_excluding_vat
|
||||
|
||||
# We estimate 1 weeks worth of work
|
||||
labour_hours = 160
|
||||
# To install internal wall insulation, a small to medium size project might be conducted by a team of 3-5 people
|
||||
# To install internal wall insulation, a small to medium size project might be conducted by a team of 3-5
|
||||
# people
|
||||
labour_days = (labour_hours / 8) / 4
|
||||
|
||||
return {
|
||||
"total": total_including_vat,
|
||||
"subtotal": total_excluding_vat,
|
||||
"vat": vat_cost,
|
||||
"total": total_cost,
|
||||
"contingency": contingency_rate * total_cost,
|
||||
"contingency_rate": contingency_rate,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
def suspended_floor_insulation(self, insulation_floor_area, material, non_insulation_materials):
|
||||
def suspended_floor_insulation(self, insulation_floor_area, material):
|
||||
"""
|
||||
We characterise the steps for suspended floor insulation as the following tasks:
|
||||
|
||||
1) Removal of Carpet and Underfelt: Where necessary, remove existing floor coverings to access the floorboards.
|
||||
2) Removal of Floor Boarding: Carefully remove floorboards to access the space beneath for insulation.
|
||||
3) Installation of Vapour Barrier: Install a vapour barrier to prevent moisture from affecting
|
||||
the insulation and floor structure.
|
||||
4) Installation of Insulation: Fit the chosen insulation material between the joists in the floor void.
|
||||
5) Refixing Floorboards: Replace and secure the floorboards after insulation installation.
|
||||
6) Re-carpeting: Lay down the carpet or other floor coverings once the insulation and floorboards are in place.
|
||||
:return:
|
||||
Given an installer cost for the works, produces an estimate for the cost of works.
|
||||
Includes contingency
|
||||
"""
|
||||
|
||||
# if the material is based on an installer cost, we return the flat price
|
||||
if material["is_installer_quote"]:
|
||||
total_cost = material["total_cost"] * insulation_floor_area
|
||||
total_cost = material["total_cost"] * insulation_floor_area
|
||||
|
||||
labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
|
||||
# To install suspended floor insulation, a small to medium size project might be conducted by a team of 3
|
||||
# people
|
||||
labour_days = (labour_hours / 8) / 3
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
demolition_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_demolition"]
|
||||
vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_vapour_barrier"]
|
||||
redecoration_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_redecoration"]
|
||||
|
||||
if (len(demolition_data) != 2) or (len(vapour_barrier_data) != 1) or (len(redecoration_data) != 2):
|
||||
raise ValueError("Incorrect number of data entries for non-insulation materials")
|
||||
|
||||
# Break out the individual material costs
|
||||
demolition_material_costs = sum([x["material_cost"] * insulation_floor_area for x in demolition_data])
|
||||
insulation_material_costs = material["material_cost"] * insulation_floor_area
|
||||
vapour_barrier_material_costs = vapour_barrier_data[0]["material_cost"] * insulation_floor_area
|
||||
redecoration_material_costs = sum([x["material_cost"] * insulation_floor_area for x in redecoration_data])
|
||||
|
||||
demolition_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in demolition_data])
|
||||
insulation_labour_costs = material["labour_cost"] * insulation_floor_area
|
||||
vapour_barrier_labour_costs = vapour_barrier_data[0]["labour_cost"] * insulation_floor_area
|
||||
redecoration_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in redecoration_data])
|
||||
|
||||
labour_costs = (demolition_labour_costs + insulation_labour_costs + vapour_barrier_labour_costs +
|
||||
redecoration_labour_costs)
|
||||
|
||||
labour_costs = labour_costs * self.labour_adjustment_factor
|
||||
|
||||
materials_costs = (demolition_material_costs + insulation_material_costs + vapour_barrier_material_costs +
|
||||
redecoration_material_costs)
|
||||
|
||||
subtotal_before_profit = labour_costs + materials_costs
|
||||
|
||||
# Because of the possiblity of damage to the existing floor, or difficulties associated to moving fittings,
|
||||
# we use a higher contingency rate
|
||||
contingency_cost = subtotal_before_profit * self.HIGH_RISK_CONTINGENCY
|
||||
preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES
|
||||
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
|
||||
|
||||
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
|
||||
|
||||
vat_cost = subtotal_before_vat * self.VAT_RATE
|
||||
|
||||
total_cost = subtotal_before_vat + vat_cost
|
||||
|
||||
demolition_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in demolition_data])
|
||||
insulation_labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
|
||||
vapour_barrier_labour_hours = vapour_barrier_data[0]["labour_hours_per_unit"] * insulation_floor_area
|
||||
redecoration_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in redecoration_data])
|
||||
|
||||
labour_hours = (demolition_labour_hours + insulation_labour_hours + vapour_barrier_labour_hours +
|
||||
redecoration_labour_hours)
|
||||
|
||||
# Assume a team of 3 people for a small to medium size project
|
||||
labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
|
||||
# To install suspended floor insulation, a small to medium size project might be conducted by a team of 3
|
||||
# people
|
||||
labour_days = (labour_hours / 8) / 3
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat_cost,
|
||||
"contingency": contingency_cost,
|
||||
"preliminaries": preliminaries_cost,
|
||||
"material": materials_costs,
|
||||
"profit": profit_cost,
|
||||
"contengency": self.CONTINGENCIES["suspended_floor_insulation"] * total_cost,
|
||||
"contingency_rate": self.CONTINGENCIES["suspended_floor_insulation"],
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
"labour_cost": labour_costs
|
||||
}
|
||||
|
||||
def solid_floor_insulation(self, insulation_floor_area, material, non_insulation_materials):
|
||||
def solid_floor_insulation(self, insulation_floor_area, material):
|
||||
"""
|
||||
We characterise the steps for solid floor insulation as the following tasks:
|
||||
|
||||
1) Removal of Carpet and Underfelt: This is the initial stage where any existing floor coverings, like carpets,
|
||||
tiles, or linoleum, are carefully removed. This exposes the solid floor beneath, which is typically concrete.
|
||||
|
||||
2) Preparation of Flooring: This step is critical. It involves:
|
||||
- Cleaning the existing floor surface thoroughly to remove debris and ensure a flat surface.
|
||||
- Assessing and repairing any damage to the concrete floor. This might include filling cracks or leveling
|
||||
uneven areas.
|
||||
|
||||
3) Installation of a Damp Proof Membrane (DPM): Before installing insulation, a DPM is often laid down to
|
||||
prevent moisture from rising into the insulation and the interior space. This step is crucial in areas prone to
|
||||
dampness.
|
||||
|
||||
4) Install Insulation: The insulation, often in the form of rigid foam boards, is laid over the DPM.
|
||||
The choice of insulation material will depend on the desired thermal properties and the available floor height.
|
||||
Care is taken to minimize thermal bridges and ensure a snug fit between insulation boards.
|
||||
|
||||
5) Laying a New Subfloor: Over the insulation, a new subfloor is often installed. This could be a layer of
|
||||
screed (a type of concrete) or wooden boarding, depending on the specific requirements and preferences.
|
||||
|
||||
6) Re-decoration and Finishing Touches: Once the subfloor is in place and has set or dried (if necessary),
|
||||
the final floor finish can be applied. This might involve:
|
||||
- Laying new tiles, wooden flooring, or other chosen materials.
|
||||
- If you're planning to re-carpet, this would be the stage to do it.
|
||||
- Skirting boards may need to be refitted or replaced.
|
||||
|
||||
7) Considerations for Doors and Fixtures: It's important to note that raising the floor level can affect door
|
||||
thresholds and other fixtures. Doors may need to be trimmed, and fixtures might need adjustments.
|
||||
based on costing data from installers, produces an estimate for the cost of works. Returns contingency
|
||||
|
||||
:param insulation_floor_area: Area of the floor to be insulated
|
||||
:param material: Selected insulation material
|
||||
:param non_insulation_materials: Non-insulation materials required for the job
|
||||
:return:
|
||||
"""
|
||||
|
||||
# if the material is based on an installer cost, we return the flat price
|
||||
if material["is_installer_quote"]:
|
||||
total_cost = material["total_cost"] * insulation_floor_area
|
||||
total_cost = material["total_cost"] * insulation_floor_area
|
||||
|
||||
labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
|
||||
# To install suspended floor insulation, a small to medium size project might be conducted by a team of 3
|
||||
# people
|
||||
labour_days = (labour_hours / 8) / 3
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
demolition_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_demolition"]
|
||||
preparation_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_preparation"]
|
||||
vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_vapour_barrier"]
|
||||
redecoration_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_redecoration"]
|
||||
|
||||
if ((len(demolition_data) != 1) or (len(preparation_data) != 2) or (len(vapour_barrier_data) != 1) or
|
||||
(len(redecoration_data) != 3)):
|
||||
raise ValueError("Incorrect number of data entries for non-insulation materials")
|
||||
|
||||
# Break out the individual material costs
|
||||
preparation_material_costs = sum([x["material_cost"] * insulation_floor_area for x in preparation_data])
|
||||
insulation_material_costs = material["material_cost"] * insulation_floor_area
|
||||
vapour_barrier_material_costs = vapour_barrier_data[0]["material_cost"] * insulation_floor_area
|
||||
redecoration_material_costs = sum([x["material_cost"] * insulation_floor_area for x in redecoration_data])
|
||||
|
||||
demolition_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in demolition_data])
|
||||
preparation_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in preparation_data])
|
||||
insulation_labour_costs = material["labour_cost"] * insulation_floor_area
|
||||
vapour_barrier_labour_costs = vapour_barrier_data[0]["labour_cost"] * insulation_floor_area
|
||||
redecoration_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in redecoration_data])
|
||||
|
||||
preparation_plant_costs = sum([x["plant_cost"] * insulation_floor_area for x in preparation_data])
|
||||
|
||||
labour_costs = (demolition_labour_costs + insulation_labour_costs + vapour_barrier_labour_costs +
|
||||
redecoration_labour_costs + preparation_labour_costs)
|
||||
|
||||
labour_costs = labour_costs * self.labour_adjustment_factor
|
||||
|
||||
materials_cost = (preparation_material_costs + insulation_material_costs + vapour_barrier_material_costs +
|
||||
redecoration_material_costs)
|
||||
|
||||
subtotal_before_profit = labour_costs + materials_cost + preparation_plant_costs
|
||||
|
||||
# We use HIGH_RISH_CONTINGENCY because of the potential for issues with moving fittings and trimming doors,
|
||||
# as well as scope for damage to the existing floor during preparation.
|
||||
contingency_cost = subtotal_before_profit * self.HIGH_RISK_CONTINGENCY
|
||||
preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES
|
||||
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
|
||||
|
||||
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
|
||||
vat_cost = subtotal_before_vat * self.VAT_RATE
|
||||
total_cost = subtotal_before_vat + vat_cost
|
||||
|
||||
demolition_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in demolition_data])
|
||||
preparation_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in preparation_data])
|
||||
insulation_labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
|
||||
vapour_barrier_labour_hours = vapour_barrier_data[0]["labour_hours_per_unit"] * insulation_floor_area
|
||||
redecoration_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in redecoration_data])
|
||||
|
||||
labour_hours = (demolition_labour_hours + insulation_labour_hours + vapour_barrier_labour_hours +
|
||||
redecoration_labour_hours + preparation_labour_hours)
|
||||
|
||||
# Assume a team of 3 people for a small to medium size project
|
||||
labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
|
||||
# To install suspended floor insulation, a small to medium size project might be conducted by a team of 3
|
||||
# people
|
||||
labour_days = (labour_hours / 8) / 3
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat_cost,
|
||||
"contingency": contingency_cost,
|
||||
"preliminaries": preliminaries_cost,
|
||||
"material": materials_cost,
|
||||
"profit": profit_cost,
|
||||
"contingency": self.CONTINGENCIES["solid_floor_insulation"] * total_cost,
|
||||
"contingency_rate": self.CONTINGENCIES["solid_floor_insulation"],
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
"labour_cost": labour_costs
|
||||
}
|
||||
|
||||
def low_energy_lighting(self, number_of_lights, number_current_lel_lights, material):
|
||||
def low_energy_lighting(self, number_of_lights, material):
|
||||
|
||||
"""
|
||||
Calculates the total cost for low energy lighting based on material and labor costs,
|
||||
including contingency, preliminaries, profit, and VAT.
|
||||
|
||||
:param number_of_lights: Int, number of light
|
||||
:param number_current_lel_lights: Int, number of low energy lights currently installed in the home
|
||||
:material: Dict, material data containing costs of fittings
|
||||
"""
|
||||
|
||||
# If there are no lights fitted in the property, we increase the contingency in case there are potential wiring
|
||||
# blockers
|
||||
|
||||
if number_current_lel_lights == 0:
|
||||
contingency = self.HIGH_RISK_CONTINGENCY
|
||||
else:
|
||||
contingency = self.CONTINGENCY
|
||||
total_cost = material["total_cost"] * number_of_lights
|
||||
|
||||
if material["is_installer_quote"]:
|
||||
total_cost = material["total_cost"] * number_of_lights * (1 + contingency)
|
||||
|
||||
labour_hours = 1
|
||||
labour_days = (labour_hours / 8)
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
material_cost = material["material_cost"] * number_of_lights
|
||||
labour_cost = material["labour_cost"] * number_of_lights * self.labour_adjustment_factor
|
||||
|
||||
subtotal_before_profit = material_cost + labour_cost
|
||||
|
||||
contingency_cost = subtotal_before_profit * contingency
|
||||
|
||||
subtotal_before_vat = subtotal_before_profit + contingency_cost
|
||||
vat_cost = subtotal_before_vat * self.VAT_RATE
|
||||
total_cost = subtotal_before_vat + vat_cost
|
||||
|
||||
labour_hours = material["labour_hours_per_unit"] * number_of_lights
|
||||
# Assume a single electrician installing
|
||||
labour_hours = 1
|
||||
labour_days = (labour_hours / 8)
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat_cost,
|
||||
"contingency": contingency_cost,
|
||||
"material": material_cost,
|
||||
"contingency": self.CONTINGENCIES["low_energy_lighting"] * total_cost,
|
||||
"contingency_rate": self.CONTINGENCIES["low_energy_lighting"],
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
"labour_cost": labour_cost
|
||||
}
|
||||
|
||||
def window_glazing(self, number_of_windows, material, is_secondary_glazing=False):
|
||||
"""
|
||||
We characterise the jobs to be done for window glazing as the following:
|
||||
1) Initial Assessment and Measurements: Before removing the existing window, it's essential to assess the
|
||||
condition of the window frame and opening. Precise measurements are taken to ensure the new double glazed
|
||||
windows fit perfectly.
|
||||
|
||||
2) Remove the Existing Window: This involves carefully dismantling and removing the old single glazed window. It
|
||||
requires skill to avoid damaging the surrounding wall and the window frame (if it's to be reused).
|
||||
|
||||
3) Dispose of the Existing Window: The old window, especially if it's a single glazed unit, needs to be
|
||||
disposed of responsibly. Glass and other materials should be recycled where possible.
|
||||
|
||||
4) Surface Preparation: The window opening might need some preparation, especially if there's damage or if
|
||||
adjustments are needed to accommodate the new window. This can include repairing or replacing parts of the
|
||||
window frame, sealing gaps, and ensuring the opening is level and square.
|
||||
|
||||
5) Install the Window Frame (if new frames are used): In many cases, double glazed windows come with their
|
||||
frames. These need to be installed securely into the window opening. This process involves aligning, leveling,
|
||||
and fixing the frame in place.
|
||||
|
||||
6) Install the Window Sill: If a new window sill is required, it is installed at this stage. It needs to be
|
||||
correctly aligned with the frame and securely attached.
|
||||
|
||||
7) Install the Double Glazed Glass Units: The glass units are carefully inserted into the frame. This step
|
||||
requires precision to ensure a snug fit without causing stress on the glass, which could lead to cracking or
|
||||
breaking.
|
||||
|
||||
8) Sealing and Weatherproofing: After the glass units are in place, it's crucial to seal around the frame and
|
||||
between the glass and frame to ensure there are no drafts and that the installation is weather-tight. This
|
||||
typically involves applying silicone sealant or other appropriate sealing materials.
|
||||
|
||||
9) Finishing Touches: This includes any cosmetic work, such as trimming, painting, or staining the frame and
|
||||
sill to match the rest of the property. It might also involve cleaning up any mess created during the
|
||||
installation.
|
||||
|
||||
10) Inspection and Testing: Finally, the new windows should be inspected to ensure they open, close, and lock
|
||||
correctly. This is also a good time to check for any gaps or issues with the sealing.
|
||||
|
||||
For this cost estimation process, we factor in initial assement into the preliminaries
|
||||
Given an isntaller quote, produces an estimate for the cost of works.
|
||||
|
||||
"""
|
||||
|
||||
if material["is_installer_quote"]:
|
||||
total_cost = material["total_cost"] * number_of_windows
|
||||
|
||||
labour_hours = material["labour_hours_per_unit"] * number_of_windows
|
||||
# To install windows, a small to medium size project might be conducted by a team of 2-3 people
|
||||
labour_days = (labour_hours / 8) / 2
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
material_cost = material["material_cost"] * number_of_windows
|
||||
|
||||
labour_cost = (
|
||||
material["labour_cost"] * number_of_windows * self.labour_adjustment_factor
|
||||
)
|
||||
multiplier = self.SECONDARY_GLAZING_SCALING_FACTOR if is_secondary_glazing else (
|
||||
self.SASH_WINDOW_INFLATION_FACTOR)
|
||||
|
||||
subtotal = (material_cost + labour_cost) * multiplier
|
||||
|
||||
contingency_cost = subtotal * self.CONTINGENCY
|
||||
preliminaries_cost = subtotal * self.PRELIMINARIES
|
||||
profit_cost = subtotal * self.PROFIT_MARGIN
|
||||
|
||||
subtotal_before_vat = subtotal + contingency_cost + preliminaries_cost + profit_cost
|
||||
|
||||
vat_cost = subtotal_before_vat * self.VAT_RATE
|
||||
|
||||
total_cost = subtotal_before_vat + vat_cost
|
||||
total_cost = material["total_cost"] * number_of_windows
|
||||
|
||||
labour_hours = material["labour_hours_per_unit"] * number_of_windows
|
||||
labour_hours = labour_hours * self.SECONDARY_GLAZING_SCALING_FACTOR if is_secondary_glazing else labour_hours
|
||||
|
||||
# Assume a team of 2
|
||||
# To install windows, a small to medium size project might be conducted by a team of 2-3 people
|
||||
labour_days = (labour_hours / 8) / 2
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat_cost,
|
||||
"contingency": contingency_cost,
|
||||
"preliminaries": preliminaries_cost,
|
||||
"material": material_cost,
|
||||
"profit": profit_cost,
|
||||
"contingency": self.CONTINGENCIES["windows_glazing"] * total_cost,
|
||||
"contingency_rate": self.CONTINGENCIES["windows_glazing"],
|
||||
"labour_hours": labour_hours,
|
||||
"labour_cost": labour_cost,
|
||||
"labour_days": labour_days
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def solar_pv(
|
||||
cls,
|
||||
n_panels: int | float,
|
||||
has_battery: bool = False,
|
||||
array_cost=None,
|
||||
n_floors: int = 1,
|
||||
battery_kwh: int = 5,
|
||||
needs_inverter=False
|
||||
solar_product,
|
||||
scaffolding_options,
|
||||
n_floors
|
||||
):
|
||||
|
||||
"""
|
||||
Calculates the total cost for solar PV based data provided by the MCS dashboard, which contains
|
||||
costing data for installations of renewable and clean energy measures.
|
||||
|
||||
The data in the dashboard is filtered on domestic building installations and then the data across the
|
||||
various regions is manually collected. There is currently no automated way to get the data from the MCS
|
||||
dashboard
|
||||
|
||||
Price can also be benchmarked against this checkatrade article:
|
||||
https://www.checkatrade.com/blog/cost-guides/cost-of-solar-panel-installation/
|
||||
:param n_panels: Number of solar panels
|
||||
:param has_battery: Bool, whether the system includes a battery
|
||||
:param array_cost: float, containing the cost of the solar PV array
|
||||
:param n_floors: int, number of floors in the property, used to estimate the cost of scaffolding
|
||||
:param battery_kwh: int, capacity of the battery in kWh. Defaulted to 5
|
||||
:param needs_inverter: Bool, whether the system needs an inverter, where the solar panels are feeding multiple
|
||||
units
|
||||
"""
|
||||
|
||||
if n_panels > INSTALLER_MAX_PANELS:
|
||||
base_cost = [c for c in INSTALLER_SOLAR_COSTS if c["n_panels"] == INSTALLER_MAX_PANELS][0]["cost"]
|
||||
cost_per_panel = [
|
||||
c for c in INSTALLER_SOLAR_COSTS if c["n_panels"] == (INSTALLER_MAX_PANELS - 1)
|
||||
][0]["cost"]
|
||||
cost_per_panel = base_cost - cost_per_panel
|
||||
system_cost = base_cost + (n_panels - INSTALLER_MAX_PANELS) * cost_per_panel
|
||||
else:
|
||||
system_cost = [c for c in INSTALLER_SOLAR_COSTS if c["n_panels"] == n_panels][0]["cost"]
|
||||
system_cost = solar_product["total_cost"]
|
||||
|
||||
subtotal = array_cost if array_cost is not None else system_cost
|
||||
if not solar_product["includes_scaffolding"]:
|
||||
# We base this on the number of floors
|
||||
scaffolding = [x["total_cost"] for x in scaffolding_options if x["size"] == n_floors]
|
||||
if not scaffolding:
|
||||
# If we have no options, handle this
|
||||
if n_floors <= 3:
|
||||
raise ValueError("No scaffolding options available for 3 or fewer floors")
|
||||
# We take the largest scaffolding option available
|
||||
scaffolding_cost = max([x["total_cost"] for x in scaffolding_options])
|
||||
else:
|
||||
scaffolding_cost = min(scaffolding)
|
||||
|
||||
if has_battery:
|
||||
battery_cost = [c for c in INSTALLER_SOLAR_BATTERY_COSTS if c["capacity_kwh"] == battery_kwh][0]["cost"]
|
||||
subtotal += battery_cost
|
||||
|
||||
if needs_inverter:
|
||||
subtotal += INSTALLER_SOLAR_PV_INVERTER_COST
|
||||
# We also add an additional labour cost
|
||||
subtotal += INSTALLER_SOLAR_PV_INVERTER_LABOUR_COST
|
||||
|
||||
# Solar doesn't have VAT but we add a high risk contingency
|
||||
# to account for design variation that we see in practice
|
||||
total_cost = subtotal * (1 + cls.HIGH_RISK_CONTINGENCY)
|
||||
system_cost += scaffolding_cost
|
||||
|
||||
# Labour hours are based on estimates from online research but an average team seems to consist of 3 people
|
||||
# and most jobs take around 2 days. Assuming an 8 hour day for 3 people across 2 days, gives us 48 hours of
|
||||
# labour
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal,
|
||||
"total": system_cost,
|
||||
"subtotal": system_cost,
|
||||
"contingency": system_cost * cls.CONTINGENCIES["solar_pv"],
|
||||
"contingency_rate": cls.CONTINGENCIES["solar_pv"],
|
||||
"vat": 0,
|
||||
"labour_hours": 48,
|
||||
"labour_days": 2,
|
||||
|
|
@ -811,6 +485,8 @@ class Costs:
|
|||
# We estimate the cost of an appliance thermostat at £400, which is the upper end of the range
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contengency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
|
|
@ -837,6 +513,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
|
|
@ -875,6 +553,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCIES["high_heat_retention_storage_heaters"],
|
||||
"contingency_rate": self.CONTINGENCIES["high_heat_retention_storage_heaters"],
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
|
|
@ -894,6 +574,8 @@ class Costs:
|
|||
# We estimate the labour hours to be 4
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": 4,
|
||||
|
|
@ -913,6 +595,8 @@ class Costs:
|
|||
# We estimate the labour hours to be 2
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": 2,
|
||||
|
|
@ -931,6 +615,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": 0,
|
||||
|
|
@ -965,6 +651,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
|
|
@ -997,6 +685,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
|
|
@ -1025,6 +715,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
|
|
@ -1047,6 +739,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": removal_labour_hours,
|
||||
|
|
@ -1110,7 +804,6 @@ class Costs:
|
|||
# To be pessimistic, assume 2 days work
|
||||
labour_cost = labour_rate * self.labour_adjustment_factor * labour_days
|
||||
# Add contingency and preliminaries
|
||||
labour_cost = labour_cost * (1 + self.CONTINGENCY + self.PRELIMINARIES)
|
||||
|
||||
vat = labour_cost * self.VAT_RATE
|
||||
|
||||
|
|
@ -1150,6 +843,8 @@ class Costs:
|
|||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"contingency": total_cost * self.CONTINGENCY,
|
||||
"contingency_rate": self.CONTINGENCY,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
|
|
@ -1169,19 +864,18 @@ class Costs:
|
|||
else:
|
||||
cost = [x for x in INSTALLER_ASHP_COSTS if x][0]["cost"]
|
||||
|
||||
# We add some contingency since there are additional costs such as resizing radiators, that could be required
|
||||
subtotal = cost * (1 + self.ASHP_CONTINGENCY)
|
||||
# The costs from installers exclude VAT
|
||||
vat = subtotal * self.VAT_RATE
|
||||
total_cost = subtotal + vat
|
||||
vat = cost * self.VAT_RATE
|
||||
cost = cost + vat
|
||||
|
||||
# We assume 5 days installation
|
||||
labour_days = 5
|
||||
labour_hours = labour_days * 8
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal,
|
||||
"total": cost,
|
||||
"contingency": cost * self.CONTINGENCIES["air_source_heat_pump"],
|
||||
"contingency_rate": self.CONTINGENCIES["air_source_heat_pump"],
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
|
|
|
|||
|
|
@ -200,7 +200,6 @@ class FloorRecommendations(Definitions):
|
|||
cost_result = self.costs.suspended_floor_insulation(
|
||||
insulation_floor_area=self.property.insulation_floor_area,
|
||||
material=material.to_dict(),
|
||||
non_insulation_materials=non_insulation_materials
|
||||
)
|
||||
|
||||
already_installed = "suspended_floor_insulation" in self.property.already_installed
|
||||
|
|
@ -213,7 +212,6 @@ class FloorRecommendations(Definitions):
|
|||
cost_result = self.costs.solid_floor_insulation(
|
||||
insulation_floor_area=self.property.insulation_floor_area,
|
||||
material=material.to_dict(),
|
||||
non_insulation_materials=non_insulation_materials
|
||||
)
|
||||
|
||||
already_installed = "solid_floor_insulation" in self.property.already_installed
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ class HotwaterRecommendations:
|
|||
"description_simulation": {
|
||||
"hot-water-energy-eff": "Poor"
|
||||
},
|
||||
"survey": survey
|
||||
"survey": survey,
|
||||
"innovation_rate": 0
|
||||
}
|
||||
if _return:
|
||||
return to_append
|
||||
|
|
@ -160,7 +161,8 @@ class HotwaterRecommendations:
|
|||
"hot-water-energy-eff": self.property.data["hot-water-energy-eff"],
|
||||
"hotwater-description": new_epc_description,
|
||||
},
|
||||
"survey": survey
|
||||
"survey": survey,
|
||||
"innovation_rate": 0
|
||||
}
|
||||
if _return:
|
||||
return to_append
|
||||
|
|
@ -222,7 +224,8 @@ class HotwaterRecommendations:
|
|||
"hot-water-energy-eff": simulation_config["hot_water_energy_eff_ending"],
|
||||
"hotwater-description": new_epc_description,
|
||||
},
|
||||
"survey": False
|
||||
"survey": False,
|
||||
"innovation_rate": 0
|
||||
}
|
||||
|
||||
self.recommendations.append(to_append)
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ class LightingRecommendations:
|
|||
|
||||
cost_result = self.costs.low_energy_lighting(
|
||||
number_of_lights=number_non_lel_outlets,
|
||||
number_current_lel_lights=number_lighting_outlets - number_non_lel_outlets,
|
||||
material=self.material
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class SecondaryHeating:
|
|||
},
|
||||
"description_simulation": {
|
||||
"secondheat-description": "None"
|
||||
}
|
||||
},
|
||||
"innovation_rate": 0.0, # No innovation rate for this measure
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class SolarPvRecommendations:
|
|||
]
|
||||
)
|
||||
|
||||
def __init__(self, property_instance):
|
||||
PANEL_SIZES = [400, 435, 440]
|
||||
|
||||
def __init__(self, property_instance, materials: list):
|
||||
"""
|
||||
:param property_instance: Instance of the Property class, for the home associated to property_id
|
||||
"""
|
||||
|
|
@ -44,6 +46,14 @@ class SolarPvRecommendations:
|
|||
|
||||
self.recommendation = []
|
||||
|
||||
self.panels_products = [
|
||||
material for material in materials if material["type"] == "solar_pv"
|
||||
]
|
||||
|
||||
self.scaffolding_options = [
|
||||
material for material in materials if material["type"] == "scaffolding"
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def trim_solar_wattage_options(scenarios_with_wattage):
|
||||
# Initialize the list with the first element, assuming the list is not empty
|
||||
|
|
@ -143,6 +153,8 @@ class SolarPvRecommendations:
|
|||
if not self.property.is_solar_pv_valid():
|
||||
return
|
||||
|
||||
raise ValueError("DOO BUILDING")
|
||||
|
||||
# If we have a buiilding level analysis, we implement separate logic
|
||||
if self.property.building_id is not None:
|
||||
self.recommend_building_analysis(phase)
|
||||
|
|
@ -201,11 +213,22 @@ class SolarPvRecommendations:
|
|||
# of this. This minimum is used in Recommendations.calculate_recommendation_impact
|
||||
minimum_sap_points = (roof_coverage_percent / 5) * self.SAP_POINTS_PER_5_PERCENT_ROOF_COVERAGE
|
||||
|
||||
for has_battery in [False, True]:
|
||||
n_panels = recommendation_config["n_panels"]
|
||||
|
||||
# Different panel sizes: 400, 435, 440
|
||||
available_products = []
|
||||
for panel_size in self.PANEL_SIZES:
|
||||
system_size = (n_panels * panel_size) / 1000
|
||||
available_products.extend([
|
||||
x for x in self.panels_products if abs(x["size"] - system_size) < 0.01
|
||||
])
|
||||
|
||||
# Given the available products in the database, we product the possible array of recommendations
|
||||
for solar_pv_product in available_products:
|
||||
|
||||
cost_result = self.costs.solar_pv(
|
||||
has_battery=has_battery,
|
||||
array_cost=non_invasive_recommendation.get("cost", None),
|
||||
n_panels=recommendation_config["n_panels"],
|
||||
product=solar_pv_product,
|
||||
scaffolding_options=self.scaffolding_options,
|
||||
n_floors=self.property.number_of_floors
|
||||
)
|
||||
kw = np.floor(recommendation_config["array_wattage"] / 100) / 10
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue