Migration Report

TPS→Setpoint
Pad Thai Padel Migration — Complete

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.

Status: Complete2026-05-1912 phases · 6 decisions9 items pending operator action

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.

⚠ Correction — 2026-05-25 (read first): two changes after the original write-up below. (1) Coaching lessons were migrated (reversing the 2026-05-19 exclusion). (2) A bug was found and fixed the same day: both the court and coaching migrations had imported TPS "reserved"/expired holds (unconfirmed, short-lived placeholders) as if they were real bookings. Those phantom rows were removed in one atomic, rehearsed, reversible operation (every deleted id logged; TPS never touched; no webhooks). Corrected totals: court bookings 47,527 → 40,332, revenue ฿59,088,381 → ฿48,665,140 (฿10,423,241 phantom removed), coaching 3,159 real lessons (1,973 completed + 1,159 cancelled + 27 scheduled). 71 removed holds were future-dated and had been wrongly blocking real court availability (now freed). Numbers in the body below are the original, pre-correction figures unless noted. Both migration scripts were patched so a pre-cutover re-run can't re-introduce holds.
Customers migrated
3,933
100% of TPS PTP customers
Court bookings
40,332
after hold-correction (was 47,527)
Revenue reconciled
฿48,665,140
after hold-correction (was ฿59,088,381)
Data loss
0.105%
50 corrupt TPS slots

In plain English

What happened, and what's next

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

What was migrated

12 phases · each independently verified · ordered by dependency.

#PhaseSetpoint outcomeSource in TPS
1Admin user1 admin confirmed in auth.users—
2Existing facility renamesPadel Galis, Royal Padel, The Padel Society preserved with corrected slugs—
3PTP facility1 facility · slug pad-thai-padel · THB · Asia/BangkokTPS club_id 388
4Academy1 academy under PTPTPS club 388
5Coaches17 coaches · accounts confirmed · linkedTPS coach_profiles ∩ club 388
6MembersMember tiers seeded from TPS membership levelsTPS membership data
7Coaches in academy17 academy_coaches rowsderived
8Courts3 courts: Indoor 1, Indoor 2, OutdoorTPS courts ∩ club 388
9Pricing1 catch-all rule ฿1,200/hr (placeholder — see #6 below)TPS pricing was per-day variable
10Classes (default)1 default class per coach · 17 totalderived
11Customers3,933 unique customers · 100% mapped · name + phone backfilledTPS users ∩ PTP bookings, ex-coaches
12Court bookings40,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

Booking status breakdown

As of 2026-05-19 16:09 UTC, after the status-drift fix.

confirmed
1,273
future (was 1,312)
completed
20,542
past, finished (was 27,698 — holds removed)
cancelled
18,517
cancelled at any time

Verification

All checks scripted and re-runnable

From scripts/ in the setpoint repo.

CheckScriptResult
Customer count + 5-sample name splitverify-customers-phase.ts3,933 / 3,933 matched · 0 mismatches
Booking count + aggregate revenue + 3-sample row spotverify-bookings-phase.ts47,527 / 47,527 at migration · 40,332 after hold-correction
Per-row strict: court, time, price, customer, statusverify-bookings-strict.ts47,527 / 47,527 on court/time/price/customer at migration (40,332 post-correction)
Court mappingverify-courts.ts3 / 3
Membersverify-members.tsall matched
Coaches in academyverify-coaches-in-academy.ts17 / 17
Classesverify-classes.ts17 / 17
Status drift fix (done today)

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

What is NOT in Setpoint

For ops awareness before cutover.

Corrupt TPS rows50

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).

Coaching lessons4,415

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 bookings after 2026-05-19~60

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

Action items between today and TPS cutover

9 items · 1 done · 8 pending owner action.

1
~60 new TPS bookings since migration startedOwner: Eng

Re-run scripts/ptp-tps-sync.ts just before TPS cutover; idempotent, only inserts new slots.

2
50 errored slots (corrupt TPS data)Owner: Ops review

Decide whether to manually re-create any (probably none — most are old, cancelled, or test data).

Status drift fix (confirmed → completed for past bookings)Owner: —

Done today (7 bookings flipped). May re-occur as time advances; harmless.

4
52 customers without phone in SetpointOwner: Ops review

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.

5
Coach availability is empty in SetpointOwner: Ops

All 17 coaches have 0 availability rows. Required before any coaching lessons can be booked through Setpoint. Manual entry or separate import.

6
Pricing rule is a single ฿1,200 catch-allOwner: Ops

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.

7
Customer passwords NOT migratedOwner: Ops + comms

TPS password hashes are not portable. Customers will receive magic-link/OTP at first login. Recommend a pre-cutover email.

Coaching lessons migrated + hold-corrected (2026-05-25)Owner: CEO / Eng

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.

9
TPS cutover communicationOwner: Ops / Marketing

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

Key calls made during this migration

Persisted here so the rationale survives the work.

DEC-APer-slot mapping (one Setpoint booking per TPS booked_slots row)

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.

DEC-BPreserve actual historical TPS prices

Rather than re-pricing through Setpoint's pricing rules. Reporting and historical revenue stay truthful.

DEC-CIdempotency via external_payment_ref

Format: tps:booked_slots.id=<N>. Re-running the script will skip rows already imported.

DEC-DCoaching excluded (boss directive, 2026-05-19)

Re-scoped mid-migration. No cleanup needed because no coaching data had been written.

DEC-EPhone conflicts skipped, not merged

Different emails = different Setpoint accounts, even when phones match in TPS. Customer self-update on first login resolves the rest.

DEC-FCancelled-in-TPS bookings kept as cancelled in Setpoint

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

All scripts persisted in repo

Main migration script
scripts/ptp-tps-sync.ts

12 phases · idempotent · safe to re-run.

Strongest verification
scripts/verify-bookings-strict.ts

Per-row check on court, time, price, customer.

Re-run before TPS cutover
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