diff --git a/.idea/Model.iml b/.idea/Model.iml
index 4413bb06..b0f9c00d 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -7,7 +7,7 @@
-
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 6f308057..1122b380 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py
index 100ecb15..7cfe023e 100644
--- a/recommendations/recommendation_utils.py
+++ b/recommendations/recommendation_utils.py
@@ -652,3 +652,64 @@ def esimtate_pitched_roof_area(floor_area: float, floor_height: float) -> float:
area = 2 * (slope * wall_width)
return area
+
+
+def estimate_windows(
+ property_type, built_form, construction_age_band, floor_area, number_habitable_rooms, extension_count
+):
+ # Base window count based on habitable rooms
+ window_count = number_habitable_rooms
+
+ # Additional windows for non-habitable rooms (e.g., kitchen, bathroom)
+ # Assuming most houses will have at least one kitchen and one bathroom
+ # Scale non-habitable windows with the number of habitable rooms
+ non_habitable_base = 2 # Base for kitchen and bathroom
+ extra_non_habitable = max(0, (number_habitable_rooms - 3) // 2) # Extra for large houses
+ window_count += non_habitable_base + extra_non_habitable
+
+ # Adjustments based on built form and property type
+ if property_type in ["House", "Bungalow"] and built_form in ["Semi-Detached", "Detached"]:
+ built_form_lookup = {
+ "Semi-Detached": 3,
+ "Detached": 4,
+ }
+ else:
+ # For Flats and Maisonettes, adjustments might be less
+ built_form_lookup = {
+ "Mid-Terrace": 0,
+ "End-Terrace": 1,
+ "Semi-Detached": 1,
+ "Detached": 2,
+ }
+ window_count += built_form_lookup.get(built_form, 0)
+
+ # Adjust for floor area (larger floor area might indicate more rooms/windows)
+ if floor_area < 85: # Small to medium properties
+ # Standard window count likely sufficient
+ pass
+ elif 85 <= floor_area <= 120: # Medium to large properties
+ # More rooms or larger rooms likely, potentially more windows
+ window_count += 1
+ elif floor_area > 120: # Very large properties
+ # Likely to have significantly more or larger rooms
+ window_count += 2
+
+ # Adjust for construction age band
+ if construction_age_band in ["England and Wales: before 1900", "England and Wales: 1900-1929"]:
+ # Older houses with smaller, more numerous windows
+ window_count += 1
+
+ # Adjust for extensions (each extension might add windows)
+ window_count += extension_count
+
+ # Adjustments for specific property types
+ if property_type in ["Flat", "Maisontte"]:
+ # Flats might have fewer windows due to shared walls
+ # Maisonettes might follow a similar pattern to flats or small houses
+ window_count -= 1
+
+ # Ensure window count is not negative
+ if window_count < 0:
+ raise ValueError("Window count cannot be negative.")
+
+ return window_count
diff --git a/recommendations/tests/test_recommendation_utils.py b/recommendations/tests/test_recommendation_utils.py
index aefc70b0..7c29bbb0 100644
--- a/recommendations/tests/test_recommendation_utils.py
+++ b/recommendations/tests/test_recommendation_utils.py
@@ -427,3 +427,94 @@ def test_external_wall_area():
for num_floors, floor_height, perimeter, built_form, expected in test_cases:
result = recommendation_utils.estimate_external_wall_area(num_floors, floor_height, perimeter, built_form)
assert result == expected, f"Test failed for {built_form}: Expected {expected}, got {result}"
+
+
+def test_estimate_windows():
+ # Based on data from an EPR that has 4 windows
+ windows_case_1 = recommendation_utils.estimate_windows(
+ property_type="Flat",
+ built_form="Semi-Detached",
+ construction_age_band="England and Wales: 1976-1982",
+ floor_area=37,
+ number_habitable_rooms=2,
+ extension_count=0,
+ )
+
+ assert windows_case_1 == 4, f"Expected 4 windows, got {windows_case_1}"
+
+ # Based on data from an EPR that has 7 winows, however two of the windows were very small, having areas of
+ # 0.21m^2 and 0.3m^2 respectively. We see 6 as a reasonable estimate for the number of windows
+ windows_case_2 = recommendation_utils.estimate_windows(
+ property_type="House",
+ built_form="Mid-Terrace",
+ construction_age_band="England and Wales: 1950-1966",
+ floor_area=69,
+ number_habitable_rooms=4,
+ extension_count=0,
+ )
+
+ assert windows_case_2 == 6, f"Expected 6 windows, got {windows_case_2}"
+
+ # Based on data from an EPR on a bungalow, that has 6 windows. Two of the windows are small, both have a 0.4m^2 area
+ # and so 5 windows is an acceptable estimate
+ windows_case_3 = recommendation_utils.estimate_windows(
+ property_type="Bungalow",
+ built_form="Mid-Terrace",
+ construction_age_band="England and Wales: 1967-1975",
+ floor_area=56,
+ number_habitable_rooms=3,
+ extension_count=0,
+ )
+
+ assert windows_case_3 == 5, f"Expected 5 windows, got {windows_case_3}"
+
+ # Based on data from an EPR on a end terrace house that has 8 windows. One of the windows is very small, with an
+ # area of 0.25 m^2 and so 7 windows is an acceptable estimate
+ windows_case_4 = recommendation_utils.estimate_windows(
+ property_type="House",
+ built_form="End-Terrace",
+ construction_age_band="England and Wales: 1967-1975",
+ floor_area=77.28,
+ number_habitable_rooms=4,
+ extension_count=0,
+ )
+
+ assert windows_case_4 == 7, f"Expected 7 windows, got {windows_case_4}"
+
+ # Based on data from an EPR on a Semi-detatched house that has 11 windows based on the associated condition report
+ # Right now, we estimate 12 windows for this property
+ windows_case_5 = recommendation_utils.estimate_windows(
+ property_type="House",
+ built_form="Semi-Detached",
+ construction_age_band="England and Wales: 1950-1966",
+ floor_area=88.4,
+ number_habitable_rooms=5,
+ extension_count=0,
+ )
+
+ assert windows_case_5 == 12, f"Expected 12 windows, got {windows_case_5}"
+
+ # Based on Khalim's flat which has 3 windows. There is no construction age band on the EPC. The windows are large
+ # so an estimate of 5 windows is a reasonable estimate
+ windows_case_6 = recommendation_utils.estimate_windows(
+ property_type="Flat",
+ built_form="",
+ construction_age_band="",
+ floor_area=100,
+ number_habitable_rooms=3,
+ extension_count=0,
+ )
+
+ assert windows_case_6 == 5, f"Expected 5 windows, got {windows_case_6}"
+
+ # Based on an EPR semi detatched house though we don't have the exact number of windows. We estimate 10
+ windows_case_7 = recommendation_utils.estimate_windows(
+ property_type="House",
+ built_form="Semi-Detached",
+ construction_age_band="England and Wales: 1967-1975",
+ floor_area=85,
+ number_habitable_rooms=4,
+ extension_count=0,
+ )
+
+ assert windows_case_7 == 10, f"Expected 10 windows, got {windows_case_7}"