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 _vulnerability_description(tenant: Tenant) -> Optional[str]: """The tenant's distinct vulnerabilities, verbatim and newline-joined. Case-insensitively distinct within the tenant, keeping the first-seen casing and document order. """ distinct: List[str] = [] seen: set[str] = set() for vulnerability in tenant.vulnerabilities: if vulnerability.lower() in seen: continue seen.add(vulnerability.lower()) distinct.append(vulnerability) return "\n".join(distinct) if distinct else None 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, "vulnerability_description": _vulnerability_description(tenant), } 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 ), )