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

638 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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**
```typescript
// 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:
```typescript
// 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**
```bash
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):
```typescript
// OLD:
export const participants = pgTable("participants", {
// ...
});
// NEW:
export const seasonParticipants = pgTable("season_participants", {
// ...
});
```
Table rename block 2 — `participant_expected_values` (~ lines 642-672):
```typescript
// 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):
```typescript
// 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):
```typescript
// 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):
```typescript
// 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):
```typescript
// 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:
- `participantsRelations``seasonParticipantsRelations`
- `participantExpectedValuesRelations``seasonParticipantExpectedValuesRelations`
- `participantQualifyingTotalsRelations``seasonParticipantQualifyingTotalsRelations`
- `participantResultsRelations``seasonParticipantResultsRelations`
- `participantSurfaceElosRelations``seasonParticipantSurfaceElosRelations`
Within `eventResultsRelations` (~ lines 952-961):
```typescript
// 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**
```bash
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:
```sql
-- 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**
```bash
git add drizzle/
git commit -m "migration: rename per-window tables to season_* prefix"
```
---
### Task 3: Rename and update `app/models/participant.ts` → `app/models/season-participant.ts`
**Files:**
- Rename: `app/models/participant.ts``app/models/season-participant.ts`
- Modify: `app/models/index.ts`
- [ ] **Step 1: Git-rename the file**
```bash
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:
- `participants``seasonParticipants`
- `participantResults``seasonParticipantResults` (if referenced)
- `participantQualifyingTotals``seasonParticipantQualifyingTotals` (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:
```typescript
export * from "./participant";
```
To:
```typescript
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**
```bash
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:**
```typescript
// 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**
```bash
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**
```bash
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**
```bash
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**
```bash
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:
```bash
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:
```bash
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**
```bash
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**
```bash
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`.