diff --git a/.idea/Model.iml b/.idea/Model.iml
index b03b31b1..05b9012b 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -7,7 +7,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index ca0e1cd9..3b05c6ac 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/backend/app/db/functions/materials_functions.py b/backend/app/db/functions/materials_functions.py
new file mode 100644
index 00000000..441d45a0
--- /dev/null
+++ b/backend/app/db/functions/materials_functions.py
@@ -0,0 +1,15 @@
+from backend.app.db.connection import db_engine
+from backend.app.db.models.materials import Material
+from sqlalchemy.orm import sessionmaker
+
+
+def get_materials():
+ """
+ This function will retrieve all materials from the database.
+ :return: A list of Material objects if successful, an empty list otherwise.
+ """
+ Session = sessionmaker(bind=db_engine)
+ with Session() as session:
+ materials = session.query(Material).all()
+
+ return materials if materials else []
diff --git a/backend/app/db/models/materials.py b/backend/app/db/models/materials.py
new file mode 100644
index 00000000..4c4a8a09
--- /dev/null
+++ b/backend/app/db/models/materials.py
@@ -0,0 +1,51 @@
+import enum
+
+from sqlalchemy import Column, Integer, String, Float, Enum, TIMESTAMP
+from sqlalchemy.orm import declarative_base
+from sqlalchemy.sql import func
+
+Base = declarative_base()
+
+
+class MaterialType(enum.Enum):
+ suspended_floor_insulation = "suspended_floor_insulation"
+ solid_floor_insulation = "solid_floor_insulation"
+ external_wall_insulation = "external_wall_insulation"
+ internal_wall_insulation = "internal_wall_insulation"
+
+
+class DepthUnit(enum.Enum):
+ mm = "mm"
+
+
+class CostUnit(enum.Enum):
+ gbp_sq_meter = "gbp_sq_meter"
+
+
+class RValueUnit(enum.Enum):
+ square_meter_kelvin_per_watt = "square_meter_kelvin_per_watt"
+
+
+class ThermalConductivityUnit(enum.Enum):
+ watt_per_meter_kelvin = "watt_per_meter_kelvin"
+
+
+class Material(Base):
+ __tablename__ = 'material'
+
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ type = Column(Enum(MaterialType, values_callable=lambda x: [e.value for e in x]), nullable=False)
+ description = Column(String, nullable=False)
+ depths = Column(String) # You may want to use a specific JSON type depending on the database
+ depth_unit = Column(Enum(DepthUnit, values_callable=lambda x: [e.value for e in x]), nullable=False)
+ cost = Column(Float)
+ cost_unit = Column(Enum(CostUnit, values_callable=lambda x: [e.value for e in x]), nullable=False)
+ r_value_per_mm = Column(Float)
+ r_value_unit = Column(Enum(RValueUnit, values_callable=lambda x: [e.value for e in x]), nullable=False)
+ thermal_conductivity = Column(Float)
+ thermal_conductivity_unit = Column(
+ Enum(ThermalConductivityUnit, values_callable=lambda x: [e.value for e in x]),
+ nullable=False
+ )
+ link = Column(String)
+ created_at = Column(TIMESTAMP, nullable=False, server_default=func.now())
diff --git a/backend/app/db/utils.py b/backend/app/db/utils.py
new file mode 100644
index 00000000..2b2f50b7
--- /dev/null
+++ b/backend/app/db/utils.py
@@ -0,0 +1,18 @@
+import enum
+
+
+def row2dict(row):
+ """
+ Generic function to convert a SQLAlchemy row to a dictionary.
+ May not be the best practice implementing like this but works for the moment
+ """
+
+ d = {}
+ for column in row.__table__.columns:
+ val = getattr(row, column.name)
+ if isinstance(val, enum.Enum):
+ val = val.value
+
+ d[column.name] = val
+
+ return d
diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py
index f06ca2ed..5fbc5f1a 100644
--- a/backend/app/plan/router.py
+++ b/backend/app/plan/router.py
@@ -11,17 +11,19 @@ from utils.logger import setup_logger
from recommendations.FloorRecommendations import FloorRecommendations
from recommendations.WallRecommendations import WallRecommendations
from utils.uvalue_estimates import classify_decile_newvalues
+from backend.app.db.utils import row2dict
+from starlette.responses import Response
# database interaction functions
from backend.app.db.functions.property_functions import (
create_property, create_property_targets, update_property_data, create_property_details_epc
)
+from backend.app.db.functions.materials_functions import get_materials
# TODO: This is placeholder until data is stored in DB
from backend.app.plan.uvalue_estimates_walls import uvalue_estimates_walls
from backend.app.plan.uvalue_estimates_floors import uvalue_estimates_floors
from backend.app.plan.temp_cleaned_data import cleaned
-from backend.app.plan.temp_materials_db import materials
logger = setup_logger()
@@ -81,10 +83,11 @@ lighting_averages = [
]
-def get_materials(materials):
+def filter_materials(materials):
materials_by_type = defaultdict(list)
for material in materials:
+ material = row2dict(material)
material_type = material["type"]
materials_by_type[material_type].append(material)
@@ -134,6 +137,9 @@ async def trigger_plan(body: PlanTriggerRequest):
)
)
+ if not input_properties:
+ return Response(status_code=204)
+
logger.info("Getting EPC data")
for p in input_properties:
p.search_address_epc()
@@ -155,11 +161,19 @@ async def trigger_plan(body: PlanTriggerRequest):
# The materials data could be cached or local so we don't need to make
# consistent requrests to the backend for
# the same data
- materials_by_type = get_materials(materials)
+ # TODO: It might not be the best choice to store the materials data in a database table since thi
+ # table probably won't be very large and won't be updated that often. It might be better to
+ # store this data in s3 load it into memory when the app starts up. We will test this
+
+ materials = get_materials()
+ materials_by_type = filter_materials(materials)
logger.info("Getting components and properties recommendations")
- recommendations = []
- for property_id, p in enumerate(input_properties):
+
+ recommendations = {}
+ for p in input_properties:
+ property_recommendations = []
+
# For each property, classiy floor area decide
total_floor_area_group_decile = classify_decile_newvalues(
decile_boundaries=floors_decile_data["decile_boundaries"],
@@ -187,11 +201,8 @@ async def trigger_plan(body: PlanTriggerRequest):
total_floor_area_group_decile=total_floor_area_group_decile
)
floor_recommender.recommend()
- # insert property id
- for rec in floor_recommender.recommendations:
- rec["property_id"] = property_id
- recommendations.extend(floor_recommender.recommendations)
+ property_recommendations.extend(floor_recommender.recommendations)
# Wall recommendations
# We would make this u-value query directly to the database
@@ -219,13 +230,12 @@ async def trigger_plan(body: PlanTriggerRequest):
materials=materials_by_type["external_wall_insulation"] + materials_by_type["internal_wall_insulation"]
)
wall_recomendations.recommend()
- # insert property id
- for rec in wall_recomendations.recommendations:
- rec["property_id"] = property_id
- recommendations.extend(wall_recomendations.recommendations)
+ property_recommendations.extend(wall_recomendations.recommendations)
- # Once we're done, we'll store:
+ recommendations[p.id] = property_recommendations
+
+ # Once we're done, we'll store:
# 1) the property data
# 2) the property details (epc)
# 3) the recommendations
@@ -238,4 +248,10 @@ async def trigger_plan(body: PlanTriggerRequest):
property_data = p.get_full_property_data()
update_property_data(property_id=p.id, portfolio_id=body.portfolio_id, property_data=property_data)
- return {"recommendations": recommendations}
+ # Upload recommendations
+ recommendations_to_upload = recommendations[p.id]
+ if not recommendations:
+ continue
+ # Create a plan
+
+ return Response(status_code=200)
diff --git a/backend/app/plan/temp_materials_db.py b/backend/app/plan/temp_materials_db.py
deleted file mode 100644
index 5305c674..00000000
--- a/backend/app/plan/temp_materials_db.py
+++ /dev/null
@@ -1,242 +0,0 @@
-suspended_floor_insulation_parts = [
- {
- # Example product
- # All product types here:
- # https://www.insulationsuperstore.co.uk/browse/insulation/brand/recticel/filterby/application/floors.html
- "id": 1,
- "type": "suspended_floor_insulation",
- "description": "Rigid Insulation Foam Boards",
- "depths": [25, 30, 40, 50, 60, 70, 75, 80, 90, 100, 110, 120, 130, 140, 150],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.04545454545454546,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.022,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.insulationsuperstore.co.uk/product/recticel-eurothane-general-purpose-pir-insulation"
- "-board-2400-x-1200-x-100mm.html"
- },
- {
- # All product types here:
- # https://www.insulationsuperstore.co.uk/browse/insulation/brand/rockwool/filterby/application/floors
- # /material/mineral-wool.html
- "id": 2,
- "type": "suspended_floor_insulation",
- "description": "Mineral Wool Floor Insulation",
- "depths": [25, 40, 50, 60, 75, 100],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.02857142857142857,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.035,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.insulationsuperstore.co.uk/product/rockwool-rwa45-acoustic-insulation-slab-100mm-2-88m2"
- "-pack.html"
- },
-]
-
-solid_floor_insulation_parts = [
- {
- # All product types here:
- # https://www.insulationexpress.co.uk/floor-insulation/solid-floor-insulation?brand=7015&p=1
- # Example screed https://www.screwfix.com/p/mapei-ultraplan-3240-self-levelling-compound-25kg/4959f
- "id": 3,
- "type": "solid_floor_insulation",
- "description": "Rigid Insulation Foam Boards with floor screed",
- "depths": [25, 50, 70, 75, 100],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.04545454545454546,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.052631578947368425,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.insulationexpress.co.uk/floor-insulation/solid-floor-insulation/k103-100mm"
- },
-
-]
-
-external_wall_insulation_parts = [
- {
- "id": 4,
- "type": "external_wall_insulation",
- "description": "Mineral Wool External Wall Insulation",
- "depths": [30, 50, 70, 80, 90, 100, 150, 200],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.0278,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.036,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://insulationgo.co.uk/100mm-rockwool-external-wall-insulation-dual-density-slabs-a1-non"
- "-combustible-slab-ewi-render-fire/"
- },
- {
- "id": 5,
- "type": "external_wall_insulation",
- "description": "Expanded Polystyrene External Wall Insulation",
- "depths": [25, 50, 100, 125],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.02703,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.037,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.insulationking.co.uk/products/polystyrene-eps70?variant=44156186558759"
- },
- {
- "id": 6,
- "type": "external_wall_insulation",
- "description": "Phenolic Foam External Wall Insulation",
- "depths": [20, 50, 100],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.043478260869565216,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.023,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.insulationshop.co/20mm_kooltherm_k5_external_wall_kingspan.html"
- },
- {
- "id": 7,
- "type": "external_wall_insulation",
- "description": "Polyisocyanurate/Polyurethane Foam External Wall Insulation",
- "depths": [],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": None,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": None,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": None
- },
- {
- "id": 8,
- "type": "external_wall_insulation",
- "description": "Wood Fiber External Wall Insulation",
- "depths": [40, 60],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.023255813953488375,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.043,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.mikewye.co.uk/product/steico-duo-dry/"
- },
- {
- "id": 9,
- "type": "external_wall_insulation",
- "description": "Aerogel External Wall Insulation",
- "depths": [10, 20, 30, 40, 50, 60, 70],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.06666666666666667,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.015,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.thermablok.co.uk/site/wp-content/uploads/2022/09/Thermablok-Aerogel-Insulation-Blanket"
- "-TDS-AIS-and-Steel-Related-Details.pdf"
- },
- {
- "id": 10,
- "type": "external_wall_insulation",
- "description": "Vacuum Insulation Panels External Wall Insulation",
- "depths": [45, 60],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.16666666666666666,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.006,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": None
- }
-]
-
-internal_wall_insulation_parts = [
- {
- "id": 11,
- "type": "internal_wall_insulation",
- "description": "Rigid Insulation Boards Internal Wall Insulation",
- "depths": [25, 40, 50, 75, 100],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.026315789473684213,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.038,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.insulationshop.co/25mm_polystyrene_insulation_eps_70jablite.html"
- },
- {
- "id": 12,
- "type": "internal_wall_insulation",
- "description": "Mineral Wool Internal Wall Insulation",
- "depths": [140],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.02857142857142857,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.035,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.rockwool.com/siteassets/rw-uk/downloads/datasheets/flexi.pdf"
- },
- {
- "id": 13,
- "type": "internal_wall_insulation",
- "description": "Insulated Plasterboard Internal Wall Insulation",
- "depths": [25, 80],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.02857142857142857,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.019,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.kingspan.com/gb/en/products/insulation-boards/wall-insulation-boards/kooltherm-k118"
- "-insulated-plasterboard/"
- },
- {
- "id": 14,
- "type": "internal_wall_insulation",
- "description": "Reflective Internal Wall Insulation",
- "depths": [],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": None,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": None,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": None
- },
- {
- "id": 15,
- "type": "internal_wall_insulation",
- "description": "Vacuum Insulation Panels Wall Insulation",
- "depths": [20, 30],
- "depth_unit": "mm",
- "cost": None,
- "cost_unit": None,
- "r_value_per_mm": 0.125,
- "r_value_unit": "square_meter_kelvin_per_watt",
- "thermal_conductivity": 0.008,
- "thermal_conductivity_unit": "watt_per_meter_kelvin",
- "link": "https://www.insulationsuperstore.co.uk/product/vacutherm-vacupor-nt-b2-vacuum-insulated-panel-1m-x"
- "-600mm-x-30mm.html"
- },
-]
-
-materials = (
- suspended_floor_insulation_parts + solid_floor_insulation_parts + external_wall_insulation_parts + \
- internal_wall_insulation_parts
-)
diff --git a/model_data/simulation_system/app.py b/model_data/simulation_system/app.py
index 62f5d2ff..c11e70eb 100644
--- a/model_data/simulation_system/app.py
+++ b/model_data/simulation_system/app.py
@@ -299,6 +299,7 @@ def app():
# Clean using averages
avgs = iterative_filtering(cleaning_averages, property_data)
+ # TODO: Should probably do a mean/median?
field_value = avgs[field].iloc[0]
if pd.isnull(field_value):
@@ -343,6 +344,7 @@ def app():
rdsap_change = ending_record[RDSAP_RESPONSE] - starting_record[RDSAP_RESPONSE]
heat_demand_change = ending_record[HEAT_DEMAND_RESPONSE] - starting_record[HEAT_DEMAND_RESPONSE]
+ # TODO: Should this be <= 0?
if rdsap_change == 0:
# Assumption: We aren't interested in records that exhibit no change
continue