Hashed conversion matching and more. #12
@@ -883,131 +883,6 @@ class ConversionService:
|
|||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
||||||
async def _find_matching_entities(
|
|
||||||
self,
|
|
||||||
advertising_campagne: str,
|
|
||||||
hotel_id: str | None,
|
|
||||||
reservation_date: Any,
|
|
||||||
guest_first_name: str | None = None,
|
|
||||||
guest_last_name: str | None = None,
|
|
||||||
guest_email: str | None = None,
|
|
||||||
advertising_partner: str | None = None,
|
|
||||||
session: AsyncSession | None = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Find matching Reservation, Customer, and HashedCustomer.
|
|
||||||
|
|
||||||
Uses two strategies with separate matching paths:
|
|
||||||
1. ID-based matching (fbclid/gclid/md5_unique_id) - returns Reservation + Customer + HashedCustomer
|
|
||||||
2. Guest detail matching (email/name) - returns Customer + HashedCustomer directly (no Reservation needed)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
advertising_campagne: Truncated tracking ID from conversion XML
|
|
||||||
hotel_id: Hotel ID for additional filtering
|
|
||||||
reservation_date: Reservation date for additional filtering
|
|
||||||
guest_first_name: Guest first name (hashed) for matching
|
|
||||||
guest_last_name: Guest last name (hashed) for matching
|
|
||||||
guest_email: Guest email (hashed) for matching
|
|
||||||
advertising_partner: Partner info (matches utm_medium for additional filtering)
|
|
||||||
session: AsyncSession to use. If None, uses self.session.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary with 'reservation', 'customer', 'hashed_customer', and 'match_type' keys.
|
|
||||||
match_type is either 'id' (high confidence) or 'guest_details' (lower confidence)
|
|
||||||
For guest_details matches, 'reservation' will be None.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if session is None:
|
|
||||||
session = self.session
|
|
||||||
result = {
|
|
||||||
"reservation": None,
|
|
||||||
"customer": None,
|
|
||||||
"hashed_customer": None,
|
|
||||||
"match_type": None, # "id" or "guest_details"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Strategy 1: Try to match by advertising data (fbclid/gclid/md5_unique_id) - ID-based, high confidence
|
|
||||||
if advertising_campagne:
|
|
||||||
matched_reservation = await self._match_by_advertising(
|
|
||||||
advertising_campagne,
|
|
||||||
hotel_id,
|
|
||||||
guest_first_name,
|
|
||||||
guest_last_name,
|
|
||||||
guest_email,
|
|
||||||
advertising_partner,
|
|
||||||
session,
|
|
||||||
)
|
|
||||||
|
|
||||||
if matched_reservation:
|
|
||||||
result["reservation"] = matched_reservation
|
|
||||||
result["match_type"] = "id" # Matched by ID
|
|
||||||
|
|
||||||
|
|
||||||
_LOGGER.info(
|
|
||||||
"Matched conversion by advertising ID data (advertisingCampagne=%s, hotel=%s)",
|
|
||||||
advertising_campagne,
|
|
||||||
hotel_id,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_LOGGER.debug(
|
|
||||||
"No match found by advertising ID data (advertisingCampagne=%s), "
|
|
||||||
"falling back to guest details matching",
|
|
||||||
advertising_campagne,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Strategy 2: If no ID-based match, try email/name-based matching - guest details, lower confidence
|
|
||||||
# For guest detail matches, match directly with HashedCustomer (skip Reservation table)
|
|
||||||
if not result["reservation"] and (
|
|
||||||
guest_email or guest_first_name or guest_last_name
|
|
||||||
):
|
|
||||||
matched_hashed_customer = await self._match_by_guest_details_hashed(
|
|
||||||
guest_first_name, guest_last_name, guest_email, session
|
|
||||||
)
|
|
||||||
|
|
||||||
if matched_hashed_customer:
|
|
||||||
result["hashed_customer"] = matched_hashed_customer
|
|
||||||
result["match_type"] = "guest_details" # Matched by guest details only
|
|
||||||
|
|
||||||
# Get the customer if it exists
|
|
||||||
if matched_hashed_customer.customer_id:
|
|
||||||
customer_query = select(Customer).where(
|
|
||||||
Customer.id == matched_hashed_customer.customer_id
|
|
||||||
)
|
|
||||||
customer_result = await session.execute(customer_query)
|
|
||||||
result["customer"] = customer_result.scalar_one_or_none()
|
|
||||||
|
|
||||||
_LOGGER.info(
|
|
||||||
"Matched conversion by guest details to hashed_customer (name=%s %s, email=%s)",
|
|
||||||
guest_first_name,
|
|
||||||
guest_last_name,
|
|
||||||
guest_email,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_LOGGER.debug(
|
|
||||||
"No match found by guest details (name=%s %s, email=%s)",
|
|
||||||
guest_first_name,
|
|
||||||
guest_last_name,
|
|
||||||
guest_email,
|
|
||||||
)
|
|
||||||
|
|
||||||
# If we found a reservation (ID-based match), get its customer and hashed_customer
|
|
||||||
if result["reservation"]:
|
|
||||||
if result["reservation"].customer_id:
|
|
||||||
customer_query = select(Customer).where(
|
|
||||||
Customer.id == result["reservation"].customer_id
|
|
||||||
)
|
|
||||||
customer_result = await session.execute(customer_query)
|
|
||||||
result["customer"] = customer_result.scalar_one_or_none()
|
|
||||||
|
|
||||||
# Get hashed customer
|
|
||||||
if result["customer"]:
|
|
||||||
hashed_query = select(HashedCustomer).where(
|
|
||||||
HashedCustomer.customer_id == result["customer"].id
|
|
||||||
)
|
|
||||||
hashed_result = await session.execute(hashed_query)
|
|
||||||
result["hashed_customer"] = hashed_result.scalar_one_or_none()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def _match_by_advertising(
|
async def _match_by_advertising(
|
||||||
self,
|
self,
|
||||||
advertising_campagne: str,
|
advertising_campagne: str,
|
||||||
|
|||||||
Reference in New Issue
Block a user