from dataclasses import dataclass from typing import Dict, List, Optional, Tuple, Union from domain.abri.models import AbriRequestRejected, PlaceRef, Tenant from etl.hubspot.deal_contacts_client import HubspotDealContactsClient from infrastructure.abri.abri_client import AbriClient def _contact_properties(tenant: Tenant) -> Dict[str, str]: candidates: Dict[str, Optional[str]] = { "firstname": tenant.forenames, "lastname": tenant.surname, "mobilephone": tenant.mobile, "phone": tenant.telephone, "secondary_phone_number": tenant.secondary_number, } properties = {name: value for name, value in candidates.items() if value} # Always written, so "not vulnerable" is distinguishable from "not synced". properties["is_vulnerable"] = "true" if tenant.vulnerabilities else "false" return properties @dataclass(frozen=True) class TenantDataSyncSummary: """PII-free record of what a sync run did: safe to log and persist.""" tenancy_reference: str contact_ids: Tuple[str, ...] vulnerable_contact_count: int TenantDataSyncResult = Union[TenantDataSyncSummary, AbriRequestRejected] class TenantDataSyncOrchestrator: """Syncs an Abri tenancy's signatories to a HubSpot deal. Tenant data flows straight from Abri to HubSpot; nothing is persisted in Domna's database or logs. """ def __init__( self, abri_client: AbriClient, hubspot: HubspotDealContactsClient, ) -> None: self._abri = abri_client self._hubspot = hubspot def run(self, place_ref: PlaceRef, deal_id: str) -> TenantDataSyncResult: tenancy = self._abri.get_tenant_data(place_ref) if isinstance(tenancy, AbriRequestRejected): return tenancy contact_ids: List[str] = [] for tenant in tenancy.tenants: contact_id = self._hubspot.create_contact(_contact_properties(tenant)) self._hubspot.associate_contact_to_deal( deal_id=deal_id, contact_id=contact_id ) contact_ids.append(contact_id) return TenantDataSyncSummary( tenancy_reference=tenancy.tenancy_reference, contact_ids=tuple(contact_ids), vulnerable_contact_count=sum( 1 for tenant in tenancy.tenants if tenant.vulnerabilities ), )