From 5febdc01a0eff24178bcfad9bb642629a07c3c07 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 1 Sep 2025 09:43:53 +0100 Subject: [PATCH 1/7] attempt 2 --- .github/workflows/months_end.yml | 2 ++ etl/MonthEndUploader.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/months_end.yml b/.github/workflows/months_end.yml index 69a1f91..2bdfb62 100644 --- a/.github/workflows/months_end.yml +++ b/.github/workflows/months_end.yml @@ -3,6 +3,8 @@ on: schedule: - cron: '0 7 29 * *' # Runs at 7:00 AM UTC on the 29th of every month workflow_dispatch: +push: + branches: [main, feature/month_end_automation_of_all] jobs: surveyed-needs-sign-off: diff --git a/etl/MonthEndUploader.py b/etl/MonthEndUploader.py index 927b5c0..6d0f97e 100644 --- a/etl/MonthEndUploader.py +++ b/etl/MonthEndUploader.py @@ -12,7 +12,7 @@ def upload_to_month_end_folder(file_name_on_sp, local_file_path, add_to_path): parent_folder = "General/Junte Kim/month end" today = datetime.today() # Format as "Month YYYY" - formatted_date = today.strftime("%B %Y") + formatted_date = today.strftime("%B %Y") + " Attempt 2" sharepoint.create_dir(formatted_date, parent_folder) sharepoint_path = parent_folder + "/" + formatted_date From a72dca6adb17dc4a436e14ea0ba5ee52e49ea061 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 1 Sep 2025 09:46:53 +0100 Subject: [PATCH 2/7] fix syntax error --- .github/workflows/months_end.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/months_end.yml b/.github/workflows/months_end.yml index 2bdfb62..f00009f 100644 --- a/.github/workflows/months_end.yml +++ b/.github/workflows/months_end.yml @@ -1,19 +1,20 @@ name: Months End + on: schedule: - - cron: '0 7 29 * *' # Runs at 7:00 AM UTC on the 29th of every month + - cron: '0 7 29 * *' # Runs at 07:00 UTC on the 29th of every month workflow_dispatch: -push: - branches: [main, feature/month_end_automation_of_all] + push: + branches: [main, feature/month_end_automation_of_all] jobs: surveyed-needs-sign-off: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.12' @@ -22,7 +23,9 @@ jobs: pip install poetry poetry install --no-root - - name: run script + - name: Run scripts + env: + PYTHONPATH: ${{ github.workspace }} run: | pwd ls -la @@ -43,5 +46,3 @@ jobs: poetry run python etl/month_end_automation_wave_2_no_16.py poetry run python etl/month_end_automation_wave_accent_housing.py poetry run python etl/month_end_automation_wave_3_layout.py - env: - PYTHONPATH: ${{ github.workspace }} \ No newline at end of file From ce5d0c505ec3aa90683cc601b91862603be59d81 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 1 Sep 2025 14:12:35 +0100 Subject: [PATCH 3/7] add thhis new file --- etl/sharepoint_installer_data_gatherer.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 etl/sharepoint_installer_data_gatherer.py diff --git a/etl/sharepoint_installer_data_gatherer.py b/etl/sharepoint_installer_data_gatherer.py new file mode 100644 index 0000000..4a42cac --- /dev/null +++ b/etl/sharepoint_installer_data_gatherer.py @@ -0,0 +1,2 @@ +print("hello world") + From 1026d90a3af4aaba2c842416c606956983f3ad9d Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 1 Sep 2025 15:44:01 +0000 Subject: [PATCH 4/7] save --- etl/models/conditionReport.py | 596 +++++++++++----------- etl/models/preSiteNoteTypes.py | 502 +++++++++--------- etl/models/topLevel.py | 54 +- etl/scraper/scraper.py | 1 + etl/sharepoint_installer_data_gatherer.py | 65 ++- 5 files changed, 641 insertions(+), 577 deletions(-) diff --git a/etl/models/conditionReport.py b/etl/models/conditionReport.py index 53f1baf..6113297 100644 --- a/etl/models/conditionReport.py +++ b/etl/models/conditionReport.py @@ -1,386 +1,386 @@ -# # SQLModel mapping for ConditionReportModel using BaseModel -# from sqlmodel import SQLModel, Field, Relationship, Column, JSON -# from typing import Optional, List -# import uuid -# from datetime import datetime -# from etl.models.topLevel import BaseModel, Documents +# SQLModel mapping for ConditionReportModel using BaseModel +from sqlmodel import SQLModel, Field, Relationship, Column, JSON +from typing import Optional, List +import uuid +from datetime import datetime +from etl.models.topLevel import BaseModel, Documents -# class AssessorDetails(BaseModel, table=True): -# assessor_name_and_id: str -# elmhurst_id: str +class AssessorDetails(BaseModel, table=True): + assessor_name_and_id: str + elmhurst_id: str -# class InspectionAndProject(BaseModel, table=True): -# inspection_date: str +class InspectionAndProject(BaseModel, table=True): + inspection_date: str -# class TheProperty(BaseModel, table=True): -# 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 TheProperty(BaseModel, table=True): + 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, table=True): -# elevation_type: str -# cavity_wall_depth: str -# is_insulation_present: bool -# insulation_type: str +class ElevationInfo(BaseModel, table=True): + elevation_type: str + cavity_wall_depth: str + is_insulation_present: bool + insulation_type: str -# main_elevation: Optional["MainElevation"] = Relationship(back_populates="elevation_info") + main_elevation: Optional["MainElevation"] = Relationship(back_populates="elevation_info") -# elevation_id: Optional[uuid.UUID] = Field(foreign_key="elevation.id") -# elevation_table: Optional["Elevation"] = Relationship(back_populates="info") + elevation_id: Optional[uuid.UUID] = Field(foreign_key="elevation.id") + elevation_table: Optional["Elevation"] = Relationship(back_populates="info") -# class MainElevation(BaseModel, table=True): -# elevation_info_id: uuid.UUID = Field(foreign_key="elevationinfo.id") +class MainElevation(BaseModel, table=True): + elevation_info_id: uuid.UUID = Field(foreign_key="elevationinfo.id") -# #SQLAlcemy things -# elevation_info: ElevationInfo = Relationship(back_populates="main_elevation") + #SQLAlcemy things + elevation_info: ElevationInfo = Relationship(back_populates="main_elevation") -# class Elevation(BaseModel, table=True): -# protected_conservatory_or_aonb: bool -# material_type: str -# visible_signs_of_existing_wall_insulation: str -# ground_level_bridge_the_dpc: bool +class Elevation(BaseModel, table=True): + protected_conservatory_or_aonb: bool + material_type: str + visible_signs_of_existing_wall_insulation: str + ground_level_bridge_the_dpc: bool -# info: List["ElevationInfo"] = Relationship(back_populates="elevation_table") + info: List["ElevationInfo"] = Relationship(back_populates="elevation_table") -# class GeneralInformation(BaseModel, table=True): -# assessor_detail_id: uuid.UUID = Field(foreign_key="assessordetails.id") -# inspection_and_project_id: uuid.UUID = Field(foreign_key="inspectionandproject.id") -# the_property_id: uuid.UUID = Field(foreign_key="theproperty.id") -# main_elevation_id: uuid.UUID = Field(foreign_key="mainelevation.id") -# elevations_id: uuid.UUID = Field(foreign_key="elevation.id") +class GeneralInformation(BaseModel, table=True): + assessor_detail_id: uuid.UUID = Field(foreign_key="assessordetails.id") + inspection_and_project_id: uuid.UUID = Field(foreign_key="inspectionandproject.id") + the_property_id: uuid.UUID = Field(foreign_key="theproperty.id") + main_elevation_id: uuid.UUID = Field(foreign_key="mainelevation.id") + elevations_id: uuid.UUID = Field(foreign_key="elevation.id") -# assessor_details: AssessorDetails = Relationship() -# inspection_and_project: InspectionAndProject = Relationship() -# the_property: TheProperty = Relationship() -# main_elevation: MainElevation = Relationship() -# elevations: Elevation = Relationship() + assessor_details: AssessorDetails = Relationship() + inspection_and_project: InspectionAndProject = Relationship() + the_property: TheProperty = Relationship() + main_elevation: MainElevation = Relationship() + elevations: Elevation = Relationship() -# class PropertyAccess(BaseModel, table=True): -# 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 -# more_than_1_5_meters_in_width_to_fence_or__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_secured_alleyway: bool +class PropertyAccess(BaseModel, table=True): + 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 + more_than_1_5_meters_in_width_to_fence_or__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_secured_alleyway: bool -# class ExternalElevation(BaseModel, table=True): -# structural_defects_of_elevation: str -# does_any_structural_defect_need_resolving_before_retrofit: bool -# 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 ExternalElevation(BaseModel, table=True): + structural_defects_of_elevation: str + does_any_structural_defect_need_resolving_before_retrofit: bool + 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, table=True): -# external_elevation_id: uuid.UUID = Field(foreign_key="externalelevation.id") -# external_elevation: ExternalElevation = Relationship() +class ExternalElevationFront(BaseModel, table=True): + external_elevation_id: uuid.UUID = Field(foreign_key="externalelevation.id") + external_elevation: ExternalElevation = Relationship() -# class ExternalElevationRear(BaseModel, table=True): -# do_all_answers_for_the_front_elevation_apply_to_this_wall: bool -# external_elevation_id: Optional[uuid.UUID] = Field(foreign_key="externalelevation.id") -# external_elevation: Optional[ExternalElevation] = Relationship() +class ExternalElevationRear(BaseModel, table=True): + do_all_answers_for_the_front_elevation_apply_to_this_wall: bool + external_elevation_id: Optional[uuid.UUID] = Field(foreign_key="externalelevation.id") + external_elevation: Optional[ExternalElevation] = Relationship() -# class ExternalElevationGableOne(BaseModel, table=True): -# do_all_answers_for_the_front_elevation_apply_to_this_wall: bool -# external_elevation_id: Optional[uuid.UUID] = Field(foreign_key="externalelevation.id") -# external_elevation: Optional[ExternalElevation] = Relationship() +class ExternalElevationGableOne(BaseModel, table=True): + do_all_answers_for_the_front_elevation_apply_to_this_wall: bool + external_elevation_id: Optional[uuid.UUID] = Field(foreign_key="externalelevation.id") + external_elevation: Optional[ExternalElevation] = Relationship() -# class ExternalElevationGableTwo(BaseModel, table=True): -# is_there_a_fourth_external_elevation: bool -# external_elevation_id: Optional[uuid.UUID] = Field(foreign_key="externalelevation.id") +class ExternalElevationGableTwo(BaseModel, table=True): + is_there_a_fourth_external_elevation: bool + external_elevation_id: Optional[uuid.UUID] = Field(foreign_key="externalelevation.id") -# class ConservatoryOrOutbuilding(BaseModel, table=True): -# is_there_a_conservatory: bool -# is_there_a_cellar_present: bool -# is_there_an_outbuilding: bool +class ConservatoryOrOutbuilding(BaseModel, table=True): + is_there_a_conservatory: bool + is_there_a_cellar_present: bool + is_there_an_outbuilding: bool -# class AccessAndElevations(BaseModel, table=True): -# property_access_id: uuid.UUID = Field(foreign_key="propertyaccess.id") -# external_elevation_front_id: uuid.UUID = Field(foreign_key="externalelevationfront.id") -# external_elevation_back_id: uuid.UUID = Field(foreign_key="externalelevationrear.id") -# external_elevation_gable_one_id: uuid.UUID = Field(foreign_key="externalelevationgableone.id") -# external_elevation_gable_two_id: uuid.UUID = Field(foreign_key="externalelevationgabletwo.id") -# conservatory_or_out_building_id: uuid.UUID = Field(foreign_key="conservatoryoroutbuilding.id") +class AccessAndElevations(BaseModel, table=True): + property_access_id: uuid.UUID = Field(foreign_key="propertyaccess.id") + external_elevation_front_id: uuid.UUID = Field(foreign_key="externalelevationfront.id") + external_elevation_back_id: uuid.UUID = Field(foreign_key="externalelevationrear.id") + external_elevation_gable_one_id: uuid.UUID = Field(foreign_key="externalelevationgableone.id") + external_elevation_gable_two_id: uuid.UUID = Field(foreign_key="externalelevationgabletwo.id") + conservatory_or_out_building_id: uuid.UUID = Field(foreign_key="conservatoryoroutbuilding.id") -# property_access: PropertyAccess = Relationship() -# external_elevation_front: ExternalElevationFront = Relationship() -# external_elevation_back: ExternalElevationRear = Relationship() -# external_elevation_gable_one: ExternalElevationGableOne = Relationship() -# external_elevation_gable_two: ExternalElevationGableTwo = Relationship() -# conservatory_or_out_building: ConservatoryOrOutbuilding = Relationship() + property_access: PropertyAccess = Relationship() + external_elevation_front: ExternalElevationFront = Relationship() + external_elevation_back: ExternalElevationRear = Relationship() + external_elevation_gable_one: ExternalElevationGableOne = Relationship() + external_elevation_gable_two: ExternalElevationGableTwo = Relationship() + conservatory_or_out_building: ConservatoryOrOutbuilding = Relationship() -# class VentilationInfo(BaseModel, table=True): -# is_there_a_ventilation_system_present_in_the_room: bool -# any_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 VentilationInfo(BaseModel, table=True): + is_there_a_ventilation_system_present_in_the_room: bool + any_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, table=True): -# 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 WindowsInfo(BaseModel, table=True): + 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, table=True): -# overall_condition_of_the_room: str -# does_the_room_have_any_defects: str -# are_there_any_sloped_ceiling_areas: Optional[bool] = None +class RoomInfo(BaseModel, table=True): + overall_condition_of_the_room: str + does_the_room_have_any_defects: str + are_there_any_sloped_ceiling_areas: Optional[bool] = None -# windows_info_id: uuid.UUID = Field(foreign_key="windowsinfo.id") -# ventilation_info_id: uuid.UUID = Field(foreign_key="ventilationinfo.id") + windows_info_id: uuid.UUID = Field(foreign_key="windowsinfo.id") + ventilation_info_id: uuid.UUID = Field(foreign_key="ventilationinfo.id") -# windows_info: WindowsInfo = Relationship() -# ventilation_info: VentilationInfo = Relationship() + windows_info: WindowsInfo = Relationship() + ventilation_info: VentilationInfo = Relationship() -# class Hallway(BaseModel, table=True): -# is_there_a_hallway: bool -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class Hallway(BaseModel, table=True): + is_there_a_hallway: bool + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# class LivingRoom(BaseModel, table=True): -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class LivingRoom(BaseModel, table=True): + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# class DiningRoom(BaseModel, table=True): -# is_there_a_dining_room: bool -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class DiningRoom(BaseModel, table=True): + is_there_a_dining_room: bool + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# class Kitchen(BaseModel, table=True): -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() -# is_there_a_cooker_hood_present_in_the_room: bool +class Kitchen(BaseModel, table=True): + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() + is_there_a_cooker_hood_present_in_the_room: bool -# class Utility(BaseModel, table=True): -# is_there_a_utility_room: bool -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class Utility(BaseModel, table=True): + is_there_a_utility_room: bool + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# class WC(BaseModel, table=True): -# is_there_a_seperated_wc: bool -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class WC(BaseModel, table=True): + is_there_a_seperated_wc: bool + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# class Landing(BaseModel, table=True): -# is_there_a_landing: bool -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class Landing(BaseModel, table=True): + is_there_a_landing: bool + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# class LoftSpace(BaseModel, table=True): -# is_the_main_loft_space_accessible: str -# is_there_more_than_one_loft_space: bool +class LoftSpace(BaseModel, table=True): + is_the_main_loft_space_accessible: str + is_there_more_than_one_loft_space: bool -# class RoomInRoof(BaseModel, table=True): -# is_there_a_room_in_roof: bool -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class RoomInRoof(BaseModel, table=True): + is_there_a_room_in_roof: bool + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# class Bedroom(BaseModel, table=True): -# double_or_single_bedroom: str -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() -# rooms_id: uuid.UUID = Field(foreign_key="rooms.id") -# rooms: Optional["Rooms"] = Relationship(back_populates="bedrooms") +class Bedroom(BaseModel, table=True): + double_or_single_bedroom: str + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() + rooms_id: uuid.UUID = Field(foreign_key="rooms.id") + rooms: Optional["Rooms"] = Relationship(back_populates="bedrooms") -# class Bathroom(BaseModel, table=True): -# is_this_an_ensuite_bathroom: bool -# room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") -# room_info: Optional[RoomInfo] = Relationship() +class Bathroom(BaseModel, table=True): + is_this_an_ensuite_bathroom: bool + room_info_id: Optional[uuid.UUID] = Field(foreign_key="roominfo.id") + room_info: Optional[RoomInfo] = Relationship() -# rooms_id: uuid.UUID = Field(foreign_key="rooms.id") -# rooms: Optional["Rooms"] = Relationship(back_populates="bathrooms") + rooms_id: uuid.UUID = Field(foreign_key="rooms.id") + rooms: Optional["Rooms"] = Relationship(back_populates="bathrooms") -# class Rooms(BaseModel, table=True): -# hallway_id: uuid.UUID = Field(foreign_key="hallway.id") -# hallway: Hallway = Relationship() +class Rooms(BaseModel, table=True): + hallway_id: uuid.UUID = Field(foreign_key="hallway.id") + hallway: Hallway = Relationship() -# living_room_id: uuid.UUID = Field(foreign_key="livingroom.id") -# living_room: LivingRoom = Relationship() + living_room_id: uuid.UUID = Field(foreign_key="livingroom.id") + living_room: LivingRoom = Relationship() -# dining_room_id: uuid.UUID = Field(foreign_key="diningroom.id") -# dining_room: DiningRoom = Relationship() + dining_room_id: uuid.UUID = Field(foreign_key="diningroom.id") + dining_room: DiningRoom = Relationship() -# kitchen_id: uuid.UUID = Field(foreign_key="kitchen.id") -# kitchen: Kitchen = Relationship() + kitchen_id: uuid.UUID = Field(foreign_key="kitchen.id") + kitchen: Kitchen = Relationship() -# utility_id: uuid.UUID = Field(foreign_key="utility.id") -# utility: Utility = Relationship() + utility_id: uuid.UUID = Field(foreign_key="utility.id") + utility: Utility = Relationship() -# wash_chamber_id: uuid.UUID = Field(foreign_key="wc.id") -# wash_chamber: WC = Relationship() + wash_chamber_id: uuid.UUID = Field(foreign_key="wc.id") + wash_chamber: WC = Relationship() -# landing_id: uuid.UUID = Field(foreign_key="landing.id") -# landing: Landing = Relationship() + landing_id: uuid.UUID = Field(foreign_key="landing.id") + landing: Landing = Relationship() -# loft_space_id: uuid.UUID = Field(foreign_key="loftspace.id") -# loft_space: LoftSpace = Relationship() + loft_space_id: uuid.UUID = Field(foreign_key="loftspace.id") + loft_space: LoftSpace = Relationship() -# room_in_roof_id: uuid.UUID = Field(foreign_key="roominroof.id") -# room_in_roof: RoomInRoof = Relationship() + room_in_roof_id: uuid.UUID = Field(foreign_key="roominroof.id") + room_in_roof: RoomInRoof = Relationship() -# bedrooms: List[Bedroom] = Relationship(back_populates="rooms") -# bathrooms: List[Bathroom] = Relationship(back_populates="rooms") + bedrooms: List[Bedroom] = Relationship(back_populates="rooms") + bathrooms: List[Bathroom] = Relationship(back_populates="rooms") -# class GeneralConditionHeatingSystem(BaseModel, table=True): -# 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 GeneralConditionHeatingSystem(BaseModel, table=True): + 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 MainHeatingOne(BaseModel, table=True): -# as_defined_by: str -# fuel: str -# type: str +class MainHeatingOne(BaseModel, table=True): + as_defined_by: str + fuel: str + type: str -# class MainHeatingTwo(BaseModel, table=True): -# is_there_a_main_heating_two: bool +class MainHeatingTwo(BaseModel, table=True): + is_there_a_main_heating_two: bool -# class SecondaryHeating(BaseModel, table=True): -# is_there_a_secondary_heating: bool -# fuel: str -# electric_heating_type: str -# gas_heating_type: str +class SecondaryHeating(BaseModel, table=True): + is_there_a_secondary_heating: bool + fuel: str + electric_heating_type: str + gas_heating_type: str -# class HeatingByRoom(BaseModel, table=True): -# rooms_heated_by_main_system_one: List[str] = Field(sa_column=Column(JSON)) -# rooms_heated_by_main_system_two: List[str] = Field(sa_column=Column(JSON)) -# rooms_heated_by_secondary_heating: List[str] = Field(sa_column=Column(JSON)) -# are_there_any_partially_heated_rooms: bool -# partially_heated_rooms: Optional[List[str]] = Field(sa_column=Column(JSON)) -# are_there_any_unheated_rooms: bool -# unheated_rooms: List[str] = Field(sa_column=Column(JSON)) +class HeatingByRoom(BaseModel, table=True): + rooms_heated_by_main_system_one: List[str] = Field(sa_column=Column(JSON)) + rooms_heated_by_main_system_two: List[str] = Field(sa_column=Column(JSON)) + rooms_heated_by_secondary_heating: List[str] = Field(sa_column=Column(JSON)) + are_there_any_partially_heated_rooms: bool + partially_heated_rooms: Optional[List[str]] = Field(sa_column=Column(JSON)) + are_there_any_unheated_rooms: bool + unheated_rooms: List[str] = Field(sa_column=Column(JSON)) -# class Renewables(BaseModel, table=True): -# 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 Renewables(BaseModel, table=True): + 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, table=True): -# general_condition_id: uuid.UUID = Field(foreign_key="generalconditionheatingsystem.id") -# general_condition: GeneralConditionHeatingSystem = Relationship() +class HeatingSystem(BaseModel, table=True): + general_condition_id: uuid.UUID = Field(foreign_key="generalconditionheatingsystem.id") + general_condition: GeneralConditionHeatingSystem = Relationship() -# main_heating_one_id: uuid.UUID = Field(foreign_key="mainheatingone.id") -# main_heating_one: MainHeatingOne = Relationship() + main_heating_one_id: uuid.UUID = Field(foreign_key="mainheatingone.id") + main_heating_one: MainHeatingOne = Relationship() -# main_heating_two_id: uuid.UUID = Field(foreign_key="mainheatingtwo.id") -# main_heating_two: MainHeatingTwo = Relationship() + main_heating_two_id: uuid.UUID = Field(foreign_key="mainheatingtwo.id") + main_heating_two: MainHeatingTwo = Relationship() -# secondary_heating_id: uuid.UUID = Field(foreign_key="secondaryheating.id") -# secondary_heating: SecondaryHeating = Relationship() + secondary_heating_id: uuid.UUID = Field(foreign_key="secondaryheating.id") + secondary_heating: SecondaryHeating = Relationship() -# heating_by_room_id: uuid.UUID = Field(foreign_key="heatingbyroom.id") -# heating_by_room: HeatingByRoom = Relationship() + heating_by_room_id: uuid.UUID = Field(foreign_key="heatingbyroom.id") + heating_by_room: HeatingByRoom = Relationship() -# renewables_id: uuid.UUID = Field(foreign_key="renewables.id") -# renewables: Renewables = Relationship() + renewables_id: uuid.UUID = Field(foreign_key="renewables.id") + renewables: Renewables = Relationship() -# class Occupant(BaseModel, table=True): -# 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_wrote_that_the_tenent_agrees_assessment_been_supplied: bool +class Occupant(BaseModel, table=True): + 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_wrote_that_the_tenent_agrees_assessment_been_supplied: bool -# class EnergyUse(BaseModel, table=True): -# property_tenure: str -# who_is_the_electricity_payer: str +class EnergyUse(BaseModel, table=True): + property_tenure: str + who_is_the_electricity_payer: str -# class HeatingFromConditionReport(BaseModel, table=True): -# room_stat_in_temperature_in_celsius: Optional[str] = None -# room_stat_location: Optional[str] = None -# is_the_heating_pattern_known: Optional[str] = None +class HeatingFromConditionReport(BaseModel, table=True): + room_stat_in_temperature_in_celsius: Optional[str] = None + room_stat_location: Optional[str] = None + is_the_heating_pattern_known: Optional[str] = None -# class ShowerAndBath(BaseModel, table=True): -# 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 ShowerAndBath(BaseModel, table=True): + 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 FridgeAndFreezers(BaseModel, table=True): -# no_of_stand_alone_seperate_fridges: int -# no_of_stand_alone_seperate_freezers: int -# no_of_stand_alone_or_integrated_fridge_freezers: int +class FridgeAndFreezers(BaseModel, table=True): + no_of_stand_alone_seperate_fridges: int + no_of_stand_alone_seperate_freezers: int + no_of_stand_alone_or_integrated_fridge_freezers: int -# class Cooker(BaseModel,table=True): -# range_fuel: str -# normal_large_range: str -# cooker_type: str +class Cooker(BaseModel,table=True): + range_fuel: str + normal_large_range: str + cooker_type: str -# class TumbleDryer(BaseModel, table=True): -# percentage_of_annual_use: int -# space_for_outdoor_drying: bool +class TumbleDryer(BaseModel, table=True): + percentage_of_annual_use: int + space_for_outdoor_drying: bool -# class OccupantAssessment(BaseModel, table=True): -# occupant_id: uuid.UUID = Field(foreign_key="occupant.id") -# occupant: Occupant = Relationship() +class OccupantAssessment(BaseModel, table=True): + occupant_id: uuid.UUID = Field(foreign_key="occupant.id") + occupant: Occupant = Relationship() -# energy_use_id: uuid.UUID = Field(foreign_key="energyuse.id") -# energy_use: EnergyUse = Relationship() + energy_use_id: uuid.UUID = Field(foreign_key="energyuse.id") + energy_use: EnergyUse = Relationship() -# heating_id: uuid.UUID = Field(foreign_key="heatingfromconditionreport.id") -# heating: HeatingFromConditionReport = Relationship() + heating_id: uuid.UUID = Field(foreign_key="heatingfromconditionreport.id") + heating: HeatingFromConditionReport = Relationship() -# shower_and_bath_id: uuid.UUID = Field(foreign_key="showerandbath.id") -# shower_and_bath: ShowerAndBath = Relationship() + shower_and_bath_id: uuid.UUID = Field(foreign_key="showerandbath.id") + shower_and_bath: ShowerAndBath = Relationship() -# # appliances: Optional[Appliances] -# # appliances_id + # appliances: Optional[Appliances] + # appliances_id -# fridge_and_freezers_id: uuid.UUID = Field(foreign_key="fridgeandfreezers.id") -# fridge_and_freezers: FridgeAndFreezers = Relationship() + fridge_and_freezers_id: uuid.UUID = Field(foreign_key="fridgeandfreezers.id") + fridge_and_freezers: FridgeAndFreezers = Relationship() -# cooker_id: uuid.UUID = Field(foreign_key="cooker.id") -# cooker: Cooker = Relationship() + cooker_id: uuid.UUID = Field(foreign_key="cooker.id") + cooker: Cooker = Relationship() -# tumble_dryer_id: uuid.UUID = Field(foreign_key="tumbledryer.id") -# tumble_dryer: TumbleDryer = Relationship() + tumble_dryer_id: uuid.UUID = Field(foreign_key="tumbledryer.id") + tumble_dryer: TumbleDryer = Relationship() -# class ConditionReportModel(BaseModel, table=True): -# project_site_name: str -# property_reference_code: str -# property_address: str -# postcode: str +class ConditionReportModel(BaseModel, table=True): + project_site_name: str + property_reference_code: str + property_address: str + postcode: str -# general_information_id: uuid.UUID = Field(foreign_key="generalinformation.id") -# general_information: GeneralInformation = Relationship() + general_information_id: uuid.UUID = Field(foreign_key="generalinformation.id") + general_information: GeneralInformation = Relationship() -# access_and_elevations_id: uuid.UUID = Field(foreign_key="accessandelevations.id") -# access_and_elevations: AccessAndElevations = Relationship() + access_and_elevations_id: uuid.UUID = Field(foreign_key="accessandelevations.id") + access_and_elevations: AccessAndElevations = Relationship() -# rooms_id: uuid.UUID = Field(foreign_key="rooms.id") -# rooms: Rooms = Relationship() + rooms_id: uuid.UUID = Field(foreign_key="rooms.id") + rooms: Rooms = Relationship() -# heating_system_id: uuid.UUID = Field(foreign_key="heatingsystem.id") -# heating_system: HeatingSystem = Relationship() + heating_system_id: uuid.UUID = Field(foreign_key="heatingsystem.id") + heating_system: HeatingSystem = Relationship() -# occupancy_assessment_id: uuid.UUID = Field(foreign_key="occupantassessment.id") -# occupancy_assessment: OccupantAssessment = Relationship() + occupancy_assessment_id: uuid.UUID = Field(foreign_key="occupantassessment.id") + occupancy_assessment: OccupantAssessment = Relationship() diff --git a/etl/models/preSiteNoteTypes.py b/etl/models/preSiteNoteTypes.py index 01d7f8b..dbd9f5c 100644 --- a/etl/models/preSiteNoteTypes.py +++ b/etl/models/preSiteNoteTypes.py @@ -1,335 +1,335 @@ -# from sqlmodel import Field, SQLModel, Relationship -# import uuid -# from typing import Optional, List -# from datetime import datetime -# from pydantic import EmailStr -# from sqlalchemy import Column -# from sqlalchemy.dialects.postgresql import UUID -# from etl.models.topLevel import BaseModel, Documents +from sqlmodel import Field, SQLModel, Relationship +import uuid +from typing import Optional, List +from datetime import datetime +from pydantic import EmailStr +from sqlalchemy import Column +from sqlalchemy.dialects.postgresql import UUID +from etl.models.topLevel import BaseModel, Documents -# class PreSiteNote(BaseModel, table=True): -# summary_info_id: uuid.UUID = Field( -# foreign_key="presitenotessummaryinfo.id", -# nullable=False -# ) +class PreSiteNote(BaseModel, table=True): + summary_info_id: uuid.UUID = Field( + foreign_key="presitenotessummaryinfo.id", + nullable=False + ) -# summary_info: Optional["PreSiteNotesSummaryInfo"] = Relationship(back_populates="pre_site_notes") + summary_info: Optional["PreSiteNotesSummaryInfo"] = Relationship(back_populates="pre_site_notes") -# # Assessor Info -# assessor_id: uuid.UUID = Field( -# foreign_key="assessorinfo.id", -# nullable=False -# ) + # Assessor Info + assessor_id: uuid.UUID = Field( + foreign_key="assessorinfo.id", + nullable=False + ) -# assessor: Optional["AssessorInfo"] = Relationship(back_populates="pre_site_notes") + assessor: Optional["AssessorInfo"] = Relationship(back_populates="pre_site_notes") -# pre_site_note_description_id: uuid.UUID = Field( -# foreign_key="propertydescription.id", -# nullable=True -# ) + pre_site_note_description_id: uuid.UUID = Field( + foreign_key="propertydescription.id", + nullable=True + ) -# pre_site_note_description: Optional["PropertyDescription"] = Relationship(back_populates="pre_site_notes") + pre_site_note_description: Optional["PropertyDescription"] = Relationship(back_populates="pre_site_notes") -# class Dimension(BaseModel, table=True): -# floor_area_m2: float -# room_height_m: float -# loss_perimeter_m: float -# party_wall_length_m: float -# property_detail_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") -# property_detail: Optional["PropertyDetail"] = Relationship(back_populates="dimensions") +class Dimension(BaseModel, table=True): + floor_area_m2: float + room_height_m: float + loss_perimeter_m: float + party_wall_length_m: float + property_detail_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") + property_detail: Optional["PropertyDetail"] = Relationship(back_populates="dimensions") -# class Walls(BaseModel, table=True): -# construction: str -# insulation: str -# insulation_thickness_mm: str -# wall_thickness_measured: bool -# wall_thickness_mm: Optional[int] -# u_value_known: bool -# u_value_w_m2_k: Optional[float] -# dry_lining: bool -# alternative_wall_present: bool +class Walls(BaseModel, table=True): + construction: str + insulation: str + insulation_thickness_mm: str + wall_thickness_measured: bool + wall_thickness_mm: Optional[int] + u_value_known: bool + u_value_w_m2_k: Optional[float] + dry_lining: bool + alternative_wall_present: bool -# class Roofs(BaseModel, table=True): -# construction: str -# insulation_type: str -# insulation_thickness: str -# u_value_known: bool +class Roofs(BaseModel, table=True): + construction: str + insulation_type: str + insulation_thickness: str + u_value_known: bool -# class Floors(BaseModel, table=True): -# floor_type: str -# ground_floor_construction: str -# ground_floor_insulation_type: Optional[str] = "" -# floor_insulation_thickness_mm: Optional[float] = -1 -# u_value_known: bool +class Floors(BaseModel, table=True): + floor_type: str + ground_floor_construction: str + ground_floor_insulation_type: Optional[str] = "" + floor_insulation_thickness_mm: Optional[float] = -1 + u_value_known: bool -# class Windows(BaseModel, table=True): -# glazing_type: str -# area_m2: float -# roof_window: bool -# orientation: str -# u_value_w_m2_k: int -# g_value: int -# property_detail_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") -# property_detail: Optional["PropertyDetail"] = Relationship(back_populates="windows") +class Windows(BaseModel, table=True): + glazing_type: str + area_m2: float + roof_window: bool + orientation: str + u_value_w_m2_k: int + g_value: int + property_detail_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") + property_detail: Optional["PropertyDetail"] = Relationship(back_populates="windows") -# class PropertyDetail(BaseModel, table=True): -# age_band: str -# wall_id: Optional[uuid.UUID] = Field(default=None, foreign_key="walls.id") -# roof_id: Optional[uuid.UUID] = Field(default=None, foreign_key="roofs.id") -# floor_id: Optional[uuid.UUID] = Field(default=None, foreign_key="floors.id") +class PropertyDetail(BaseModel, table=True): + age_band: str + wall_id: Optional[uuid.UUID] = Field(default=None, foreign_key="walls.id") + roof_id: Optional[uuid.UUID] = Field(default=None, foreign_key="roofs.id") + floor_id: Optional[uuid.UUID] = Field(default=None, foreign_key="floors.id") -# # Relationships -# dimensions: List[Dimension] = Relationship(back_populates="property_detail") -# windows: List[Windows] = Relationship(back_populates="property_detail") + # Relationships + dimensions: List[Dimension] = Relationship(back_populates="property_detail") + windows: List[Windows] = Relationship(back_populates="property_detail") -# class Door(BaseModel, table=True): -# no_of_doors: int -# no_of_insulated_doors: int -# u_value_w_m2_k: Optional[str] +class Door(BaseModel, table=True): + no_of_doors: int + no_of_insulated_doors: int + u_value_w_m2_k: Optional[str] -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="door") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="door") -# class VentilationAndCooling(BaseModel, table=True): -# no_of_open_fireplaces: int -# ventilation_type: str -# space_cooling_system_present: bool +class VentilationAndCooling(BaseModel, table=True): + no_of_open_fireplaces: int + ventilation_type: str + space_cooling_system_present: bool -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="ventilation_and_cooling") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="ventilation_and_cooling") -# class Lighting(BaseModel, table=True): -# total_no_of_light_fittings: int -# total_no_of_lel_fittings: int +class Lighting(BaseModel, table=True): + total_no_of_light_fittings: int + total_no_of_lel_fittings: int -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="lighting") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="lighting") -# class HeatingSystemControls(BaseModel, table=True): -# control_type: str -# flue_type: str -# fan_assisted_flue: bool -# heat_emitter_type: str -# electricity_meter_type: Optional[str] = "" -# mains_gas_available: Optional[bool] = False +class HeatingSystemControls(BaseModel, table=True): + control_type: str + flue_type: str + fan_assisted_flue: bool + heat_emitter_type: str + electricity_meter_type: Optional[str] = "" + mains_gas_available: Optional[bool] = False -# class HeatingFromPreSiteNotes(BaseModel, table=True): -# type: str -# heating_source: str -# efficiency_source: str -# heating_fuel: str -# brand_name: str -# model_name: str -# model_qualifer: str -# sap_2009_table: Optional[str] = "" -# percentage_of_heated_floor_area_served: Optional[str] = "" -# controls_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingsystemcontrols.id") +class HeatingFromPreSiteNotes(BaseModel, table=True): + type: str + heating_source: str + efficiency_source: str + heating_fuel: str + brand_name: str + model_name: str + model_qualifer: str + sap_2009_table: Optional[str] = "" + percentage_of_heated_floor_area_served: Optional[str] = "" + controls_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingsystemcontrols.id") -# property_description: Optional["PropertyDescription"] = Relationship( -# back_populates="main_heating", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating_id]"} -# ) -# property_description2: Optional["PropertyDescription"] = Relationship( -# back_populates="main_heating2", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating2_id]"} -# ) + property_description: Optional["PropertyDescription"] = Relationship( + back_populates="main_heating", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating_id]"} + ) + property_description2: Optional["PropertyDescription"] = Relationship( + back_populates="main_heating2", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating2_id]"} + ) -# class HeatingType(BaseModel, table=True): -# heating_type: str -# fuel_type: str +class HeatingType(BaseModel, table=True): + heating_type: str + fuel_type: str -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="secondary_heating_type") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="secondary_heating_type") -# class WaterHeating(BaseModel, table=True): -# heating_type: str -# fuel_type: str +class WaterHeating(BaseModel, table=True): + heating_type: str + fuel_type: str -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="water_heating") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="water_heating") -# class HotWaterCylinder(BaseModel, table=True): -# volume: str -# insulation_type: str -# insulation_thickness: str -# thermostat: bool +class HotWaterCylinder(BaseModel, table=True): + volume: str + insulation_type: str + insulation_thickness: str + thermostat: bool -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="hot_water_cylinder") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="hot_water_cylinder") -# class SolarWaterHeating(BaseModel, table=True): -# solar_water_heating_details_known: bool +class SolarWaterHeating(BaseModel, table=True): + solar_water_heating_details_known: bool -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="solar_water_heating") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="solar_water_heating") -# class ShowerAndBaths(BaseModel, table=True): -# no_of_rooms_with_baths_and_or_shower: int -# no_of_rooms_with_mixer_shower_and_no_baths: int -# no_of_rooms_with_mixer_shower_and_baths: int +class ShowerAndBaths(BaseModel, table=True): + no_of_rooms_with_baths_and_or_shower: int + no_of_rooms_with_mixer_shower_and_no_baths: int + no_of_rooms_with_mixer_shower_and_baths: int -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="shower_and_baths") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="shower_and_baths") -# class FlueGasHeatRecoverySystem(BaseModel, table=True): -# fghrs_present: bool +class FlueGasHeatRecoverySystem(BaseModel, table=True): + fghrs_present: bool -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="flue_gas_heat_recovery_system") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="flue_gas_heat_recovery_system") -# class PhotovoltaicPanel(BaseModel, table=True): -# pvs_are_connected_to_dwelling_electricity_meter: bool -# percentage_of_external_roof_area_with_pvs: str +class PhotovoltaicPanel(BaseModel, table=True): + pvs_are_connected_to_dwelling_electricity_meter: bool + percentage_of_external_roof_area_with_pvs: str -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="photovoltaic_panel") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="photovoltaic_panel") -# class WindTurbine(BaseModel, table=True): -# wind_turbine: bool +class WindTurbine(BaseModel, table=True): + wind_turbine: bool -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="wind_turbine") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="wind_turbine") -# class OtherDetails(BaseModel, table=True): -# electricity_meter_type: str -# main_gas_avalible: bool +class OtherDetails(BaseModel, table=True): + electricity_meter_type: str + main_gas_avalible: bool -# property_description: Optional["PropertyDescription"] = Relationship(back_populates="other_details") + property_description: Optional["PropertyDescription"] = Relationship(back_populates="other_details") -# class PropertyDescription(BaseModel, table=True): -# built_form: str -# detachment_or_position: str -# no_of_main_property: int -# no_of_extension_1: Optional[int] = 0 -# no_of_extension_2: Optional[int] = 0 -# no_of_extension_3: Optional[int] = 0 -# no_of_extension_4: Optional[int] = 0 -# no_of_habitable_rooms: int -# no_of_heated_rooms: int -# heated_basement: bool -# conservatory_type: str -# percentage_of_draught_proofed: int -# terrain_type: str -# conservatory: bool +class PropertyDescription(BaseModel, table=True): + built_form: str + detachment_or_position: str + no_of_main_property: int + no_of_extension_1: Optional[int] = 0 + no_of_extension_2: Optional[int] = 0 + no_of_extension_3: Optional[int] = 0 + no_of_extension_4: Optional[int] = 0 + no_of_habitable_rooms: int + no_of_heated_rooms: int + heated_basement: bool + conservatory_type: str + percentage_of_draught_proofed: int + terrain_type: str + conservatory: bool -# main_property_id: uuid.UUID = Field(foreign_key="propertydetail.id") -# ex1_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") -# ex2_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") -# ex3_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") -# ex4_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") + main_property_id: uuid.UUID = Field(foreign_key="propertydetail.id") + ex1_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") + ex2_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") + ex3_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") + ex4_property_id: Optional[uuid.UUID] = Field(default=None, foreign_key="propertydetail.id") -# door_id: Optional[uuid.UUID] = Field(default=None, foreign_key="door.id") -# ventilation_and_cooling_id: Optional[uuid.UUID] = Field(default=None, foreign_key="ventilationandcooling.id") -# lighting_id: Optional[uuid.UUID] = Field(default=None, foreign_key="lighting.id") -# water_heating_id: Optional[uuid.UUID] = Field(default=None, foreign_key="waterheating.id") -# hot_water_cylinder_id: Optional[uuid.UUID] = Field(default=None, foreign_key="hotwatercylinder.id") -# solar_water_heating_id: Optional[uuid.UUID] = Field(default=None, foreign_key="solarwaterheating.id") -# shower_and_baths_id: Optional[uuid.UUID] = Field(default=None, foreign_key="showerandbaths.id") -# flue_gas_heat_recovery_system_id: Optional[uuid.UUID] = Field(default=None, foreign_key="fluegasheatrecoverysystem.id") -# photovoltaic_panel_id: Optional[uuid.UUID] = Field(default=None, foreign_key="photovoltaicpanel.id") -# wind_turbine_id: Optional[uuid.UUID] = Field(default=None, foreign_key="windturbine.id") -# other_details_id: Optional[uuid.UUID] = Field(default=None, foreign_key="otherdetails.id") -# main_heating_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingfrompresitenotes.id") -# main_heating2_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingfrompresitenotes.id") -# secondary_heating_type_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingtype.id") + door_id: Optional[uuid.UUID] = Field(default=None, foreign_key="door.id") + ventilation_and_cooling_id: Optional[uuid.UUID] = Field(default=None, foreign_key="ventilationandcooling.id") + lighting_id: Optional[uuid.UUID] = Field(default=None, foreign_key="lighting.id") + water_heating_id: Optional[uuid.UUID] = Field(default=None, foreign_key="waterheating.id") + hot_water_cylinder_id: Optional[uuid.UUID] = Field(default=None, foreign_key="hotwatercylinder.id") + solar_water_heating_id: Optional[uuid.UUID] = Field(default=None, foreign_key="solarwaterheating.id") + shower_and_baths_id: Optional[uuid.UUID] = Field(default=None, foreign_key="showerandbaths.id") + flue_gas_heat_recovery_system_id: Optional[uuid.UUID] = Field(default=None, foreign_key="fluegasheatrecoverysystem.id") + photovoltaic_panel_id: Optional[uuid.UUID] = Field(default=None, foreign_key="photovoltaicpanel.id") + wind_turbine_id: Optional[uuid.UUID] = Field(default=None, foreign_key="windturbine.id") + other_details_id: Optional[uuid.UUID] = Field(default=None, foreign_key="otherdetails.id") + main_heating_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingfrompresitenotes.id") + main_heating2_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingfrompresitenotes.id") + secondary_heating_type_id: Optional[uuid.UUID] = Field(default=None, foreign_key="heatingtype.id") -# # Relationships -# main_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_property_id]"}) -# ex1_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex1_property_id]"}) -# ex2_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex2_property_id]"}) -# ex3_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex3_property_id]"}) -# ex4_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex4_property_id]"}) + # Relationships + main_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_property_id]"}) + ex1_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex1_property_id]"}) + ex2_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex2_property_id]"}) + ex3_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex3_property_id]"}) + ex4_property: Optional["PropertyDetail"] = Relationship(sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.ex4_property_id]"}) -# # Related Models -# door: Optional["Door"] = Relationship(back_populates="property_description") -# ventilation_and_cooling: Optional["VentilationAndCooling"] = Relationship(back_populates="property_description") -# lighting: Optional["Lighting"] = Relationship(back_populates="property_description") -# water_heating: Optional["WaterHeating"] = Relationship(back_populates="property_description") -# hot_water_cylinder: Optional["HotWaterCylinder"] = Relationship(back_populates="property_description") -# solar_water_heating: Optional["SolarWaterHeating"] = Relationship(back_populates="property_description") -# shower_and_baths: Optional["ShowerAndBaths"] = Relationship(back_populates="property_description") -# flue_gas_heat_recovery_system: Optional["FlueGasHeatRecoverySystem"] = Relationship(back_populates="property_description") -# photovoltaic_panel: Optional["PhotovoltaicPanel"] = Relationship(back_populates="property_description") -# wind_turbine: Optional["WindTurbine"] = Relationship(back_populates="property_description") -# other_details: Optional["OtherDetails"] = Relationship(back_populates="property_description") -# main_heating: Optional["HeatingFromPreSiteNotes"] = Relationship(back_populates="property_description", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating_id]"}) -# main_heating2: Optional["HeatingFromPreSiteNotes"] = Relationship(back_populates="property_description", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating2_id]"}) -# secondary_heating_type: Optional["HeatingType"] = Relationship(back_populates="property_description") + # Related Models + door: Optional["Door"] = Relationship(back_populates="property_description") + ventilation_and_cooling: Optional["VentilationAndCooling"] = Relationship(back_populates="property_description") + lighting: Optional["Lighting"] = Relationship(back_populates="property_description") + water_heating: Optional["WaterHeating"] = Relationship(back_populates="property_description") + hot_water_cylinder: Optional["HotWaterCylinder"] = Relationship(back_populates="property_description") + solar_water_heating: Optional["SolarWaterHeating"] = Relationship(back_populates="property_description") + shower_and_baths: Optional["ShowerAndBaths"] = Relationship(back_populates="property_description") + flue_gas_heat_recovery_system: Optional["FlueGasHeatRecoverySystem"] = Relationship(back_populates="property_description") + photovoltaic_panel: Optional["PhotovoltaicPanel"] = Relationship(back_populates="property_description") + wind_turbine: Optional["WindTurbine"] = Relationship(back_populates="property_description") + other_details: Optional["OtherDetails"] = Relationship(back_populates="property_description") + main_heating: Optional["HeatingFromPreSiteNotes"] = Relationship(back_populates="property_description", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating_id]"}) + main_heating2: Optional["HeatingFromPreSiteNotes"] = Relationship(back_populates="property_description", sa_relationship_kwargs={"foreign_keys": "[PropertyDescription.main_heating2_id]"}) + secondary_heating_type: Optional["HeatingType"] = Relationship(back_populates="property_description") -# pre_site_notes: Optional["PreSiteNote"] = Relationship(back_populates="pre_site_note_description") + pre_site_notes: Optional["PreSiteNote"] = Relationship(back_populates="pre_site_note_description") -# class AssessorInfo(BaseModel, table=True): -# accreditation_number: str -# name: str -# phone_number: Optional[str] = None -# email_address: Optional[EmailStr] = None +class AssessorInfo(BaseModel, table=True): + accreditation_number: str + name: str + phone_number: Optional[str] = None + email_address: Optional[EmailStr] = None -# company_id: Optional[uuid.UUID] = Field(default=None, foreign_key="companyinfo.id") -# company: Optional["CompanyInfo"] = Relationship(back_populates="assessors") - -# pre_site_notes: List["PreSiteNote"] = Relationship(back_populates="assessor") -# documents: List["Documents"] = Relationship(back_populates="author") - - -# class PreSiteNotesSummaryInfo(BaseModel, table=True): -# reference_number: str -# epc_language: str -# uprn: Optional[str] = "" -# postcode: str -# region: str -# address: str -# town: str -# county: Optional[str] = "" -# property_tenure: str -# transaction_type: str -# inspection_date: datetime -# current_sap: str -# potential_sap: str -# current_ei: str -# potential_ei: str -# current_annual_emissions: str -# current_annual_emission_including_0925_multiplayer: str -# current_annual_energy_costs: str - -# pre_site_notes: List["PreSiteNote"] = Relationship(back_populates="summary_info") - -# class CompanyInfo(BaseModel, table=True): -# address: str -# trading_name: str -# post_code: str -# fax_number: Optional[str] = None -# related_party_disclosure: Optional[str] = None - -# assessors: List[AssessorInfo] = Relationship(back_populates="company") - - -# class Insulation(BaseModel, table=True): -# type: str - - - -# PreSiteNote.update_forward_refs() -# AssessorInfo.update_forward_refs() + company_id: Optional[uuid.UUID] = Field(default=None, foreign_key="companyinfo.id") + company: Optional["CompanyInfo"] = Relationship(back_populates="assessors") + + pre_site_notes: List["PreSiteNote"] = Relationship(back_populates="assessor") + documents: List["Documents"] = Relationship(back_populates="author") + + +class PreSiteNotesSummaryInfo(BaseModel, table=True): + reference_number: str + epc_language: str + uprn: Optional[str] = "" + postcode: str + region: str + address: str + town: str + county: Optional[str] = "" + property_tenure: str + transaction_type: str + inspection_date: datetime + current_sap: str + potential_sap: str + current_ei: str + potential_ei: str + current_annual_emissions: str + current_annual_emission_including_0925_multiplayer: str + current_annual_energy_costs: str + + pre_site_notes: List["PreSiteNote"] = Relationship(back_populates="summary_info") + +class CompanyInfo(BaseModel, table=True): + address: str + trading_name: str + post_code: str + fax_number: Optional[str] = None + related_party_disclosure: Optional[str] = None + + assessors: List[AssessorInfo] = Relationship(back_populates="company") + + +class Insulation(BaseModel, table=True): + type: str + + + +PreSiteNote.update_forward_refs() +AssessorInfo.update_forward_refs() diff --git a/etl/models/topLevel.py b/etl/models/topLevel.py index 98cb724..e892a26 100644 --- a/etl/models/topLevel.py +++ b/etl/models/topLevel.py @@ -22,38 +22,38 @@ class BaseModel(SQLModel): ) ) -# class Buildings(BaseModel, table=True): -# address: str -# postcode: str -# UPRN: str -# landlord_id: str -# domna_id: str +class Buildings(BaseModel, table=True): + address: str + postcode: str + UPRN: str + landlord_id: str + domna_id: str -# documents: List["Documents"] = Relationship(back_populates="building") + documents: List["Documents"] = Relationship(back_populates="building") -# class Documents(BaseModel, table=True): -# assessor_id: uuid.UUID = Field( -# foreign_key="assessorinfo.id", -# nullable=False -# ) -# author: Optional["AssessorInfo"] = Relationship(back_populates="documents") -# created_at: datetime -# document_type: ReportType +class Documents(BaseModel, table=True): + assessor_id: uuid.UUID = Field( + foreign_key="assessorinfo.id", + nullable=False + ) + author: Optional["AssessorInfo"] = Relationship(back_populates="documents") + created_at: datetime + document_type: ReportType -# building_id: uuid.UUID = Field(foreign_key="buildings.id", nullable=False) -# building: Optional["Buildings"] = Relationship(back_populates="documents") + building_id: uuid.UUID = Field(foreign_key="buildings.id", nullable=False) + building: Optional["Buildings"] = Relationship(back_populates="documents") -# target_table: str -# target_id: uuid.UUID + target_table: str + target_id: uuid.UUID -# class ReportType(str, Enum): -# QUIDOS_PRESITE_NOTE = "QUIDOS_PRESITE_NOTE" -# 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" -# OSMOSIS_CONDITION_PAS_2035_REPORT = "OSMOSIS_CONDITION_PAS_2035_REPORT" -# DOMNA_CONDITION_PAS_2035_REPORT = "DOMNA_CONDITION_PAS_2035_REPORT" +class ReportType(str, Enum): + QUIDOS_PRESITE_NOTE = "QUIDOS_PRESITE_NOTE" + 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" + OSMOSIS_CONDITION_PAS_2035_REPORT = "OSMOSIS_CONDITION_PAS_2035_REPORT" + DOMNA_CONDITION_PAS_2035_REPORT = "DOMNA_CONDITION_PAS_2035_REPORT" class uploaded_files(BaseModel, table=True): __tablename__ = "uploaded_files" diff --git a/etl/scraper/scraper.py b/etl/scraper/scraper.py index b4f0691..58f9e09 100644 --- a/etl/scraper/scraper.py +++ b/etl/scraper/scraper.py @@ -28,6 +28,7 @@ class SharePointInstaller(Enum): OSMOSIS_WAVE_3 = os.getenv("OSMOSIS_SHAREPOINT_ID", "350a3b48-8311-4506-8abb-69bafc280d6f") OSMOSIS_WAVE_2 = os.getenv("OSMOSIS_SHAREPOINT_ID", "bc925a9a-ad0b-4de9-9a3c-e61014cc7489") WARMFRONT = os.getenv("WARMFRONT_SHARPOINT_ID", "bea71c30-d366-454c-a484-ae4d6fd95bc4") + NEW_JJC = os.getenv("NEW JJC", "10d96eba-b4f9-4e30-804f-05a8b60507b0") class SharePointScraper(): """ diff --git a/etl/sharepoint_installer_data_gatherer.py b/etl/sharepoint_installer_data_gatherer.py index 4a42cac..1cf2d48 100644 --- a/etl/sharepoint_installer_data_gatherer.py +++ b/etl/sharepoint_installer_data_gatherer.py @@ -1,2 +1,65 @@ -print("hello world") +import os +os.environ["SHAREPOINT_CLIENT_ID"] = "6832a4c5-fb8c-4082-a746-4f51e1020f0d" +os.environ["SHAREPOINT_CLIENT_SECRET"] = "xpC8Q~Frww48SM1V-D8lGy5iOY7P_cJ7FF3jgarQ" +os.environ["SHAREPOINT_TENANT_ID"] = "10d5af8b-2cfd-4882-9ccd-b96e4812dacf" +from etl.scraper.scraper import SharePointScraper, SharePointInstaller +from etl.fileReader.pdfReaderToText import pdfReaderToText +import pandas as pd +sharepoint = SharePointScraper(SharePointInstaller.NEW_JJC) +file_paths = sharepoint.download_file_for_each_address() +master_path = "Baxter Kelly/Calico/CALICO-001" +address_files = sharepoint.get_folders_in_path(master_path) + +def extract_rating(text: str) -> str: + # Remove label if present + text = text.strip() + if text.lower().startswith("potential sap rating:"): + text = text.split(":", 1)[1].strip() + # Remove spaces and make uppercase + return text.replace(" ", "").upper() + +def files_to_download(files_to_download_sharepoint_info): + file_names_to_download = {} + only_pdf = [".pdf"] + for file in files_to_download_sharepoint_info['value']: + if 'file' in file: + if any(file["name"].endswith(ext) for ext in only_pdf): + file_names_to_download.update({file["name"]: file['@microsoft.graph.downloadUrl']}) + + return file_names_to_download + +def download_to_local(download, scraper, address): + each_file = [] + for file_name, url in download.items(): + content = scraper.get_file_content(url) + path = scraper.create_temp_file(content, f"{address}/{file_name}") + each_file.append(path) + + return {address: each_file} + +all_address_to_work_on = [] +if "value" in address_files: + for address in address_files["value"]: + name_of_address = address["name"] + to_download = sharepoint.get_folders_in_path(f"{master_path}/{name_of_address}") + download = files_to_download(to_download) + address_to_files = download_to_local(download,sharepoint, name_of_address) + all_address_to_work_on.append(address_to_files) + + +final_data = [] + +for eachaddress in all_address_to_work_on: + for addressName, files in eachaddress.items(): + for file in files: + pdf = pdfReaderToText(file) + if "Summary Information".lower() == pdf.text_list[0].lower(): + potential_sap_rating = f"{pdf.text_list[pdf.text_list.index("Emissions (t/year):")-1]}" + potential_sap_rating = extract_rating(potential_sap_rating) + row = {"address": addressName, "potential sap rating": potential_sap_rating} + final_data.append(row) + + + +pd.DataFrame(final_data) \ No newline at end of file From 27a622731118a50920c7ea0853b454bbb17cb029 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 1 Sep 2025 22:13:21 +0000 Subject: [PATCH 5/7] nick code --- etl/MonthEndUploader.py | 18 +++++++++++++++ etl/sharepoint_installer_data_gatherer.py | 28 +++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/etl/MonthEndUploader.py b/etl/MonthEndUploader.py index 6d0f97e..0afa353 100644 --- a/etl/MonthEndUploader.py +++ b/etl/MonthEndUploader.py @@ -22,3 +22,21 @@ def upload_to_month_end_folder(file_name_on_sp, local_file_path, add_to_path): print("Uploading to sharepoint...") sharepoint.upload_file(local_file_path, sharepoint_path, file_name_on_sp) print(f"Finished upload of {local_file_path} to sharepoint. It's found under {sharepoint_path}/{file_name_on_sp}") + + +def upload_to_nick_folder(file_name_on_sp, local_file_path, add_to_path=None): + sharepoint = SharePointScraper(SharePointInstaller.OSMOSIS_WAVE_2) + + parent_folder = "General/Junte Kim/For Nick" + today = datetime.today() + formatted_date = today.strftime("%Y-%m-%d%H-%M-S") + + sharepoint.create_dir(formatted_date, parent_folder) + sharepoint_path = parent_folder + "/" + formatted_date + if add_to_path: + sharepoint.create_dir(add_to_path, sharepoint_path) + sharepoint_path += "/" + add_to_path + + print("Uploading to sharepoint...") + sharepoint.upload_file(local_file_path, sharepoint_path, file_name_on_sp) + print(f"Finished upload of {local_file_path} to sharepoint. It's found under {sharepoint_path}/{file_name_on_sp}") diff --git a/etl/sharepoint_installer_data_gatherer.py b/etl/sharepoint_installer_data_gatherer.py index 1cf2d48..d612c6f 100644 --- a/etl/sharepoint_installer_data_gatherer.py +++ b/etl/sharepoint_installer_data_gatherer.py @@ -5,6 +5,7 @@ os.environ["SHAREPOINT_TENANT_ID"] = "10d5af8b-2cfd-4882-9ccd-b96e4812dacf" from etl.scraper.scraper import SharePointScraper, SharePointInstaller from etl.fileReader.pdfReaderToText import pdfReaderToText import pandas as pd +from MonthEndUploader import upload_to_nick_folder sharepoint = SharePointScraper(SharePointInstaller.NEW_JJC) file_paths = sharepoint.download_file_for_each_address() @@ -55,11 +56,30 @@ for eachaddress in all_address_to_work_on: for file in files: pdf = pdfReaderToText(file) if "Summary Information".lower() == pdf.text_list[0].lower(): - potential_sap_rating = f"{pdf.text_list[pdf.text_list.index("Emissions (t/year):")-1]}" - potential_sap_rating = extract_rating(potential_sap_rating) - row = {"address": addressName, "potential sap rating": potential_sap_rating} + current_sap_rating = pdf.text_list[pdf.text_list.index("Current SAP rating:") + 1] + house_no = pdf.text_list[pdf.text_list.index("House No:") + 1] + street = pdf.text_list[pdf.text_list.index("Street:") + 1] + post_code = pdf.text_list[pdf.text_list.index("Postcode:") + 1] + address = f"{house_no} {street.title()}" + floor_area = pdf.text_list[pdf.text_list.index("Lowest Floor:") + 1] + fuel_bill = pdf.text_list[pdf.text_list.index("Fuel Bill:") + 1] + row = { + "address": address, + "postcode": post_code, + "current sap rating": current_sap_rating, + "floor_area ": floor_area, + "fuel_bill ": fuel_bill, + } final_data.append(row) -pd.DataFrame(final_data) \ No newline at end of file +df = pd.DataFrame(final_data) + +file_name = "installer.xlsx" +df.to_excel(file_name, index=False) + +# Get local path +file_path = os.path.abspath(file_name) +upload_to_nick_folder(file_name, file_path) +print(f"File saved at: {file_path}") From ae116c43ad4f98e5c9626f3f64b1a889d88fdf55 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 5 Sep 2025 09:40:29 +0000 Subject: [PATCH 6/7] changes --- .github/workflows/months_end.yml | 8 ++--- etl/MonthEndUploader.py | 11 ++++-- etl/month_end_automation_wave_2_no_11.py | 2 +- etl/month_end_automation_wave_2_no_13.py | 2 +- etl/month_end_automation_wave_2_no_16.py | 4 +++ etl/month_end_automation_wave_2_no_8.py | 2 +- etl/month_end_automation_wave_3_layout.py | 24 ++++++------- etl/osmosis_data/asset_list.xlsx | Bin 20673 -> 20669 bytes ...osmosis_monday_to_sharepoint_automation.py | 32 +++++++++--------- 9 files changed, 46 insertions(+), 39 deletions(-) diff --git a/.github/workflows/months_end.yml b/.github/workflows/months_end.yml index f00009f..a575cd3 100644 --- a/.github/workflows/months_end.yml +++ b/.github/workflows/months_end.yml @@ -32,17 +32,17 @@ jobs: poetry run python etl/month_end_automation_wave_2_layout.py poetry run python etl/month_end_automation_wave_2_no_3.py poetry run python etl/month_end_automation_wave_2_no_4.py - poetry run python etl/month_end_automation_wave_2_no_5.py + # poetry run python etl/month_end_automation_wave_2_no_5.py poetry run python etl/month_end_automation_wave_2_no_6.py poetry run python etl/month_end_automation_wave_2_no_7.py poetry run python etl/month_end_automation_wave_2_no_8.py poetry run python etl/month_end_automation_wave_2_no_9.py - poetry run python etl/month_end_automation_wave_2_no_10.py + # poetry run python etl/month_end_automation_wave_2_no_10.py poetry run python etl/month_end_automation_wave_2_no_11.py poetry run python etl/month_end_automation_wave_2_no_12.py - poetry run python etl/month_end_automation_wave_2_no_13.py + # poetry run python etl/month_end_automation_wave_2_no_13.py poetry run python etl/month_end_automation_wave_2_no_14.py poetry run python etl/month_end_automation_wave_2_no_15.py - poetry run python etl/month_end_automation_wave_2_no_16.py + # poetry run python etl/month_end_automation_wave_2_no_16.py poetry run python etl/month_end_automation_wave_accent_housing.py poetry run python etl/month_end_automation_wave_3_layout.py diff --git a/etl/MonthEndUploader.py b/etl/MonthEndUploader.py index 0afa353..c3f4f65 100644 --- a/etl/MonthEndUploader.py +++ b/etl/MonthEndUploader.py @@ -12,10 +12,17 @@ def upload_to_month_end_folder(file_name_on_sp, local_file_path, add_to_path): parent_folder = "General/Junte Kim/month end" today = datetime.today() # Format as "Month YYYY" - formatted_date = today.strftime("%B %Y") + " Attempt 2" - + formatted_date = today.strftime("%B %Y") sharepoint.create_dir(formatted_date, parent_folder) sharepoint_path = parent_folder + "/" + formatted_date + + + # Make day month year folder + formatted_date = today.strftime("%d-%m-%y") + sharepoint.create_dir(formatted_date, sharepoint_path) + sharepoint_path += "/" + formatted_date + + # Make company folder sharepoint.create_dir(add_to_path, sharepoint_path) sharepoint_path += "/" + add_to_path diff --git a/etl/month_end_automation_wave_2_no_11.py b/etl/month_end_automation_wave_2_no_11.py index 874eaa3..db24dbe 100644 --- a/etl/month_end_automation_wave_2_no_11.py +++ b/etl/month_end_automation_wave_2_no_11.py @@ -153,7 +153,7 @@ filtered_dfs.append(post_epc) # POST EPR post_epr = df[ - df["post-epc status"].str.lower().isin(["post epr completed"]) + df["post-epc status"].str.lower().isin(["post epr completed", "Post epr desktop based completed"]) ].copy() post_epr["job_type"] = "POST epr" filtered_dfs.append(post_epr) diff --git a/etl/month_end_automation_wave_2_no_13.py b/etl/month_end_automation_wave_2_no_13.py index 7637222..958c126 100644 --- a/etl/month_end_automation_wave_2_no_13.py +++ b/etl/month_end_automation_wave_2_no_13.py @@ -200,7 +200,7 @@ timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') # Upload to sharepoint attribute = ['address', 'client', 'job_type', 'rate'] -master_folder_name = "Stonewater SHDF 2.0 - Operations" +master_folder_name = "Stonewater SHDF 3.0 - Operations" file_name = f"{master_folder_name}_{timestamp}.xlsx" combined_with_rates[attribute].to_excel(file_name, index=False) diff --git a/etl/month_end_automation_wave_2_no_16.py b/etl/month_end_automation_wave_2_no_16.py index 664ebff..5505f11 100644 --- a/etl/month_end_automation_wave_2_no_16.py +++ b/etl/month_end_automation_wave_2_no_16.py @@ -212,6 +212,10 @@ master_folder_name = "NCHA SHDF Wave 3 On Hold" file_name = f"{master_folder_name}_{timestamp}.xlsx" combined_with_rates[attribute].to_excel(file_name, index=False) + +print("hello world") +file_name = "WCHG SHDF 2.1 Mansard_2025-08-29_10-46.xlsx" +master_folder_name="junte was here" file_path = os.path.abspath(file_name) upload_to_month_end_folder(file_name, file_path, master_folder_name) diff --git a/etl/month_end_automation_wave_2_no_8.py b/etl/month_end_automation_wave_2_no_8.py index d465dd6..7f2ab25 100644 --- a/etl/month_end_automation_wave_2_no_8.py +++ b/etl/month_end_automation_wave_2_no_8.py @@ -96,7 +96,7 @@ def get_df(df, column_name, success_critera, job_name=None): # RA -ra = get_df(df, "ra", ["completed rdsap 9.9", "completed rdsap 10"], "RA") +ra = get_df(df, "ra", ["completed rdsap 9.9", "complete rdsap 10"], "RA") filtered_dfs.append(ra) diff --git a/etl/month_end_automation_wave_3_layout.py b/etl/month_end_automation_wave_3_layout.py index 32125a4..dd3765b 100644 --- a/etl/month_end_automation_wave_3_layout.py +++ b/etl/month_end_automation_wave_3_layout.py @@ -24,11 +24,11 @@ class BoardID(Enum): board_ids = [ - "9349630181", # WCHG Walkups-Operations x - "8830772914", # "L&Q London" x - "9601691730", # Cardo Wales & West - Wave 3 x - "9660895490", # Northumberland County SHDF Wave 3 x - "9641491000", # Watford Warm Homes x + "9349630181", # WCHG Walkups-Operations + "8830772914", # "L&Q London" + "9601691730", # Cardo Wales & West - Wave 3 + "9660895490", # Northumberland County SHDF Wave 3 + "9641491000", # Watford Warm Homes "9671463094", # Seddon ] @@ -244,11 +244,6 @@ for board, all_records in board_to_record.items(): if not v3.empty: filtered_dfs.append(v3) - # Coordination stage 2 Please complete - cors2 = get_df(df, "rc stage 2", ["to invoice"], "Coordination Stage 2") - if not cors2.empty: - filtered_dfs.append(cors2) - # Design archetype complex design = get_df(df, "design invoicing status", ["to invoice"]) design1 = get_df(design, "design invoice type", ["archetype (complex)"], "Design Archetype Complex") @@ -287,24 +282,25 @@ for board, all_records in board_to_record.items(): if not lodg2.empty: filtered_dfs.append(lodg2) - # POST EPC + # POST EPC + Retrofit Evaluation post_epc = get_df(df, "post epc & eval. invoicing status", ["epc to invoice"], "POST EPC") if not post_epc.empty: filtered_dfs.append(post_epc) - # POST EPR post_epr = get_df(df, "post epc & eval. invoicing status", ["epr to invoice"], "POST EPR") if not post_epr.empty: filtered_dfs.append(post_epr) + # Retrofit Evaluation might be need for just EPR + # post att post_att = get_df(df, "post att invoicing status", ["to invoice"], "POST ATT") if not post_att.empty: filtered_dfs.append(post_epc) - # Retrofit Evaluation - rc = get_df(df, "rc stage 2 invoicing status", ["to invoice"], "retrofit evaluation") + # Coordination Stage 2 + rc = get_df(df, "rc stage 2 invoicing status", ["to invoice"], "Coordination Stage 2") if not rc.empty: filtered_dfs.append(rc) diff --git a/etl/osmosis_data/asset_list.xlsx b/etl/osmosis_data/asset_list.xlsx index 822b436c5566f8a0ca16a938bb22f974b4eeb705..d26670cd0ce5f335c739b6dd67ad3958b2c2f206 100644 GIT binary patch literal 20669 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 C^AKkM literal 20673 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 Date: Fri, 5 Sep 2025 09:41:17 +0000 Subject: [PATCH 7/7] update git ignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 428dd7a..dba1391 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ __pycache__/ data/ *ipynb etl/survery_data.csv -foo.env.py \ No newline at end of file +foo.env.py +*.xlsx +*.csv \ No newline at end of file