mirror of
https://github.com/Hestia-Homes/survey-extraction.git
synced 2026-07-22 00:38:55 +00:00
Merge pull request #62 from Hestia-Homes/feature/condition_report_etl
Feature/condition report etl
This commit is contained in:
commit
188972ce64
21 changed files with 2377 additions and 27 deletions
|
|
@ -22,7 +22,8 @@
|
|||
"lindacong.vscode-book-reader",
|
||||
"4ops.terraform",
|
||||
"fabiospampinato.vscode-todo-plus",
|
||||
"jgclark.vscode-todo-highlight"
|
||||
"jgclark.vscode-todo-highlight",
|
||||
"corentinartaud.pdfpreview"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
etl/condition_report_etl.py
Normal file
6
etl/condition_report_etl.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from etl.surveyedData.surveryedData import surveyedDataProcessor
|
||||
condition_report_file_path = "/workspaces/survey-extractor/etl/files/osmosis_condition_report.pdf"
|
||||
sdp = surveyedDataProcessor("123 Fake Street", [condition_report_file_path])
|
||||
|
||||
|
||||
# TODO: add the ability to add document type, and sharepoint or s3 link so we can process access it again
|
||||
|
|
@ -63,6 +63,7 @@ for pipeline in pipelines.results:
|
|||
for stage in pipeline.stages:
|
||||
if stage.label.upper().strip() not in [s.upper() for s in exclude_stage.get(pipeline_name, [])]:
|
||||
for deals in hubspot.get_all_deals_from_stage_id(stage.id):
|
||||
print(f"Scraping deal {deals['deal_id']}")
|
||||
time.sleep(1)
|
||||
deal_notes_by_week = {"Week 1": [], "Week 2": [], "Week 3": []}
|
||||
notes = hubspot.get_notes_from_deals_id(deals["deal_id"])
|
||||
|
|
@ -71,7 +72,12 @@ for pipeline in pipelines.results:
|
|||
week_label = get_week_label(note["created_at"])
|
||||
if not week_label:
|
||||
continue
|
||||
html_body = note['note']
|
||||
html_body = note.get('note')
|
||||
if not html_body:
|
||||
print(f"Skipping note with missing 'note' field: {note}")
|
||||
continue
|
||||
|
||||
print(f"Debugging purposes html_body looks like {html_body}")
|
||||
soup = BeautifulSoup(html_body, "html.parser")
|
||||
plain_text = soup.get_text(separator="\n")
|
||||
deal_notes_by_week[week_label].append(plain_text)
|
||||
|
|
@ -85,11 +91,12 @@ for pipeline in pipelines.results:
|
|||
except:
|
||||
owner_name = "Couldn't find owner information"
|
||||
|
||||
# Unique identifier to Domna Homes' hubspot
|
||||
portal_id = 145275138
|
||||
|
||||
notes_data[pipeline_name].append({
|
||||
"Deal Name": deal_name.upper(),
|
||||
"Deal URL": f"https://app-eu1.hubspot.com/contacts/{portal_id}/record/0-3/{deals["deal_id"]}/",
|
||||
"Deal URL": f"https://app-eu1.hubspot.com/contacts/{portal_id}/record/0-3/{deals['deal_id']}/",
|
||||
"Deal Owner": owner_name,
|
||||
"Deal Stage": stage.label.upper(),
|
||||
"Value": deals["value"],
|
||||
|
|
|
|||
BIN
etl/files/osmosis_condition_report.pdf
Normal file
BIN
etl/files/osmosis_condition_report.pdf
Normal file
Binary file not shown.
|
|
@ -34,4 +34,5 @@ class Documents(BaseModel, table=True):
|
|||
target_table: str
|
||||
target_id: uuid.UUID
|
||||
|
||||
Documents.update_forward_refs()
|
||||
Documents.update_forward_refs()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ from etl.scraper.scraper import SharePointScraper
|
|||
# board_id = "4965130190"
|
||||
# liz green
|
||||
board_id = "6097500103"
|
||||
# Cocuun
|
||||
# board_id = "5185076280"
|
||||
monday_key = "eyJhbGciOiJIUzI1NiJ9.eyJ0aWQiOjQ5ODc2ODQxOCwiYWFpIjoxMSwidWlkIjozNjE3ODAzNCwiaWFkIjoiMjAyNS0wNC0xMVQxMToyMzoxNy40NjdaIiwicGVyIjoibWU6d3JpdGUiLCJhY3RpZCI6MTM5OTc4MjMsInJnbiI6InVzZTEifQ.-2Lit4s46ZF6AXuMW9t0TxIaFLkHqD4Yo-PyM9i2XZY"
|
||||
monday = MondayClient(monday_key)
|
||||
|
||||
|
|
@ -24,6 +26,7 @@ parent_folder = "/Osmosis ACD/Osmosis ACD Projects/"
|
|||
# Change this per installer
|
||||
# parent_folder += "Stonewater/Wates/REDO"
|
||||
parent_folder += "Installer Documentation/Platform Housing Group/Liv Green"
|
||||
# parent_folder += "Installer Documentation/Platform Housing Group/Cocuun/REDO"
|
||||
|
||||
import re
|
||||
|
||||
|
|
@ -32,7 +35,7 @@ def sanitize_name(name: str, ignore_dot = False) -> str:
|
|||
if ignore_dot:
|
||||
name = ''.join(char for char in name if char.isalnum() or char.isspace() or char == ")" or char == "(").strip()
|
||||
else:
|
||||
name = ''.join(char for char in name if char.isalnum() or char.isspace() or char == "." or char == ")" or char == "(").strip()
|
||||
name = ''.join(char for char in name if char.isalnum() or char.isspace() or char == "." or char == ")" or char == "(" or char == "-" or char == "_").strip()
|
||||
|
||||
name = re.sub(r'\s+', ' ', name)
|
||||
# Remove or replace other SharePoint-invalid characters if necessary
|
||||
|
|
@ -138,7 +141,7 @@ if not name_id or not files_id:
|
|||
|
||||
items = get_all_items(board_id, monday)
|
||||
for i,item in enumerate(tqdm(items)):
|
||||
if i>123:
|
||||
if i>329:
|
||||
item_name = item["name"]
|
||||
item_name = sanitize_name(item_name, ignore_dot=True)
|
||||
print(f"Item name is {item_name}")
|
||||
|
|
@ -157,6 +160,4 @@ for i,item in enumerate(tqdm(items)):
|
|||
if to_upload:
|
||||
upload_to_sharepoint(to_upload, item_name)
|
||||
|
||||
|
||||
|
||||
# Liv green # Cocuun # Wates
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from etl.utils.logger import Logger
|
||||
import logging
|
||||
import pymupdf
|
||||
from etl.pdfReader.sitenotes import QuidosSiteNotesExtractor, CSR
|
||||
from etl.pdfReader.sitenotes import QuidosSiteNotesExtractor, CSR, ConditionReport
|
||||
from etl.pdfReader.reportType import ReportType
|
||||
|
||||
class pdfReaderToText():
|
||||
|
|
@ -40,6 +40,8 @@ class pdfReaderToText():
|
|||
self.type = ReportType.ENERGY_PERFORMANCE_REPORT
|
||||
elif "Chartered Surveyor Report: Recommending Extraction of Defective Cavity Wall Insulation " in self.text_list:
|
||||
self.type = ReportType.CHARTED_SURVEYOR_REPORT
|
||||
elif "Osmosis ACD NEW PAS 2035 Condition Report".lower() in self.text_list[0].lower():
|
||||
self.type = ReportType.OSMOSIS_CONDITION_PAS_2035_REPORT
|
||||
else:
|
||||
pass
|
||||
return self.type
|
||||
|
|
@ -51,4 +53,6 @@ class pdfReaderToText():
|
|||
return QuidosSiteNotesExtractor(self.text_list)
|
||||
elif self.type == ReportType.CHARTED_SURVEYOR_REPORT:
|
||||
return CSR(self.text_list)
|
||||
elif self.type == ReportType.OSMOSIS_CONDITION_PAS_2035_REPORT:
|
||||
return ConditionReport(self.text_list)
|
||||
|
||||
|
|
@ -6,4 +6,5 @@ class ReportType(Enum):
|
|||
CHARTED_SURVEYOR_REPORT = "charted_surveyor_report"
|
||||
ENERGY_PERFORMANCE_REPORT = "energy_performance_report"
|
||||
U_VALUE_CALCULATOR_REPORT = "u_value_calculator_report"
|
||||
OVERWRITING_U_VALUE_DECLARATION_FORM = "overwriting_u_value_declaration_form"
|
||||
OVERWRITING_U_VALUE_DECLARATION_FORM = "overwriting_u_value_declaration_form"
|
||||
OSMOSIS_CONDITION_PAS_2035_REPORT = "osmosis_condition_pas_2035_report"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,16 @@ from etl.transform.preSiteNoteTypes import (
|
|||
WindTurbine, OtherDetails, Windows, Heating, HeatingSystemControls,
|
||||
HeatingType, Insulation
|
||||
)
|
||||
from etl.transform.conditionReportTypes import (
|
||||
ConditionReport, AssessorDetails, InspectionAndProject, TheProperty,
|
||||
MainElevation, Elevation, ElevationInfo, PropertyAccess, ExternalElevation, ExternalElevationFront,
|
||||
ExternalElevationGableOne, ExternalElevationGableTwo, ExternalElevationRear, ConservatoryOrOutbuilding,
|
||||
AccessAndElevations, Hallway, RoomInfo, WindowsInfo, VentilationInfo, LivingRoom, DiningRoom, Kitchen, Rooms,
|
||||
Utility, WC, Landing, Bedroom, Bathroom, LoftSpace, RoomInRoof, HeatingSystem, GeneralConditionHeatingSystem,
|
||||
MainHeatingOne, MainHeatingTwo, SecondaryHeating, HeatingByRoom, Renewables, Occupant, EnergyUse, Heating, ShowerAndBath, FridgeAndFreezers, Cooker, TumbleDryer
|
||||
)
|
||||
from datetime import datetime
|
||||
from pprint import pprint
|
||||
|
||||
class SiteNotesExtractor():
|
||||
def __init__(self, data_list):
|
||||
|
|
@ -18,6 +27,25 @@ class SiteNotesExtractor():
|
|||
return [i for i, v in enumerate(lst) if v == value][x]
|
||||
except IndexError:
|
||||
return None # Return None if the value does not occur twice
|
||||
|
||||
def get_x_value(self, lst, value, x=1):
|
||||
index = self.get_x_occurance(lst, value, x)
|
||||
return lst[index+1]
|
||||
|
||||
|
||||
def get_next_value(self, lst, value, avoid=[]):
|
||||
get_value = lambda key: None if lst[lst.index(value) + 1] in avoid else lst[lst.index(key) + 1]
|
||||
return get_value(value)
|
||||
|
||||
def get_next_value_greedy(self, lst, value, upto):
|
||||
index = lst.index(value) + 1
|
||||
str = ""
|
||||
for i in range(upto):
|
||||
str += lst[index+i]
|
||||
str += " "
|
||||
|
||||
return str[:-1]
|
||||
|
||||
|
||||
def two_columns_processor(self, data, sub_titles_to_gather, avoid, indexAdd = 1):
|
||||
def get_value(key):
|
||||
|
|
@ -58,10 +86,583 @@ class CSR(SiteNotesExtractor):
|
|||
self.insulation_info = Insulation(
|
||||
type=dict_.get('detailed_description_of_existing_cavity_wall_insulation_', "")
|
||||
) if dict_ is not None else None
|
||||
|
||||
|
||||
|
||||
class ConditionReport(SiteNotesExtractor):
|
||||
def __init__(self, data_list):
|
||||
super().__init__(data_list)
|
||||
self.type = ReportType.OSMOSIS_CONDITION_PAS_2035_REPORT
|
||||
self.setup()
|
||||
|
||||
def setup(self):
|
||||
assesor_details, inspection_and_project, the_property, main_elevation, elevations = self.get_section_1()
|
||||
access_and_elevations = self.get_section_2()
|
||||
rooms = self.get_section_3()
|
||||
general_condition_of_heating_system, main_heating_one, main_heating_two, secondary_heating, heating_by_room, renewables = self.get_section_4()
|
||||
occupants, energy_use, heating, shower_and_bath, appliances, fridge_and_freezers, cooker, tumble_dryer = self.get_section_5()
|
||||
site_name, reference_code, address, postcode = self.get_section_0()
|
||||
|
||||
# TODO: Complete this function
|
||||
self.make_condition_report_object(
|
||||
assesor_details,
|
||||
inspection_and_project,
|
||||
the_property,
|
||||
main_elevation,
|
||||
elevations,
|
||||
access_and_elevations,
|
||||
rooms,
|
||||
|
||||
)
|
||||
|
||||
def get_section_0(self):
|
||||
data = self.get_data_between("Project Site Name", "1. General Information")
|
||||
site_name = self.get_next_value(data, "Project Site Name")
|
||||
reference_code = self.get_next_value(data, "Property Reference Code")
|
||||
address = self.get_next_value(data, "Property Address")
|
||||
postcode = self.get_data_between("Postcode", "Main Image")[1:]
|
||||
postcode = " ".join(postcode)
|
||||
|
||||
return site_name, reference_code, address, postcode
|
||||
|
||||
def get_section_1(self):
|
||||
assessor_details = self.get_assessor_details()
|
||||
inspection_and_project = self.get_inspection_and_project()
|
||||
the_property = self.get_the_property()
|
||||
main_elevation = self.get_main_elevation()
|
||||
elevations = self.get_all_elevations()
|
||||
return assessor_details, inspection_and_project, the_property, main_elevation, elevations
|
||||
|
||||
def get_assessor_details(self):
|
||||
data = self.get_data_between("1.1 Assessor details","1.2 Inspection & Project")
|
||||
assessor_name = self.get_next_value(data, "Assessor Name & ID")
|
||||
elmhirst_id = self.get_next_value(data, "Please enter name & Elmhurst ID")
|
||||
return AssessorDetails(assessor_name_and_id=assessor_name, elmhurst_id=elmhirst_id)
|
||||
|
||||
def get_inspection_and_project(self):
|
||||
data = self.get_data_between("1.2 Inspection & Project", "1.3 The property")
|
||||
date = self.get_next_value(data, "Inspection Date")
|
||||
return InspectionAndProject(inspection_date=date)
|
||||
|
||||
def get_the_property(self):
|
||||
data = self.get_data_between("1.3 The property", "Main elevation")
|
||||
return TheProperty(
|
||||
house_type=self.get_next_value(data, "House Type"),
|
||||
on_which_floor_is_the_flat_located = self.get_next_value(data, "On which floor is the flat located?"),
|
||||
is_there_a_corridor = True if self.get_next_value(data, "Is there a corridor?").lower() == "yes" else False,
|
||||
is_it_heated = True if self.get_next_value(data, "Is it heated?").lower() == "yes" else False,
|
||||
it_there_a_balcony = True if self.get_next_value(data, "Is there a balcony?").lower() == "yes" else False,
|
||||
classification_type = self.get_next_value(data, "Classification Type"),
|
||||
orientation_front_elevation = self.get_next_value(data, "Orientation (front elevation)"),
|
||||
orientation_in_degrees_front_elevation = self.get_next_value(data, "Orientation in degrees (front elevation)"),
|
||||
exposure_zone = self.get_next_value(data, "Exposure Zone"),
|
||||
main_wall_construction = self.get_next_value(data, "Main Wall Construction")
|
||||
)
|
||||
|
||||
def get_main_elevation(self):
|
||||
data = self.get_data_between("Main elevation", "Elevation")
|
||||
elevation = ElevationInfo(
|
||||
elevation_type=self.get_next_value(data, "Elevation type"),
|
||||
cavity_wall_depth=self.get_next_value(data, "Cavity wall depth (in mm)"),
|
||||
is_insulation_present=True if self.get_next_value(data, "Is insulation present?").lower() == "yes" else False,
|
||||
insulation_type= self.get_next_value(data, "Insulation type"),
|
||||
)
|
||||
return MainElevation(
|
||||
elevation_info=elevation
|
||||
)
|
||||
|
||||
|
||||
def get_all_elevations(self):
|
||||
elevations = []
|
||||
i = 1
|
||||
while(f"Elevation {i}" in self.raw_data):
|
||||
i += 1
|
||||
|
||||
no_of_elevation = i-1
|
||||
print(f"There are {no_of_elevation} elevation")
|
||||
|
||||
for i in range(no_of_elevation):
|
||||
data = self.get_data_between(f"Elevation {i+1}", "2. Access & Elevations")
|
||||
elevations.append(ElevationInfo(
|
||||
elevation_type=self.get_next_value(data, "Elevation type"),
|
||||
cavity_wall_depth=self.get_next_value(data, "Cavity wall depth (in mm)"),
|
||||
is_insulation_present=True if self.get_next_value(data, "Is insulation present?").lower() == "yes" else False,
|
||||
insulation_type= self.get_next_value(data, "Insulation type"),
|
||||
))
|
||||
|
||||
return Elevation(
|
||||
info=elevations,
|
||||
protected_conservatory_or_aonb=self.get_next_value(data, "the Significance Survey below is required."),
|
||||
material_type=self.get_next_value(data, "Material Type"),
|
||||
are_there_any_visible_signs_of_existing_wall_insulation=self.get_next_value(data, "Are there any visible signs of existing wall insulation?"),
|
||||
does_the_existing_ground_level_on_any_elevation_bridge_the_dpc=True if self.get_next_value(data, "DPC?").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
def get_section_2(self):
|
||||
pa = self.get_property_access()
|
||||
front,one,back, two = self.get_external_elevation()
|
||||
co = self.get_conservatory_or_outbuilding()
|
||||
|
||||
return AccessAndElevations(
|
||||
property_access=pa,
|
||||
external_elevation_front=front,
|
||||
external_elevation_back=back,
|
||||
external_elevation_gable_one=one,
|
||||
external_elevation_gable_two=two,
|
||||
conservatory_or_out_building=co,
|
||||
)
|
||||
|
||||
def get_property_access(self):
|
||||
data = self.get_data_between("2.1 Property Access", "2.2 External Elevations")
|
||||
property_access = PropertyAccess(
|
||||
are_there_any_road_restriction_in_the_locality=True if self.get_next_value(data, "locality?").lower() == "yes" else False,
|
||||
is_on_street_parking_available=True if self.get_next_value(data, "Is on-street parking available?").lower() == "yes" else False,
|
||||
are_there_any_overhead_wires_or_cables=True if self.get_next_value(data, "Are there any overhead wires or cables?") == "yes" else False,
|
||||
is_the_access_gated=True if self.get_next_value(data, "Is the access gated?").lower() == "yes" else False,
|
||||
is_there_restricted_space_for_contractors_to_access_the_wall_area=True if self.get_next_value(data, "area?").lower() == "yes" else False,
|
||||
is_there_restricted_space_for_contractors_to_access_the_roof_area=True if self.get_x_value(data, "area?", 1).lower() == "yes" else False,
|
||||
is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighbouring_boundary_along_the_full_gable_elevation=True if self.get_next_value(data, "boundary along the full gable elevation?").lower() == "yes" else False,
|
||||
is_access_to_the_rear_provided_by_use_of_a_ginnel=True if self.get_next_value(data, "Is access to the rear provided by use of a ginnel?").lower() == "yes" else False,
|
||||
is_access_to_the_rear_provided_by_use_of_a_secured_alleyway=True if self.get_next_value(data, "Is access to the rear provided by use of a secured alleyway?").lower() == "yes" else False,
|
||||
)
|
||||
return property_access
|
||||
|
||||
def get_external_elevation(self):
|
||||
# Front
|
||||
data = self.get_data_between("2.2.1. External Elevation - Front", '2.2.2. External Elevation - Gable 1')
|
||||
ee = ExternalElevation(
|
||||
structural_defects_of_elevation=self.get_next_value(data, "Structural defects of elevation"),
|
||||
does_any_structural_defect_need_resolving_before_retrofit=True if self.get_next_value(data,"Does any structural defect need resolving before retrofit?").lower() == "yes" else False,
|
||||
are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework=True if self.get_next_value(data, "rainwater goods or pipework?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_movement=True if self.get_next_value(data, "Are there any visible signs of movement?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_cracking_to_the_existing_external_finish=True if self.get_next_value(data, "external finish?").lower() == "yes" else False,
|
||||
)
|
||||
front = ExternalElevationFront(
|
||||
external_elevation=ee
|
||||
)
|
||||
# Gable 1
|
||||
data = self.get_data_between("2.2.2. External Elevation - Gable 1", "2.2.3. External Elevation - Rear")
|
||||
state = True if self.get_next_value(data, "cracking)").lower() == "yes" else False
|
||||
if state:
|
||||
gable_one = ExternalElevationGableOne(
|
||||
do_all_answers_for_the_front_elevation_apply_to_this_wall=state
|
||||
)
|
||||
else:
|
||||
gable_one_elevation = ExternalElevation(
|
||||
structural_defects_of_elevation=self.get_next_value(data, "Structural defects of elevation"),
|
||||
does_any_structural_defect_need_resolving_before_retrofit=True if self.get_next_value(data,"Does any structural defect need resolving before retrofit?").lower() == "yes" else False,
|
||||
are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework=True if self.get_next_value(data, "rainwater goods or pipework?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_movement=True if self.get_next_value(data, "Are there any visible signs of movement?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_cracking_to_the_existing_external_finish=True if self.get_next_value(data, "external finish?").lower() == "yes" else False,
|
||||
)
|
||||
gable_one = ExternalElevationGableOne(
|
||||
do_all_answers_for_the_front_elevation_apply_to_this_wall=state,
|
||||
external_elevation=gable_one_elevation
|
||||
)
|
||||
# Rear
|
||||
data = self.get_data_between("2.2.3. External Elevation - Rear", "2.2.4. External Elevation - Gable 2")
|
||||
state = True if self.get_next_value(data, "cracking)").lower() == "yes" else False
|
||||
if state:
|
||||
rear = ExternalElevationRear(do_all_answers_for_the_front_elevation_apply_to_this_wall=state)
|
||||
else:
|
||||
elevation = ExternalElevation(
|
||||
structural_defects_of_elevation=self.get_next_value(data, "Structural defects of elevation"),
|
||||
does_any_structural_defect_need_resolving_before_retrofit=True if self.get_next_value(data,"Does any structural defect need resolving before retrofit?").lower() == "yes" else False,
|
||||
are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework=True if self.get_next_value(data, "rainwater goods or pipework?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_movement=True if self.get_next_value(data, "Are there any visible signs of movement?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_cracking_to_the_existing_external_finish=True if self.get_next_value(data, "external finish?").lower() == "yes" else False,
|
||||
)
|
||||
rear = ExternalElevationRear(
|
||||
do_all_answers_for_the_front_elevation_apply_to_this_wall=state,
|
||||
external_elevation=elevation
|
||||
)
|
||||
|
||||
# Gable 2
|
||||
data = self.get_data_between("2.2.4. External Elevation - Gable 2", "2.3. Conservatory or Outbuilding")
|
||||
pprint(self.raw_data)
|
||||
state = True if self.get_next_value(data, "Is there a 4th external elevation?").lower() == "yes" else False
|
||||
if state is False:
|
||||
gable_two = ExternalElevationGableTwo(is_there_a_fourth_external_elevation=state)
|
||||
else:
|
||||
gable_two_elevation = ExternalElevation(
|
||||
structural_defects_of_elevation=self.get_next_value(data, "Structural defects of elevation"),
|
||||
does_any_structural_defect_need_resolving_before_retrofit=True if self.get_next_value(data,"Does any structural defect need resolving before retrofit?").lower() == "yes" else False,
|
||||
are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework=True if self.get_next_value(data, "rainwater goods or pipework?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_movement=True if self.get_next_value(data, "Are there any visible signs of movement?").lower() == "yes" else False,
|
||||
are_there_any_visible_signs_of_cracking_to_the_existing_external_finish=True if self.get_next_value(data, "external finish?").lower() == "yes" else False,
|
||||
)
|
||||
gable_two = ExternalElevationGableTwo(
|
||||
is_there_a_fourth_external_elevation=state,
|
||||
external_elevation=gable_two_elevation
|
||||
)
|
||||
|
||||
return front, gable_one, rear, gable_two
|
||||
|
||||
|
||||
def get_conservatory_or_outbuilding(self):
|
||||
data = self.get_data_between("2.3. Conservatory or Outbuilding", "3. Rooms")
|
||||
return ConservatoryOrOutbuilding(
|
||||
is_there_a_conservatory=True if self.get_next_value(data, "Is there a Conservatory?").lower() == "yes" else False,
|
||||
is_there_a_cellar_present=True if self.get_next_value(data, "Is there a cellar present?").lower() == "yes" else False,
|
||||
is_there_an_outbuilding=True if self.get_next_value(data, "Is there an Outbuilding?").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
def get_section_3(self):
|
||||
hallway = self.get_hallway()
|
||||
living_room = self.get_living_room()
|
||||
dining_room = self.get_dining_room()
|
||||
kitchen = self.get_kitchen()
|
||||
utility = self.get_utility()
|
||||
wc = self.get_wc()
|
||||
landing = self.get_landing()
|
||||
bedrooms = self.get_bedrooms()
|
||||
bathrooms = self.get_bathroom()
|
||||
loft_space = self.get_loft_space()
|
||||
room_in_roof = self.get_room_in_roof()
|
||||
|
||||
return Rooms(
|
||||
hallway=hallway,
|
||||
living_room=living_room,
|
||||
dining_room=dining_room,
|
||||
kitchen=kitchen,
|
||||
utility=utility,
|
||||
wash_chamber=wc,
|
||||
landing=landing,
|
||||
bedrooms=bedrooms,
|
||||
bathrooms=bathrooms,
|
||||
loft_space=loft_space,
|
||||
room_in_roof=room_in_roof,
|
||||
)
|
||||
|
||||
def get_room_info(self, data):
|
||||
ventilation = VentilationInfo(
|
||||
is_there_a_ventilation_system_present_in_the_room=True if self.get_next_value(data, "Is there a ventilation system present in the room?").lower() == "yes" else False,
|
||||
are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room=True if self.get_next_value(data, "excessive condensation within the room?").lower() == "yes" else False,
|
||||
are_there_sufficient_undercuts_on_the_closed_door=self.get_next_value(data, "- min 7.6mm per 1000mm width door)?"),
|
||||
is_there_any_open_flue_heating_appliances_within_the_room=True if self.get_next_value(data, "Is there any open flue heating appliances within the room?").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
windows_state = True if self.get_next_value(data, "Does the room have any windows?").lower() == "yes" else False
|
||||
if windows_state:
|
||||
windows = WindowsInfo(
|
||||
does_the_room_have_any_windows=windows_state,
|
||||
condition_of_the_windows=self.get_next_value(data, "Condition of the windows"),
|
||||
do_the_windows_have_trickle_vents=True if self.get_next_value(data, "Do the windows have trickle vents?").lower() == "yes" else False,
|
||||
input_trickle_vent_product_code_or_measurement=self.get_next_value(data, "in both cases."),
|
||||
are_the_windows_openable=True if self.get_next_value(data, "Are the windows openable?").lower() == "yes" else False,
|
||||
)
|
||||
else:
|
||||
windows = WindowsInfo(
|
||||
does_the_room_have_any_windows=windows_state
|
||||
)
|
||||
|
||||
return RoomInfo(
|
||||
overall_condition_of_the_room=self.get_next_value(data, "Overall condition of the room"),
|
||||
does_the_room_have_any_defects=self.get_next_value(data, "cracking in walls, etc.)"),
|
||||
windows_info=windows,
|
||||
ventilation_info=ventilation,
|
||||
)
|
||||
|
||||
|
||||
def get_hallway(self):
|
||||
data = self.get_data_between("Hallway", "Living Room")
|
||||
room = self.get_room_info(data)
|
||||
|
||||
return Hallway(
|
||||
is_there_a_hallway=True if self.get_next_value(data, "Is there a hallway?").lower() == "yes" else False,
|
||||
room_info=room,
|
||||
)
|
||||
|
||||
def get_living_room(self):
|
||||
data = self.get_data_between("Living Room", "Dining Room")
|
||||
room = self.get_room_info(data)
|
||||
return LivingRoom(
|
||||
room_info=room,
|
||||
)
|
||||
|
||||
def get_dining_room(self):
|
||||
data = self.get_data_between("Dining Room", "Kitchen")
|
||||
dining_room_state = True if self.get_next_value(data, "Is there a dining room?").lower() == "yes" else False
|
||||
if dining_room_state:
|
||||
room = self.get_room_info(data)
|
||||
else:
|
||||
room = None
|
||||
|
||||
return DiningRoom(
|
||||
is_there_a_dining_room=dining_room_state,
|
||||
room_info=room,
|
||||
)
|
||||
|
||||
def get_kitchen(self):
|
||||
data = self.get_data_between("Kitchen", "Utility")
|
||||
room = self.get_room_info(data)
|
||||
|
||||
return Kitchen(
|
||||
room_info=room,
|
||||
is_there_a_cooker_hood_present_in_the_room=True if self.get_next_value(data, "Is there a cooker hood present in the room?").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
def get_utility(self):
|
||||
data = self.get_data_between("Utility", "WC")
|
||||
utility_state = True if self.get_next_value(data, "Is there a utility room?").lower() == "yes" else False
|
||||
|
||||
if utility_state:
|
||||
room = self.get_room_info(data)
|
||||
else:
|
||||
room = None
|
||||
|
||||
return Utility(
|
||||
is_there_a_utility_room=utility_state,
|
||||
room_info=room
|
||||
)
|
||||
|
||||
def get_wc(self):
|
||||
data = self.get_data_between("WC", "Landing")
|
||||
wc_state = True if self.get_next_value(data, "Is there a separated WC?").lower() == "yes" else False
|
||||
|
||||
if wc_state:
|
||||
room = self.get_room_info(data)
|
||||
else:
|
||||
room = None
|
||||
|
||||
return WC(
|
||||
is_there_a_seperated_wc=wc_state,
|
||||
room_info=room
|
||||
)
|
||||
|
||||
def get_landing(self):
|
||||
data = self.get_data_between("Landing", "Bedroom")
|
||||
landing_state = True if self.get_next_value(data, "Is there a landing?").lower() == "yes" else False
|
||||
|
||||
if landing_state:
|
||||
room_info = self.get_room_info(data)
|
||||
else:
|
||||
room_info = None
|
||||
return Landing(
|
||||
is_there_a_landing=landing_state,
|
||||
room_info=room_info
|
||||
)
|
||||
|
||||
def get_bedrooms(self):
|
||||
bedrooms = []
|
||||
i = 1
|
||||
while(f"Bedroom {i}" in self.raw_data):
|
||||
i += 1
|
||||
|
||||
no_of_bedrooms = i-1
|
||||
print(f"There are {no_of_bedrooms} bedrooms")
|
||||
|
||||
for i in range(no_of_bedrooms):
|
||||
data = self.get_data_between(f"Bedroom {i+1}", "Bathroom")
|
||||
roominfo = self.get_room_info(data)
|
||||
bedrooms.append(Bedroom(
|
||||
double_or_single_bedroom=self.get_next_value(data, "Double or Single Bedroom?"),
|
||||
room_info=roominfo
|
||||
))
|
||||
return bedrooms
|
||||
|
||||
def get_bathroom(self):
|
||||
bathrooms = []
|
||||
i = 1
|
||||
while(f"Bathroom {i}" in self.raw_data):
|
||||
i += 1
|
||||
|
||||
no_of_bathrooms = i-1
|
||||
print(f"There are {no_of_bathrooms} bathrooms")
|
||||
|
||||
for i in range(no_of_bathrooms):
|
||||
data = self.get_data_between(f"Bathroom {i+1}", "Loft Space")
|
||||
roominfo = self.get_room_info(data)
|
||||
bathrooms.append(Bathroom(
|
||||
is_this_an_ensuite_bathroom=self.get_next_value(data, "Is this an ensuite bathroom?"),
|
||||
room_info=roominfo
|
||||
))
|
||||
|
||||
return bathrooms
|
||||
|
||||
def get_loft_space(self):
|
||||
data = self.get_data_between("Loft Space", "Room in Roof")
|
||||
return LoftSpace(
|
||||
is_the_main_loft_space_accessible=self.get_next_value(data, "Is the main loft space accessible?"),
|
||||
is_there_more_than_one_loft_space=True if self.get_next_value(data, "extension)?").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
def get_room_in_roof(self):
|
||||
data = self.get_data_between("Room in Roof", "4. Heating System")
|
||||
room_in_roof_state = True if self.get_next_value(data, "Is there a room in roof?").lower() == "yes" else False
|
||||
room_info = None
|
||||
if room_in_roof_state:
|
||||
room_info = self.get_room_info(data)
|
||||
return RoomInRoof(
|
||||
is_there_a_room_in_roof=room_in_roof_state,
|
||||
room_info=room_info
|
||||
)
|
||||
|
||||
def get_section_4(self):
|
||||
general_condition_of_heating_system = self.get_general_condition_of_heating_system()
|
||||
main_heating_one = self.get_main_heating_one()
|
||||
main_heating_two = self.get_main_heating_two()
|
||||
secondary_heating = self.get_secondary_heating()
|
||||
heating_by_room = self.get_heating_by_room()
|
||||
renewables = self.get_renewables()
|
||||
return general_condition_of_heating_system, main_heating_one, main_heating_two, secondary_heating, heating_by_room, renewables
|
||||
|
||||
def get_main_heating_one(self):
|
||||
data = self.get_data_between("Main Heating 1", "Main Heating 2")
|
||||
return MainHeatingOne(
|
||||
as_defined_by=self.get_next_value(data, "As defined by"),
|
||||
fuel=self.get_next_value(data, "Fuel:"),
|
||||
type=self.get_next_value_greedy(data, "Type", 2)
|
||||
)
|
||||
|
||||
def get_main_heating_two(self):
|
||||
data = self.get_data_between("Main Heating 2", "Secondary Heating")
|
||||
return MainHeatingTwo(
|
||||
is_there_a_main_heating_two=True if self.get_next_value(data, "Is there a Main Heating 2?").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
def get_secondary_heating(self):
|
||||
data = self.get_data_between("Secondary Heating", "Heating by room")
|
||||
return SecondaryHeating(
|
||||
is_there_a_secondary_heating=True if self.get_next_value(data, "Is there a Secondary Heating?").lower() == "yes" else False,
|
||||
fuel=self.get_next_value(data, "Fuel"),
|
||||
electric_heating_type=self.get_next_value_greedy(data, "Type", 2),
|
||||
gas_heating_type=self.get_x_value(data, "Type", 1),
|
||||
)
|
||||
|
||||
def get_heating_by_room(self):
|
||||
data = self.get_data_between("Heating by room", "Renewables")
|
||||
list_of_main_heating_system_by_one = self.get_data_between("Rooms heated by Main System 1:", "Rooms Heated by Main System 2")[1:]
|
||||
list_of_main_heating_system_by_two = self.get_data_between("Rooms Heated by Main System 2", "Rooms heated by Secondary Heating:")[1:]
|
||||
list_of_rooms_heated_by_secondary_heating = self.get_data_between("Rooms heated by Secondary Heating:", "Are there any partially heated rooms?")[1:]
|
||||
are_there_any_partially_heated_rooms = True if self.get_next_value(data, "Are there any partially heated rooms?").lower() == "yes" else False
|
||||
are_there_any_unheated_rooms = True if self.get_next_value(data, "Are there any unheated rooms?").lower() == "yes" else False
|
||||
list_of_partially_heated_rooms = self.get_data_between("Are there any partially heated rooms?", "Are there any unheated rooms?")[2:]
|
||||
list_of_unheated_rooms = self.get_data_between("Are there any unheated rooms?", "Renewables")[2:]
|
||||
|
||||
def remove_special_character(lst):
|
||||
return_lst = []
|
||||
for i, item in enumerate(lst):
|
||||
if '\xa0' in item or 'Rooms' in item or len(item) == 1 or item.lower() == "None".lower():
|
||||
pass
|
||||
else:
|
||||
return_lst.append(item)
|
||||
return return_lst
|
||||
|
||||
return HeatingByRoom(
|
||||
rooms_heated_by_main_system_one=remove_special_character(list_of_main_heating_system_by_one),
|
||||
rooms_heated_by_main_system_two=remove_special_character(list_of_main_heating_system_by_two),
|
||||
rooms_heated_by_secondary_heating=remove_special_character(list_of_rooms_heated_by_secondary_heating),
|
||||
are_there_any_partially_heated_rooms=are_there_any_partially_heated_rooms,
|
||||
are_there_any_unheated_rooms=are_there_any_unheated_rooms,
|
||||
list_of_partially_heated_rooms=remove_special_character(list_of_partially_heated_rooms),
|
||||
unheated_rooms=remove_special_character(list_of_unheated_rooms),
|
||||
)
|
||||
|
||||
def get_renewables(self):
|
||||
data = self.get_data_between("Renewables", "5. Occupancy Assessment")
|
||||
|
||||
return Renewables(
|
||||
is_there_any_renewable_energy_system_in_place=True if self.get_next_value(data, "Is there any renewable energy system in place?").lower() == "yes" else False,
|
||||
suitable_roof_orientation_for_solar_pv_water=self.get_next_value(data, "Suitable roof orientation for Solar PV/water:"),
|
||||
is_there_a_water_tank=True if self.get_next_value(data, "Is there a Water Tank?").lower() == "yes" else False,
|
||||
type=self.get_next_value(data, "Type"),
|
||||
size=self.get_next_value(data, "Size"),
|
||||
tank_location=self.get_next_value(data, "Tank Location"),
|
||||
is_the_tank_insulated=True if self.get_next_value(data, "Is the tank Insulated") else False,
|
||||
type_of_insulation=self.get_next_value(data, "Type of Insulation"),
|
||||
thickness_of_insulation_in_mm=int(self.get_next_value(data, "Thickness of Insulation (mm)")),
|
||||
)
|
||||
|
||||
def get_general_condition_of_heating_system(self):
|
||||
data = self.get_data_between("4. Heating System", "Main Heating 1")
|
||||
return GeneralConditionHeatingSystem(
|
||||
is_the_heating_system_in_working_order=True if self.get_next_value(data, "Is the Heating System in working order?") else False,
|
||||
does_the_occupant_have_a_smart_meter=True if self.get_next_value(data, "Does the occupant have a Smart Meter?") else False,
|
||||
are_there_any_smart_monitoring_devices=True if self.get_next_value(data, "Are there any smart monitoring devices (Switchee etc)?") else False,
|
||||
are_the_gas_and_electricity_meters_accessible=True if self.get_next_value(data, "Are the Gas and Electricity Meters accessible?") else False,
|
||||
dual_or_single_electric_meter=self.get_next_value(data, "Dual or single electric meter?"),
|
||||
)
|
||||
|
||||
def get_section_5(self):
|
||||
occupants = self.get_occupants()
|
||||
energy_use = self.get_energy_use()
|
||||
heating = self.get_heating()
|
||||
shower_and_bath = self.get_shower_and_bath()
|
||||
appliances = self.get_appliances()
|
||||
fridge_and_freezers = self.get_fridge_and_freezers()
|
||||
cooker = self.get_cooker()
|
||||
tumble_dryer = self.get_tumble_dryer()
|
||||
return occupants, energy_use, heating, shower_and_bath, appliances, fridge_and_freezers, cooker, tumble_dryer
|
||||
|
||||
def get_occupants(self):
|
||||
data = self.get_data_between("Occupants", "Energy use")
|
||||
second_data = self.get_data_between("Tumble dryer", "Media summary")
|
||||
return Occupant(
|
||||
name=self.get_next_value(second_data, "Name of the occupant:"),
|
||||
have_evidence_of_12_months_of_fuel_bill_data= True if self.get_next_value(second_data, "Have you evidenced 12 months of fuel bill data?").lower() == "yes" else False,
|
||||
total_number_of_occupants=int(self.get_next_value(data, "Total number of occupants:")),
|
||||
no_of_adult_occupants=int(self.get_next_value(data, "No. of Adult Occupants (18+)")),
|
||||
no_of_child_occupants=int(self.get_next_value(data, "No. of Child Occupants (Under 18)")),
|
||||
no_of_occupant_of_a_pensionable_age=int(self.get_next_value(data, "No. of occupant of a pensionable age")),
|
||||
are_there_any_vulnerable_people=True if self.get_next_value(data, "Are there any vulnerable people?").lower() == "yes" else False,
|
||||
is_there_anyone_with_a_disability=True if self.get_next_value(data, "Is there anyone with a disability?").lower() == "yes" else False,
|
||||
status_of_occupant=self.get_next_value(data, "Status of the occupant:"),
|
||||
landlord_has_written_confirmation_that_the_tenent_agrees_to_the_assessment_been_supplied=True if self.get_next_value(data, "the assessment been supplied").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
def get_energy_use(self):
|
||||
data = self.get_data_between("Energy use", "Heating")
|
||||
return EnergyUse(
|
||||
property_tenure=self.get_next_value(data, "Property tenure:"),
|
||||
who_is_the_electricity_payer=self.get_next_value(data, "Who is the electricity bill payer?")
|
||||
)
|
||||
|
||||
def get_heating(self):
|
||||
data = self.get_data_between("Heating", "Shower & bath")
|
||||
return Heating(
|
||||
room_stat_in_temperature_in_celsius=self.get_next_value(data, "Room Stat Temperature (in °C)", avoid=["Room Stat Location", '\xa0']),
|
||||
room_stat_location=self.get_next_value(data, "Room Stat Location", avoid = ["Is the heating pattern known?", '\xa0']),
|
||||
is_the_heating_pattern_known=self.get_next_value(data, "Is the heating pattern known?", avoid=["Shower & bath", '\xa0']),
|
||||
)
|
||||
|
||||
def get_shower_and_bath(self):
|
||||
data = self.get_data_between("Shower & bath", "Appliances")
|
||||
return ShowerAndBath(
|
||||
shower_type=self.get_next_value(data, "Shower Type:"),
|
||||
do_you_know_the_no_of_showers_per_day_per_week=True if self.get_next_value(data, "Do you know the number of showers per day or per week?").lower() == "yes" else False,
|
||||
please_input_no_of_showers_and_specify_a_day_or_a_week=self.get_next_value(data, '"per week"'),
|
||||
do_you_know_the_number_of_baths_per_day_or_per_week=self.get_next_value(data, "Do you know the number of baths per day or per week?"),
|
||||
)
|
||||
|
||||
def get_appliances(self):
|
||||
print("Skipped appliances due to not having this example yet")
|
||||
|
||||
def get_fridge_and_freezers(self):
|
||||
data = self.get_data_between("Fridge & freezers", "Cooker")
|
||||
return FridgeAndFreezers(
|
||||
no_of_stand_alone_seperate_fridges=int(self.get_next_value(data,"No. of Standalone (separate) Fridges:")),
|
||||
no_of_stand_alone_seperate_freezers=int(self.get_next_value(data, "No. of Standalone (separate) Freezers:")),
|
||||
no_of_stand_alone_or_integrated_fridge_freezers=int(self.get_next_value(data, "No. of Standalone or Integrated Fridge Freezers:"))
|
||||
)
|
||||
|
||||
def get_cooker(self):
|
||||
data = self.get_data_between("Cooker", "Tumble dryer")
|
||||
return Cooker(
|
||||
cooker_type=self.get_next_value(data, "Cooker Type:"),
|
||||
normal_large_range=self.get_next_value(data, "Normal - Large - Range"),
|
||||
range_fuel=self.get_next_value(data, "Range Fuel:")
|
||||
)
|
||||
|
||||
def get_tumble_dryer(self):
|
||||
data = self.get_data_between("Tumble dryer", "Have you evidenced 12 months of fuel bill data?")
|
||||
return TumbleDryer(
|
||||
percentage_of_annual_use=int(self.get_next_value(data, "Percentage of annual use:")),
|
||||
space_for_outdoor_drying=True if self.get_next_value(data,"Space for outdoor drying?").lower() == "yes" else False,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class QuidosSiteNotesExtractor(SiteNotesExtractor):
|
||||
def __init__(self, data_list):
|
||||
super().__init__(data_list)
|
||||
|
|
@ -125,7 +726,7 @@ class QuidosSiteNotesExtractor(SiteNotesExtractor):
|
|||
self.survey_information = PreSiteNotesSummaryInfo(
|
||||
reference_number = get_value('Reference Number'),
|
||||
epc_language = get_value('EPC Language'),
|
||||
uprn = get_value('UPRN'),
|
||||
uprn = get_value('UPRN').lstrip('0'),
|
||||
postcode = get_value('Postcode'),
|
||||
region = get_value('Region'),
|
||||
address = ','.join(get_value('Address').split(',')[:-1]).strip(),
|
||||
|
|
@ -294,7 +895,6 @@ class QuidosSiteNotesExtractor(SiteNotesExtractor):
|
|||
no_of_heated_rooms = int(get_value('Number of Heated Habitable Rooms')),
|
||||
heated_basement = False if get_value('Heated Basement') == "NO" else True,
|
||||
conservatory_type = get_value('Conservatory Type'),
|
||||
terrain_type = get_value('Terrain Type'),
|
||||
percentage_of_draught_proofed= int(get_value('Percentage of Draught Proofed(%)')),
|
||||
main_property=PropertyDetail(
|
||||
age_band=age_bands[0],
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ from datetime import datetime, timedelta
|
|||
|
||||
def previous_monday():
|
||||
today = datetime.today()
|
||||
last_monday = today - timedelta(days=today.weekday() + 7) # Go back to last week's Monday
|
||||
# return f"W.C. 31.09.2000"
|
||||
return f"W.C. {last_monday.strftime('%d.%m.%Y')}"
|
||||
monday = today - timedelta(days=today.weekday()) # weekday() = 0 for Monday
|
||||
return f"W.C. {monday.strftime('%d.%m.%Y')}"
|
||||
|
||||
WEEK_COMMENCING = os.getenv("WEEK_COMMENCING", previous_monday())
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class surveyedDataProcessor():
|
|||
self.files = files
|
||||
self.pre_site_note = None
|
||||
self.csr = None
|
||||
self.condition_report = None
|
||||
self.identify_files()
|
||||
self.hubspot_deal_id = None
|
||||
|
||||
|
|
@ -34,6 +35,8 @@ class surveyedDataProcessor():
|
|||
self.address = self.pre_site_note.survey_information.address
|
||||
elif pdf.type == ReportType.CHARTED_SURVEYOR_REPORT:
|
||||
self.csr = pdf.get_reader()
|
||||
elif pdf.type == ReportType.OSMOSIS_CONDITION_PAS_2035_REPORT:
|
||||
self.condition_report = pdf.get_reader()
|
||||
|
||||
def load_pre_site_notes_summary_table(self, db_session):
|
||||
summary_data = self.pre_site_note.survey_information.model_dump()
|
||||
|
|
|
|||
284
etl/transform/conditionReportTypes.py
Normal file
284
etl/transform/conditionReportTypes.py
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
from etl.transform.preSiteNoteTypes import BaseModel, Optional, List
|
||||
|
||||
class AssessorDetails(BaseModel):
|
||||
assessor_name_and_id: str
|
||||
elmhurst_id: str
|
||||
|
||||
class InspectionAndProject(BaseModel):
|
||||
inspection_date: str
|
||||
|
||||
class TheProperty(BaseModel):
|
||||
house_type: str
|
||||
on_which_floor_is_the_flat_located: str
|
||||
is_there_a_corridor: bool
|
||||
is_it_heated: bool
|
||||
it_there_a_balcony: bool
|
||||
classification_type: str
|
||||
orientation_front_elevation: str
|
||||
orientation_in_degrees_front_elevation: str
|
||||
exposure_zone: str
|
||||
main_wall_construction: str
|
||||
|
||||
class ElevationInfo(BaseModel):
|
||||
elevation_type: str
|
||||
cavity_wall_depth: str
|
||||
is_insulation_present: bool
|
||||
insulation_type: str
|
||||
|
||||
class MainElevation(BaseModel):
|
||||
elevation_info: ElevationInfo
|
||||
|
||||
class Elevation(BaseModel):
|
||||
info: List[ElevationInfo]
|
||||
protected_conservatory_or_aonb: bool
|
||||
material_type: str
|
||||
are_there_any_visible_signs_of_existing_wall_insulation: str
|
||||
does_the_existing_ground_level_on_any_elevation_bridge_the_dpc: bool
|
||||
|
||||
class GeneralInformation(BaseModel):
|
||||
assessor_details: AssessorDetails
|
||||
inspection_and_project: InspectionAndProject
|
||||
the_property: TheProperty
|
||||
main_elevation: MainElevation
|
||||
elevations: Elevation
|
||||
|
||||
class PropertyAccess(BaseModel):
|
||||
are_there_any_road_restriction_in_the_locality: bool
|
||||
is_on_street_parking_available: bool
|
||||
are_there_any_overhead_wires_or_cables: bool
|
||||
is_the_access_gated: bool
|
||||
is_there_restricted_space_for_contractors_to_access_the_wall_area: bool
|
||||
is_there_restricted_space_for_contractors_to_access_the_roof_area: bool
|
||||
is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighbouring_boundary_along_the_full_gable_elevation: bool
|
||||
is_access_to_the_rear_provided_by_use_of_a_ginnel: bool
|
||||
is_access_to_the_rear_provided_by_use_of_a_ginnel: bool
|
||||
is_access_to_the_rear_provided_by_use_of_a_secured_alleyway: bool
|
||||
|
||||
class ExternalElevation(BaseModel):
|
||||
structural_defects_of_elevation: str
|
||||
does_any_structural_defect_need_resolving_before_retrofit: bool
|
||||
are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework: bool
|
||||
are_there_any_visible_signs_of_movement: bool
|
||||
are_there_any_visible_signs_of_cracking_to_the_existing_external_finish: bool
|
||||
|
||||
class ExternalElevationFront(BaseModel):
|
||||
external_elevation: ExternalElevation
|
||||
|
||||
class ExternalElevationGableOne(BaseModel):
|
||||
do_all_answers_for_the_front_elevation_apply_to_this_wall: bool
|
||||
external_elevation: Optional[ExternalElevation] = None
|
||||
|
||||
class ExternalElevationRear(BaseModel):
|
||||
do_all_answers_for_the_front_elevation_apply_to_this_wall: bool
|
||||
external_elevation: Optional[ExternalElevation] = None
|
||||
|
||||
class ExternalElevationGableTwo(BaseModel):
|
||||
is_there_a_fourth_external_elevation: bool
|
||||
external_elevation: Optional[ExternalElevation] = None
|
||||
|
||||
class ConservatoryOrOutbuilding(BaseModel):
|
||||
is_there_a_conservatory: bool
|
||||
is_there_a_cellar_present: bool
|
||||
is_there_an_outbuilding: bool
|
||||
|
||||
class AccessAndElevations(BaseModel):
|
||||
property_access: PropertyAccess
|
||||
external_elevation_front: ExternalElevationFront
|
||||
external_elevation_back: ExternalElevationRear
|
||||
external_elevation_gable_one: ExternalElevationGableOne
|
||||
external_elevation_gable_two: ExternalElevationGableTwo
|
||||
conservatory_or_out_building: ConservatoryOrOutbuilding
|
||||
|
||||
class VentilationInfo(BaseModel):
|
||||
is_there_a_ventilation_system_present_in_the_room: bool
|
||||
are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room: bool
|
||||
are_there_sufficient_undercuts_on_the_closed_door: str
|
||||
is_there_any_open_flue_heating_appliances_within_the_room: bool
|
||||
|
||||
class WindowsInfo(BaseModel):
|
||||
does_the_room_have_any_windows: bool
|
||||
condition_of_the_windows: Optional[str] = None
|
||||
do_the_windows_have_trickle_vents: Optional[bool] = None
|
||||
are_the_windows_openable: Optional[bool] = None
|
||||
input_trickle_vent_product_code_or_measurement: Optional[str] = None
|
||||
|
||||
class RoomInfo(BaseModel):
|
||||
overall_condition_of_the_room: str
|
||||
does_the_room_have_any_defects: str
|
||||
are_there_any_sloped_ceiling_areas: Optional[bool] = None
|
||||
windows_info: Optional[WindowsInfo] = None
|
||||
ventilation_info: Optional[VentilationInfo] = None
|
||||
|
||||
class Hallway(BaseModel):
|
||||
is_there_a_hallway: bool
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class LivingRoom(BaseModel):
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class DiningRoom(BaseModel):
|
||||
is_there_a_dining_room: bool
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class Kitchen(BaseModel):
|
||||
room_info: Optional[RoomInfo]
|
||||
is_there_a_cooker_hood_present_in_the_room: bool
|
||||
|
||||
|
||||
class Utility(BaseModel):
|
||||
is_there_a_utility_room: bool
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class WC(BaseModel):
|
||||
is_there_a_seperated_wc: bool
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class Landing(BaseModel):
|
||||
is_there_a_landing: bool
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class Bedroom(BaseModel):
|
||||
double_or_single_bedroom: str
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class Bathroom(BaseModel):
|
||||
is_this_an_ensuite_bathroom: bool
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class LoftSpace(BaseModel):
|
||||
is_the_main_loft_space_accessible: str
|
||||
is_there_more_than_one_loft_space: bool
|
||||
|
||||
class RoomInRoof(BaseModel):
|
||||
is_there_a_room_in_roof: bool
|
||||
room_info: Optional[RoomInfo]
|
||||
|
||||
class Rooms(BaseModel):
|
||||
hallway: Hallway
|
||||
living_room: LivingRoom
|
||||
dining_room: DiningRoom
|
||||
kitchen: Kitchen
|
||||
utility: Utility
|
||||
wash_chamber: WC
|
||||
landing: Landing
|
||||
bedrooms: List[Bedroom]
|
||||
bathrooms: List[Bathroom]
|
||||
loft_space: LoftSpace
|
||||
room_in_roof: RoomInRoof
|
||||
|
||||
class GeneralConditionHeatingSystem(BaseModel):
|
||||
is_the_heating_system_in_working_order: bool
|
||||
does_the_occupant_have_a_smart_meter: bool
|
||||
are_there_any_smart_monitoring_devices: bool
|
||||
are_the_gas_and_electricity_meters_accessible: bool
|
||||
dual_or_single_electric_meter: str
|
||||
|
||||
class SecondaryHeating(BaseModel):
|
||||
is_there_a_secondary_heating: bool
|
||||
fuel: str
|
||||
electric_heating_type: str
|
||||
gas_heating_type: str
|
||||
|
||||
class MainHeatingOne(BaseModel):
|
||||
as_defined_by: str
|
||||
fuel: str
|
||||
type: str
|
||||
|
||||
class MainHeatingTwo(BaseModel):
|
||||
is_there_a_main_heating_two: bool
|
||||
|
||||
class HeatingByRoom(BaseModel):
|
||||
rooms_heated_by_main_system_one: List[str]
|
||||
rooms_heated_by_main_system_two: List[str]
|
||||
rooms_heated_by_secondary_heating: List[str]
|
||||
are_there_any_partially_heated_rooms: bool
|
||||
partially_heated_rooms: Optional[List[str]] = []
|
||||
are_there_any_unheated_rooms: bool
|
||||
unheated_rooms: List[str]
|
||||
|
||||
class Renewables(BaseModel):
|
||||
is_there_any_renewable_energy_system_in_place: bool
|
||||
suitable_roof_orientation_for_solar_pv_water: str
|
||||
is_there_a_water_tank: bool
|
||||
type: str
|
||||
size: str
|
||||
tank_location: str
|
||||
is_the_tank_insulated: bool
|
||||
type_of_insulation: str
|
||||
thickness_of_insulation_in_mm: int
|
||||
|
||||
class HeatingSystem(BaseModel):
|
||||
general_condition: GeneralConditionHeatingSystem
|
||||
main_heating_one: MainHeatingOne
|
||||
main_heating_two: MainHeatingTwo
|
||||
secondary_heating: SecondaryHeating
|
||||
heating_by_room: HeatingByRoom
|
||||
renewables: Renewables
|
||||
|
||||
|
||||
class Occupant(BaseModel):
|
||||
name: str
|
||||
have_evidence_of_12_months_of_fuel_bill_data: bool
|
||||
total_number_of_occupants: int
|
||||
no_of_adult_occupants: int
|
||||
no_of_child_occupants: int
|
||||
no_of_occupant_of_a_pensionable_age: int
|
||||
are_there_any_vulnerable_people: bool
|
||||
is_there_anyone_with_a_disability: bool
|
||||
status_of_occupant: str
|
||||
landlord_has_written_confirmation_that_the_tenent_agrees_to_the_assessment_been_supplied: bool
|
||||
|
||||
class TumbleDryer(BaseModel):
|
||||
percentage_of_annual_use: int
|
||||
space_for_outdoor_drying: bool
|
||||
|
||||
class Cooker(BaseModel):
|
||||
range_fuel: str
|
||||
normal_large_range: str
|
||||
cooker_type: str
|
||||
|
||||
class FridgeAndFreezers(BaseModel):
|
||||
no_of_stand_alone_seperate_fridges: int
|
||||
no_of_stand_alone_seperate_freezers: int
|
||||
no_of_stand_alone_or_integrated_fridge_freezers: int
|
||||
|
||||
class Appliances(BaseModel):
|
||||
find_out_with_one_in: str
|
||||
|
||||
class ShowerAndBath(BaseModel):
|
||||
shower_type: str
|
||||
do_you_know_the_no_of_showers_per_day_per_week: bool
|
||||
please_input_no_of_showers_and_specify_a_day_or_a_week: str
|
||||
do_you_know_the_number_of_baths_per_day_or_per_week: str
|
||||
class Heating(BaseModel):
|
||||
# TODO find one with an example of this one
|
||||
room_stat_in_temperature_in_celsius: Optional[str] = None
|
||||
room_stat_location: Optional[str] = None
|
||||
is_the_heating_pattern_known: Optional[str] = None
|
||||
|
||||
class EnergyUse(BaseModel):
|
||||
property_tenure: str
|
||||
who_is_the_electricity_payer: str
|
||||
|
||||
|
||||
class OccupantAssessment(BaseModel):
|
||||
occupant: Occupant
|
||||
energy_use: EnergyUse
|
||||
heating: Heating
|
||||
shower_and_bath: ShowerAndBath
|
||||
appliances: Optional[Appliances]
|
||||
fridge_and_freezers: FridgeAndFreezers
|
||||
cooker: Cooker
|
||||
tumble_dryer: TumbleDryer
|
||||
|
||||
|
||||
class ConditionReport(BaseModel):
|
||||
project_site_name: str
|
||||
property_reference_code: str
|
||||
property_address: str
|
||||
postcode: str
|
||||
general_information: GeneralInformation
|
||||
access_and_elevations: AccessAndElevations
|
||||
rooms: Rooms
|
||||
heating_system: HeatingSystem
|
||||
occupancy_assessment: OccupantAssessment
|
||||
0
frontend/.env
Normal file
0
frontend/.env
Normal file
2
frontend/.env.development
Normal file
2
frontend/.env.development
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# DATABASE_URL=postgresql://postgres:makingwarmhomes@db:5432/postgres
|
||||
DATABASE_URL=postgresql://postgres:makingwarmhomes@terraform-20250331175522503500000002.cdgzupxvdyp0.eu-west-2.rds.amazonaws.com:5432/surveyDB
|
||||
1
frontend/.env.production
Normal file
1
frontend/.env.production
Normal file
|
|
@ -0,0 +1 @@
|
|||
DATABASE_URL=postgresql://postgres:makingwarmhomes@terraform-20250331175522503500000002.cdgzupxvdyp0.eu-west-2.rds.amazonaws.com:5432/surveyDB
|
||||
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
|
|
@ -31,7 +31,7 @@ yarn-error.log*
|
|||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
# .env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
|
|
|||
1370
frontend/package-lock.json
generated
1370
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,19 +9,25 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.44.0",
|
||||
"next": "15.3.2",
|
||||
"pg": "^8.16.0",
|
||||
"postgres": "^3.4.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"next": "15.3.2"
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/pg": "^8.15.2",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.2",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
frontend/src/app/db/db.ts
Normal file
9
frontend/src/app/db/db.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
|
||||
if (!process.env.DATABASE_URL) {
|
||||
throw new Error("DATABASE_URL is not defined in environment variables");
|
||||
}
|
||||
|
||||
const sqlClient = postgres(process.env.DATABASE_URL);
|
||||
export const db = drizzle(sqlClient);
|
||||
10
frontend/src/app/db/schema/buildings.ts
Normal file
10
frontend/src/app/db/schema/buildings.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const buildings = pgTable('buildings', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
address: text('address').notNull(),
|
||||
postcode: text('postcode').notNull(),
|
||||
UPRN: text('UPRN').notNull(),
|
||||
landlordId: text('landlord_id').notNull(),
|
||||
domnaId: text('domna_id').notNull(),
|
||||
});
|
||||
|
|
@ -1,6 +1,55 @@
|
|||
// app/page.tsx
|
||||
import { db } from './db/db'; // adjust path if needed
|
||||
import { buildings } from './db/schema/buildings';
|
||||
|
||||
type Building = {
|
||||
id: string;
|
||||
address: string;
|
||||
postcode: string;
|
||||
uprn: string;
|
||||
landlordId: string;
|
||||
domnaId: string;
|
||||
};
|
||||
|
||||
export default async function Home() {
|
||||
const buildingRows = await db.select().from(buildings);
|
||||
|
||||
const buildingsData: Building[] = buildingRows.map((b) => ({
|
||||
id: b.id.toString(),
|
||||
address: b.address,
|
||||
postcode: b.postcode,
|
||||
uprn: b.UPRN,
|
||||
landlordId: b.landlordId,
|
||||
domnaId: b.domnaId,
|
||||
}));
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<h1>Hello Next.js!</h1>
|
||||
<>
|
||||
<h1>Buildings List</h1>
|
||||
<table border={1} cellPadding={8} cellSpacing={0}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Address</th>
|
||||
<th>Postcode</th>
|
||||
<th>UPRN</th>
|
||||
<th>Landlord ID</th>
|
||||
<th>Domna ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{buildingsData.map((b) => (
|
||||
<tr key={b.id}>
|
||||
<td>{b.id}</td>
|
||||
<td>{b.address}</td>
|
||||
<td>{b.postcode}</td>
|
||||
<td>{b.uprn}</td>
|
||||
<td>{b.landlordId}</td>
|
||||
<td>{b.domnaId}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue