diff --git a/.idea/Model.iml b/.idea/Model.iml
index b0f9c00d..4413bb06 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -7,7 +7,7 @@
-
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 1122b380..6f308057 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/etl/costs/app.py b/etl/costs/app.py
index 4d53ce28..30eff735 100644
--- a/etl/costs/app.py
+++ b/etl/costs/app.py
@@ -75,6 +75,7 @@ def app():
ewi_costs = pd.read_excel(DATA_DIRECTORY, sheet_name="external_wall_insulation", header=0)
lel_costs = pd.read_excel(DATA_DIRECTORY, sheet_name="low_energy_lighting", header=0)
flat_roof_costs = pd.read_excel(DATA_DIRECTORY, sheet_name="flat_roof_insulation", header=0)
+ window_costs = pd.read_excel(DATA_DIRECTORY, sheet_name="window_glazing", header=0)
# Form a single table to be uploaded
costs = pd.concat(
diff --git a/etl/epc_clean/epc_attributes/WindowAttributes.py b/etl/epc_clean/epc_attributes/WindowAttributes.py
index e962cd31..cd767359 100644
--- a/etl/epc_clean/epc_attributes/WindowAttributes.py
+++ b/etl/epc_clean/epc_attributes/WindowAttributes.py
@@ -52,7 +52,7 @@ class WindowAttributes(Definitions):
raise ValueError('Invalid description')
def process(self) -> Dict[str, Union[str, bool]]:
- result: Dict[str, Union[str, bool]] = {
+ result: Dict[str, Union[str, bool, None]] = {
"has_glazing": False,
"glazing_coverage": None,
"glazing_type": None,
@@ -83,4 +83,8 @@ class WindowAttributes(Definitions):
if not result["glazing_coverage"]:
result["glazing_coverage"] = "full"
+ # We reset some values if the glazing is single
+ if result["glazing_type"] == "single":
+ result["has_glazing"] = False
+
return result
diff --git a/etl/epc_clean/tests/test_data/test_window_attributes_cases.py b/etl/epc_clean/tests/test_data/test_window_attributes_cases.py
index 1eeeee21..f01ccba9 100644
--- a/etl/epc_clean/tests/test_data/test_window_attributes_cases.py
+++ b/etl/epc_clean/tests/test_data/test_window_attributes_cases.py
@@ -30,7 +30,8 @@ windows_cases = [
'glazing_type': 'triple', 'no_data': False},
{'original_description': 'Gwydrau triphlyg rhannol', 'has_glazing': True, 'glazing_coverage': 'partial',
'glazing_type': 'triple', 'no_data': False},
- {'original_description': 'Single glazed', 'has_glazing': True, 'glazing_coverage': 'full', 'glazing_type': 'single',
+ {'original_description': 'Single glazed', 'has_glazing': False, 'glazing_coverage': None,
+ 'glazing_type': 'single',
'no_data': False},
{'original_description': 'Some double glazing', 'has_glazing': True, 'glazing_coverage': 'partial',
'glazing_type': 'double', 'no_data': False},
@@ -46,7 +47,8 @@ windows_cases = [
'glazing_type': 'double', 'no_data': False},
{'original_description': 'Gwydrau dwbl gan mwyaf', 'has_glazing': True, 'glazing_coverage': 'most',
'glazing_type': 'double', 'no_data': False},
- {'original_description': 'Gwydrau sengl', 'has_glazing': True, 'glazing_coverage': 'full', 'glazing_type': 'single',
+ {'original_description': 'Gwydrau sengl', 'has_glazing': False, 'glazing_coverage': None,
+ 'glazing_type': 'single',
'no_data': False},
{'original_description': 'Ffenestri perfformiad uchel', 'has_glazing': True, 'glazing_coverage': 'full',
'glazing_type': 'high performance', 'no_data': False},
diff --git a/recommendations/Costs.py b/recommendations/Costs.py
index 0d9031b2..84a7468c 100644
--- a/recommendations/Costs.py
+++ b/recommendations/Costs.py
@@ -64,6 +64,12 @@ class Costs:
VAT_RATE = 0.2
PROFIT_MARGIN = 0.2
+ # Based on this greenmatch article, on average, a Sash window is around 50% more expensive than a casement window.
+ # Therefore, for a conservative cost estimate, and allowance for a more premium window type, we inflate the material
+ # cost of the windows to allow for a sash window type
+ # https://www.greenmatch.co.uk/windows/double-glazing/cost
+ SASH_WINDOW_INFLATION_FACTOR = 1.5
+
def __init__(self, property_instance):
"""
Initializes the Costs class with a property instance.
@@ -719,3 +725,65 @@ class Costs:
"labour_days": labour_days,
"labour_cost": labour_costs
}
+
+ def window_glazing(self, number_of_windows, material):
+ """
+ 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
+
+ :param number_of_windows:
+ :return:
+ """
+
+ number_of_windows = 7
+ windows_cost = 345
+
+ material_cost = windows_cost * number_of_windows * self.SASH_WINDOW_INFLATION_FACTOR
+
+ subtotal = material_cost
+
+ contingency_cost = subtotal * 0.2
+ preliminaries_cost = subtotal * 0.2
+ profit_cost = subtotal * 0.2
+
+ subtotal_before_vat = subtotal + contingency_cost + preliminaries_cost + profit_cost
+
+ vat_cost = subtotal_before_vat * 0.2
+
+ total_cost = subtotal_before_vat + vat_cost
diff --git a/recommendations/WindowsRecommendations.py b/recommendations/WindowsRecommendations.py
index e5a11f8a..d3424d1b 100644
--- a/recommendations/WindowsRecommendations.py
+++ b/recommendations/WindowsRecommendations.py
@@ -9,7 +9,7 @@ class WindowsRecommendations:
self.property = property_instance
self.costs = Costs(self.property)
- self.recommendations = []
+ self.recommendation = []
self.glazing_materials = [
material for material in materials if material["type"] == "window_glazing"
@@ -29,5 +29,28 @@ class WindowsRecommendations:
:return:
"""
- if not self.property.number_of_windows:
+ number_of_windows = self.property.number_of_windows
+
+ if not number_of_windows:
raise ValueError("Number of windows not specified")
+
+ if self.property.windows["has_glazing"] & self.property.windows["glazing_coverage"] == "full":
+ return
+
+ # We then price the job based on the number of windows that there are
+
+ cost_result = {}
+
+ description = None
+
+ self.recommendation = [
+ {
+ "parts": [],
+ "type": "window_glazing",
+ "description": description,
+ "starting_u_value": None,
+ "new_u_value": None,
+ "sap_points": None,
+ **cost_result
+ }
+ ]
diff --git a/recommendations/tests/test_window_recommendations.py b/recommendations/tests/test_window_recommendations.py
new file mode 100644
index 00000000..da50d25c
--- /dev/null
+++ b/recommendations/tests/test_window_recommendations.py
@@ -0,0 +1,30 @@
+from recommendations.WindowsRecommendations import WindowsRecommendations
+from backend.Property import Property
+from unittest.mock import Mock
+
+
+class TestWindowRecommendations:
+
+ def test_fully_single_glazed(self):
+ """
+ For this property, we expect all windows to be single glazed and should recommend full double glazing
+ :return:
+ """
+
+ property_1 = Property(
+ id=1,
+ postcode='1',
+ address1='1',
+ epc_client=Mock(),
+ data={
+ "county": "Wychavon"
+ }
+ )
+ property_1.windows = {
+ 'original_description': 'Single glazed', 'has_glazing': False, 'glazing_coverage': 'full',
+ 'glazing_type': 'single',
+ 'no_data': False
+ }
+ property_1.number_of_windows = 5
+
+ recommender = WindowsRecommendations(property_instance=property_1, materials=[])