mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Share the unsuccessful-outcome vocabulary between differ and abandonment
Extract the abandonment outcome strings to a single UnsuccessfulOutcome Literal + UNSUCCESSFUL_OUTCOMES tuple in domain.abri.models, alongside SlotCode. The differ's NEGATIVE_OUTCOMES now references the tuple and the reason-code mapping is keyed on the Literal, so the two can no longer drift and a mistyped outcome fails type-checking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
693356a220
commit
13e3e78749
3 changed files with 31 additions and 17 deletions
|
|
@ -1,7 +1,7 @@
|
|||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from domain.abri.models import AbandonReason
|
||||
from domain.abri.models import AbandonReason, UnsuccessfulOutcome
|
||||
|
||||
|
||||
def abandonment_date(
|
||||
|
|
@ -31,11 +31,13 @@ class UnmappableOutcomeError(Exception):
|
|||
super().__init__(f"no abandonment reason code for outcome {outcome!r}")
|
||||
|
||||
|
||||
# Only outcomes that trip the abandonment trigger (HubspotDealDiffer's
|
||||
# NEGATIVE_OUTCOMES) can reach here. Keyed on the outcome lower-cased, matching
|
||||
# how the differ compares. Tenant Refusal -> CRA is the client's steer but is
|
||||
# ambiguous (REFSURV/REFUSAL also fit) - confirmation pending, see the codes below.
|
||||
_REASON_BY_OUTCOME: dict[str, AbandonReason] = {
|
||||
# Only UNSUCCESSFUL_OUTCOMES can reach here (they trip the abandonment trigger),
|
||||
# keyed lower-cased to match how the differ compares. "cancelled"/"no show" are
|
||||
# deliberately unmapped for now - they never match the real "Cancelled / No Show"
|
||||
# dropdown value, so they cannot occur, and would raise loudly if they ever did.
|
||||
# Tenant Refusal -> CRA is the client's steer but is ambiguous (REFSURV/REFUSAL
|
||||
# also fit) - confirmation pending, see the reason-code reference below.
|
||||
_REASON_BY_OUTCOME: dict[UnsuccessfulOutcome, AbandonReason] = {
|
||||
"no answer": AbandonReason.CARDED,
|
||||
"tenant refusal": AbandonReason.CRA,
|
||||
"not viable": AbandonReason.NOACCESS,
|
||||
|
|
@ -49,10 +51,11 @@ def abandon_reason_for_outcome(outcome: Optional[str]) -> AbandonReason:
|
|||
case- and whitespace-insensitively. Raises UnmappableOutcomeError for an
|
||||
empty or unrecognised outcome, since abandon_reason is mandatory.
|
||||
"""
|
||||
reason = _REASON_BY_OUTCOME.get((outcome or "").strip().lower())
|
||||
if reason is None:
|
||||
raise UnmappableOutcomeError(outcome)
|
||||
return reason
|
||||
normalized = (outcome or "").strip().lower()
|
||||
for mapped_outcome, reason in _REASON_BY_OUTCOME.items():
|
||||
if mapped_outcome == normalized:
|
||||
return reason
|
||||
raise UnmappableOutcomeError(outcome)
|
||||
|
||||
|
||||
# Reason Code - Description
|
||||
|
|
|
|||
|
|
@ -7,6 +7,22 @@ PlaceRef = NewType("PlaceRef", str)
|
|||
|
||||
SlotCode = Literal["AM", "PM", "AD"]
|
||||
|
||||
# HubSpot deal outcomes (lower-cased) that mean a survey was unsuccessful and,
|
||||
# once the attempt threshold is reached, abandon the job. The single source of
|
||||
# truth shared by the abandonment trigger (etl HubspotDealDiffer) and the
|
||||
# reason-code mapping (domain.abri.abandonment).
|
||||
UnsuccessfulOutcome = Literal[
|
||||
"no answer", "cancelled", "no show", "tenant refusal", "not viable"
|
||||
]
|
||||
|
||||
UNSUCCESSFUL_OUTCOMES: tuple[UnsuccessfulOutcome, ...] = (
|
||||
"no answer",
|
||||
"cancelled",
|
||||
"no show",
|
||||
"tenant refusal",
|
||||
"not viable",
|
||||
)
|
||||
|
||||
|
||||
class AbandonReason(str, Enum):
|
||||
"""OpenHousing's coded reason a job was abandoned.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
||||
from domain.abri.models import UNSUCCESSFUL_OUTCOMES
|
||||
from etl.hubspot.project_data import ProjectData
|
||||
from etl.hubspot.utils import parse_hs_bool, parse_hs_date, parse_hs_int
|
||||
|
||||
|
|
@ -15,13 +16,7 @@ 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",
|
||||
]
|
||||
NEGATIVE_OUTCOMES = UNSUCCESSFUL_OUTCOMES
|
||||
# Verified against the portal (2026-07-07): Abri stock-condition deals
|
||||
# carry no project association, so the project_code match is the branch
|
||||
# that fires in production; the id below is the project object id
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue