mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
'Cancelled' and 'no show' were separate strings in the outcome vocabulary, but HubSpot only ever emits the one dropdown value 'Cancelled / No Show' - so neither phantom string could ever match. Replace both with the real value (mapped to NOACCESS, best-guess pending client), which also fixes the long-standing differ mismatch: the abandonment trigger now actually fires on Cancelled / No Show. Drops the now-unused REQT enum member. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
124 lines
2.9 KiB
Python
124 lines
2.9 KiB
Python
from dataclasses import dataclass
|
|
from datetime import date
|
|
from enum import Enum
|
|
from typing import Literal, NewType, Optional, Tuple, Union
|
|
|
|
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.
|
|
|
|
Values are Abri's own reason codes. Only the members reachable from a
|
|
HubSpot abandonment outcome are defined; the outcome-to-code mapping lives
|
|
in domain.abri.abandonment.
|
|
"""
|
|
|
|
CARDED = "CARDED"
|
|
CRA = "CRA"
|
|
NOACCESS = "NOACCESS"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LogJobRequest:
|
|
client_ref: str
|
|
place_ref: PlaceRef
|
|
appointment_date: date
|
|
appointment_time: SlotCode
|
|
short_description: str
|
|
long_description: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class JobLogged:
|
|
job_no: str
|
|
logged_info: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AbriRequestRejected:
|
|
code: str
|
|
message: str
|
|
|
|
|
|
LogJobResult = Union[JobLogged, AbriRequestRejected]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AmendJobRequest:
|
|
job_no: str
|
|
appointment_date: date
|
|
appointment_time: SlotCode
|
|
resource: Optional[str] = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AppointmentAmended:
|
|
job_no: str
|
|
appointment_date: str
|
|
appointment_time: str
|
|
|
|
|
|
AmendJobResult = Union[AppointmentAmended, AbriRequestRejected]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Tenant:
|
|
"""A tenancy signatory, minimised to the fields the HubSpot sync needs.
|
|
|
|
Person title, per-person references, the main-contact flag and tenancy
|
|
dates are deliberately dropped at parse time (data minimisation); mobile
|
|
and telephone are the single best number of each kind, pre-selected by
|
|
priority, and secondary_number is the best of the numbers they left
|
|
unused.
|
|
"""
|
|
|
|
forenames: str
|
|
surname: str
|
|
mobile: Optional[str]
|
|
telephone: Optional[str]
|
|
secondary_number: Optional[str]
|
|
vulnerabilities: Tuple[str, ...]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TenancyData:
|
|
"""Tenancy signatories for a place; the reference is PII-safe."""
|
|
|
|
tenancy_reference: str
|
|
tenants: Tuple[Tenant, ...]
|
|
|
|
|
|
GetTenantDataResult = Union[TenancyData, AbriRequestRejected]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AbandonJobRequest:
|
|
job_no: str
|
|
reason: AbandonReason
|
|
date_abandoned: date
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class JobAbandoned:
|
|
job_no: str
|
|
|
|
|
|
AbandonJobResult = Union[JobAbandoned, AbriRequestRejected]
|