mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Merge pull request #1445 from Hestia-Homes/feature/abri-api-integration
Abri deal abandonment check
This commit is contained in:
commit
7abf466b56
6 changed files with 284 additions and 1 deletions
|
|
@ -23,6 +23,7 @@ class HubspotDealData(SQLModel, table=True):
|
|||
landlord_property_id: Optional[str] = Field(default=None)
|
||||
uprn: Optional[str] = Field(default=None)
|
||||
outcome: Optional[str] = Field(default=None)
|
||||
number_of_attempts: Optional[str] = Field(default=None)
|
||||
outcome_notes: Optional[str] = Field(default=None)
|
||||
booking_status: Optional[str] = Field(default=None)
|
||||
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ class HubspotClient:
|
|||
"dealstage",
|
||||
"pipeline",
|
||||
"outcome",
|
||||
"number_of_attempts",
|
||||
"outcome_notes",
|
||||
"booking_status",
|
||||
"project_code",
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ class HubspotDataToDb:
|
|||
),
|
||||
"uprn": listing.get("national_uprn", None) if listing else None,
|
||||
"outcome": deal_data.get("outcome"),
|
||||
"number_of_attempts": deal_data.get("number_of_attempts"),
|
||||
"outcome_notes": deal_data.get("outcome_notes"),
|
||||
"booking_status": deal_data.get("booking_status"),
|
||||
"project_code": deal_data.get("project_code"),
|
||||
|
|
@ -322,6 +323,7 @@ class HubspotDataToDb:
|
|||
),
|
||||
uprn=listing.get("national_uprn") if listing else None,
|
||||
outcome=deal_data.get("outcome"),
|
||||
number_of_attempts=deal_data.get("number_of_attempts"),
|
||||
outcome_notes=deal_data.get("outcome_notes"),
|
||||
booking_status=deal_data.get("booking_status"),
|
||||
project_code=deal_data.get("project_code"),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Dict, List, Optional
|
|||
|
||||
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
||||
from etl.hubspot.project_data import ProjectData
|
||||
from etl.hubspot.utils import parse_hs_bool, parse_hs_date
|
||||
from etl.hubspot.utils import parse_hs_bool, parse_hs_date, parse_hs_int
|
||||
|
||||
|
||||
class HubspotDealDiffer:
|
||||
|
|
@ -13,6 +13,14 @@ class HubspotDealDiffer:
|
|||
]
|
||||
RETROFIT_DESIGN_COMPLETE = "uploaded"
|
||||
LODGEMENT_COMPLETE: List[str] = ["lodgement complete", "measures lodged"]
|
||||
ABANDONMENT_ATTEMPTS_THRESHOLD = 3
|
||||
NEGATIVE_OUTCOMES: List[str] = [
|
||||
"no answer",
|
||||
"cancelled",
|
||||
"no show",
|
||||
"tenant refusal",
|
||||
"not viable",
|
||||
]
|
||||
ABRI_CONDITION_ASSOCIATED_PROJECT_ID = "123456"
|
||||
ABRI_CONDITION_PROJECT_CODE = "Abri Condition"
|
||||
|
||||
|
|
@ -52,6 +60,7 @@ class HubspotDealDiffer:
|
|||
# --- Field mappings ---
|
||||
FIELD_MAP = {
|
||||
"outcome": "outcome",
|
||||
"number_of_attempts": "number_of_attempts",
|
||||
"dealstage": "dealstage",
|
||||
"dealname": "dealname",
|
||||
"project_code": "project_code",
|
||||
|
|
@ -220,6 +229,24 @@ class HubspotDealDiffer:
|
|||
|
||||
return parse_hs_date(new_deal.get("expected_commencement_date")) is not None
|
||||
|
||||
@staticmethod
|
||||
def check_for_abri_deal_abandonment(
|
||||
new_deal: Dict[str, str],
|
||||
new_project: Optional[ProjectData],
|
||||
old_deal: HubspotDealData,
|
||||
) -> bool:
|
||||
if not HubspotDealDiffer._is_abri_condition_deal(new_deal, new_project):
|
||||
return False
|
||||
|
||||
if HubspotDealDiffer._is_abandoned(
|
||||
old_deal.number_of_attempts, old_deal.outcome
|
||||
):
|
||||
return False
|
||||
|
||||
return HubspotDealDiffer._is_abandoned(
|
||||
new_deal.get("number_of_attempts"), new_deal.get("outcome")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def check_for_magicplan_trigger(
|
||||
new_deal: Dict[str, str], old_deal: HubspotDealData
|
||||
|
|
@ -228,6 +255,19 @@ class HubspotDealDiffer:
|
|||
old_outcome = (old_deal.outcome or "").lower()
|
||||
return new_outcome == "surveyed" and old_outcome != "surveyed"
|
||||
|
||||
@staticmethod
|
||||
def _is_abandoned(
|
||||
number_of_attempts: Optional[str], outcome: Optional[str]
|
||||
) -> bool:
|
||||
attempts: Optional[int] = parse_hs_int(number_of_attempts)
|
||||
if (
|
||||
attempts is None
|
||||
or attempts < HubspotDealDiffer.ABANDONMENT_ATTEMPTS_THRESHOLD
|
||||
):
|
||||
return False
|
||||
|
||||
return (outcome or "").lower() in HubspotDealDiffer.NEGATIVE_OUTCOMES
|
||||
|
||||
@staticmethod
|
||||
def _is_abri_condition_deal(
|
||||
new_deal: Dict[str, str], new_project: Optional[ProjectData]
|
||||
|
|
|
|||
|
|
@ -867,6 +867,210 @@ def test_abri_tenant_data_fetch__non_abri_project_id_with_abri_code__returns_fal
|
|||
assert result is False
|
||||
|
||||
|
||||
# ====================================
|
||||
# ABRI DEAL ABANDONMENT TRIGGER TESTS
|
||||
# ====================================
|
||||
|
||||
|
||||
def test_abri_deal_abandonment__third_attempt_with_negative_outcome__returns_true() -> (
|
||||
None
|
||||
):
|
||||
deal_id = uuid.uuid4()
|
||||
|
||||
# Arrange
|
||||
old_deal = make_old_deal(
|
||||
id=deal_id,
|
||||
project_code="Abri Condition",
|
||||
outcome=None,
|
||||
)
|
||||
new_deal = make_new_deal(
|
||||
deal_id,
|
||||
project_code="Abri Condition",
|
||||
number_of_attempts="3",
|
||||
outcome="No Answer",
|
||||
)
|
||||
|
||||
# Act
|
||||
result = HubspotDealDiffer.check_for_abri_deal_abandonment(
|
||||
new_deal=new_deal,
|
||||
new_project=None,
|
||||
old_deal=old_deal,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_abri_deal_abandonment__non_abri_deal__returns_false() -> None:
|
||||
deal_id = uuid.uuid4()
|
||||
|
||||
# Arrange
|
||||
old_deal = make_old_deal(
|
||||
id=deal_id,
|
||||
project_code="Southern Retrofit",
|
||||
outcome=None,
|
||||
)
|
||||
new_deal = make_new_deal(
|
||||
deal_id,
|
||||
project_code="Southern Retrofit",
|
||||
number_of_attempts="3",
|
||||
outcome="No Answer",
|
||||
)
|
||||
|
||||
# Act
|
||||
result = HubspotDealDiffer.check_for_abri_deal_abandonment(
|
||||
new_deal=new_deal,
|
||||
new_project=None,
|
||||
old_deal=old_deal,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"new_overrides",
|
||||
[
|
||||
{"number_of_attempts": "2"},
|
||||
{"number_of_attempts": ""},
|
||||
{"number_of_attempts": "not-a-number"},
|
||||
{},
|
||||
],
|
||||
)
|
||||
def test_abri_deal_abandonment__fewer_than_three_parseable_attempts__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",
|
||||
outcome=None,
|
||||
)
|
||||
new_deal = make_new_deal(
|
||||
deal_id,
|
||||
project_code="Abri Condition",
|
||||
outcome="No Answer",
|
||||
**new_overrides,
|
||||
)
|
||||
|
||||
# Act
|
||||
result = HubspotDealDiffer.check_for_abri_deal_abandonment(
|
||||
new_deal=new_deal,
|
||||
new_project=None,
|
||||
old_deal=old_deal,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"new_overrides",
|
||||
[
|
||||
{"outcome": "Surveyed"},
|
||||
{"outcome": ""},
|
||||
{},
|
||||
],
|
||||
)
|
||||
def test_abri_deal_abandonment__outcome_not_negative__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",
|
||||
outcome=None,
|
||||
)
|
||||
new_deal = make_new_deal(
|
||||
deal_id,
|
||||
project_code="Abri Condition",
|
||||
number_of_attempts="3",
|
||||
**new_overrides,
|
||||
)
|
||||
|
||||
# Act
|
||||
result = HubspotDealDiffer.check_for_abri_deal_abandonment(
|
||||
new_deal=new_deal,
|
||||
new_project=None,
|
||||
old_deal=old_deal,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"outcome",
|
||||
[
|
||||
"No Answer",
|
||||
"Cancelled",
|
||||
"No Show",
|
||||
"Tenant Refusal",
|
||||
"Not Viable",
|
||||
"NO ANSWER",
|
||||
],
|
||||
)
|
||||
def test_abri_deal_abandonment__each_negative_outcome__returns_true(
|
||||
outcome: str,
|
||||
) -> None:
|
||||
deal_id = uuid.uuid4()
|
||||
|
||||
# Arrange
|
||||
old_deal = make_old_deal(
|
||||
id=deal_id,
|
||||
project_code="Abri Condition",
|
||||
outcome=None,
|
||||
)
|
||||
new_deal = make_new_deal(
|
||||
deal_id,
|
||||
project_code="Abri Condition",
|
||||
number_of_attempts="3",
|
||||
outcome=outcome,
|
||||
)
|
||||
|
||||
# Act
|
||||
result = HubspotDealDiffer.check_for_abri_deal_abandonment(
|
||||
new_deal=new_deal,
|
||||
new_project=None,
|
||||
old_deal=old_deal,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_abri_deal_abandonment__deal_already_abandoned__returns_false() -> None:
|
||||
deal_id = uuid.uuid4()
|
||||
|
||||
# Arrange
|
||||
old_deal = make_old_deal(
|
||||
id=deal_id,
|
||||
project_code="Abri Condition",
|
||||
number_of_attempts="3",
|
||||
outcome="No Answer",
|
||||
)
|
||||
new_deal = make_new_deal(
|
||||
deal_id,
|
||||
project_code="Abri Condition",
|
||||
number_of_attempts="4",
|
||||
outcome="No Answer",
|
||||
)
|
||||
|
||||
# Act
|
||||
result = HubspotDealDiffer.check_for_abri_deal_abandonment(
|
||||
new_deal=new_deal,
|
||||
new_project=None,
|
||||
old_deal=old_deal,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result is False
|
||||
|
||||
|
||||
# =======================
|
||||
# DB UPDATE TRIGGER TESTS
|
||||
# =======================
|
||||
|
|
@ -924,6 +1128,32 @@ def test_db_update_trigger__dealname_changed__returns_true() -> None:
|
|||
assert result is True
|
||||
|
||||
|
||||
def test_db_update_trigger__number_of_attempts_changed__returns_true() -> None:
|
||||
deal_id = uuid.uuid4()
|
||||
|
||||
# Arrange
|
||||
old_deal = make_old_deal(
|
||||
id=deal_id,
|
||||
number_of_attempts="2",
|
||||
)
|
||||
new_deal = make_new_deal(
|
||||
deal_id,
|
||||
hs_object_id="1",
|
||||
number_of_attempts="3",
|
||||
)
|
||||
|
||||
# Act
|
||||
result = HubspotDealDiffer.check_for_db_update_trigger(
|
||||
new_deal=new_deal,
|
||||
new_company=None,
|
||||
new_listing=None,
|
||||
old_deal=old_deal,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_db_update_trigger__company_changed__returns_true() -> None:
|
||||
deal_id = uuid.uuid4()
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ def parse_hs_date(value: Optional[str]) -> Optional[datetime]:
|
|||
return None
|
||||
|
||||
|
||||
def parse_hs_int(value: Optional[str]) -> Optional[int]:
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def parse_hs_bool(value: Optional[str]) -> Optional[bool]:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue