/** * Cleanup script: remove duplicate stops from the Tuxedo Corn 2026 tour. * Keeps the row with the latest created_at per (brand_id, date, city, state, location, time). * Run: DATABASE_URL="postgresql://..." npx tsx scripts/cleanup-duplicate-stops.ts */ import { Pool } from "pg"; const pool = new Pool({ connectionString: process.env.DATABASE_ADMIN_URL ?? process.env.DATABASE_URL ?? "postgres://postgres:postgres@localhost:5432/route_commerce", }); async function main() { const client = await pool.connect(); try { await client.query("BEGIN"); // Find duplicate groups (same brand_id, date, city, state, location, time) const dupes = await client.query(` SELECT brand_id::text, date, city, state, location, time, count(*) as cnt, min(created_at::text) as oldest, max(created_at::text) as newest FROM stops WHERE deleted_at IS NULL GROUP BY brand_id, date, city, state, location, time HAVING count(*) > 1 ORDER BY date, city `); console.log(`Found ${dupes.rowCount} duplicate groups`); if (dupes.rowCount === 0) { await client.query("COMMIT"); console.log("No duplicates found."); return; } let totalDeleted = 0; for (const row of dupes.rows) { // Keep the row with the latest created_at, delete the rest const del = await client.query(` DELETE FROM stops WHERE brand_id = $1 AND date = $2 AND city = $3 AND state = $4 AND location = $5 AND time IS NOT DISTINCT FROM $6 AND deleted_at IS NULL AND created_at < ( SELECT max(created_at) FROM stops s2 WHERE s2.brand_id = stops.brand_id AND s2.date = stops.date AND s2.city = stops.city AND s2.state = stops.state AND s2.location = stops.location AND (s2.time IS NOT DISTINCT FROM stops.time) AND s2.deleted_at IS NULL ) `, [row.brand_id, row.date, row.city, row.state, row.location, row.time]); totalDeleted += del.rowCount ?? 0; console.log(` Deleted ${del.rowCount} duplicate(s) for ${row.city}, ${row.state} on ${row.date}`); } await client.query("COMMIT"); console.log(`\nDone. Total duplicates removed: ${totalDeleted}`); } catch (e) { await client.query("ROLLBACK"); throw e; } finally { client.release(); await pool.end(); } } main().catch((e) => { console.error(e); process.exit(1); });