brackt/docs/superpowers/plans/2026-05-01-phase1a-rename-per-window-tables.md
Chris Parsons 81af8907cc
Add design + 5 phase plans: canonical tournament & participant layer
Spec and phased implementation plans for eliminating data duplication
across overlapping qualifying-points windows (golf, tennis, CS2) by
introducing canonical tournaments, participants, tournament_results,
and surface-Elo tables above the existing per-window layer.

- Phase 1a: rename existing per-window tables to season_* prefix
- Phase 1b: create canonical tables + nullable FKs
- Phase 2:  backfill canonical from existing in-flight windows
- Phase 3:  cutover - sync service, admin UI, simulator read switch
- Phase 4:  cleanup - drop deprecated tables and routes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 05:20:34 +00:00

26 KiB
Raw Blame History

Phase 1a — Rename Per-Window Tables Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Rename the existing per-window tables (participants, participant_expected_values, participant_qualifying_totals, participant_results, participant_surface_elos) to season_-prefixed names, and rename event_results.participant_id to season_participant_id. Rename app/models/participant.ts to app/models/season-participant.ts. Ship a fully green build with zero behavior changes.

Architecture: Pure mechanical rename across ~100 files. No new tables, no new columns, no logic changes. This phase must land cleanly before Phase 1b can create the new canonical participants table (which reuses the freed name).

Tech Stack: Drizzle ORM, PostgreSQL, React Router 7, TypeScript, Vitest.

Reference: docs/superpowers/specs/2026-05-01-canonical-tournament-layer-design.md — Phase 1a section.

Rename mapping (authoritative):

Old (table / column / symbol) New
table participants season_participants
table participant_expected_values season_participant_expected_values
table participant_qualifying_totals season_participant_qualifying_totals
table participant_results season_participant_results
table participant_surface_elos season_participant_surface_elos
column event_results.participant_id event_results.season_participant_id
Drizzle export participants seasonParticipants
Drizzle export participantExpectedValues seasonParticipantExpectedValues
Drizzle export participantQualifyingTotals seasonParticipantQualifyingTotals
Drizzle export participantResults seasonParticipantResults
Drizzle export participantSurfaceElos seasonParticipantSurfaceElos
Drizzle export participantsRelations seasonParticipantsRelations
Drizzle export participantExpectedValuesRelations seasonParticipantExpectedValuesRelations
Drizzle export participantQualifyingTotalsRelations seasonParticipantQualifyingTotalsRelations
Drizzle export participantResultsRelations seasonParticipantResultsRelations
Drizzle export participantSurfaceElosRelations seasonParticipantSurfaceElosRelations
File app/models/participant.ts app/models/season-participant.ts
Relation field eventResults.participant eventResults.seasonParticipant

Files affected (counts from pre-flight audit):

  • database/schema.ts (1 — critical)
  • app/models/index.ts (1)
  • app/models/ — 11 model files + 2 test files
  • app/routes/ — ~20 route files
  • app/services/ — probability-updater + 19 simulator files
  • app/utils/sports-data-sync.server.ts
  • server/socket.ts, test files
  • cypress/ — verify none reference these symbols by name

Note on granularity: Because this is a mechanical rename, tasks are batched by scope rather than broken into micro-TDD steps. Each task ends with typecheck + test run as the verification step.


Task 0: Create baseline snapshot (one-time, before any code changes)

Why: Phase 2 verification depends on proving QP totals didn't drift. Capture now.

Files:

  • Create: scripts/capture-baseline.ts

  • Step 1: Write the capture script

// scripts/capture-baseline.ts
import { db } from "~/db/context";
import { sql } from "drizzle-orm";
import { writeFileSync, mkdirSync } from "fs";
import { join } from "path";

const OUT_DIR = join(process.cwd(), "test-fixtures", "baselines");

async function main() {
  mkdirSync(OUT_DIR, { recursive: true });

  const qpTotals = await db.execute(sql`
    SELECT pqt.*, ss.id as sports_season_id, s.name as sport_name
    FROM participant_qualifying_totals pqt
    JOIN sports_seasons ss ON ss.id = pqt.sports_season_id
    JOIN sports s ON s.id = ss.sport_id
    WHERE ss.scoring_pattern = 'qualifying_points'
    ORDER BY ss.id, pqt.participant_id
  `);

  const eventResults = await db.execute(sql`
    SELECT er.*, se.sports_season_id
    FROM event_results er
    JOIN scoring_events se ON se.id = er.scoring_event_id
    JOIN sports_seasons ss ON ss.id = se.sports_season_id
    WHERE ss.scoring_pattern = 'qualifying_points'
      AND er.qualifying_points_awarded IS NOT NULL
    ORDER BY se.sports_season_id, er.scoring_event_id, er.participant_id
  `);

  const surfaceElos = await db.execute(sql`
    SELECT pse.*, ss.sport_id
    FROM participant_surface_elos pse
    JOIN sports_seasons ss ON ss.id = pse.sports_season_id
    ORDER BY ss.id, pse.participant_id
  `);

  writeFileSync(join(OUT_DIR, "qp-totals-pre-phase1.json"), JSON.stringify(qpTotals.rows, null, 2));
  writeFileSync(join(OUT_DIR, "event-results-pre-phase1.json"), JSON.stringify(eventResults.rows, null, 2));
  writeFileSync(join(OUT_DIR, "surface-elos-pre-phase1.json"), JSON.stringify(surfaceElos.rows, null, 2));

  console.log(`Wrote baselines: ${qpTotals.rows.length} QP totals, ${eventResults.rows.length} event results, ${surfaceElos.rows.length} surface elo rows.`);
}

main().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); });
  • Step 2: Run against production data read replica (or local dev DB loaded from npm run db:sync-prod)

Run: npx tsx scripts/capture-baseline.ts Expected: three JSON files created under test-fixtures/baselines/; non-zero row counts for QP totals and surface elos; Masters 2026 results visible in event-results-pre-phase1.json.

  • Step 3: Also capture Monte Carlo simulator output baselines

Extend scripts/capture-baseline.ts to, for each in-flight qualifying-points sports_seasons row, run the matching simulator with a fixed random seed and save its output. This JSON is what Phase 3 Task 6's tennis-simulator regression test reads.

Pattern:

// Pseudocode appended to scripts/capture-baseline.ts main():
import { getSimulator } from "~/services/simulations/registry";
// ... for each ss in qualifying-points seasons:
const sim = getSimulator(ss.simulatorType!);
const output = await sim.run(ss.id, { seed: 42, iterations: 10000 });
writeFileSync(join(OUT_DIR, `sim-output-${ss.simulatorType}-${ss.id}.json`), JSON.stringify(output, null, 2));

Check the existing Simulator interface in app/services/simulations/types.ts for the actual method signature; adapt accordingly. If a simulator does not currently accept a seed, add one as a non-breaking optional param in this task — it's needed for reproducible baselines.

  • Step 4: Commit the baseline fixtures
git add scripts/capture-baseline.ts test-fixtures/baselines/
git commit -m "chore: capture pre-rename baselines for qualifying-points data and simulator output"

Task 1: Rename schema exports and table names in database/schema.ts

Why first: Schema is the source of truth; the whole codebase compiles against its exports. Easiest to do in one sweep.

Files:

  • Modify: database/schema.ts

  • Step 1: Rename table definitions and string names

Apply these replacements in database/schema.ts. The safest order: string names first (they're unambiguous), then export names (which must be done with care to avoid breaking references in relations).

Table rename block 1 — participants (~ lines 376-390):

// OLD:
export const participants = pgTable("participants", {
  // ...
});
// NEW:
export const seasonParticipants = pgTable("season_participants", {
  // ...
});

Table rename block 2 — participant_expected_values (~ lines 642-672):

// OLD:
export const participantExpectedValues = pgTable("participant_expected_values", { ... });
// NEW:
export const seasonParticipantExpectedValues = pgTable("season_participant_expected_values", { ... });

Table rename block 3 — participant_qualifying_totals (~ lines 537-550):

// OLD:
export const participantQualifyingTotals = pgTable("participant_qualifying_totals", { ... });
// NEW:
export const seasonParticipantQualifyingTotals = pgTable("season_participant_qualifying_totals", { ... });

Table rename block 4 — participant_results (~ lines 414-428):

// OLD:
export const participantResults = pgTable("participant_results", { ... });
// NEW:
export const seasonParticipantResults = pgTable("season_participant_results", { ... });

Table rename block 5 — participant_surface_elos (~ lines 1219-1235):

// OLD:
export const participantSurfaceElos = pgTable("participant_surface_elos", { ... });
// NEW:
export const seasonParticipantSurfaceElos = pgTable("season_participant_surface_elos", { ... });

Column rename in eventResults table (~ lines 458-476):

// OLD:
participantId: uuid("participant_id").notNull().references(() => participants.id, { onDelete: "cascade" }),
// NEW:
seasonParticipantId: uuid("season_participant_id").notNull().references(() => seasonParticipants.id, { onDelete: "cascade" }),
  • Step 2: Rename relations exports and their field references

In database/schema.ts, update each *Relations export and every one()/many() call that referenced the renamed tables. Replacements needed:

  • participantsRelationsseasonParticipantsRelations
  • participantExpectedValuesRelationsseasonParticipantExpectedValuesRelations
  • participantQualifyingTotalsRelationsseasonParticipantQualifyingTotalsRelations
  • participantResultsRelationsseasonParticipantResultsRelations
  • participantSurfaceElosRelationsseasonParticipantSurfaceElosRelations

Within eventResultsRelations (~ lines 952-961):

// OLD:
participant: one(participants, {
  fields: [eventResults.participantId],
  references: [participants.id],
}),
// NEW:
seasonParticipant: one(seasonParticipants, {
  fields: [eventResults.seasonParticipantId],
  references: [seasonParticipants.id],
}),

Update every other relation one()/many() that referenced participants, participantResults, participantQualifyingTotals, participantSurfaceElos, or participantExpectedValues to use the new table export names.

  • Step 3: Verify schema parses

Run: npm run typecheck Expected: Will fail with many errors in model/route files that still import old names. That's fine for now — schema.ts itself compiling is the win. Confirm the errors are only in files outside database/schema.ts.

  • Step 4: Commit schema rename alone
git add database/schema.ts
git commit -m "refactor(schema): rename per-window tables to season_* prefix

Renames participants, participant_expected_values, participant_qualifying_totals,
participant_results, participant_surface_elos to season_* prefixed names.
Renames event_results.participant_id to season_participant_id.
Phase 1a of canonical tournament layer migration."

Task 2: Generate and hand-edit the Drizzle rename migration

Why: drizzle-kit generate treats renames as drop+create by default; this loses data. For safe renames, we generate with --custom and hand-edit, OR use drizzle-kit generate and answer its interactive rename prompts. Using custom is safer here because multiple renames in one migration can confuse the prompt sequence.

Files:

  • Create: drizzle/0087_rename_per_window_tables.sql (filename assigned by drizzle-kit; adjust to whatever it generates)

  • Create: drizzle/meta/0087_snapshot.json (generated)

  • Step 1: Generate a custom migration scaffold

Run: npm run db:generate -- --custom --name=rename_per_window_tables Expected: Creates a new empty .sql file in drizzle/ and a paired snapshot in drizzle/meta/.

  • Step 2: Write the rename SQL manually

Open the new drizzle/XXXX_rename_per_window_tables.sql and replace its contents with:

-- Rename per-window tables to season_* prefix
-- Part of canonical tournament layer migration (Phase 1a)

-- Drop the FK on event_results.participant_id before renaming target
ALTER TABLE "event_results" DROP CONSTRAINT IF EXISTS "event_results_participant_id_participants_id_fk";

-- Rename tables
ALTER TABLE "participants" RENAME TO "season_participants";
ALTER TABLE "participant_expected_values" RENAME TO "season_participant_expected_values";
ALTER TABLE "participant_qualifying_totals" RENAME TO "season_participant_qualifying_totals";
ALTER TABLE "participant_results" RENAME TO "season_participant_results";
ALTER TABLE "participant_surface_elos" RENAME TO "season_participant_surface_elos";

-- Rename column on event_results
ALTER TABLE "event_results" RENAME COLUMN "participant_id" TO "season_participant_id";

-- Re-add the FK against the renamed table
ALTER TABLE "event_results"
  ADD CONSTRAINT "event_results_season_participant_id_season_participants_id_fk"
  FOREIGN KEY ("season_participant_id") REFERENCES "season_participants"("id")
  ON DELETE cascade ON UPDATE no action;

-- Rename any unique indexes that embed the old table names
ALTER INDEX IF EXISTS "participant_surface_elos_unique" RENAME TO "season_participant_surface_elos_unique";
-- (Add further ALTER INDEX ... RENAME TO for any other indexes whose names embed old table names.
--  Run `\d <new_table_name>` in psql against a fresh migrate to confirm nothing lingers.)

Note: FK and index constraint names are auto-generated based on table names. After the ALTER TABLE ... RENAME TO, Postgres does NOT auto-rename the FK constraint on event_results pointing at the old name — we drop and re-create explicitly above. Run the migration against a scratch DB and inspect \d event_results and \d season_participants to catch any leftover naming.

  • Step 3: Verify snapshot is consistent

Run: npm run db:generate Expected: "No schema changes" — confirms the snapshot chain is valid. If drizzle-kit wants to generate another migration, stop and diagnose; the snapshot is out of sync with the custom SQL.

  • Step 4: Run migration on local DB

Run: npm run db:migrate Expected: New migration applied; existing data intact (verify row counts match pre-rename via psql ... -c "SELECT count(*) FROM season_participants;" = prior SELECT count(*) FROM participants;).

  • Step 5: Commit migration
git add drizzle/
git commit -m "migration: rename per-window tables to season_* prefix"

Task 3: Rename and update app/models/participant.tsapp/models/season-participant.ts

Files:

  • Rename: app/models/participant.tsapp/models/season-participant.ts

  • Modify: app/models/index.ts

  • Step 1: Git-rename the file

git mv app/models/participant.ts app/models/season-participant.ts
  • Step 2: Update the file's internal symbol references

Open app/models/season-participant.ts. Replace all references to renamed Drizzle exports:

  • participantsseasonParticipants
  • participantResultsseasonParticipantResults (if referenced)
  • participantQualifyingTotalsseasonParticipantQualifyingTotals (if referenced)

Update any exported function names that embed "participant" that logically should now be "seasonParticipant" — but only if they are window-scoped. E.g., a function findParticipantsForSeason likely stays named as-is since callers in routes don't distinguish; a function createParticipant(sportsSeasonId, data) likely stays named. Rename only if the name would be genuinely misleading. Default: keep exported function names to minimize route-layer churn.

  • Step 3: Update app/models/index.ts

Change:

export * from "./participant";

To:

export * from "./season-participant";
  • Step 4: Verify file compiles in isolation

Run: npx tsc --noEmit app/models/season-participant.ts (may still reference other unrenamed files — acceptable as long as the error is about imports, not the rename itself.)

  • Step 5: Commit
git add app/models/season-participant.ts app/models/index.ts
git commit -m "refactor: rename participant.ts model file to season-participant.ts"

Task 4: Update all model files referencing renamed schema exports

Files (update each to use new export names):

  • app/models/draft-pick.ts

  • app/models/draft-utils.ts

  • app/models/event-result.ts

  • app/models/group-stage-match.ts

  • app/models/participant-result.ts

  • app/models/qualifying-points.ts

  • app/models/scoring-calculator.ts

  • app/models/scoring-event.ts

  • app/models/sports-season.ts

  • app/models/surface-elo.ts

  • app/models/team-score-events.ts

  • Step 1: In each file, replace imports and references

Replacement map for imports and schema symbol references throughout the file body:

Old symbol New symbol
participants (Drizzle table) seasonParticipants
participantExpectedValues seasonParticipantExpectedValues
participantQualifyingTotals seasonParticipantQualifyingTotals
participantResults seasonParticipantResults
participantSurfaceElos seasonParticipantSurfaceElos
eventResults.participantId eventResults.seasonParticipantId
Relation access .participant on an eventResults row .seasonParticipant

Keep unchanged:

  • Local variable names like participant, participants, participantId — these are runtime identifiers used by business logic; renaming them would cascade into hundreds of lines for zero benefit. Only the schema symbol and Drizzle relation field change.
  • Function signatures and param names.
  • JSDoc.

Example draft-pick.ts update:

// BEFORE
import { participants, draftPicks } from "database/schema";
const [p] = await db.select().from(participants).where(eq(participants.id, participantId));

// AFTER
import { seasonParticipants, draftPicks } from "database/schema";
const [p] = await db.select().from(seasonParticipants).where(eq(seasonParticipants.id, participantId));
//                                   ^^^^^^^^^^^^^^^^^       ^^^^^^^^^^^^^^^^^ schema symbol only
// `participantId` param name stays.
  • Step 2: Run typecheck after each batch of 3 files

Run: npm run typecheck 2>&1 | head -40 Expected: Errors shrink with each file updated. Stop and diagnose if error count grows.

  • Step 3: Update model test files

Files:

  • app/models/__tests__/sports-season.clone.test.ts
  • app/models/__tests__/auto-pick.test.ts
  • app/models/__tests__/executeAutoPick.timer.test.ts

Same replacement rules. Run affected tests after update:

Run: npm run test:run -- app/models/__tests__ Expected: PASS.

  • Step 4: Commit
git add app/models/
git commit -m "refactor(models): update model layer to use renamed schema exports"

Task 5: Update all route files referencing renamed schema exports

Files:

  • app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts

  • app/routes/admin.sports-seasons.$id.events.$eventId.server.ts

  • app/routes/admin.sports-seasons.$id.events.$eventId.cs2-setup.tsx

  • app/routes/admin.sports-seasons.$id.expected-values.server.ts

  • app/routes/admin.sports-seasons.$id.surface-elo.tsx

  • app/routes/admin.sports-seasons.$id.simulate.tsx

  • app/routes/admin.sports-seasons.$id.standings.tsx

  • app/routes/admin.sports-seasons.$id.elo-ratings.tsx

  • app/routes/admin.sports-seasons.$id.tsx

  • app/routes/admin.sports-seasons.$id.golf-skills.tsx

  • app/routes/admin.sports-seasons.$id.regular-standings.tsx

  • app/routes/admin.sports-seasons.$id.futures-odds.tsx

  • app/routes/admin.data-sync.tsx

  • app/routes/api/draft.force-manual-pick.ts

  • app/routes/api/draft.make-pick.ts

  • app/routes/api/draft.replace-pick.ts

  • app/routes/api/seasons.$seasonId.draft.ts

  • app/routes/leagues/$leagueId.draft-board.$seasonId.tsx

  • app/routes/leagues/$leagueId.draft.$seasonId.tsx

  • app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts

  • Step 1: Apply replacement rules (same map as Task 4) to each file

Use find-and-replace per-file. After every ~5 files, run:

Run: npm run typecheck 2>&1 | wc -l (Track error count; should decrease monotonically.)

  • Step 2: Verify route tests still pass

Run: npm run test:run -- app/routes Expected: PASS (or same-as-before count; no new failures).

  • Step 3: Commit
git add app/routes/
git commit -m "refactor(routes): update route layer to use renamed schema exports"

Task 6: Update simulators and services

Files:

  • app/services/probability-updater.ts

  • app/services/standings-sync/index.ts

  • app/services/simulations/registry.ts

  • app/services/simulations/simulator-config.ts

  • app/services/simulations/types.ts

  • app/services/simulations/afl-simulator.ts

  • app/services/simulations/auto-racing-simulator.ts

  • app/services/simulations/bracket-simulator.ts

  • app/services/simulations/cs-major-simulator.ts

  • app/services/simulations/darts-simulator.ts

  • app/services/simulations/golf-simulator.ts

  • app/services/simulations/llws-simulator.ts

  • app/services/simulations/mlb-simulator.ts

  • app/services/simulations/nba-simulator.ts

  • app/services/simulations/ncaa-football-simulator.ts

  • app/services/simulations/ncaam-simulator.ts

  • app/services/simulations/ncaaw-simulator.ts

  • app/services/simulations/nfl-simulator.ts

  • app/services/simulations/nhl-simulator.ts

  • app/services/simulations/snooker-simulator.ts

  • app/services/simulations/tennis-simulator.ts

  • app/services/simulations/ucl-simulator.ts

  • app/services/simulations/wnba-simulator.ts

  • app/services/simulations/world-cup-simulator.ts

  • app/utils/sports-data-sync.server.ts

  • server/socket.ts

  • server/__tests__/timer-autodraft.test.ts

  • Step 1: Apply replacement rules

Same map as Task 4. Simulators are largely symmetric — one simulator's pattern will reveal all; batch 5 at a time and verify with typecheck.

  • Step 2: Run simulator unit tests

Run: npm run test:run -- app/services/simulations/__tests__ Expected: PASS. These tests consume Drizzle types extensively — a good regression canary.

  • Step 3: Run full typecheck

Run: npm run typecheck Expected: PASS (0 errors). Any remaining errors indicate a file missed in earlier tasks.

  • Step 4: Commit
git add app/services/ app/utils/ server/
git commit -m "refactor(services): update simulators and services for renamed schema"

Task 7: Full test suite + Cypress verification

Why: Mechanical renames are easy to get 99% right and 1% broken in a way typecheck doesn't catch — especially relation-field renames (.participant.seasonParticipant).

  • Step 1: Run full unit test suite

Run: npm run test:run Expected: PASS. If failures, inspect each: a common miss is a relation accessor like result.participant.name that needs to become result.seasonParticipant.name.

  • Step 2: Run a key E2E flow

Run: npx cypress run --spec "cypress/e2e/draft-room.cy.ts" (or equivalent flagship flow) Expected: PASS. E2E is the final guard — it exercises real relation loading.

  • Step 3: Manual smoke in dev

Run: npm run dev Manually:

  1. Load the admin dashboard for an in-flight golf sports season — verify participant list renders.
  2. Load a league's draft room — verify participant cards render.
  3. Open admin surface-elo page for the tennis sports season — verify Elo values load.

Fix any broken UI before committing. (The rename may have missed a relation accessor somewhere.)

  • Step 4: Final commit if any fixups were needed
git add -A
git commit -m "refactor: fix straggler relation accessors after rename"

Task 8: Verify baselines still match

Why: The entire point of Phase 1a is zero data change. Verify.

  • Step 1: Re-capture and diff

Run:

npx tsx scripts/capture-baseline.ts --out=/tmp/post-phase1a

(Update the script to accept --out or copy the baseline JSON files from test-fixtures/baselines/ and re-run capture manually into /tmp/post-phase1a/.)

Run:

diff test-fixtures/baselines/qp-totals-pre-phase1.json /tmp/post-phase1a/qp-totals-pre-phase1.json
diff test-fixtures/baselines/event-results-pre-phase1.json /tmp/post-phase1a/event-results-pre-phase1.json
diff test-fixtures/baselines/surface-elos-pre-phase1.json /tmp/post-phase1a/surface-elos-pre-phase1.json

Expected: All three diffs empty.

  • Step 2: Tag the branch
git tag phase-1a-complete

Task 9: Update documentation

Files:

  • Modify: docs/agents/database.md — if it references the old table names

  • Modify: docs/agents/domain-models.md — if it describes these tables

  • Step 1: Scan docs for old names

Run: grep -l "participant_qualifying_totals\|participant_expected_values\|participant_results\|participant_surface_elos\|event_results.participant_id" docs/ Expected: list of docs to update.

  • Step 2: Update each doc to use new names

For each file found, apply the rename map. Keep prose clear; where it helps readers, note "renamed from X in 2026-05".

  • Step 3: Commit
git add docs/
git commit -m "docs: update table names in agent guides"

Self-Review Checklist

Before marking this plan complete, verify:

  • git log --oneline phase-1a-complete shows ~8 commits corresponding to Tasks 09.
  • grep -rn "from \"./participant\"" app/ returns zero results.
  • grep -rn "participantExpectedValues\|participantQualifyingTotals\|participantSurfaceElos" app/ database/ server/ scripts/ returns zero results (excluding string literals in UI labels, which are fine).
  • grep -rn "participantResults\b" app/ database/ server/ returns zero results.
  • npm run test:all is green.
  • psql confirms the renamed tables exist and the old names do not:
    SELECT tablename FROM pg_tables WHERE tablename LIKE '%participant%' OR tablename LIKE '%season_participant%';
    
    Expected: season_participants, season_participant_expected_values, season_participant_qualifying_totals, season_participant_results, season_participant_surface_elos. None without the season_ prefix.

Rollback

If Phase 1a must be undone:

  1. git revert the commits from Tasks 19 (skip Task 0 baseline capture — keep fixtures).
  2. Create a reverse migration that renames all five tables back and the column back, re-creates the old FK constraint.
  3. Run npm run db:migrate.

Phase 1b must not begin until Phase 1a is shipped, green in production for at least 24 hours, and the tag phase-1a-complete is on main.