mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
345 lines
14 KiB
Python
345 lines
14 KiB
Python
import os
|
|
from sqlmodel import select
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, Optional
|
|
|
|
from backend.app.db.models.hubspot_deal_data import HubspotDealData
|
|
from etl.hubspot.company_data import CompanyData
|
|
from etl.hubspot.hubspotClient import HubspotClient
|
|
from etl.hubspot.s3_uploader import S3Uploader
|
|
from backend.app.db.connection import db_read_session
|
|
from backend.app.db.models.organisation import Organisation
|
|
from etl.hubspot.utils import parse_hs_date
|
|
from utils.logger import setup_logger
|
|
|
|
|
|
logger = setup_logger()
|
|
|
|
|
|
class HubspotDataToDb:
|
|
def __init__(self):
|
|
self.s3 = S3Uploader(
|
|
aws_access_key=os.getenv("AWS_ACCESS_KEY"),
|
|
aws_secret_key=os.getenv("AWS_SECRET_KEY"),
|
|
region=os.getenv("AWS_REGION"),
|
|
)
|
|
|
|
def read_org_table(self, limit: int = 10):
|
|
with db_read_session() as session:
|
|
records = session.exec(select(Organisation).limit(limit)).all()
|
|
return records
|
|
|
|
def get_org_names(self, limit: int = 10) -> list[str]:
|
|
"""Returns a list of organisation names."""
|
|
records = self.read_org_table(limit)
|
|
return [org.name for org in records if org.name]
|
|
|
|
def upsert_organisation(self, company_data: CompanyData) -> Organisation:
|
|
"""Upserts a company record. Updates if hubspot_company_id exists, otherwise creates new."""
|
|
with db_read_session() as session:
|
|
hubspot_id = company_data.get("hs_object_id")
|
|
company_name = company_data.get("name")
|
|
|
|
# Check if company already exists
|
|
existing = session.exec(
|
|
select(Organisation).where(
|
|
Organisation.hubspot_company_id == hubspot_id
|
|
)
|
|
).first()
|
|
|
|
if existing:
|
|
# Update existing record
|
|
existing.name = company_name
|
|
existing.updated_at = datetime.now(timezone.utc)
|
|
session.add(existing)
|
|
record = existing
|
|
else:
|
|
# Create new record
|
|
record = Organisation(
|
|
hubspot_company_id=hubspot_id,
|
|
name=company_name,
|
|
)
|
|
session.add(record)
|
|
|
|
session.commit()
|
|
return record
|
|
|
|
def find_all_deals_with_company_id(self, company_id: str):
|
|
"""Returns a list of deals for a given company_id."""
|
|
with db_read_session() as session:
|
|
return (
|
|
session.query(HubspotDealData)
|
|
.filter(HubspotDealData.company_id == company_id)
|
|
.all()
|
|
)
|
|
|
|
def find_deal_with_deal_id(self, deal_id: str) -> Optional[HubspotDealData]:
|
|
with db_read_session() as session:
|
|
return (
|
|
session.query(HubspotDealData)
|
|
.filter(HubspotDealData.deal_id == deal_id)
|
|
.one_or_none()
|
|
)
|
|
|
|
def upsert_deal(
|
|
self,
|
|
deal_data: Dict[str, str],
|
|
company: Optional[str],
|
|
listing: Optional[dict[str, str]],
|
|
hubspot_client: HubspotClient,
|
|
):
|
|
"""
|
|
Inserts or updates a deal record.
|
|
Also uploads photos if present and adds S3 URL.
|
|
"""
|
|
with db_read_session() as session:
|
|
deal_id = deal_data.get("hs_object_id")
|
|
|
|
statement = select(HubspotDealData).where(
|
|
HubspotDealData.deal_id == deal_id
|
|
)
|
|
existing = session.exec(statement).first()
|
|
|
|
if existing:
|
|
self._handle_existing_photo_upload(existing, hubspot_client)
|
|
|
|
print(f"🔄 Updating existing deal (deal_id={deal_id})")
|
|
self._update_existing_deal(existing, deal_data, listing, company)
|
|
|
|
session.add(existing)
|
|
session.commit()
|
|
session.refresh(existing)
|
|
return existing
|
|
|
|
else:
|
|
print(f"🆕 Inserting new deal (deal_id={deal_id})")
|
|
new_record: HubspotDealData = self._build_new_deal(
|
|
deal_id, deal_data, listing, company
|
|
)
|
|
|
|
# Handle upload at insert time
|
|
self._handle_new_photo_upload(new_record, hubspot_client)
|
|
|
|
session.add(new_record)
|
|
session.commit()
|
|
session.refresh(new_record)
|
|
return new_record
|
|
|
|
def _update_existing_deal(
|
|
self,
|
|
existing: HubspotDealData,
|
|
deal_data: Dict[str, str],
|
|
listing: Optional[dict[str, str]],
|
|
company: Optional[str],
|
|
):
|
|
for attr, value in {
|
|
"dealname": deal_data.get("dealname"),
|
|
"dealstage": deal_data.get("dealstage"),
|
|
"listing_id": listing.get("listing_id", None) if listing else None,
|
|
"landlord_property_id": (
|
|
listing.get("owner_property_id", None) if listing else None
|
|
),
|
|
"uprn": listing.get("national_uprn", None) if listing else None,
|
|
"outcome": deal_data.get("outcome"),
|
|
"outcome_notes": deal_data.get("outcome_notes"),
|
|
"project_code": deal_data.get("project_code"),
|
|
"company_id": company,
|
|
"major_condition_issue_description": deal_data.get(
|
|
"major_condition_issue_description"
|
|
),
|
|
"major_condition_issue_photos": deal_data.get(
|
|
"major_condition_issue_photos"
|
|
),
|
|
"coordination_status": deal_data.get("coordination_status__stage_1_"),
|
|
"design_status": deal_data.get("retrofit_design_status"),
|
|
"coordination_comments": deal_data.get("coordination_comments"),
|
|
"pashub_link": deal_data.get("pashub_link"),
|
|
"sharepoint_link": deal_data.get("sharepoint_link"),
|
|
"dampmould_growth": deal_data.get("dampmould_growth"),
|
|
"damp_mould_and_repairs_comments": deal_data.get(
|
|
"damp_mould_and_repairs_comments"
|
|
),
|
|
"pre_sap": deal_data.get("pre_sap_score_dropdown"),
|
|
"batch": deal_data.get("batch"),
|
|
"block_reference": deal_data.get("block_reference"),
|
|
"epc_prn": deal_data.get("epc_prn"),
|
|
"potential_post_sap_score_dropdown": deal_data.get(
|
|
"potential_post_sap_score_dropdown"
|
|
),
|
|
"ei_score": deal_data.get("ei_score"),
|
|
"ei_score__potential_": deal_data.get("ei_score__potential_"),
|
|
"epc_sap_score": deal_data.get("epc_sap_score"),
|
|
"epc_sap_score__potential_": deal_data.get("epc_sap_score__potential_"),
|
|
"coordinator": deal_data.get("coordinator"),
|
|
"mtp_completion_date": parse_hs_date(deal_data.get("mtp_completion_date")),
|
|
"mtp_re_model_completion_date": parse_hs_date(
|
|
deal_data.get("mtp_re_model_completion_date")
|
|
),
|
|
"ioe_v3_completion_date": parse_hs_date(
|
|
deal_data.get("ioe_v3_completion_date")
|
|
),
|
|
"proposed_measures": deal_data.get("proposed_measures_dropdown"),
|
|
"approved_package": deal_data.get("approved_package"),
|
|
"designer": deal_data.get("designer"),
|
|
"design_completion_date": parse_hs_date(
|
|
deal_data.get("design_completion_date")
|
|
),
|
|
"actual_measures_installed": deal_data.get("actual_measures_installed"),
|
|
"installer": deal_data.get("installer"),
|
|
"installer_handover": deal_data.get("installer_handover"),
|
|
"lodgement_status": deal_data.get("lodgement_status"),
|
|
"measures_lodgement_date": parse_hs_date(
|
|
deal_data.get("measures_lodgement_date")
|
|
),
|
|
"lodgement_date": parse_hs_date(deal_data.get("lodgement_date")),
|
|
"expected_commencement_date": parse_hs_date(
|
|
deal_data.get("expected_commencement_date")
|
|
),
|
|
"surveyor": deal_data.get("surveyor"),
|
|
"confirmed_survey_date": parse_hs_date(
|
|
deal_data.get("confirmed_survey_date")
|
|
),
|
|
"confirmed_survey_time": deal_data.get("confirmed_survey_time"),
|
|
"surveyed_date": parse_hs_date(deal_data.get("surveyed_date")),
|
|
"design_type": deal_data.get("design_type"),
|
|
}.items():
|
|
setattr(existing, attr, value or getattr(existing, attr))
|
|
|
|
def _build_new_deal(
|
|
self,
|
|
deal_id: str,
|
|
deal_data: Dict[str, str],
|
|
listing: Optional[dict[str, str]],
|
|
company: Optional[str],
|
|
) -> HubspotDealData:
|
|
return HubspotDealData(
|
|
deal_id=deal_id,
|
|
dealname=deal_data.get("dealname"),
|
|
dealstage=deal_data.get("dealstage"),
|
|
listing_id=listing.get("listing_id") if listing else None,
|
|
landlord_property_id=(
|
|
listing.get("owner_property_id") if listing else None
|
|
),
|
|
uprn=listing.get("national_uprn") if listing else None,
|
|
outcome=deal_data.get("outcome"),
|
|
outcome_notes=deal_data.get("outcome_notes"),
|
|
project_code=deal_data.get("project_code"),
|
|
company_id=company,
|
|
major_condition_issue_description=deal_data.get(
|
|
"major_condition_issue_description"
|
|
),
|
|
major_condition_issue_photos=deal_data.get("major_condition_issue_photos"),
|
|
coordination_status=deal_data.get("coordination_status__stage_1_"),
|
|
design_status=deal_data.get("retrofit_design_status"),
|
|
coordination_comments=deal_data.get("coordination_comments"),
|
|
pashub_link=deal_data.get("pashub_link"),
|
|
sharepoint_link=deal_data.get("sharepoint_link"),
|
|
dampmould_growth=deal_data.get("dampmould_growth"),
|
|
damp_mould_and_repairs_comments=deal_data.get(
|
|
"damp_mould_and_repairs_comments"
|
|
),
|
|
pre_sap=deal_data.get("pre_sap_score_dropdown"),
|
|
batch=deal_data.get("batch"),
|
|
block_reference=deal_data.get("block_reference"),
|
|
epc_prn=deal_data.get("epc_prn"),
|
|
potential_post_sap_score_dropdown=deal_data.get(
|
|
"potential_post_sap_score_dropdown"
|
|
),
|
|
ei_score=deal_data.get("ei_score"),
|
|
ei_score__potential_=deal_data.get("ei_score__potential_"),
|
|
epc_sap_score=deal_data.get("epc_sap_score"),
|
|
epc_sap_score__potential_=deal_data.get("epc_sap_score__potential_"),
|
|
coordinator=deal_data.get("coordinator"),
|
|
mtp_completion_date=parse_hs_date(deal_data.get("mtp_completion_date")),
|
|
mtp_re_model_completion_date=parse_hs_date(
|
|
deal_data.get("mtp_re_model_completion_date")
|
|
),
|
|
ioe_v3_completion_date=parse_hs_date(
|
|
deal_data.get("ioe_v3_completion_date")
|
|
),
|
|
proposed_measures=deal_data.get("proposed_measures_dropdown"),
|
|
approved_package=deal_data.get("approved_package"),
|
|
designer=deal_data.get("designer"),
|
|
design_completion_date=parse_hs_date(
|
|
deal_data.get("design_completion_date")
|
|
),
|
|
actual_measures_installed=deal_data.get("actual_measures_installed"),
|
|
installer=deal_data.get("installer"),
|
|
installer_handover=deal_data.get("installer_handover"),
|
|
lodgement_status=deal_data.get("lodgement_status"),
|
|
measures_lodgement_date=parse_hs_date(
|
|
deal_data.get("measures_lodgement_date")
|
|
),
|
|
lodgement_date=parse_hs_date(deal_data.get("lodgement_date")),
|
|
expected_commencement_date=parse_hs_date(
|
|
deal_data.get("expected_commencement_date")
|
|
),
|
|
surveyor=deal_data.get("surveyor"),
|
|
confirmed_survey_date=parse_hs_date(deal_data.get("confirmed_survey_date")),
|
|
confirmed_survey_time=deal_data.get("confirmed_survey_time"),
|
|
surveyed_date=parse_hs_date(deal_data.get("surveyed_date")),
|
|
design_type=deal_data.get("design_type"),
|
|
)
|
|
|
|
def _handle_existing_photo_upload(
|
|
self,
|
|
existing_deal: HubspotDealData,
|
|
hubspot_client: HubspotClient,
|
|
):
|
|
# if self._needs_photo_upload(existing):
|
|
|
|
fresh_deal = hubspot_client.from_deal_id_get_info(existing_deal.deal_id)
|
|
fresh_photo_url = fresh_deal.get("major_condition_issue_photos")
|
|
|
|
if not fresh_photo_url:
|
|
print(f"⚠️ Photo URL missing for deal_id {existing_deal.deal_id}")
|
|
return
|
|
|
|
if fresh_photo_url != existing_deal.major_condition_issue_photos:
|
|
logger.info(
|
|
f"Hubspot image URL changed from {existing_deal.major_condition_issue_photos} to {fresh_photo_url}"
|
|
)
|
|
self._upload_photo_to_s3(existing_deal, fresh_photo_url, hubspot_client)
|
|
else:
|
|
logger.info(f"Hubspot image URL unchanged: {fresh_photo_url}")
|
|
|
|
def _handle_new_photo_upload(
|
|
self,
|
|
record: HubspotDealData,
|
|
hubspot_client: HubspotClient,
|
|
):
|
|
if record.major_condition_issue_photos:
|
|
self._upload_photo_to_s3(
|
|
record,
|
|
record.major_condition_issue_photos,
|
|
hubspot_client,
|
|
)
|
|
|
|
def _upload_photo_to_s3(
|
|
self,
|
|
record: HubspotDealData,
|
|
hubspot_photo_url: str,
|
|
hubspot_client: HubspotClient,
|
|
):
|
|
try:
|
|
local_file = hubspot_client.download_file_from_url(hubspot_photo_url)
|
|
|
|
s3_url = self.s3.upload_file(
|
|
local_file,
|
|
"retrofit-data-dev",
|
|
prefix="hubspot/awaabs_law_evidence/",
|
|
)
|
|
|
|
record.major_condition_issue_evidence_s3_url = s3_url
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Failed to upload photo for deal_id {record.deal_id}: {e}")
|
|
finally:
|
|
if "local_file" in locals() and os.path.exists(local_file):
|
|
os.remove(local_file)
|
|
|
|
def _needs_photo_upload(self, old_deal: HubspotDealData) -> bool:
|
|
return bool(
|
|
old_deal.major_condition_issue_photos
|
|
and not old_deal.major_condition_issue_evidence_s3_url
|
|
)
|