diff --git a/.github/workflows/hubspot_deal_notes.yml b/.github/workflows/hubspot_surveyed_needs_sign_off.yml
similarity index 79%
rename from .github/workflows/hubspot_deal_notes.yml
rename to .github/workflows/hubspot_surveyed_needs_sign_off.yml
index ce80afe..2b036d6 100644
--- a/.github/workflows/hubspot_deal_notes.yml
+++ b/.github/workflows/hubspot_surveyed_needs_sign_off.yml
@@ -1,7 +1,7 @@
-name: Deal Notes From HubSpot Scraper
+name: Daily Surved
on:
schedule:
- - cron: '0 19 * * 0'
+ - cron: '0 17 * * 1-5'
workflow_dispatch:
jobs:
@@ -24,6 +24,6 @@ jobs:
run: |
pwd
ls -la
- poetry run python etl/dimitra_hubspot_notes_gather.py
+ poetry run python etl/hubspot_surveyed_needs_sign_off.py
env:
PYTHONPATH: ${{ github.workspace }}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index ea20d57..27782c1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,6 +3,7 @@
"python.REPL.sendToNativeREPL": true,
"notebook.output.scrolling": true,
"terminal.integrated.defaultProfile.linux": "bash",
+ "editor.rulers": [67],
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash"
diff --git a/alembic/env.py b/alembic/env.py
index d915dc1..bbecde1 100644
--- a/alembic/env.py
+++ b/alembic/env.py
@@ -5,8 +5,9 @@ from sqlalchemy import pool
from alembic import context
from sqlmodel import SQLModel
-from etl.load.topLevel import *
-from etl.load.preSiteNoteTypes import *
+from etl.models.topLevel import *
+from etl.models.preSiteNoteTypes import *
+from etl.models.conditionReport import *
import os
diff --git a/alembic/things_alembic_doesn't_automate_for.txt b/alembic/things_alembic_doesn't_automate_for.txt
new file mode 100644
index 0000000..b4f20e7
--- /dev/null
+++ b/alembic/things_alembic_doesn't_automate_for.txt
@@ -0,0 +1,19 @@
+Source: https://alembic.sqlalchemy.org/en/latest/autogenerate.html
+
+Autogenerate can not detect:
+
+ Changes of table name. These will come out as an add/drop of two different tables, and should be hand-edited into a name change instead.
+
+ Changes of column name. Like table name changes, these are detected as a column add/drop pair, which is not at all the same as a name change.
+
+ Anonymously named constraints. Give your constraints a name, e.g. UniqueConstraint('col1', 'col2', name="my_name"). See the section The Importance of Naming Constraints for background on how to configure automatic naming schemes for constraints.
+
+ Special SQLAlchemy types such as Enum when generated on a backend which doesn’t support ENUM directly - this because the representation of such a type in the non-supporting database, i.e. a CHAR+ CHECK constraint, could be any kind of CHAR+CHECK. For SQLAlchemy to determine that this is actually an ENUM would only be a guess, something that’s generally a bad idea. To implement your own “guessing” function here, use the sqlalchemy.events.DDLEvents.column_reflect() event to detect when a CHAR (or whatever the target type is) is reflected, and change it to an ENUM (or whatever type is desired) if it is known that that’s the intent of the type. The sqlalchemy.events.DDLEvents.after_parent_attach() can be used within the autogenerate process to intercept and un-attach unwanted CHECK constraints.
+
+
+
+Autogenerate can’t currently, but will eventually detect:
+
+ Some free-standing constraint additions and removals may not be supported, including PRIMARY KEY, EXCLUDE, CHECK; these are not necessarily implemented within the autogenerate detection system and also may not be supported by the supporting SQLAlchemy dialect.
+
+ Sequence additions, removals - not yet implemented.
diff --git a/alembic/versions/42a19efef660_condition_table_from_osmosis_added_as_i_.py b/alembic/versions/42a19efef660_condition_table_from_osmosis_added_as_i_.py
new file mode 100644
index 0000000..d3fc585
--- /dev/null
+++ b/alembic/versions/42a19efef660_condition_table_from_osmosis_added_as_i_.py
@@ -0,0 +1,166 @@
+"""condition table from osmosis added as i didn't add
+
+Revision ID: 42a19efef660
+Revises: 956c4ea39b29
+Create Date: 2025-06-17 12:45:14.976944
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+
+# revision identifiers, used by Alembic.
+revision: str = '42a19efef660'
+down_revision: Union[str, None] = '956c4ea39b29'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('assessordetails',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('assessor_name_and_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('elmhurst_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('conservatoryoroutbuilding',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('is_there_a_conservatory', sa.Boolean(), nullable=False),
+ sa.Column('is_there_a_cellar_present', sa.Boolean(), nullable=False),
+ sa.Column('is_there_an_outbuilding', sa.Boolean(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('elevation',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('protected_conservatory_or_aonb', sa.Boolean(), nullable=False),
+ sa.Column('material_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('are_there_any_visible_signs_of_existing_wall_insulation', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('does_the_existing_ground_level_on_any_elevation_bridge_the_dpc', sa.Boolean(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('elevationinfo',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('elevation_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('cavity_wall_depth', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('is_insulation_present', sa.Boolean(), nullable=False),
+ sa.Column('insulation_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('externalelevation',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('structural_defects_of_elevation', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('does_any_structural_defect_need_resolving_before_retrofit', sa.Boolean(), nullable=False),
+ sa.Column('are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework', sa.Boolean(), nullable=False),
+ sa.Column('are_there_any_visible_signs_of_movement', sa.Boolean(), nullable=False),
+ sa.Column('are_there_any_visible_signs_of_cracking_to_the_existing_external_finish', sa.Boolean(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('generalconditionheatingsystem',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('is_the_heating_system_in_working_order', sa.Boolean(), nullable=False),
+ sa.Column('does_the_occupant_have_a_smart_meter', sa.Boolean(), nullable=False),
+ sa.Column('are_there_any_smart_monitoring_devices', sa.Boolean(), nullable=False),
+ sa.Column('are_the_gas_and_electricity_meters_accessible', sa.Boolean(), nullable=False),
+ sa.Column('dual_or_single_electric_meter', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('heating_from_condition_report',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('room_stat_in_temperature_in_celsius', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column('room_stat_location', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column('is_the_heating_pattern_known', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('inspectionandproject',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('inspection_date', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('mainheatingone',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('as_defined_by', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('fuel', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('mainheatingtwo',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('is_there_a_main_heating_two', sa.Boolean(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('propertyaccess',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('are_there_any_road_restriction_in_the_locality', sa.Boolean(), nullable=False),
+ sa.Column('is_on_street_parking_available', sa.Boolean(), nullable=False),
+ sa.Column('are_there_any_overhead_wires_or_cables', sa.Boolean(), nullable=False),
+ sa.Column('is_the_access_gated', sa.Boolean(), nullable=False),
+ sa.Column('is_there_restricted_space_for_contractors_to_access_the_wall_area', sa.Boolean(), nullable=False),
+ sa.Column('is_there_restricted_space_for_contractors_to_access_the_roof_area', sa.Boolean(), nullable=False),
+ sa.Column('is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighbouring_boundary_along_the_full_gable_elevation', sa.Boolean(), nullable=False),
+ sa.Column('is_access_to_the_rear_provided_by_use_of_a_ginnel', sa.Boolean(), nullable=False),
+ sa.Column('is_access_to_the_rear_provided_by_use_of_a_secured_alleyway', sa.Boolean(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('secondaryheating',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('is_there_a_secondary_heating', sa.Boolean(), nullable=False),
+ sa.Column('fuel', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('electric_heating_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('gas_heating_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('theproperty',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('house_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('on_which_floor_is_the_flat_located', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('is_there_a_corridor', sa.Boolean(), nullable=False),
+ sa.Column('is_it_heated', sa.Boolean(), nullable=False),
+ sa.Column('it_there_a_balcony', sa.Boolean(), nullable=False),
+ sa.Column('classification_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('orientation_front_elevation', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('orientation_in_degrees_front_elevation', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('exposure_zone', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('main_wall_construction', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('conditionreport',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('project_site_name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('property_reference_code', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('property_address', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('postcode', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('assessor_details_id', sa.Uuid(), nullable=True),
+ sa.Column('inspection_id', sa.Uuid(), nullable=True),
+ sa.Column('property_id', sa.Uuid(), nullable=True),
+ sa.Column('general_condition_id', sa.Uuid(), nullable=True),
+ sa.ForeignKeyConstraint(['assessor_details_id'], ['assessordetails.id'], ),
+ sa.ForeignKeyConstraint(['general_condition_id'], ['generalconditionheatingsystem.id'], ),
+ sa.ForeignKeyConstraint(['inspection_id'], ['inspectionandproject.id'], ),
+ sa.ForeignKeyConstraint(['property_id'], ['theproperty.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('conditionreport')
+ op.drop_table('theproperty')
+ op.drop_table('secondaryheating')
+ op.drop_table('propertyaccess')
+ op.drop_table('mainheatingtwo')
+ op.drop_table('mainheatingone')
+ op.drop_table('inspectionandproject')
+ op.drop_table('heating_from_condition_report')
+ op.drop_table('generalconditionheatingsystem')
+ op.drop_table('externalelevation')
+ op.drop_table('elevationinfo')
+ op.drop_table('elevation')
+ op.drop_table('conservatoryoroutbuilding')
+ op.drop_table('assessordetails')
+ # ### end Alembic commands ###
diff --git a/alembic/versions/544f060f1eb8_add_elevation.py b/alembic/versions/544f060f1eb8_add_elevation.py
new file mode 100644
index 0000000..e4a9167
--- /dev/null
+++ b/alembic/versions/544f060f1eb8_add_elevation.py
@@ -0,0 +1,132 @@
+"""add elevation
+
+Revision ID: 544f060f1eb8
+Revises: 881142a89edb
+Create Date: 2025-06-19 09:54:43.770068
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = '544f060f1eb8'
+down_revision: Union[str, None] = '881142a89edb'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('secondaryheating')
+ op.drop_table('heating_from_condition_report')
+ op.drop_table('generalconditionheatingsystem')
+ op.drop_table('conditionreport')
+ op.drop_table('mainheatingtwo')
+ op.drop_table('conservatoryoroutbuilding')
+ op.drop_table('mainheatingone')
+ op.drop_table('elevation')
+ op.drop_table('generalinformation')
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('elevation',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('protected_conservatory_or_aonb', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('material_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_visible_signs_of_existing_wall_insulation', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('does_the_existing_ground_level_on_any_elevation_bridge_the_dpc', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='elevation_pkey')
+ )
+ op.create_table('mainheatingone',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('as_defined_by', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('fuel', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='mainheatingone_pkey')
+ )
+ op.create_table('conservatoryoroutbuilding',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_conservatory', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_cellar_present', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_an_outbuilding', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='conservatoryoroutbuilding_pkey')
+ )
+ op.create_table('mainheatingtwo',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_main_heating_two', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='mainheatingtwo_pkey')
+ )
+ op.create_table('conditionreport',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('project_site_name', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('property_reference_code', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('property_address', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('postcode', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('general_information_id', sa.UUID(), autoincrement=False, nullable=True),
+ sa.ForeignKeyConstraint(['general_information_id'], ['generalinformation.id'], name='conditionreport_general_information_id_fkey'),
+ sa.PrimaryKeyConstraint('id', name='conditionreport_pkey')
+ )
+ op.create_table('generalconditionheatingsystem',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_the_heating_system_in_working_order', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('does_the_occupant_have_a_smart_meter', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_smart_monitoring_devices', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_the_gas_and_electricity_meters_accessible', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('dual_or_single_electric_meter', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='generalconditionheatingsystem_pkey')
+ )
+ op.create_table('heating_from_condition_report',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('room_stat_in_temperature_in_celsius', sa.VARCHAR(), autoincrement=False, nullable=True),
+ sa.Column('room_stat_location', sa.VARCHAR(), autoincrement=False, nullable=True),
+ sa.Column('is_the_heating_pattern_known', sa.VARCHAR(), autoincrement=False, nullable=True),
+ sa.PrimaryKeyConstraint('id', name='heating_from_condition_report_pkey')
+ )
+ op.create_table('externalelevation',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('structural_defects_of_elevation', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('does_any_structural_defect_need_resolving_before_retrofit', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_signs_of_water_penetration_caused_by_failed_rainw', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_visible_signs_of_movement', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_visible_signs_of_cracking_to_the_existing_externa', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='externalelevation_pkey')
+ )
+ op.create_table('generalinformation',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('assessor_details_id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('inspection_and_project_id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('the_property_id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.ForeignKeyConstraint(['assessor_details_id'], ['assessordetails.id'], name='generalinformation_assessor_details_id_fkey'),
+ sa.ForeignKeyConstraint(['inspection_and_project_id'], ['inspectionandproject.id'], name='generalinformation_inspection_and_project_id_fkey'),
+ sa.ForeignKeyConstraint(['the_property_id'], ['theproperty.id'], name='generalinformation_the_property_id_fkey'),
+ sa.PrimaryKeyConstraint('id', name='generalinformation_pkey')
+ )
+ op.create_table('secondaryheating',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_secondary_heating', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('fuel', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('electric_heating_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('gas_heating_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='secondaryheating_pkey')
+ )
+ op.create_table('propertyaccess',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_road_restriction_in_the_locality', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_on_street_parking_available', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_overhead_wires_or_cables', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_the_access_gated', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_restricted_space_for_contractors_to_access_the_wall_ar', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_restricted_space_for_contractors_to_access_the_roof_ar', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighb', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_access_to_the_rear_provided_by_use_of_a_ginnel', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_access_to_the_rear_provided_by_use_of_a_secured_alleyway', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='propertyaccess_pkey')
+ )
+ # ### end Alembic commands ###
diff --git a/alembic/versions/771a2bc48ac2_add_general_information_class.py b/alembic/versions/771a2bc48ac2_add_general_information_class.py
new file mode 100644
index 0000000..f5dbcf0
--- /dev/null
+++ b/alembic/versions/771a2bc48ac2_add_general_information_class.py
@@ -0,0 +1,73 @@
+"""add general information class
+
+Revision ID: 771a2bc48ac2
+Revises: f2f205448dff
+Create Date: 2025-06-18 14:56:08.762223
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = '771a2bc48ac2'
+down_revision: Union[str, None] = 'f2f205448dff'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('generalinformation',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('assessor_details_id', sa.Uuid(), nullable=False),
+ sa.Column('inspection_and_project_id', sa.Uuid(), nullable=False),
+ sa.Column('the_property_id', sa.Uuid(), nullable=False),
+ sa.ForeignKeyConstraint(['assessor_details_id'], ['assessordetails.id'], ),
+ sa.ForeignKeyConstraint(['inspection_and_project_id'], ['inspectionandproject.id'], ),
+ sa.ForeignKeyConstraint(['the_property_id'], ['theproperty.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.add_column('conditionreport', sa.Column('general_information_id', sa.Uuid(), nullable=True))
+ op.drop_constraint('conditionreport_general_condition_id_fkey', 'conditionreport', type_='foreignkey')
+ op.drop_constraint('conditionreport_assessor_details_id_fkey', 'conditionreport', type_='foreignkey')
+ op.create_foreign_key(None, 'conditionreport', 'generalinformation', ['general_information_id'], ['id'])
+ op.drop_column('conditionreport', 'general_condition_id')
+ op.drop_column('conditionreport', 'assessor_details_id')
+ op.add_column('externalelevation', sa.Column('are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework', sa.Boolean(), nullable=False))
+ op.add_column('externalelevation', sa.Column('are_there_any_visible_signs_of_cracking_to_the_existing_external_finish', sa.Boolean(), nullable=False))
+ op.drop_column('externalelevation', 'water_penetration_from_failed_rainwater_goods')
+ op.drop_column('externalelevation', 'cracking_to_external_finish_visible')
+ op.add_column('propertyaccess', sa.Column('is_there_restricted_space_for_contractors_to_access_the_wall_area', sa.Boolean(), nullable=False))
+ op.add_column('propertyaccess', sa.Column('is_there_restricted_space_for_contractors_to_access_the_roof_area', sa.Boolean(), nullable=False))
+ op.add_column('propertyaccess', sa.Column('is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighbouring_boundary_along_the_full_gable_elevation', sa.Boolean(), nullable=False))
+ op.drop_column('propertyaccess', 'sufficient_boundary_width')
+ op.drop_column('propertyaccess', 'restricted_wall_access')
+ op.drop_column('propertyaccess', 'restricted_roof_access')
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('propertyaccess', sa.Column('restricted_roof_access', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.add_column('propertyaccess', sa.Column('restricted_wall_access', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.add_column('propertyaccess', sa.Column('sufficient_boundary_width', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.drop_column('propertyaccess', 'is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighbouring_boundary_along_the_full_gable_elevation')
+ op.drop_column('propertyaccess', 'is_there_restricted_space_for_contractors_to_access_the_roof_area')
+ op.drop_column('propertyaccess', 'is_there_restricted_space_for_contractors_to_access_the_wall_area')
+ op.add_column('externalelevation', sa.Column('cracking_to_external_finish_visible', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.add_column('externalelevation', sa.Column('water_penetration_from_failed_rainwater_goods', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.drop_column('externalelevation', 'are_there_any_visible_signs_of_cracking_to_the_existing_external_finish')
+ op.drop_column('externalelevation', 'are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework')
+ op.add_column('conditionreport', sa.Column('assessor_details_id', sa.UUID(), autoincrement=False, nullable=True))
+ op.add_column('conditionreport', sa.Column('general_condition_id', sa.UUID(), autoincrement=False, nullable=True))
+ op.drop_constraint(None, 'conditionreport', type_='foreignkey')
+ op.create_foreign_key('conditionreport_assessor_details_id_fkey', 'conditionreport', 'assessordetails', ['assessor_details_id'], ['id'])
+ op.create_foreign_key('conditionreport_general_condition_id_fkey', 'conditionreport', 'generalconditionheatingsystem', ['general_condition_id'], ['id'])
+ op.drop_column('conditionreport', 'general_information_id')
+ op.drop_table('generalinformation')
+ # ### end Alembic commands ###
diff --git a/alembic/versions/881142a89edb_delete_everythign_and_start_again.py b/alembic/versions/881142a89edb_delete_everythign_and_start_again.py
new file mode 100644
index 0000000..22aa91d
--- /dev/null
+++ b/alembic/versions/881142a89edb_delete_everythign_and_start_again.py
@@ -0,0 +1,176 @@
+"""delete everythign and start again
+
+Revision ID: 881142a89edb
+Revises: 771a2bc48ac2
+Create Date: 2025-06-18 15:38:53.955796
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = '881142a89edb'
+down_revision: Union[str, None] = '771a2bc48ac2'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.execute("DROP TABLE conditionreport CASCADE")
+ op.execute("DROP TABLE generalinformation CASCADE")
+ op.execute("DROP TABLE theproperty CASCADE")
+ op.execute("DROP TABLE assessordetails CASCADE")
+ op.execute("DROP TABLE inspectionandproject CASCADE")
+ op.execute("DROP TABLE propertyaccess CASCADE")
+ op.execute("DROP TABLE externalelevation CASCADE")
+ op.execute("DROP TABLE generalconditionheatingsystem CASCADE")
+ op.execute("DROP TABLE heating_from_condition_report CASCADE")
+ op.execute("DROP TABLE mainheatingone CASCADE")
+ op.execute("DROP TABLE mainheatingtwo CASCADE")
+ op.execute("DROP TABLE secondaryheating CASCADE")
+ op.execute("DROP TABLE conservatoryoroutbuilding CASCADE")
+ op.execute("DROP TABLE elevation CASCADE")
+ op.execute("DROP TABLE elevationinfo CASCADE")
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('inspectionandproject',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('inspection_date', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='inspectionandproject_pkey'),
+ postgresql_ignore_search_path=False
+ )
+ op.create_table('elevation',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('protected_conservatory_or_aonb', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('material_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_visible_signs_of_existing_wall_insulation', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('does_the_existing_ground_level_on_any_elevation_bridge_the_dpc', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='elevation_pkey')
+ )
+ op.create_table('mainheatingone',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('as_defined_by', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('fuel', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='mainheatingone_pkey')
+ )
+ op.create_table('assessordetails',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('assessor_name_and_id', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('elmhurst_id', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='assessordetails_pkey'),
+ postgresql_ignore_search_path=False
+ )
+ op.create_table('elevationinfo',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('elevation_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('cavity_wall_depth', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('is_insulation_present', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('insulation_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='elevationinfo_pkey')
+ )
+ op.create_table('externalelevation',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('structural_defects_of_elevation', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('does_any_structural_defect_need_resolving_before_retrofit', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_signs_of_water_penetration_caused_by_failed_rainw', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_visible_signs_of_movement', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_visible_signs_of_cracking_to_the_existing_externa', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='externalelevation_pkey')
+ )
+ op.create_table('conditionreport',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('project_site_name', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('property_reference_code', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('property_address', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('postcode', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('inspection_id', sa.UUID(), autoincrement=False, nullable=True),
+ sa.Column('property_id', sa.UUID(), autoincrement=False, nullable=True),
+ sa.Column('general_information_id', sa.UUID(), autoincrement=False, nullable=True),
+ sa.ForeignKeyConstraint(['inspection_id'], ['inspectionandproject.id'], name='conditionreport_inspection_id_fkey'),
+ sa.ForeignKeyConstraint(['property_id'], ['theproperty.id'], name='conditionreport_property_id_fkey'),
+ sa.PrimaryKeyConstraint('id', name='conditionreport_pkey')
+ )
+ op.create_table('theproperty',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('house_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('on_which_floor_is_the_flat_located', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_corridor', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_it_heated', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('it_there_a_balcony', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('classification_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('orientation_front_elevation', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('orientation_in_degrees_front_elevation', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('exposure_zone', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('main_wall_construction', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='theproperty_pkey'),
+ postgresql_ignore_search_path=False
+ )
+ op.create_table('generalconditionheatingsystem',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_the_heating_system_in_working_order', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('does_the_occupant_have_a_smart_meter', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_smart_monitoring_devices', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_the_gas_and_electricity_meters_accessible', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('dual_or_single_electric_meter', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='generalconditionheatingsystem_pkey')
+ )
+ op.create_table('mainheatingtwo',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_main_heating_two', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='mainheatingtwo_pkey')
+ )
+ op.create_table('generalinformation',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('assessor_details_id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('inspection_and_project_id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('the_property_id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.ForeignKeyConstraint(['assessor_details_id'], ['assessordetails.id'], name='generalinformation_assessor_details_id_fkey'),
+ sa.ForeignKeyConstraint(['inspection_and_project_id'], ['inspectionandproject.id'], name='generalinformation_inspection_and_project_id_fkey'),
+ sa.ForeignKeyConstraint(['the_property_id'], ['theproperty.id'], name='generalinformation_the_property_id_fkey'),
+ sa.PrimaryKeyConstraint('id', name='generalinformation_pkey')
+ )
+ op.create_table('heating_from_condition_report',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('room_stat_in_temperature_in_celsius', sa.VARCHAR(), autoincrement=False, nullable=True),
+ sa.Column('room_stat_location', sa.VARCHAR(), autoincrement=False, nullable=True),
+ sa.Column('is_the_heating_pattern_known', sa.VARCHAR(), autoincrement=False, nullable=True),
+ sa.PrimaryKeyConstraint('id', name='heating_from_condition_report_pkey')
+ )
+ op.create_table('propertyaccess',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_road_restriction_in_the_locality', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_on_street_parking_available', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('are_there_any_overhead_wires_or_cables', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_the_access_gated', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_restricted_space_for_contractors_to_access_the_wall_ar', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_restricted_space_for_contractors_to_access_the_roof_ar', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighb', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_access_to_the_rear_provided_by_use_of_a_ginnel', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_access_to_the_rear_provided_by_use_of_a_secured_alleyway', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='propertyaccess_pkey')
+ )
+ op.create_table('secondaryheating',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_secondary_heating', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('fuel', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('electric_heating_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.Column('gas_heating_type', sa.VARCHAR(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='secondaryheating_pkey')
+ )
+ op.create_table('conservatoryoroutbuilding',
+ sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_conservatory', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_a_cellar_present', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.Column('is_there_an_outbuilding', sa.BOOLEAN(), autoincrement=False, nullable=False),
+ sa.PrimaryKeyConstraint('id', name='conservatoryoroutbuilding_pkey')
+ )
+ # ### end Alembic commands ###
diff --git a/alembic/versions/956c4ea39b29_condition_table_from_osmosis.py b/alembic/versions/956c4ea39b29_condition_table_from_osmosis.py
new file mode 100644
index 0000000..d4b904a
--- /dev/null
+++ b/alembic/versions/956c4ea39b29_condition_table_from_osmosis.py
@@ -0,0 +1,34 @@
+"""condition table from osmosis
+
+Revision ID: 956c4ea39b29
+Revises: 427e65da69c1
+Create Date: 2025-06-17 10:49:55.213111
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = '956c4ea39b29'
+down_revision: Union[str, None] = '427e65da69c1'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_foreign_key(None, 'documents', 'assessorinfo', ['assessor_id'], ['id'])
+ op.create_foreign_key(None, 'presitenote', 'assessorinfo', ['assessor_id'], ['id'])
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_constraint(None, 'presitenote', type_='foreignkey')
+ op.drop_constraint(None, 'documents', type_='foreignkey')
+ # ### end Alembic commands ###
diff --git a/alembic/versions/c373a9a083f1_is_there_an_fourth_external_elevation.py b/alembic/versions/c373a9a083f1_is_there_an_fourth_external_elevation.py
new file mode 100644
index 0000000..eb3904a
--- /dev/null
+++ b/alembic/versions/c373a9a083f1_is_there_an_fourth_external_elevation.py
@@ -0,0 +1,38 @@
+"""is there an fourth external elevation
+
+Revision ID: c373a9a083f1
+Revises: ceffde5b28ac
+Create Date: 2025-06-20 15:08:31.752580
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = 'c373a9a083f1'
+down_revision: Union[str, None] = 'ceffde5b28ac'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('externalelevationgabletwo', sa.Column('is_there_a_fourth_external_elevation', sa.Boolean(), nullable=False))
+ op.drop_column('externalelevationgabletwo', 'do_all_answers_for_the_front_elevation_apply_to_this_wall')
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('externalelevationgabletwo', sa.Column('do_all_answers_for_the_front_elevation_apply_to_this_wall', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.drop_column('externalelevationgabletwo', 'is_there_a_fourth_external_elevation')
+ op.add_column('externalelevation', sa.Column('are_there_any_visible_signs_of_cracking_to_the_existing_externa', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.add_column('externalelevation', sa.Column('any_signs_of_water_penetration_caused_by_failed_rainwater_goods', sa.BOOLEAN(), autoincrement=False, nullable=False))
+ op.drop_column('externalelevation', 'are_there_any_visible_signs_of_cracking_to_the_existing_external_finish')
+ op.drop_column('externalelevation', 'any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework')
+ # ### end Alembic commands ###
diff --git a/alembic/versions/ceffde5b28ac_added_new_column.py b/alembic/versions/ceffde5b28ac_added_new_column.py
new file mode 100644
index 0000000..5b29461
--- /dev/null
+++ b/alembic/versions/ceffde5b28ac_added_new_column.py
@@ -0,0 +1,43 @@
+"""added new column
+
+Revision ID: ceffde5b28ac
+Revises: 544f060f1eb8
+Create Date: 2025-06-19 10:23:37.567361
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+
+# revision identifiers, used by Alembic.
+revision: str = 'ceffde5b28ac'
+down_revision: Union[str, None] = '544f060f1eb8'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('elevation',
+ sa.Column('id', sa.Uuid(), nullable=False),
+ sa.Column('protected_conservatory_or_aonb', sa.Boolean(), nullable=False),
+ sa.Column('material_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('visible_signs_of_existing_wall_insulation', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column('ground_level_bridge_the_dpc', sa.Boolean(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.add_column('elevationinfo', sa.Column('elevation_id', sa.Uuid(), nullable=True))
+ op.create_foreign_key(None, 'elevationinfo', 'elevation', ['elevation_id'], ['id'])
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_constraint(None, 'elevationinfo', type_='foreignkey')
+ op.drop_column('elevationinfo', 'elevation_id')
+ op.drop_table('elevation')
+ # ### end Alembic commands ###
diff --git a/alembic/versions/f2f205448dff_rename_ntables.py b/alembic/versions/f2f205448dff_rename_ntables.py
new file mode 100644
index 0000000..3324f1e
--- /dev/null
+++ b/alembic/versions/f2f205448dff_rename_ntables.py
@@ -0,0 +1,78 @@
+"""rename ntables
+
+Revision ID: f2f205448dff
+Revises: 42a19efef660
+Create Date: 2025-06-18 10:09:39.973162
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+
+# revision identifiers, used by Alembic.
+revision: str = 'f2f205448dff'
+down_revision: Union[str, None] = '42a19efef660'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # Drop foreign keys referencing 'heating'
+ op.drop_constraint('propertydescription_main_heating_id_fkey', 'propertydescription', type_='foreignkey')
+ op.drop_constraint('propertydescription_main_heating2_id_fkey', 'propertydescription', type_='foreignkey')
+
+ # Rename 'heating' table to 'heatingfrompresitenotes'
+ op.rename_table('heating', 'heatingfrompresitenotes')
+
+ # Rename/re-add cleaned up columns (ensure you're within 63 char limit!)
+ op.drop_column('externalelevation', 'are_there_any_visible_signs_of_cracking_to_the_existing_externa')
+ op.drop_column('externalelevation', 'are_there_any_signs_of_water_penetration_caused_by_failed_rainw')
+ op.add_column('externalelevation', sa.Column(
+ 'water_penetration_from_failed_rainwater_goods', sa.Boolean(), nullable=False))
+ op.add_column('externalelevation', sa.Column(
+ 'cracking_to_external_finish_visible', sa.Boolean(), nullable=False))
+
+ # Update propertyaccess fields
+ op.drop_column('propertyaccess', 'is_there_restricted_space_for_contractors_to_access_the_wall_ar')
+ op.drop_column('propertyaccess', 'is_there_restricted_space_for_contractors_to_access_the_roof_ar')
+ op.drop_column('propertyaccess', 'is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighb')
+ op.add_column('propertyaccess', sa.Column(
+ 'restricted_wall_access', sa.Boolean(), nullable=False))
+ op.add_column('propertyaccess', sa.Column(
+ 'restricted_roof_access', sa.Boolean(), nullable=False))
+ op.add_column('propertyaccess', sa.Column(
+ 'sufficient_boundary_width', sa.Boolean(), nullable=False))
+
+ # Create new foreign keys to heatingfrompresitenotes
+ op.create_foreign_key('propertydescription_main_heating_id_fkey', 'propertydescription', 'heatingfrompresitenotes', ['main_heating_id'], ['id'])
+ op.create_foreign_key('propertydescription_main_heating2_id_fkey', 'propertydescription', 'heatingfrompresitenotes', ['main_heating2_id'], ['id'])
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ # Drop new foreign keys
+ op.drop_constraint('propertydescription_main_heating_id_fkey', 'propertydescription', type_='foreignkey')
+ op.drop_constraint('propertydescription_main_heating2_id_fkey', 'propertydescription', type_='foreignkey')
+
+ # Rename 'heatingfrompresitenotes' back to 'heating'
+ op.rename_table('heatingfrompresitenotes', 'heating')
+
+ # Revert column changes
+ op.drop_column('externalelevation', 'water_penetration_from_failed_rainwater_goods')
+ op.drop_column('externalelevation', 'cracking_to_external_finish_visible')
+ op.add_column('externalelevation', sa.Column('are_there_any_signs_of_water_penetration_caused_by_failed_rainw', sa.Boolean(), nullable=False))
+ op.add_column('externalelevation', sa.Column('are_there_any_visible_signs_of_cracking_to_the_existing_externa', sa.Boolean(), nullable=False))
+
+ op.drop_column('propertyaccess', 'restricted_wall_access')
+ op.drop_column('propertyaccess', 'restricted_roof_access')
+ op.drop_column('propertyaccess', 'sufficient_boundary_width')
+ op.add_column('propertyaccess', sa.Column('is_there_restricted_space_for_contractors_to_access_the_wall_ar', sa.Boolean(), nullable=False))
+ op.add_column('propertyaccess', sa.Column('is_there_restricted_space_for_contractors_to_access_the_roof_ar', sa.Boolean(), nullable=False))
+ op.add_column('propertyaccess', sa.Column('is_there_more_than_1_point_5_meters_in_width_to_fence_or_neighb', sa.Boolean(), nullable=False))
+
+ # Restore original foreign keys
+ op.create_foreign_key('propertydescription_main_heating_id_fkey', 'propertydescription', 'heating', ['main_heating_id'], ['id'])
+ op.create_foreign_key('propertydescription_main_heating2_id_fkey', 'propertydescription', 'heating', ['main_heating2_id'], ['id'])
diff --git a/etl/condition_report_etl.py b/etl/condition_report_etl.py
index 5284172..e826406 100644
--- a/etl/condition_report_etl.py
+++ b/etl/condition_report_etl.py
@@ -1,6 +1,14 @@
+from pprint import pprint
+from etl.db.db import get_db_session, init_db
+
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])
+pprint(sdp.condition_report.master_obj)
+init_db()
+with get_db_session() as db_session:
+ sdp.load_condition_report(db_session)
-# TODO: add the ability to add document type, and sharepoint or s3 link so we can process access it again
\ No newline at end of file
+# TODO: add the ability to add document type, and sharepoint or s3 link so we can process access it again
+# Terraform lambda set up to start this job from a s3 link
\ No newline at end of file
diff --git a/etl/daily_script.py b/etl/daily_script.py
index b2c76d9..1689167 100644
--- a/etl/daily_script.py
+++ b/etl/daily_script.py
@@ -1,5 +1,5 @@
import os
-from pdfReader.pdfReaderToText import pdfReaderToText
+from fileReader.pdfReaderToText import pdfReaderToText
from etl.scraper.scraper import SharePointScraper, SharePointInstaller, WEEK_COMMENCING
from pprint import pprint, pformat
import logging
diff --git a/etl/development.py b/etl/development.py
index f70c68d..9319e1c 100644
--- a/etl/development.py
+++ b/etl/development.py
@@ -1,6 +1,6 @@
from etl.scraper.scraper import SharePointScraper, SharePointInstaller
from pprint import pformat
-from etl.pdfReader.pdfReaderToText import pdfReaderToText
+from etl.fileReader.pdfReaderToText import pdfReaderToText
from etl.surveyedData.surveryedData import surveyedDataProcessor
import pandas as pd
diff --git a/etl/pdfReader/__init__.py b/etl/fileReader/__init__.py
similarity index 100%
rename from etl/pdfReader/__init__.py
rename to etl/fileReader/__init__.py
diff --git a/etl/pdfReader/pdfReaderToText.py b/etl/fileReader/pdfReaderToText.py
similarity index 69%
rename from etl/pdfReader/pdfReaderToText.py
rename to etl/fileReader/pdfReaderToText.py
index 34e37ad..bc9643f 100644
--- a/etl/pdfReader/pdfReaderToText.py
+++ b/etl/fileReader/pdfReaderToText.py
@@ -1,8 +1,8 @@
from etl.utils.logger import Logger
import logging
import pymupdf
-from etl.pdfReader.sitenotes import QuidosSiteNotesExtractor, CSR, ConditionReport
-from etl.pdfReader.reportType import ReportType
+from etl.fileReader.sitenotes import QuidosSiteNotesExtractor, CSR, WarmHomesConditionReport, ECOConditionReport, RDSAPEnergyReport
+from etl.fileReader.reportType import ReportType
class pdfReaderToText():
@@ -24,6 +24,7 @@ class pdfReaderToText():
self.all_text += text
self.text_list = self.all_text.split('\n')
+ print(self.text_list)
def get_list_of_text(self):
return self.text_list
@@ -41,7 +42,11 @@ class pdfReaderToText():
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
+ self.type = ReportType.WARM_HOMES_CONDITION_REPORT
+ elif "Domna NEW PAS 2035 ECO Condition Report".lower() in self.text_list[0].lower():
+ self.type = ReportType.ECO_CONDITION_REPORT
+ elif "ENERGY REPORT".lower() == self.text_list[0].lower():
+ self.type = ReportType.RDSAP_ENERGY_REPORT
else:
pass
return self.type
@@ -53,6 +58,10 @@ 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)
+ elif self.type == ReportType.WARM_HOMES_CONDITION_REPORT:
+ return WarmHomesConditionReport(self.text_list)
+ elif self.type == ReportType.ECO_CONDITION_REPORT:
+ return ECOConditionReport(self.text_list)
+ elif self.type == ReportType.RDSAP_ENERGY_REPORT:
+ return RDSAPEnergyReport(self.text_list)
\ No newline at end of file
diff --git a/etl/fileReader/reportType.py b/etl/fileReader/reportType.py
new file mode 100644
index 0000000..07ac12e
--- /dev/null
+++ b/etl/fileReader/reportType.py
@@ -0,0 +1,15 @@
+from enum import Enum
+
+
+class ReportType(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"
+ ECO_CONDITION_REPORT = "osmosis_condition_pas_2035_report"
+ WARM_HOMES_CONDITION_REPORT = "warm_homes_condition_pas_2035_report"
+ RDSAP_ENERGY_REPORT = "rdsap_energy_report"
+ LIG_XML = "lodgement_xml_needed_for_lodgement_to_like_trademark"
+ RDSAP_XML = "reduce_xml_needed_to_generate_full_sap_xml"
+ FULLSAP_XML = "full_xml_needed_for_co_ordination"
diff --git a/etl/pdfReader/sitenotes.py b/etl/fileReader/sitenotes.py
similarity index 96%
rename from etl/pdfReader/sitenotes.py
rename to etl/fileReader/sitenotes.py
index d3d74f8..f46726c 100644
--- a/etl/pdfReader/sitenotes.py
+++ b/etl/fileReader/sitenotes.py
@@ -1,11 +1,11 @@
-from etl.pdfReader.reportType import ReportType
+from etl.fileReader.reportType import ReportType
from etl.transform.preSiteNoteTypes import (
CompanyInfo, PreSiteNotesSummaryInfo, AssessorInfo,
PropertyDescription, PropertyDetail, Dimension,
Walls, Roofs, Floors, Door, VentilationAndCooling,
Lighting, WaterHeating, HotWaterCylinder, SolarWaterHeating,
ShowerAndBaths, FlueGasHeatRecoverySystem, PhotovoltaicPanel,
- WindTurbine, OtherDetails, Windows, Heating, HeatingSystemControls,
+ WindTurbine, OtherDetails, Windows, HeatingFromPreSiteNotes, HeatingSystemControls,
HeatingType, Insulation
)
from etl.transform.conditionReportTypes import (
@@ -14,7 +14,8 @@ from etl.transform.conditionReportTypes import (
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
+ MainHeatingOne, MainHeatingTwo, SecondaryHeating, HeatingByRoom, Renewables, Occupant, EnergyUse, HeatingFromConditionReport, ShowerAndBath, FridgeAndFreezers, Cooker, TumbleDryer,
+ GeneralInformation, OccupantAssessment
)
from datetime import datetime
from pprint import pprint
@@ -87,30 +88,52 @@ class CSR(SiteNotesExtractor):
type=dict_.get('detailed_description_of_existing_cavity_wall_insulation_', "")
) if dict_ is not None else None
-
-class ConditionReport(SiteNotesExtractor):
+class RDSAPEnergyReport(SiteNotesExtractor):
def __init__(self, data_list):
super().__init__(data_list)
- self.type = ReportType.OSMOSIS_CONDITION_PAS_2035_REPORT
- self.setup_condition_report()
+ self.type = ReportType.RDSAP_ENERGY_REPORT
+ self.master_obj = self.setup_energy_report()
+
+ def setup_energy_report(self):
+ pass
+
+class ECOConditionReport(SiteNotesExtractor):
+ def __init__(self, data_list):
+ super().__init__(data_list)
+ self.type = ReportType.ECO_CONDITION_REPORT
+ self.master_obj = self.setup_condition_report()
def setup_condition_report(self):
- assesor_details, inspection_and_project, the_property, main_elevation, elevations = self.get_section_1()
+ pass
+
+
+
+class WarmHomesConditionReport(SiteNotesExtractor):
+ def __init__(self, data_list):
+ super().__init__(data_list)
+ self.type = ReportType.WARM_HOMES_CONDITION_REPORT
+ self.master_obj = self.setup_condition_report()
+
+ def setup_condition_report(self):
+ general_information = 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()
+ heating_system = self.get_section_4()
+ occupant_assessment = self.get_section_5()
site_name, reference_code, address, postcode = self.get_section_0()
+
return ConditionReportModel(
project_site_name=site_name,
property_reference_code=reference_code,
property_address=address,
postcode=postcode,
-
-
+ general_information=general_information,
+ access_and_elevations=access_and_elevations,
+ rooms=rooms,
+ heating_system=heating_system,
+ occupancy_assessment=occupant_assessment,
)
-
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")
@@ -127,7 +150,14 @@ class ConditionReport(SiteNotesExtractor):
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
+
+ return GeneralInformation(
+ assessor_details=assessor_details,
+ inspection_and_project=inspection_and_project,
+ the_property=the_property,
+ main_elevation=main_elevation,
+ elevations=elevations
+ )
def get_assessor_details(self):
data = self.get_data_between("1.1 Assessor details","1.2 Inspection & Project")
@@ -501,7 +531,15 @@ class ConditionReport(SiteNotesExtractor):
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
+
+ return HeatingSystem(
+ general_condition=general_condition_of_heating_system,
+ main_heating_one=main_heating_one,
+ main_heating_two=main_heating_two,
+ secondary_heating=secondary_heating,
+ heating_by_room=heating_by_room,
+ renewables=renewables
+ )
def get_main_heating_one(self):
data = self.get_data_between("Main Heating 1", "Main Heating 2")
@@ -589,7 +627,16 @@ class ConditionReport(SiteNotesExtractor):
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
+ return OccupantAssessment(
+ occupant=occupants,
+ energy_use=energy_use,
+ heating=heating,
+ shower_and_bath=shower_and_bath,
+ appliances=appliances,
+ fridge_and_freezers=fridge_and_freezers,
+ cooker=cooker,
+ tumble_dryer=tumble_dryer
+ )
def get_occupants(self):
data = self.get_data_between("Occupants", "Energy use")
@@ -616,7 +663,7 @@ class ConditionReport(SiteNotesExtractor):
def get_heating(self):
data = self.get_data_between("Heating", "Shower & bath")
- return Heating(
+ return HeatingFromConditionReport(
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']),
diff --git a/etl/fileReader/xmlReader.py b/etl/fileReader/xmlReader.py
new file mode 100644
index 0000000..d49b9d4
--- /dev/null
+++ b/etl/fileReader/xmlReader.py
@@ -0,0 +1,43 @@
+from etl.utils.logger import Logger
+import logging
+from xml.dom.minidom import parse
+import os
+from etl.fileReader.reportType import ReportType
+
+class xmlReader():
+ def __init__(self, file_path):
+ self.source_path = file_path
+ self.logger = Logger(name='xmlReader', level=logging.INFO).get_logger()
+ self.xml_obj = None
+ self.type = None
+ self.get_xml_obj()
+
+
+ def get_xml_obj(self):
+ try:
+ if not os.path.exists(self.source_path):
+ self.logger.error(f"File not found: {self.source_path}")
+ return None
+
+ with open(self.source_path, 'r', encoding='utf-8') as file:
+ self.xml_obj = parse(file)
+ self.get_type()
+ return self.xml_obj
+
+ except Exception as e:
+ self.logger.error(f"Failed to parse XML file {self.source_path}: {e}")
+ self.xml_obj = None
+ return self.xml_obj
+
+ def get_type(self):
+ xmlHeaderName = self.xml_obj.documentElement.tagName
+ xmlHeaderName = xmlHeaderName.lower()
+ if xmlHeaderName == 'RdSap-Report'.lower():
+ self.type = ReportType.LIG_XML
+ elif xmlHeaderName == "SurveyRec".lower():
+ self.type = ReportType.RDSAP_XML
+ elif xmlHeaderName == "ImportExportRecord".lower():
+ self.type = ReportType.FULLSAP_XML
+ else:
+ pass
+ return self.type
diff --git a/etl/hubSpotClient/hubspot.py b/etl/hubSpotClient/hubspot.py
index a05c309..3b4913e 100644
--- a/etl/hubSpotClient/hubspot.py
+++ b/etl/hubSpotClient/hubspot.py
@@ -7,7 +7,10 @@ import time
from pydantic import ValidationError
from etl.utils.logger import Logger
import logging
-
+import traceback
+from hubspot.crm.objects.notes import SimplePublicObjectInput as NoteInput
+from hubspot.crm.associations import BatchInputPublicAssociation, PublicAssociation
+import time
class DealStage(Enum):
@@ -41,6 +44,7 @@ class HubSpotClient():
except Exception as e:
return "Unknown Deal" # Fallback if the deal name is not found
+
def get_listings_from_deals_id(self, deals_id):
from hubspot.crm.objects import PublicObjectSearchRequest
found_notes = []
@@ -77,7 +81,7 @@ class HubSpotClient():
def get_domna_and_landlord_id(self, deals_id):
data = self.get_listings_from_deals_id(deals_id)
- return data.properties['domna_property_id'], data.properties['owner_property_id'], data.properties['national_uprn']
+ return data.properties['domna_property_id'], data.properties['owner_property_id'], data.properties.get('national_uprn', '') or ''
def get_notes_from_deals_id(self, deals_id):
from hubspot.crm.objects import PublicObjectSearchRequest
@@ -205,6 +209,9 @@ class HubSpotClient():
for deal in found_deals:
domna_id, landlord_id, uprn = self.get_domna_and_landlord_id(deal.id)
try:
+ deal_name = deal.properties['dealname']
+ self.logger.info(f"Validating <{deal_name}>")
+ # input(f"Press enter to verfiy <{deal_name}>")
all_deals.append(SubmissionInfoFromDeal(
deal_id= deal.properties["hs_object_id"],
deal_name=deal.properties["dealname"],
@@ -220,14 +227,46 @@ class HubSpotClient():
uprn = uprn,
))
except Exception as e:
+ def format_error_note(e):
+ note_text = "⚠️ Automated Verification Failed:
"
+
+ if hasattr(e, "errors") and callable(e.errors):
+ note_text += "❌ Validation Errors:
"
+ for error in e.errors():
+ loc = error.get('loc', 'N/A')
+ msg = error.get('msg', 'N/A')
+ error_type = error.get('type', 'N/A')
+ error_input = error.get('input', 'N/A')
+
+ note_text += (
+ f"• Field: {loc}
"
+ f" - Message: {msg}
"
+ f" - Type: {error_type}
"
+ f" - Input: {error_input}
"
+ )
+ else:
+ note_text += (
+ "❗ Non-validation error:
"
+ f"
{str(e)}