udpating costing with installer quotes

This commit is contained in:
Khalim Conn-Kowlessar 2024-06-26 16:31:57 +01:00
parent 8fcae893c7
commit 8a5e98d3ba
5 changed files with 86 additions and 22 deletions

View file

@ -88,3 +88,4 @@ class Material(Base):
plant_cost = Column(Float)
total_cost = Column(Float)
notes = Column(String)
is_installer_quote = Column(Boolean, nullable=False, default=False)

View file

@ -284,16 +284,16 @@ async def trigger_plan(body: PlanTriggerRequest):
property_id, is_new = create_property(
session, body.portfolio_id, epc_searcher.address_clean, epc_searcher.postcode_clean, epc_searcher.uprn
)
if not is_new:
continue
create_property_targets(
session,
property_id=property_id,
portfolio_id=body.portfolio_id,
epc_target=body.goal_value,
heat_demand_target=None
)
# if not is_new:
# continue
#
# create_property_targets(
# session,
# property_id=property_id,
# portfolio_id=body.portfolio_id,
# epc_target=body.goal_value,
# heat_demand_target=None
# )
epc_records = {
'original_epc': epc_searcher.newest_epc.copy(),

View file

@ -7,10 +7,13 @@ from sqlalchemy.orm import Session
from sqlalchemy import create_engine
from backend.app.db.models.materials import Material
from recommendations.recommendation_utils import calculate_r_value_per_mm
import inspect
DATA_DIRECTORY = Path(__file__).parent / "local_data" / "Hestia Materials.xlsx"
src_file_path = inspect.getfile(lambda: None)
DATA_DIRECTORY = Path(src_file_path).parent / "local_data" / "20240626 Hestia Materials.xlsx"
# Environment file is at the same level as this file
ENV_FILE = Path(__file__).parent / "etl" / "costs" / ".env"
ENV_FILE = Path(src_file_path).parent / "etl" / "costs" / ".env"
dotenv.load_dotenv(ENV_FILE)
DB_USERNAME = os.getenv('DB_USERNAME')
@ -87,7 +90,8 @@ def app():
solid_floor_costs,
ewi_costs,
lel_costs,
flat_roof_costs
flat_roof_costs,
window_costs
]
)

View file

@ -6,6 +6,14 @@ from etl.non_intrusive_surveys.upload.UploadNonIntrusives import UploadNonIntrus
PORTFOLIO_ID = 82
USER_ID = 8
already_installed = [
{
'address': 'Flat 3 2 Linacre Lane',
'postcode': 'L20 5AH',
"already_installed": ["windows_glazing"]
}
]
def app():
"""
@ -91,6 +99,14 @@ def app():
asset_list = pd.DataFrame(asset_list)
# Store overrides in s3
already_installed_filename = f"{USER_ID}/{PORTFOLIO_ID}/already_installed.json"
save_csv_to_s3(
dataframe=pd.DataFrame(already_installed),
bucket_name="retrofit-plan-inputs-dev",
file_name=already_installed_filename
)
# Store the asset list in s3
filename = f"{USER_ID}/{PORTFOLIO_ID}/non_intrusives.csv"
save_csv_to_s3(
@ -105,7 +121,7 @@ def app():
"goal": "Increase EPC",
"goal_value": "A",
"trigger_file_path": filename,
"already_installed_file_path": "",
"already_installed_file_path": already_installed_filename,
"patches_file_path": "",
"non_invasive_recommendations_file_path": "",
"budget": None,

View file

@ -104,9 +104,9 @@ DOUBLE_RADIATOR_COST = 300
FLUE_COST = 600
PIPEWORK_COST = 750 # Min cost is £500
# This is the cost per meter squared for cavity extraction
# https://www.checkatrade.com/blog/cost-guides/cavity-wall-insulation-removal-cost/
CAVITY_EXTRACTION_COST = 21.5
# Based on SCIS figures
# TODO: Add this to databse
CAVITY_EXTRACTION_COST = 25
class Costs:
@ -203,6 +203,20 @@ class Costs:
:return: A dictionary containing detailed cost breakdown.
"""
# CWI usually takes 1 day
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,
}
material_cost_per_m2 = material["material_cost"]
base_material_cost = material_cost_per_m2 * wall_area
@ -220,11 +234,6 @@ class Costs:
total_cost = subtotal_before_vat + vat_cost
labour_hours = material["labour_hours_per_unit"] * wall_area
# Assume a team of 2
labour_days = (labour_hours / 8) / 2
if is_extraction_and_refill:
# bump up the cost of the work
total_cost = total_cost + CAVITY_EXTRACTION_COST * wall_area
@ -314,6 +323,22 @@ class Costs:
: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"] * wall_area
labour_hours = material["labour_hours_per_unit"] * wall_area
# 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_cost,
"labour_hours": labour_hours,
"labour_days": labour_days,
}
# Extract and check the different types of data we'll need
demolition_data = [x for x in non_insulation_materials if x["type"] == "iwi_wall_demolition"]
vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "iwi_vapour_barrier"]
@ -619,6 +644,24 @@ class Costs:
:return:
"""
if material["is_installer_quote"]:
total_cost = material["total_cost"] * wall_area
# Add on a buffer for scaffolding
if self.property.data["property-type"] == "House":
total_cost += self.EWI_SCAFFOLDING_PRELIMINARIES * total_cost
labour_hours = material["labour_hours_per_unit"] * wall_area
# 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_cost,
"labour_hours": labour_hours,
"labour_days": labour_days,
}
# For semi detatched and detatched houses, as well as maisonettes, we price for scaffolding
if self.property.data["property-type"] == "House":