Migration Report
One-time, idempotent migration of historical Pad Thai Padel data from TPS production into Setpoint production. Per-row verified end-to-end on court, time, price, and customer. Ready for cutover decision.
TLDR for CEO / Stakeholders
40,332 court bookings and 3,933 customers migrated → every booking's court, time, price, and customer verified per-row against TPS → revenue reconciles to ฿48,665,140 (after the 2026-05-25 hold-correction — see banner).
Boss directive applied (2026-05-19): coaching lessons (4,415 TPS bookings) intentionally excluded (since migrated — see Update above). Data loss: 0.105% — 50 of 47,577 TPS slots skipped because the source rows in TPS were corrupt. Outstanding items (9) for operators listed below; the biggest are deciding on coaching migration, setting up coach availability + pricing rules, and communicating the TPS cutover.
In plain English
Pad Thai Padel's history is now in Setpoint. Every court booking the club has ever taken on TPS — from the very first one to the most recent — is represented inside Setpoint with the right court, the right customer, the right time, and the right price. Reporting on revenue, repeat customers, and court utilization will all "just work" from day one.
We kept the granularity TPS had. Where TPS recorded a recurring weekly booking as multiple slots, Setpoint has one row per slot. This matches how Setpoint expects to think about a booking — one court, one time window, one status — and means cancellations, completions, and check-ins all work on the right unit.
Coaching lessons were later migrated (2026-05-25), then hold-corrected. The original May 19 migration excluded them per CEO directive; that decision was subsequently reversed and coaching lessons were migrated into Setpoint, then phantom reserved/expired holds were removed, leaving 3,159 real lessons (see the Correction banner above). Forward coach availability still needs to be set before new lessons can be booked through Setpoint.
What's left for ops before TPS can be turned off: coach availability needs to be entered in Setpoint, the pricing rules need to move from a single ฿1,200 catch-all to TPS's actual variable rates, and customers need to be told they'll log in via magic link the first time (passwords don't carry over from TPS — those hashes are not portable).
Inventory
12 phases · each independently verified · ordered by dependency.
| # | Phase | Setpoint outcome | Source in TPS |
|---|---|---|---|
| 1 | Admin user | 1 admin confirmed in auth.users | — |
| 2 | Existing facility renames | Padel Galis, Royal Padel, The Padel Society preserved with corrected slugs | — |
| 3 | PTP facility | 1 facility · slug pad-thai-padel · THB · Asia/Bangkok | TPS club_id 388 |
| 4 | Academy | 1 academy under PTP | TPS club 388 |
| 5 | Coaches | 17 coaches · accounts confirmed · linked | TPS coach_profiles ∩ club 388 |
| 6 | Members | Member tiers seeded from TPS membership levels | TPS membership data |
| 7 | Coaches in academy | 17 academy_coaches rows | derived |
| 8 | Courts | 3 courts: Indoor 1, Indoor 2, Outdoor | TPS courts ∩ club 388 |
| 9 | Pricing | 1 catch-all rule ฿1,200/hr (placeholder — see #6 below) | TPS pricing was per-day variable |
| 10 | Classes (default) | 1 default class per coach · 17 total | derived |
| 11 | Customers | 3,933 unique customers · 100% mapped · name + phone backfilled | TPS users ∩ PTP bookings, ex-coaches |
| 12 | Court bookings | 40,332 bookings · per slot · external_payment_ref preserved (47,527 migrated, 7,195 reserved/expired holds since removed) | TPS booked_slots ∩ club 388 ∩ bookings.type='booking' |
Revenue: ฿48,665,140 (sum of price_amount/100 in Setpoint, after the 2026-05-25 hold-correction; was ฿59,088,381 before removing ฿10,423,241 of phantom reserved/expired holds).
Status
As of 2026-05-19 16:09 UTC, after the status-drift fix.
Verification
From scripts/ in the setpoint repo.
| Check | Script | Result |
|---|---|---|
| Customer count + 5-sample name split | verify-customers-phase.ts | 3,933 / 3,933 matched · 0 mismatches |
| Booking count + aggregate revenue + 3-sample row spot | verify-bookings-phase.ts | 47,527 / 47,527 at migration · 40,332 after hold-correction |
| Per-row strict: court, time, price, customer, status | verify-bookings-strict.ts | 47,527 / 47,527 on court/time/price/customer at migration (40,332 post-correction) |
| Court mapping | verify-courts.ts | 3 / 3 |
| Members | verify-members.ts | all matched |
| Coaches in academy | verify-coaches-in-academy.ts | 17 / 17 |
| Classes | verify-classes.ts | 17 / 17 |
During strict verification, 7 bookings whose end_time had passed since the migration ran were still marked confirmed. These were not bugs — they were "time drift" between snapshot and verification. They've been flipped to completed to keep the status board honest. 0 drifted remain as of 2026-05-19 16:09 UTC.
Time will continue to advance, so this fix may need re-running periodically until TPS is turned off (or never, if a Setpoint-side cron / trigger handles status transitions natively).
Exclusions
For ops awareness before cutover.
5 referenced courts that don't belong to PTP · 13 had end_time ≤ start_time (zero-duration) · 32 were exact-overlap duplicates inside TPS itself
Logged at migration time. Total slot revenue lost: small (≈฿60–80k worst case).
Excluded 2026-05-19; MIGRATED 2026-05-25; then HOLD-CORRECTED → 3,159 real lessons — no longer excluded
Migrated (availability gate bypassed for the historical insert), then ~1,592 phantom reserved/expired holds removed. Final: 1,973 completed + 1,159 cancelled + 27 scheduled. See correction banner.
TPS is still live; bookings created during/after the migration window are in TPS but not Setpoint
Goes away once TPS is cut over. Until then, re-run scripts/ptp-tps-sync.ts to catch up — script is idempotent.
Outstanding for Operators
9 items · 1 done · 8 pending owner action.
Re-run scripts/ptp-tps-sync.ts just before TPS cutover; idempotent, only inserts new slots.
Decide whether to manually re-create any (probably none — most are old, cancelled, or test data).
Done today (7 bookings flipped). May re-occur as time advances; harmless.
TPS had phone conflicts (one phone used by multiple TPS accounts). Setpoint phone is UNIQUE — kept first-write, skipped rest. Customers can self-update at first login.
All 17 coaches have 0 availability rows. Required before any coaching lessons can be booked through Setpoint. Manual entry or separate import.
TPS used variable per-day pricing. Setpoint pricing engine supports it (court+day+time → sport+day+time → court+day → sport+day → default), but live rules need setup. Migrated bookings preserved actual TPS prices, so reporting is fine.
TPS password hashes are not portable. Customers will receive magic-link/OTP at first login. Recommend a pre-cutover email.
Done — 3,159 lessons (1,973 completed + 1,159 cancelled + 27 scheduled) after removing ~1,592 phantom reserved/expired holds; 24 upcoming skipped (court conflict, logged). Reversed the May 19 exclusion. See correction banner.
Customers on the TPS mobile app today continue to land in TPS until OpenRally routing flips PTP to Setpoint. Schedule the flip + customer comms together.
Decision Log
Persisted here so the rationale survives the work.
Not per-TPS-booking. This preserves recurring weekly bookings as discrete sessions and gives each one its own status, court, and price — the model Setpoint expects.
Rather than re-pricing through Setpoint's pricing rules. Reporting and historical revenue stay truthful.
Format: tps:booked_slots.id=<N>. Re-running the script will skip rows already imported.
Re-scoped mid-migration. No cleanup needed because no coaching data had been written.
Different emails = different Setpoint accounts, even when phones match in TPS. Customer self-update on first login resolves the rest.
Even if end_time is in the past — preserves the audit trail, matches what an operator would expect to see in history.
Files & Re-Run
scripts/ptp-tps-sync.ts12 phases · idempotent · safe to re-run.
scripts/verify-bookings-strict.tsPer-row check on court, time, price, customer.
npx tsx scripts/ptp-tps-sync.ts # idempotent, only inserts new rows npx tsx scripts/verify-bookings-strict.ts npx tsx scripts/verify-customers-phase.ts