Hashed conversion matching and more. #12

Merged
jonas merged 13 commits from hashed_conversion_matching into main 2025-11-19 19:40:07 +00:00
4 changed files with 87 additions and 4 deletions
Showing only changes of commit d88a53327f - Show all commits

View File

@@ -0,0 +1,65 @@
"""add hashed_customer_id to reservations with cascade delete
Revision ID: 08fe946414d8
Revises: 70b2579d1d96
Create Date: 2025-11-19 14:57:27.178924
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '08fe946414d8'
down_revision: Union[str, Sequence[str], None] = '70b2579d1d96'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('hashed_customers', 'customer_id',
existing_type=sa.INTEGER(),
nullable=True)
op.drop_constraint(op.f('hashed_customers_customer_id_fkey'), 'hashed_customers', type_='foreignkey')
op.create_foreign_key(None, 'hashed_customers', 'customers', ['customer_id'], ['id'], ondelete='SET NULL')
op.drop_constraint(op.f('reservations_customer_id_fkey'), 'reservations', type_='foreignkey')
op.create_foreign_key(None, 'reservations', 'customers', ['customer_id'], ['id'], ondelete='SET NULL')
# Add hashed_customer_id column to reservations with cascade delete
op.add_column('reservations', sa.Column('hashed_customer_id', sa.Integer(), nullable=True))
op.create_index(op.f('ix_reservations_hashed_customer_id'), 'reservations', ['hashed_customer_id'], unique=False)
op.create_foreign_key(None, 'reservations', 'hashed_customers', ['hashed_customer_id'], ['id'], ondelete='CASCADE')
# ### end Alembic commands ###
# Data migration: Populate hashed_customer_id from customer relationship
connection = op.get_bind()
update_stmt = sa.text("""
UPDATE reservations r
SET hashed_customer_id = hc.id
FROM hashed_customers hc
WHERE r.customer_id = hc.customer_id
AND hc.customer_id IS NOT NULL
""")
connection.execute(update_stmt)
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
# Drop the hashed_customer_id column and its constraints
op.drop_constraint(None, 'reservations', type_='foreignkey')
op.drop_index(op.f('ix_reservations_hashed_customer_id'), table_name='reservations')
op.drop_column('reservations', 'hashed_customer_id')
op.drop_constraint(None, 'reservations', type_='foreignkey')
op.create_foreign_key(op.f('reservations_customer_id_fkey'), 'reservations', 'customers', ['customer_id'], ['id'])
op.drop_constraint(None, 'hashed_customers', type_='foreignkey')
op.create_foreign_key(op.f('hashed_customers_customer_id_fkey'), 'hashed_customers', 'customers', ['customer_id'], ['id'])
op.alter_column('hashed_customers', 'customer_id',
existing_type=sa.INTEGER(),
nullable=False)
# ### end Alembic commands ###

View File

@@ -253,7 +253,8 @@ class ConversionService:
# Load all reservations with their hashed customers in one query # Load all reservations with their hashed customers in one query
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
query = select(Reservation).options( query = select(Reservation).options(
selectinload(Reservation.customer).selectinload(Customer.hashed_version) selectinload(Reservation.customer).selectinload(Customer.hashed_version),
selectinload(Reservation.hashed_customer)
) )
result = await session.execute(query) result = await session.execute(query)
reservations = result.scalars().all() reservations = result.scalars().all()
@@ -265,9 +266,11 @@ class ConversionService:
hotel_code = reservation.hotel_code hotel_code = reservation.hotel_code
if hotel_code not in self._reservation_cache: if hotel_code not in self._reservation_cache:
self._reservation_cache[hotel_code] = [] self._reservation_cache[hotel_code] = []
# Cache the hashed customer instead of raw customer # Cache the hashed customer - prefer direct relationship, fall back to customer relationship
hashed_customer = None hashed_customer = None
if reservation.customer and reservation.customer.hashed_version: if reservation.hashed_customer:
hashed_customer = reservation.hashed_customer
elif reservation.customer and reservation.customer.hashed_version:
hashed_customer = reservation.customer.hashed_version hashed_customer = reservation.customer.hashed_version
self._reservation_cache[hotel_code].append( self._reservation_cache[hotel_code].append(
(reservation, hashed_customer) (reservation, hashed_customer)

View File

@@ -478,6 +478,7 @@ class Reservation(Base):
__tablename__ = "reservations" __tablename__ = "reservations"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
customer_id = Column(Integer, ForeignKey("customers.id", ondelete="SET NULL")) customer_id = Column(Integer, ForeignKey("customers.id", ondelete="SET NULL"))
hashed_customer_id = Column(Integer, ForeignKey("hashed_customers.id", ondelete="CASCADE"))
unique_id = Column(String, unique=True) unique_id = Column(String, unique=True)
md5_unique_id = Column(String(32), unique=True) # max length 32 guaranteed md5_unique_id = Column(String(32), unique=True) # max length 32 guaranteed
start_date = Column(Date) start_date = Column(Date)
@@ -507,6 +508,7 @@ class Reservation(Base):
room_classification_code = Column(String) room_classification_code = Column(String)
room_type = Column(String) room_type = Column(String)
customer = relationship("Customer", back_populates="reservations") customer = relationship("Customer", back_populates="reservations")
hashed_customer = relationship("HashedCustomer", backref="reservations")
# Table for tracking acknowledged requests by client # Table for tracking acknowledged requests by client

View File

@@ -7,7 +7,7 @@ from typing import Optional
from sqlalchemy import and_, select from sqlalchemy import and_, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from .db import AckedRequest, Customer, Reservation from .db import AckedRequest, Customer, HashedCustomer, Reservation
from .schemas import ReservationData from .schemas import ReservationData
@@ -63,6 +63,19 @@ class ReservationService:
reservation = self._convert_reservation_data_to_db( reservation = self._convert_reservation_data_to_db(
reservation_data, customer_id reservation_data, customer_id
) )
# Automatically populate hashed_customer_id from the customer
# Since hashed_customer is always created when a customer is created,
# we can get it by querying for the hashed_customer with matching customer_id
hashed_customer_result = await self.session.execute(
select(HashedCustomer).where(
HashedCustomer.customer_id == customer_id
)
)
hashed_customer = hashed_customer_result.scalar_one_or_none()
if hashed_customer:
reservation.hashed_customer_id = hashed_customer.id
self.session.add(reservation) self.session.add(reservation)
if auto_commit: if auto_commit: