from __future__ import annotations import hashlib import json from collections.abc import Mapping from dataclasses import dataclass from datetime import date, datetime from decimal import Decimal, InvalidOperation from typing import Any TARGET_OBJECT = "Month_End_ARR__c" EXTERNAL_ID_FIELD = "Outbound_Key__c" TARGET_FIELDS = ( "Account__c", "Account_ID__c", "Order_Region__c", "Product_Family__c", "Product_Type__c", "Revenue_Month__c", "Month_End_ARR_Amount__c", ) class RowValidationError(ValueError): pass @dataclass(frozen=True) class MonthEndARRRecord: source_account_id: str external_key: str source_hash: str payload: dict[str, Any] def map_month_end_arr_row( row: Mapping[str, Any], *, external_id_field: str = EXTERNAL_ID_FIELD ) -> MonthEndARRRecord | None: account_id = _optional_text(row.get("account_id")) if not account_id: return None region = _required_text(row.get("region"), "region") product_family = _required_text(row.get("product_family"), "product_family") product_type = _required_text(row.get("product_type"), "product_type") revenue_month = _parse_date(row.get("revenue_month")) amount = _parse_amount(row.get("month_end_arr_amount")) key_material = ["month_end_arr_v1", account_id, revenue_month.isoformat(), region, product_family, product_type] external_key = hashlib.sha256( json.dumps(key_material, ensure_ascii=False, separators=(",", ":")).encode() ).hexdigest() payload: dict[str, Any] = { "attributes": {"type": TARGET_OBJECT}, external_id_field: external_key, "Account_ID__c": account_id, "Order_Region__c": region, "Product_Family__c": product_family, "Product_Type__c": product_type, "Revenue_Month__c": revenue_month.isoformat(), "Month_End_ARR_Amount__c": float(amount), } source_hash = hashlib.sha256( json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode() ).hexdigest() return MonthEndARRRecord( source_account_id=account_id, external_key=external_key, source_hash=source_hash, payload=payload, ) def bind_month_end_arr_account(record: MonthEndARRRecord, account_record_id: str) -> MonthEndARRRecord: payload = {**record.payload, "Account__c": account_record_id} source_hash = hashlib.sha256( json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode() ).hexdigest() return MonthEndARRRecord( source_account_id=record.source_account_id, external_key=record.external_key, source_hash=source_hash, payload=payload, ) def validate_target_contract( describe: Mapping[str, Any], records: list[MonthEndARRRecord], *, external_id_field: str = EXTERNAL_ID_FIELD, required_fields: tuple[str, ...] = TARGET_FIELDS, ) -> None: described_fields = { str(field.get("name") or field.get("apiName")): field for field in describe.get("fields", []) if isinstance(field, Mapping) and (field.get("name") or field.get("apiName")) } missing = sorted((set(required_fields) | {external_id_field}) - described_fields.keys()) if missing: raise RowValidationError(f"target object is missing fields: {', '.join(missing)}") external_field = described_fields[external_id_field] if external_field.get("unique") is not True: raise RowValidationError(f"target field {external_id_field} must be unique before outbound writes are enabled") if external_field.get("createable") is False or external_field.get("updateable") is False: raise RowValidationError( f"target field {external_id_field} is not createable/updateable for the integration user" ) for field_name in required_fields: field = described_fields[field_name] if field.get("createable") is False or field.get("updateable") is False: raise RowValidationError(f"target field {field_name} is not createable/updateable for the integration user") _validate_picklist_values(described_fields, records, "Order_Region__c") _validate_picklist_values(described_fields, records, "Product_Type__c") def _validate_picklist_values( fields: Mapping[str, Mapping[str, Any]], records: list[MonthEndARRRecord], field_name: str ) -> None: field = fields[field_name] if str(field.get("type", "")).lower() != "picklist": return options = field.get("picklistValues") or field.get("options") or [] allowed = { str(option.get("value")) for option in options if isinstance(option, Mapping) and option.get("value") is not None and option.get("active", True) is not False } invalid = sorted( {str(record.payload[field_name]) for record in records if str(record.payload[field_name]) not in allowed} ) if invalid: raise RowValidationError(f"invalid {field_name} values for target picklist: {', '.join(invalid)}") def _optional_text(value: Any) -> str: if value is None: return "" return str(value).strip() def _required_text(value: Any, field: str) -> str: text = _optional_text(value) if not text: raise RowValidationError(f"{field} is required") return text def _parse_date(value: Any) -> date: if isinstance(value, datetime): return value.date() if isinstance(value, date): return value text = _required_text(value, "revenue_month") for fmt in ("%Y-%m-%d", "%Y-%m", "%Y/%m/%d", "%m/%d/%Y"): try: return datetime.strptime(text[:10] if fmt == "%Y-%m-%d" else text, fmt).date() except ValueError: pass raise RowValidationError(f"revenue_month is not a supported date: {text}") def _parse_amount(value: Any) -> Decimal: if value is None or (isinstance(value, str) and not value.strip()): raise RowValidationError("month_end_arr_amount is required") text = str(value).strip().replace(",", "").removeprefix("$") try: amount = Decimal(text) except InvalidOperation as error: raise RowValidationError(f"month_end_arr_amount is not numeric: {value}") from error if not amount.is_finite(): raise RowValidationError("month_end_arr_amount must be finite") return amount