mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
move trigger check to HubspotDealDiffer
This commit is contained in:
parent
d9b441962f
commit
3934e2d359
5 changed files with 68 additions and 93 deletions
|
|
@ -1,59 +0,0 @@
|
||||||
"""Builds the one-per-scrape SQS message for the Abri OpenHousing lambda.
|
|
||||||
|
|
||||||
The scraper decides which flows fire (the differ predicates live in one
|
|
||||||
tested place); the lambda is a dumb dispatcher. When any predicate fires,
|
|
||||||
one message carries every fired flow plus the deal fields they need, so
|
|
||||||
flows for the same deal cannot interleave across concurrent lambda
|
|
||||||
invocations.
|
|
||||||
|
|
||||||
Abandonment (no-access) detection stays unwired until the CancelJob flow
|
|
||||||
exists — check_for_abri_deal_abandonment is deliberately not called here.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
|
||||||
from etl.hubspot.hubspot_deal_differ import HubspotDealDiffer
|
|
||||||
from etl.hubspot.project_data import ProjectData
|
|
||||||
from etl.hubspot.utils import parse_hs_date
|
|
||||||
|
|
||||||
|
|
||||||
def abri_trigger_message_for_scrape(
|
|
||||||
hubspot_deal_id: str,
|
|
||||||
new_deal: Dict[str, str],
|
|
||||||
new_project: Optional[ProjectData],
|
|
||||||
new_listing: Optional[Dict[str, str]],
|
|
||||||
old_deal: HubspotDealData,
|
|
||||||
) -> Optional[Dict[str, Any]]:
|
|
||||||
"""The Abri trigger message for this scrape, or None if no flow fired."""
|
|
||||||
flows: List[str] = []
|
|
||||||
if HubspotDealDiffer.check_for_abri_job_amendment(
|
|
||||||
new_deal=new_deal, new_project=new_project, old_deal=old_deal
|
|
||||||
):
|
|
||||||
flows.append("amend_job")
|
|
||||||
if HubspotDealDiffer.check_for_abri_job_logging(
|
|
||||||
new_deal=new_deal, new_project=new_project, old_deal=old_deal
|
|
||||||
):
|
|
||||||
flows.append("log_job")
|
|
||||||
if HubspotDealDiffer.check_for_abri_tenant_data_fetch(
|
|
||||||
new_deal=new_deal, new_project=new_project, old_deal=old_deal
|
|
||||||
):
|
|
||||||
flows.append("sync_tenant_data")
|
|
||||||
|
|
||||||
if not flows:
|
|
||||||
return None
|
|
||||||
|
|
||||||
confirmed_survey_date = parse_hs_date(new_deal.get("confirmed_survey_date"))
|
|
||||||
listing = new_listing or {}
|
|
||||||
return {
|
|
||||||
"hubspot_deal_id": hubspot_deal_id,
|
|
||||||
"flows": flows,
|
|
||||||
"place_ref": listing.get("owner_property_id"),
|
|
||||||
"deal_name": new_deal.get("dealname"),
|
|
||||||
"confirmed_survey_date": (
|
|
||||||
confirmed_survey_date.date().isoformat()
|
|
||||||
if confirmed_survey_date is not None
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
"confirmed_survey_time": new_deal.get("confirmed_survey_time"),
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
||||||
from etl.hubspot.project_data import ProjectData
|
from etl.hubspot.project_data import ProjectData
|
||||||
|
|
@ -189,6 +189,46 @@ class HubspotDealDiffer:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_abri_triggers_and_construct_message(
|
||||||
|
hubspot_deal_id: str,
|
||||||
|
new_deal: Dict[str, str],
|
||||||
|
new_project: Optional[ProjectData],
|
||||||
|
new_listing: Optional[Dict[str, str]],
|
||||||
|
old_deal: HubspotDealData,
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
flows: List[str] = []
|
||||||
|
if HubspotDealDiffer.check_for_abri_job_amendment(
|
||||||
|
new_deal=new_deal, new_project=new_project, old_deal=old_deal
|
||||||
|
):
|
||||||
|
flows.append("amend_job")
|
||||||
|
if HubspotDealDiffer.check_for_abri_job_logging(
|
||||||
|
new_deal=new_deal, new_project=new_project, old_deal=old_deal
|
||||||
|
):
|
||||||
|
flows.append("log_job")
|
||||||
|
if HubspotDealDiffer.check_for_abri_tenant_data_fetch(
|
||||||
|
new_deal=new_deal, new_project=new_project, old_deal=old_deal
|
||||||
|
):
|
||||||
|
flows.append("sync_tenant_data")
|
||||||
|
|
||||||
|
if not flows:
|
||||||
|
return None
|
||||||
|
|
||||||
|
confirmed_survey_date = parse_hs_date(new_deal.get("confirmed_survey_date"))
|
||||||
|
listing = new_listing or {}
|
||||||
|
return {
|
||||||
|
"hubspot_deal_id": hubspot_deal_id,
|
||||||
|
"flows": flows,
|
||||||
|
"place_ref": listing.get("owner_property_id"),
|
||||||
|
"deal_name": new_deal.get("dealname"),
|
||||||
|
"confirmed_survey_date": (
|
||||||
|
confirmed_survey_date.date().isoformat()
|
||||||
|
if confirmed_survey_date is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
"confirmed_survey_time": new_deal.get("confirmed_survey_time"),
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_for_abri_job_logging(
|
def check_for_abri_job_logging(
|
||||||
new_deal: Dict[str, str],
|
new_deal: Dict[str, str],
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from backend.app.config import get_settings
|
||||||
from etl.hubspot.hubspotClient import HubspotClient
|
from etl.hubspot.hubspotClient import HubspotClient
|
||||||
from etl.hubspot.hubspotDataTodB import CompanyData, HubspotDataToDb
|
from etl.hubspot.hubspotDataTodB import CompanyData, HubspotDataToDb
|
||||||
from etl.hubspot.project_data import ProjectData
|
from etl.hubspot.project_data import ProjectData
|
||||||
from etl.hubspot.abri_flow_triggers import abri_trigger_message_for_scrape
|
from etl.hubspot.abri_flow_triggers import check_for_abri_triggers_and_construct_message
|
||||||
from etl.hubspot.hubspot_deal_differ import HubspotDealDiffer
|
from etl.hubspot.hubspot_deal_differ import HubspotDealDiffer
|
||||||
from etl.hubspot.hubspot_trigger_orchestrator_trigger_request import (
|
from etl.hubspot.hubspot_trigger_orchestrator_trigger_request import (
|
||||||
HubspotTriggerOrchestratorTriggerRequest,
|
HubspotTriggerOrchestratorTriggerRequest,
|
||||||
|
|
@ -128,14 +128,14 @@ def handler(body: dict[str, Any], context: Any) -> None:
|
||||||
sqs_client, hubspot_deal, listing, hubspot_deal_id
|
sqs_client, hubspot_deal, listing, hubspot_deal_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# None means no Abri flow fired this scrape; a dict is the message
|
abri_message: Optional[Dict[str, Any]] = (
|
||||||
# naming the flows that did.
|
HubspotDealDiffer.check_abri_triggers_and_construct_message(
|
||||||
abri_message = abri_trigger_message_for_scrape(
|
hubspot_deal_id=hubspot_deal_id,
|
||||||
hubspot_deal_id=hubspot_deal_id,
|
new_deal=hubspot_deal,
|
||||||
new_deal=hubspot_deal,
|
new_project=project,
|
||||||
new_project=project,
|
new_listing=listing,
|
||||||
new_listing=listing,
|
old_deal=db_deal,
|
||||||
old_deal=db_deal,
|
)
|
||||||
)
|
)
|
||||||
if abri_message is not None:
|
if abri_message is not None:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,11 @@ from typing import Any, Dict
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
||||||
from etl.hubspot.abri_flow_triggers import abri_trigger_message_for_scrape
|
from etl.hubspot.abri_flow_triggers import check_for_abri_triggers_and_construct_message
|
||||||
|
|
||||||
BASE_TIME = datetime(2025, 12, 1, 12, 0, 0)
|
BASE_TIME = datetime(2025, 12, 1, 12, 0, 0)
|
||||||
|
|
||||||
ABRI_PROJECT_CODE = (
|
ABRI_PROJECT_CODE = "[Abri Stock Condition - Privately Funded - 062026 - 1247536318670]"
|
||||||
"[Abri Stock Condition - Privately Funded - 062026 - 1247536318670]"
|
|
||||||
)
|
|
||||||
|
|
||||||
DEAL_ID = "9876543210"
|
DEAL_ID = "9876543210"
|
||||||
|
|
||||||
|
|
@ -42,15 +40,13 @@ def make_new_deal(**overrides: str) -> Dict[str, str]:
|
||||||
|
|
||||||
def test_a_first_confirmed_survey_date_builds_a_log_job_message() -> None:
|
def test_a_first_confirmed_survey_date_builds_a_log_job_message() -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
old_deal = make_old_deal(
|
old_deal = make_old_deal(project_code=ABRI_PROJECT_CODE, confirmed_survey_date=None)
|
||||||
project_code=ABRI_PROJECT_CODE, confirmed_survey_date=None
|
|
||||||
)
|
|
||||||
new_deal = make_new_deal(
|
new_deal = make_new_deal(
|
||||||
confirmed_survey_date="2026-06-24", confirmed_survey_time="14:30"
|
confirmed_survey_date="2026-06-24", confirmed_survey_time="14:30"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
@ -80,7 +76,7 @@ def test_a_changed_confirmed_survey_date_builds_an_amend_job_message() -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
@ -102,7 +98,7 @@ def test_a_first_expected_commencement_date_builds_a_tenant_sync_message() -> No
|
||||||
new_deal = make_new_deal(expected_commencement_date="2026-07-01")
|
new_deal = make_new_deal(expected_commencement_date="2026-07-01")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
@ -135,7 +131,7 @@ def test_several_flows_firing_in_one_scrape_share_one_message() -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
@ -159,7 +155,7 @@ def test_no_message_when_no_abri_trigger_fires() -> None:
|
||||||
new_deal = make_new_deal(outcome="left voicemail")
|
new_deal = make_new_deal(outcome="left voicemail")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
@ -174,12 +170,10 @@ def test_no_message_when_no_abri_trigger_fires() -> None:
|
||||||
def test_no_message_for_a_non_abri_deal() -> None:
|
def test_no_message_for_a_non_abri_deal() -> None:
|
||||||
# Arrange: a survey date first set, but on another project's deal.
|
# Arrange: a survey date first set, but on another project's deal.
|
||||||
old_deal = make_old_deal(project_code="Other", confirmed_survey_date=None)
|
old_deal = make_old_deal(project_code="Other", confirmed_survey_date=None)
|
||||||
new_deal = make_new_deal(
|
new_deal = make_new_deal(project_code="Other", confirmed_survey_date="2026-06-24")
|
||||||
project_code="Other", confirmed_survey_date="2026-06-24"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
@ -200,7 +194,7 @@ def test_abandonment_alone_stays_unwired_and_builds_no_message() -> None:
|
||||||
new_deal = make_new_deal(number_of_attempts="3", outcome="no answer")
|
new_deal = make_new_deal(number_of_attempts="3", outcome="no answer")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
@ -220,13 +214,11 @@ def test_abandonment_alone_stays_unwired_and_builds_no_message() -> None:
|
||||||
def test_a_missing_listing_still_builds_the_message_without_a_place_ref() -> None:
|
def test_a_missing_listing_still_builds_the_message_without_a_place_ref() -> None:
|
||||||
# Arrange: validation in the abri lambda is the visibility surface for
|
# Arrange: validation in the abri lambda is the visibility surface for
|
||||||
# a fired flow that lacks its place_ref; the scraper still sends.
|
# a fired flow that lacks its place_ref; the scraper still sends.
|
||||||
old_deal = make_old_deal(
|
old_deal = make_old_deal(project_code=ABRI_PROJECT_CODE, confirmed_survey_date=None)
|
||||||
project_code=ABRI_PROJECT_CODE, confirmed_survey_date=None
|
|
||||||
)
|
|
||||||
new_deal = make_new_deal(confirmed_survey_date="2026-06-24")
|
new_deal = make_new_deal(confirmed_survey_date="2026-06-24")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=new_deal,
|
new_deal=new_deal,
|
||||||
new_project=None,
|
new_project=None,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ canned LogJob success carrying a fake job_no. Concretely, the script
|
||||||
1. fetches the deal, listing and project from the real HubSpot portal
|
1. fetches the deal, listing and project from the real HubSpot portal
|
||||||
(exactly what the scraper lambda sees),
|
(exactly what the scraper lambda sees),
|
||||||
2. reads the deal's row from the real (dev) deal database,
|
2. reads the deal's row from the real (dev) deal database,
|
||||||
3. runs the real differ predicates via abri_trigger_message_for_scrape,
|
3. runs the real differ predicates via abri_message_if_triggered,
|
||||||
4. validates the real message contract (AbriTriggerRequest),
|
4. validates the real message contract (AbriTriggerRequest),
|
||||||
5. runs the real dispatch through the real AbriOrchestrator, so the fake
|
5. runs the real dispatch through the real AbriOrchestrator, so the fake
|
||||||
job_no is written to the deal's client_booking_reference property in
|
job_no is written to the deal's client_booking_reference property in
|
||||||
|
|
@ -129,7 +129,9 @@ def main() -> None:
|
||||||
from applications.abri.abri_trigger_request import AbriTriggerRequest
|
from applications.abri.abri_trigger_request import AbriTriggerRequest
|
||||||
from applications.abri.dispatch import dispatch_abri_flows
|
from applications.abri.dispatch import dispatch_abri_flows
|
||||||
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
||||||
from etl.hubspot.abri_flow_triggers import abri_trigger_message_for_scrape
|
from etl.hubspot.abri_flow_triggers import (
|
||||||
|
check_for_abri_triggers_and_construct_message,
|
||||||
|
)
|
||||||
from etl.hubspot.hubspotClient import HubspotClient
|
from etl.hubspot.hubspotClient import HubspotClient
|
||||||
from infrastructure.abri.abri_client import AbriClient
|
from infrastructure.abri.abri_client import AbriClient
|
||||||
from infrastructure.abri.config import AbriConfig
|
from infrastructure.abri.config import AbriConfig
|
||||||
|
|
@ -196,7 +198,7 @@ def main() -> None:
|
||||||
f"job_no={deal_row.client_booking_reference}"
|
f"job_no={deal_row.client_booking_reference}"
|
||||||
)
|
)
|
||||||
|
|
||||||
message = abri_trigger_message_for_scrape(
|
message = check_for_abri_triggers_and_construct_message(
|
||||||
hubspot_deal_id=DEAL_ID,
|
hubspot_deal_id=DEAL_ID,
|
||||||
new_deal=hubspot_deal,
|
new_deal=hubspot_deal,
|
||||||
new_project=project,
|
new_project=project,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue