From cc23b43f401c045ac065b0bc0b091de0783c20bf Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 24 Jul 2026 15:51:38 +0000 Subject: [PATCH] =?UTF-8?q?Amend=20an=20Abri=20booking=20only=20on=20a=20c?= =?UTF-8?q?hange=20to=20a=20complete=20booking,=20never=20when=20logging?= =?UTF-8?q?=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- etl/hubspot/hubspot_deal_differ.py | 32 +++++++++++++------- etl/hubspot/tests/test_abri_flow_triggers.py | 5 ++- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/etl/hubspot/hubspot_deal_differ.py b/etl/hubspot/hubspot_deal_differ.py index 96311ae4a..6a2203225 100644 --- a/etl/hubspot/hubspot_deal_differ.py +++ b/etl/hubspot/hubspot_deal_differ.py @@ -358,21 +358,31 @@ class HubspotDealDiffer: new_surveyor, ) - # A first-time survey date logs a new job, not an amendment. - if old_deal.confirmed_survey_date is None: + # Only a complete booking (both date and surveyor set now) can be + # amended; a lone field is nothing to amend, and a pair still forming + # waits for the log flow. + # TODO: a cleared survey date (was a complete booking, now not) no + # longer fires this check; the amendoptiappt route cannot express a + # cancellation, so cancellation semantics (the canceljob route) need a + # deliberate decision (existing differ TODO, issue #1478 out of scope). + if new_survey_date is None or new_surveyor is None: return False - # TODO: a cleared survey date (was set, now empty) still fires this - # check, but the amendoptiappt route cannot express a cancellation. - # Cancellation semantics (the "delete" action / canceljob route) need - # a deliberate decision before this trigger is wired to a consumer. - if old_deal.confirmed_survey_date != new_survey_date: - return True + change_occurred = ( + old_deal.confirmed_survey_date != new_survey_date + or old_deal.confirmed_survey_time != new_survey_time + or old_surveyor != new_surveyor + ) + if not change_occurred: + return False - if old_deal.confirmed_survey_time != new_survey_time: - return True + # The scrape that completes the pair logs the job; it never amends one. + if HubspotDealDiffer.check_for_abri_job_logging( + new_deal=new_deal, new_project=new_project, old_deal=old_deal + ): + return False - return old_surveyor != new_surveyor + return True @staticmethod def check_for_abri_tenant_data_fetch( diff --git a/etl/hubspot/tests/test_abri_flow_triggers.py b/etl/hubspot/tests/test_abri_flow_triggers.py index 37f4b5e1b..d8bbfb668 100644 --- a/etl/hubspot/tests/test_abri_flow_triggers.py +++ b/etl/hubspot/tests/test_abri_flow_triggers.py @@ -448,15 +448,18 @@ def test_a_first_expected_commencement_date_builds_a_tenant_sync_message() -> No def test_several_flows_firing_in_one_scrape_share_one_message() -> None: - # Arrange: date changed and commencement date first set together. + # Arrange: on a complete booking, the date changed (amend) and the + # commencement date was first set (tenant sync) together. old_deal = make_old_deal( project_code=ABRI_PROJECT_CODE, confirmed_survey_date=datetime(2026, 6, 18, tzinfo=timezone.utc), + third_party_surveyor_identifier="THIRDPARTY", expected_commencement_date=None, ) new_deal = make_new_deal( confirmed_survey_date="2026-06-24", confirmed_survey_time="14:30", + third_party_surveyor_identifier="THIRDPARTY", expected_commencement_date="2026-07-01", )