mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +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': 17, 'array_kwp': 17 * PANEL_SIZE, 'cost': 6637.95, 'installer': 'CEG'},
|
||||||
{'n_panels': 18, 'array_kwp': 18 * PANEL_SIZE, 'cost': 6792.57, '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
|
# 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
|
# https://midsummerwholesale.co.uk/buy/solshare
|
||||||
|
|
@ -185,6 +183,20 @@ class Costs:
|
||||||
# When there is less uncertainty, a lower contingency rate is used
|
# When there is less uncertainty, a lower contingency rate is used
|
||||||
LOW_RISK_CONTINGENCY = 0.05
|
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
|
# 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%
|
# 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
|
# rate, on the total cost before VAT, as recommended by SPONs
|
||||||
|
|
@ -258,33 +270,14 @@ class Costs:
|
||||||
labour_hours = 8
|
labour_hours = 8
|
||||||
labour_days = 1
|
labour_days = 1
|
||||||
|
|
||||||
# if the material is based on an installer cost, we return the flat price
|
total_cost = material["total_cost"] * wall_area
|
||||||
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
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_including_vat,
|
"total": total_cost,
|
||||||
"subtotal": total_excluding_vat,
|
"contingency": self.CONTINGENCIES["cavity_wall_insulation"] * total_cost,
|
||||||
"vat": vat_cost,
|
"contingency_rate": self.CONTINGENCIES["cavity_wall_insulation"],
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
"labour_days": labour_days
|
"labour_days": labour_days,
|
||||||
}
|
}
|
||||||
|
|
||||||
def loft_and_flat_insulation(self, floor_area, material):
|
def loft_and_flat_insulation(self, floor_area, material):
|
||||||
|
|
@ -295,25 +288,20 @@ class Costs:
|
||||||
:return: A dictionary containing detailed cost breakdown.
|
:return: A dictionary containing detailed cost breakdown.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if material["is_installer_quote"]:
|
total_cost = material["total_cost"] * floor_area
|
||||||
total_cost = material["total_cost"] * floor_area
|
if material["type"] == "loft_insulation":
|
||||||
|
contingency_rate = self.CONTINGENCIES["loft_insulation"]
|
||||||
return {
|
contingency = contingency_rate * total_cost
|
||||||
"total": total_cost,
|
else:
|
||||||
"labour_hours": 8,
|
contingency_rate = self.CONTINGENCIES["flat_roof_insulation"]
|
||||||
"labour_days": 1,
|
contingency = contingency_rate * total_cost
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_including_vat,
|
"total": total_cost,
|
||||||
"subtotal": total_excluding_vat,
|
"contingency": contingency,
|
||||||
"vat": vat_cost,
|
"contingency_rate": contingency_rate,
|
||||||
"labour_hours": 8,
|
"labour_hours": 8,
|
||||||
"labour_days": 1
|
"labour_days": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
def solid_wall_insulation(self, wall_area, material):
|
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 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
|
labour_hours = material["labour_hours_per_unit"] * wall_area
|
||||||
# people
|
|
||||||
labour_days = (labour_hours / 8) / 4
|
|
||||||
|
|
||||||
return {
|
# To install internal wall insulation, a small to medium size project might be conducted by a team of 3-5
|
||||||
"total": total_cost,
|
# people
|
||||||
"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
|
|
||||||
labour_days = (labour_hours / 8) / 4
|
labour_days = (labour_hours / 8) / 4
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_including_vat,
|
"total": total_cost,
|
||||||
"subtotal": total_excluding_vat,
|
"contingency": contingency_rate * total_cost,
|
||||||
"vat": vat_cost,
|
"contingency_rate": contingency_rate,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
"labour_days": labour_days,
|
"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:
|
Given an installer cost for the works, produces an estimate for the cost of works.
|
||||||
|
Includes contingency
|
||||||
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:
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# if the material is based on an installer cost, we return the flat price
|
# 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
|
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
|
# To install suspended floor insulation, a small to medium size project might be conducted by a team of 3
|
||||||
# people
|
# 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_days = (labour_hours / 8) / 3
|
labour_days = (labour_hours / 8) / 3
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
"subtotal": subtotal_before_vat,
|
"contengency": self.CONTINGENCIES["suspended_floor_insulation"] * total_cost,
|
||||||
"vat": vat_cost,
|
"contingency_rate": self.CONTINGENCIES["suspended_floor_insulation"],
|
||||||
"contingency": contingency_cost,
|
|
||||||
"preliminaries": preliminaries_cost,
|
|
||||||
"material": materials_costs,
|
|
||||||
"profit": profit_cost,
|
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
"labour_days": labour_days,
|
"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:
|
based on costing data from installers, produces an estimate for the cost of works. Returns contingency
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
:param insulation_floor_area: Area of the floor to be insulated
|
:param insulation_floor_area: Area of the floor to be insulated
|
||||||
:param material: Selected insulation material
|
:param material: Selected insulation material
|
||||||
:param non_insulation_materials: Non-insulation materials required for the job
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# if the material is based on an installer cost, we return the flat price
|
total_cost = material["total_cost"] * insulation_floor_area
|
||||||
if material["is_installer_quote"]:
|
|
||||||
total_cost = material["total_cost"] * insulation_floor_area
|
|
||||||
|
|
||||||
labour_hours = material["labour_hours_per_unit"] * 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
|
# To install suspended floor insulation, a small to medium size project might be conducted by a team of 3
|
||||||
# people
|
# 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_days = (labour_hours / 8) / 3
|
labour_days = (labour_hours / 8) / 3
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
"subtotal": subtotal_before_vat,
|
"contingency": self.CONTINGENCIES["solid_floor_insulation"] * total_cost,
|
||||||
"vat": vat_cost,
|
"contingency_rate": self.CONTINGENCIES["solid_floor_insulation"],
|
||||||
"contingency": contingency_cost,
|
|
||||||
"preliminaries": preliminaries_cost,
|
|
||||||
"material": materials_cost,
|
|
||||||
"profit": profit_cost,
|
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
"labour_days": labour_days,
|
"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,
|
Calculates the total cost for low energy lighting based on material and labor costs,
|
||||||
including contingency, preliminaries, profit, and VAT.
|
including contingency, preliminaries, profit, and VAT.
|
||||||
|
|
||||||
:param number_of_lights: Int, number of light
|
: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
|
: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
|
# If there are no lights fitted in the property, we increase the contingency in case there are potential wiring
|
||||||
# blockers
|
# blockers
|
||||||
|
|
||||||
if number_current_lel_lights == 0:
|
total_cost = material["total_cost"] * number_of_lights
|
||||||
contingency = self.HIGH_RISK_CONTINGENCY
|
|
||||||
else:
|
|
||||||
contingency = self.CONTINGENCY
|
|
||||||
|
|
||||||
if material["is_installer_quote"]:
|
labour_hours = 1
|
||||||
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_days = (labour_hours / 8)
|
labour_days = (labour_hours / 8)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
"subtotal": subtotal_before_vat,
|
"contingency": self.CONTINGENCIES["low_energy_lighting"] * total_cost,
|
||||||
"vat": vat_cost,
|
"contingency_rate": self.CONTINGENCIES["low_energy_lighting"],
|
||||||
"contingency": contingency_cost,
|
|
||||||
"material": material_cost,
|
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
"labour_days": labour_days,
|
"labour_days": labour_days,
|
||||||
"labour_cost": labour_cost
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def window_glazing(self, number_of_windows, material, is_secondary_glazing=False):
|
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:
|
Given an isntaller quote, produces an estimate for the cost of works.
|
||||||
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
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if material["is_installer_quote"]:
|
total_cost = material["total_cost"] * number_of_windows
|
||||||
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
|
|
||||||
|
|
||||||
labour_hours = material["labour_hours_per_unit"] * 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
|
# To install windows, a small to medium size project might be conducted by a team of 2-3 people
|
||||||
|
|
||||||
# Assume a team of 2
|
|
||||||
labour_days = (labour_hours / 8) / 2
|
labour_days = (labour_hours / 8) / 2
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
"subtotal": subtotal_before_vat,
|
"contingency": self.CONTINGENCIES["windows_glazing"] * total_cost,
|
||||||
"vat": vat_cost,
|
"contingency_rate": self.CONTINGENCIES["windows_glazing"],
|
||||||
"contingency": contingency_cost,
|
|
||||||
"preliminaries": preliminaries_cost,
|
|
||||||
"material": material_cost,
|
|
||||||
"profit": profit_cost,
|
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
"labour_cost": labour_cost,
|
"labour_days": labour_days,
|
||||||
"labour_days": labour_days
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def solar_pv(
|
def solar_pv(
|
||||||
cls,
|
cls,
|
||||||
n_panels: int | float,
|
solar_product,
|
||||||
has_battery: bool = False,
|
scaffolding_options,
|
||||||
array_cost=None,
|
n_floors
|
||||||
n_floors: int = 1,
|
|
||||||
battery_kwh: int = 5,
|
|
||||||
needs_inverter=False
|
|
||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
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:
|
system_cost = solar_product["total_cost"]
|
||||||
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"]
|
|
||||||
|
|
||||||
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:
|
system_cost += scaffolding_cost
|
||||||
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)
|
|
||||||
|
|
||||||
# Labour hours are based on estimates from online research but an average team seems to consist of 3 people
|
# 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
|
# and most jobs take around 2 days. Assuming an 8 hour day for 3 people across 2 days, gives us 48 hours of
|
||||||
# labour
|
# labour
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": system_cost,
|
||||||
"subtotal": subtotal,
|
"subtotal": system_cost,
|
||||||
|
"contingency": system_cost * cls.CONTINGENCIES["solar_pv"],
|
||||||
|
"contingency_rate": cls.CONTINGENCIES["solar_pv"],
|
||||||
"vat": 0,
|
"vat": 0,
|
||||||
"labour_hours": 48,
|
"labour_hours": 48,
|
||||||
"labour_days": 2,
|
"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
|
# We estimate the cost of an appliance thermostat at £400, which is the upper end of the range
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contengency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
|
|
@ -837,6 +513,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
|
|
@ -875,6 +553,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"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,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
|
|
@ -894,6 +574,8 @@ class Costs:
|
||||||
# We estimate the labour hours to be 4
|
# We estimate the labour hours to be 4
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": 4,
|
"labour_hours": 4,
|
||||||
|
|
@ -913,6 +595,8 @@ class Costs:
|
||||||
# We estimate the labour hours to be 2
|
# We estimate the labour hours to be 2
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": 2,
|
"labour_hours": 2,
|
||||||
|
|
@ -931,6 +615,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": 0,
|
"labour_hours": 0,
|
||||||
|
|
@ -965,6 +651,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
|
|
@ -997,6 +685,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
|
|
@ -1025,6 +715,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
|
|
@ -1047,6 +739,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": removal_labour_hours,
|
"labour_hours": removal_labour_hours,
|
||||||
|
|
@ -1110,7 +804,6 @@ class Costs:
|
||||||
# To be pessimistic, assume 2 days work
|
# To be pessimistic, assume 2 days work
|
||||||
labour_cost = labour_rate * self.labour_adjustment_factor * labour_days
|
labour_cost = labour_rate * self.labour_adjustment_factor * labour_days
|
||||||
# Add contingency and preliminaries
|
# Add contingency and preliminaries
|
||||||
labour_cost = labour_cost * (1 + self.CONTINGENCY + self.PRELIMINARIES)
|
|
||||||
|
|
||||||
vat = labour_cost * self.VAT_RATE
|
vat = labour_cost * self.VAT_RATE
|
||||||
|
|
||||||
|
|
@ -1150,6 +843,8 @@ class Costs:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": total_cost,
|
||||||
|
"contingency": total_cost * self.CONTINGENCY,
|
||||||
|
"contingency_rate": self.CONTINGENCY,
|
||||||
"subtotal": subtotal_before_vat,
|
"subtotal": subtotal_before_vat,
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
|
|
@ -1169,19 +864,18 @@ class Costs:
|
||||||
else:
|
else:
|
||||||
cost = [x for x in INSTALLER_ASHP_COSTS if x][0]["cost"]
|
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
|
# The costs from installers exclude VAT
|
||||||
vat = subtotal * self.VAT_RATE
|
vat = cost * self.VAT_RATE
|
||||||
total_cost = subtotal + vat
|
cost = cost + vat
|
||||||
|
|
||||||
# We assume 5 days installation
|
# We assume 5 days installation
|
||||||
labour_days = 5
|
labour_days = 5
|
||||||
labour_hours = labour_days * 8
|
labour_hours = labour_days * 8
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": total_cost,
|
"total": cost,
|
||||||
"subtotal": subtotal,
|
"contingency": cost * self.CONTINGENCIES["air_source_heat_pump"],
|
||||||
|
"contingency_rate": self.CONTINGENCIES["air_source_heat_pump"],
|
||||||
"vat": vat,
|
"vat": vat,
|
||||||
"labour_hours": labour_hours,
|
"labour_hours": labour_hours,
|
||||||
"labour_days": labour_days,
|
"labour_days": labour_days,
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,6 @@ class FloorRecommendations(Definitions):
|
||||||
cost_result = self.costs.suspended_floor_insulation(
|
cost_result = self.costs.suspended_floor_insulation(
|
||||||
insulation_floor_area=self.property.insulation_floor_area,
|
insulation_floor_area=self.property.insulation_floor_area,
|
||||||
material=material.to_dict(),
|
material=material.to_dict(),
|
||||||
non_insulation_materials=non_insulation_materials
|
|
||||||
)
|
)
|
||||||
|
|
||||||
already_installed = "suspended_floor_insulation" in self.property.already_installed
|
already_installed = "suspended_floor_insulation" in self.property.already_installed
|
||||||
|
|
@ -213,7 +212,6 @@ class FloorRecommendations(Definitions):
|
||||||
cost_result = self.costs.solid_floor_insulation(
|
cost_result = self.costs.solid_floor_insulation(
|
||||||
insulation_floor_area=self.property.insulation_floor_area,
|
insulation_floor_area=self.property.insulation_floor_area,
|
||||||
material=material.to_dict(),
|
material=material.to_dict(),
|
||||||
non_insulation_materials=non_insulation_materials
|
|
||||||
)
|
)
|
||||||
|
|
||||||
already_installed = "solid_floor_insulation" in self.property.already_installed
|
already_installed = "solid_floor_insulation" in self.property.already_installed
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,8 @@ class HotwaterRecommendations:
|
||||||
"description_simulation": {
|
"description_simulation": {
|
||||||
"hot-water-energy-eff": "Poor"
|
"hot-water-energy-eff": "Poor"
|
||||||
},
|
},
|
||||||
"survey": survey
|
"survey": survey,
|
||||||
|
"innovation_rate": 0
|
||||||
}
|
}
|
||||||
if _return:
|
if _return:
|
||||||
return to_append
|
return to_append
|
||||||
|
|
@ -160,7 +161,8 @@ class HotwaterRecommendations:
|
||||||
"hot-water-energy-eff": self.property.data["hot-water-energy-eff"],
|
"hot-water-energy-eff": self.property.data["hot-water-energy-eff"],
|
||||||
"hotwater-description": new_epc_description,
|
"hotwater-description": new_epc_description,
|
||||||
},
|
},
|
||||||
"survey": survey
|
"survey": survey,
|
||||||
|
"innovation_rate": 0
|
||||||
}
|
}
|
||||||
if _return:
|
if _return:
|
||||||
return to_append
|
return to_append
|
||||||
|
|
@ -222,7 +224,8 @@ class HotwaterRecommendations:
|
||||||
"hot-water-energy-eff": simulation_config["hot_water_energy_eff_ending"],
|
"hot-water-energy-eff": simulation_config["hot_water_energy_eff_ending"],
|
||||||
"hotwater-description": new_epc_description,
|
"hotwater-description": new_epc_description,
|
||||||
},
|
},
|
||||||
"survey": False
|
"survey": False,
|
||||||
|
"innovation_rate": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
self.recommendations.append(to_append)
|
self.recommendations.append(to_append)
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,6 @@ class LightingRecommendations:
|
||||||
|
|
||||||
cost_result = self.costs.low_energy_lighting(
|
cost_result = self.costs.low_energy_lighting(
|
||||||
number_of_lights=number_non_lel_outlets,
|
number_of_lights=number_non_lel_outlets,
|
||||||
number_current_lel_lights=number_lighting_outlets - number_non_lel_outlets,
|
|
||||||
material=self.material
|
material=self.material
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ class SecondaryHeating:
|
||||||
},
|
},
|
||||||
"description_simulation": {
|
"description_simulation": {
|
||||||
"secondheat-description": "None"
|
"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
|
:param property_instance: Instance of the Property class, for the home associated to property_id
|
||||||
"""
|
"""
|
||||||
|
|
@ -44,6 +46,14 @@ class SolarPvRecommendations:
|
||||||
|
|
||||||
self.recommendation = []
|
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
|
@staticmethod
|
||||||
def trim_solar_wattage_options(scenarios_with_wattage):
|
def trim_solar_wattage_options(scenarios_with_wattage):
|
||||||
# Initialize the list with the first element, assuming the list is not empty
|
# 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():
|
if not self.property.is_solar_pv_valid():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
raise ValueError("DOO BUILDING")
|
||||||
|
|
||||||
# If we have a buiilding level analysis, we implement separate logic
|
# If we have a buiilding level analysis, we implement separate logic
|
||||||
if self.property.building_id is not None:
|
if self.property.building_id is not None:
|
||||||
self.recommend_building_analysis(phase)
|
self.recommend_building_analysis(phase)
|
||||||
|
|
@ -201,11 +213,22 @@ class SolarPvRecommendations:
|
||||||
# of this. This minimum is used in Recommendations.calculate_recommendation_impact
|
# 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
|
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(
|
cost_result = self.costs.solar_pv(
|
||||||
has_battery=has_battery,
|
product=solar_pv_product,
|
||||||
array_cost=non_invasive_recommendation.get("cost", None),
|
scaffolding_options=self.scaffolding_options,
|
||||||
n_panels=recommendation_config["n_panels"],
|
|
||||||
n_floors=self.property.number_of_floors
|
n_floors=self.property.number_of_floors
|
||||||
)
|
)
|
||||||
kw = np.floor(recommendation_config["array_wattage"] / 100) / 10
|
kw = np.floor(recommendation_config["array_wattage"] / 100) / 10
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue