From da0ef3e5e0abdbd5e0bd227eeb09d5aae8be62aa Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:24:11 +0000 Subject: [PATCH 01/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20triggers?= =?UTF-8?q?=20when=20commencement=20date=20is=20first=20set=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/hubspot_deal_differ.py | 8 +++++ etl/hubspot/tests/test_hubspot_deal_differ.py | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/etl/hubspot/hubspot_deal_differ.py b/etl/hubspot/hubspot_deal_differ.py index c4e9c0148..0a20c12d8 100644 --- a/etl/hubspot/hubspot_deal_differ.py +++ b/etl/hubspot/hubspot_deal_differ.py @@ -206,6 +206,14 @@ class HubspotDealDiffer: return old_deal.confirmed_survey_time != new_deal.get("confirmed_survey_time") + @staticmethod + def check_for_abri_tenant_data_fetch( + new_deal: Dict[str, str], + new_project: Optional[ProjectData], + old_deal: HubspotDealData, + ) -> bool: + raise NotImplementedError + @staticmethod def _is_abri_condition_deal( new_deal: Dict[str, str], new_project: Optional[ProjectData] diff --git a/etl/hubspot/tests/test_hubspot_deal_differ.py b/etl/hubspot/tests/test_hubspot_deal_differ.py index 544242623..8c47f97c8 100644 --- a/etl/hubspot/tests/test_hubspot_deal_differ.py +++ b/etl/hubspot/tests/test_hubspot_deal_differ.py @@ -687,6 +687,39 @@ def test_abri_survey_amendment__date_first_set_on_abri_deal__returns_false() -> assert result is False +# ==================================== +# ABRI TENANT DATA FETCH TRIGGER TESTS +# ==================================== + + +def test_abri_tenant_data_fetch__commencement_date_first_set_on_abri_deal__returns_true() -> ( + None +): + deal_id = uuid.uuid4() + + # Arrange + old_deal = make_old_deal( + id=deal_id, + project_code="Abri Condition", + expected_commencement_date=None, + ) + new_deal = make_new_deal( + deal_id, + project_code="Abri Condition", + expected_commencement_date="2026-08-01", + ) + + # Act + result = HubspotDealDiffer.check_for_abri_tenant_data_fetch( + new_deal=new_deal, + new_project=None, + old_deal=old_deal, + ) + + # Assert + assert result is True + + # ======================= # DB UPDATE TRIGGER TESTS # ======================= From 2f1dfaa5a6f0f3ccf5b64fb0c9a1b775c00c7962 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:24:36 +0000 Subject: [PATCH 02/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20triggers?= =?UTF-8?q?=20when=20commencement=20date=20is=20first=20set=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/hubspot_deal_differ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etl/hubspot/hubspot_deal_differ.py b/etl/hubspot/hubspot_deal_differ.py index 0a20c12d8..4ebed8f3d 100644 --- a/etl/hubspot/hubspot_deal_differ.py +++ b/etl/hubspot/hubspot_deal_differ.py @@ -212,7 +212,7 @@ class HubspotDealDiffer: new_project: Optional[ProjectData], old_deal: HubspotDealData, ) -> bool: - raise NotImplementedError + return True @staticmethod def _is_abri_condition_deal( From 0b8f0f17a9f5762206f24656e4c4b2c36e6cd9e2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:25:07 +0000 Subject: [PATCH 03/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20does=20not?= =?UTF-8?q?=20trigger=20for=20non-abri=20deals=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/tests/test_hubspot_deal_differ.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/etl/hubspot/tests/test_hubspot_deal_differ.py b/etl/hubspot/tests/test_hubspot_deal_differ.py index 8c47f97c8..e57007684 100644 --- a/etl/hubspot/tests/test_hubspot_deal_differ.py +++ b/etl/hubspot/tests/test_hubspot_deal_differ.py @@ -720,6 +720,32 @@ def test_abri_tenant_data_fetch__commencement_date_first_set_on_abri_deal__retur assert result is True +def test_abri_tenant_data_fetch__non_abri_deal__returns_false() -> None: + deal_id = uuid.uuid4() + + # Arrange + old_deal = make_old_deal( + id=deal_id, + project_code="Southern Retrofit", + expected_commencement_date=None, + ) + new_deal = make_new_deal( + deal_id, + project_code="Southern Retrofit", + expected_commencement_date="2026-08-01", + ) + + # Act + result = HubspotDealDiffer.check_for_abri_tenant_data_fetch( + new_deal=new_deal, + new_project=None, + old_deal=old_deal, + ) + + # Assert + assert result is False + + # ======================= # DB UPDATE TRIGGER TESTS # ======================= From 426aa9a15e40af10af9a4795cba601c71e110cff Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:25:26 +0000 Subject: [PATCH 04/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20does=20not?= =?UTF-8?q?=20trigger=20for=20non-abri=20deals=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/hubspot_deal_differ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etl/hubspot/hubspot_deal_differ.py b/etl/hubspot/hubspot_deal_differ.py index 4ebed8f3d..134a6b80e 100644 --- a/etl/hubspot/hubspot_deal_differ.py +++ b/etl/hubspot/hubspot_deal_differ.py @@ -212,7 +212,7 @@ class HubspotDealDiffer: new_project: Optional[ProjectData], old_deal: HubspotDealData, ) -> bool: - return True + return HubspotDealDiffer._is_abri_condition_deal(new_deal, new_project) @staticmethod def _is_abri_condition_deal( From c18f4feb3efd1140f534a9436c4ac791219daa57 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:26:02 +0000 Subject: [PATCH 05/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20ignores=20?= =?UTF-8?q?already-set=20commencement=20dates=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/tests/test_hubspot_deal_differ.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/etl/hubspot/tests/test_hubspot_deal_differ.py b/etl/hubspot/tests/test_hubspot_deal_differ.py index e57007684..a241c5189 100644 --- a/etl/hubspot/tests/test_hubspot_deal_differ.py +++ b/etl/hubspot/tests/test_hubspot_deal_differ.py @@ -746,6 +746,32 @@ def test_abri_tenant_data_fetch__non_abri_deal__returns_false() -> None: assert result is False +def test_abri_tenant_data_fetch__commencement_date_already_set__returns_false() -> None: + deal_id = uuid.uuid4() + + # Arrange + old_deal = make_old_deal( + id=deal_id, + project_code="Abri Condition", + expected_commencement_date=datetime(2026, 7, 1, tzinfo=timezone.utc), + ) + new_deal = make_new_deal( + deal_id, + project_code="Abri Condition", + expected_commencement_date="2026-08-01", + ) + + # Act + result = HubspotDealDiffer.check_for_abri_tenant_data_fetch( + new_deal=new_deal, + new_project=None, + old_deal=old_deal, + ) + + # Assert + assert result is False + + # ======================= # DB UPDATE TRIGGER TESTS # ======================= From b4d2d9cd3743b689ed79019ff7c387f3000f868c Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:26:20 +0000 Subject: [PATCH 06/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20ignores=20?= =?UTF-8?q?already-set=20commencement=20dates=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/hubspot_deal_differ.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/etl/hubspot/hubspot_deal_differ.py b/etl/hubspot/hubspot_deal_differ.py index 134a6b80e..7b2e70e27 100644 --- a/etl/hubspot/hubspot_deal_differ.py +++ b/etl/hubspot/hubspot_deal_differ.py @@ -212,7 +212,10 @@ class HubspotDealDiffer: new_project: Optional[ProjectData], old_deal: HubspotDealData, ) -> bool: - return HubspotDealDiffer._is_abri_condition_deal(new_deal, new_project) + if not HubspotDealDiffer._is_abri_condition_deal(new_deal, new_project): + return False + + return old_deal.expected_commencement_date is None @staticmethod def _is_abri_condition_deal( From 47842da7b93e96f276004322cd23cbac95427621 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:27:22 +0000 Subject: [PATCH 07/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20requires?= =?UTF-8?q?=20a=20parseable=20new=20commencement=20date=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/tests/test_hubspot_deal_differ.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/etl/hubspot/tests/test_hubspot_deal_differ.py b/etl/hubspot/tests/test_hubspot_deal_differ.py index a241c5189..0a5e96e9b 100644 --- a/etl/hubspot/tests/test_hubspot_deal_differ.py +++ b/etl/hubspot/tests/test_hubspot_deal_differ.py @@ -772,6 +772,42 @@ def test_abri_tenant_data_fetch__commencement_date_already_set__returns_false() assert result is False +@pytest.mark.parametrize( + "new_overrides", + [ + {}, + {"expected_commencement_date": ""}, + {"expected_commencement_date": "not-a-date"}, + ], +) +def test_abri_tenant_data_fetch__no_parseable_new_commencement_date__returns_false( + new_overrides: Dict[str, str], +) -> None: + deal_id = uuid.uuid4() + + # Arrange + old_deal = make_old_deal( + id=deal_id, + project_code="Abri Condition", + expected_commencement_date=None, + ) + new_deal = make_new_deal( + deal_id, + project_code="Abri Condition", + **new_overrides, + ) + + # Act + result = HubspotDealDiffer.check_for_abri_tenant_data_fetch( + new_deal=new_deal, + new_project=None, + old_deal=old_deal, + ) + + # Assert + assert result is False + + # ======================= # DB UPDATE TRIGGER TESTS # ======================= From 5df1237b909d58bd92711ac5c42eac50caceec16 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:27:42 +0000 Subject: [PATCH 08/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20requires?= =?UTF-8?q?=20a=20parseable=20new=20commencement=20date=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/hubspot_deal_differ.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/etl/hubspot/hubspot_deal_differ.py b/etl/hubspot/hubspot_deal_differ.py index 7b2e70e27..c138182d9 100644 --- a/etl/hubspot/hubspot_deal_differ.py +++ b/etl/hubspot/hubspot_deal_differ.py @@ -215,7 +215,10 @@ class HubspotDealDiffer: if not HubspotDealDiffer._is_abri_condition_deal(new_deal, new_project): return False - return old_deal.expected_commencement_date is None + if old_deal.expected_commencement_date is not None: + return False + + return parse_hs_date(new_deal.get("expected_commencement_date")) is not None @staticmethod def _is_abri_condition_deal( From a36fe0013224eeed681b970fa865382ff3d143e3 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:28:25 +0000 Subject: [PATCH 09/10] =?UTF-8?q?Abri=20tenant=20data=20fetch=20resolves?= =?UTF-8?q?=20abri=20deals=20by=20associated=20project=20id=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- etl/hubspot/tests/test_hubspot_deal_differ.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/etl/hubspot/tests/test_hubspot_deal_differ.py b/etl/hubspot/tests/test_hubspot_deal_differ.py index 0a5e96e9b..660481dd2 100644 --- a/etl/hubspot/tests/test_hubspot_deal_differ.py +++ b/etl/hubspot/tests/test_hubspot_deal_differ.py @@ -808,6 +808,65 @@ def test_abri_tenant_data_fetch__no_parseable_new_commencement_date__returns_fal assert result is False +def test_abri_tenant_data_fetch__associated_abri_project_id__returns_true() -> None: + deal_id = uuid.uuid4() + + # Arrange + old_deal = make_old_deal( + id=deal_id, + project_code="Southern Retrofit", + expected_commencement_date=None, + ) + new_deal = make_new_deal( + deal_id, + project_code="Southern Retrofit", + expected_commencement_date="2026-08-01", + ) + new_project = ProjectData( + project_id=HubspotDealDiffer.ABRI_CONDITION_ASSOCIATED_PROJECT_ID, + name="Abri Condition", + ) + + # Act + result = HubspotDealDiffer.check_for_abri_tenant_data_fetch( + new_deal=new_deal, + new_project=new_project, + old_deal=old_deal, + ) + + # Assert + assert result is True + + +def test_abri_tenant_data_fetch__non_abri_project_id_with_abri_code__returns_false() -> ( + None +): + deal_id = uuid.uuid4() + + # Arrange + old_deal = make_old_deal( + id=deal_id, + project_code="Abri Condition", + expected_commencement_date=None, + ) + new_deal = make_new_deal( + deal_id, + project_code="Abri Condition", + expected_commencement_date="2026-08-01", + ) + new_project = ProjectData(project_id="999999", name="Southern Retrofit") + + # Act + result = HubspotDealDiffer.check_for_abri_tenant_data_fetch( + new_deal=new_deal, + new_project=new_project, + old_deal=old_deal, + ) + + # Assert + assert result is False + + # ======================= # DB UPDATE TRIGGER TESTS # ======================= From 05ca0bb2801a134f85165034c458c1e331e3569e Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 08:33:10 +0000 Subject: [PATCH 10/10] move static method below non-statics --- etl/hubspot/hubspot_deal_differ.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/etl/hubspot/hubspot_deal_differ.py b/etl/hubspot/hubspot_deal_differ.py index c138182d9..fbdc69b61 100644 --- a/etl/hubspot/hubspot_deal_differ.py +++ b/etl/hubspot/hubspot_deal_differ.py @@ -220,6 +220,14 @@ class HubspotDealDiffer: return parse_hs_date(new_deal.get("expected_commencement_date")) is not None + @staticmethod + def check_for_magicplan_trigger( + new_deal: Dict[str, str], old_deal: HubspotDealData + ) -> bool: + new_outcome = (new_deal.get("outcome") or "").lower() + old_outcome = (old_deal.outcome or "").lower() + return new_outcome == "surveyed" and old_outcome != "surveyed" + @staticmethod def _is_abri_condition_deal( new_deal: Dict[str, str], new_project: Optional[ProjectData] @@ -233,14 +241,6 @@ class HubspotDealDiffer: new_project_code = (new_deal.get("project_code") or "").lower() return new_project_code == HubspotDealDiffer.ABRI_CONDITION_PROJECT_CODE.lower() - @staticmethod - def check_for_magicplan_trigger( - new_deal: Dict[str, str], old_deal: HubspotDealData - ) -> bool: - new_outcome = (new_deal.get("outcome") or "").lower() - old_outcome = (old_deal.outcome or "").lower() - return new_outcome == "surveyed" and old_outcome != "surveyed" - @staticmethod def _has_valid_pashub_link(new_pashub_link: str) -> bool: return bool(new_pashub_link)