From d6c555b06e723a3ddaadd74cbdb47cca8d7c12bb Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Fri, 1 May 2026 23:14:15 +0000 Subject: [PATCH] docs(agents): describe canonical/per-window split Update database.md with the canonical/per-window model explanation, the syncTournamentResults fan-out contract, the check constraint, and the tennis-Men/Women quirk. Update domain-models.md to reflect the renamed per-window entities and the new canonical layer. Also update the drizzle-kit gotchas section with lessons from the migration: do not use --custom for renames; FK constraint naming can mismatch Drizzle convention. Phase 4 Task 6. Co-Authored-By: Claude Opus 4.7 --- docs/agents/database.md | 25 ++++++++++++++++++++++++- docs/agents/domain-models.md | 17 ++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/docs/agents/database.md b/docs/agents/database.md index 6a6d64f..9d9a53e 100644 --- a/docs/agents/database.md +++ b/docs/agents/database.md @@ -13,10 +13,33 @@ **NEVER** manually create migration SQL files or edit `drizzle/meta/_journal.json`. The journal and snapshots must stay in sync — drizzle-kit manages both. Missing snapshots cause `drizzle-kit generate` to fail with "data is malformed". -- Always use `drizzle-kit generate` (or `drizzle-kit generate --custom` for interactive rename prompts) +- Always use `drizzle-kit generate` and answer any interactive rename prompts (`"Is X renamed from Y?"`) — do NOT use `--custom` for renames, it copies the previous snapshot instead of diffing, which causes the next run to generate a duplicate migration - Snapshot JSON must only contain PostgreSQL-valid fields — do not add `"autoincrement"` (Postgres uses sequences, and invalid fields cause Zod validation failures) - After generating, run `drizzle-kit generate` again to confirm "No schema changes" — validates the snapshot chain - For destructive migrations (drop/rename), use `IF EXISTS` / `IF NOT EXISTS` guards +- Some existing FK constraints use Postgres auto-generated `*_fkey` names instead of the Drizzle `*_fk` convention. If drizzle-kit generates `DROP CONSTRAINT "___id_fk"` and it fails with "does not exist", add `IF EXISTS` (via `sed -i` on the SQL file) — a mismatched name is the likely cause + +## Canonical vs. Per-Window Tables (Qualifying-Points Sports) + +Golf, tennis, and CS2 use a split model to support rolling windows that overlap (e.g., a June and September tennis window both containing Wimbledon 2026 + US Open 2026). + +- **Canonical** (real-world identity, shared across windows): + - `tournaments` — real-world tournaments (Wimbledon 2026 is one row per sport) + - `participants` — real-world players or teams (Djokovic, NAVI) + - `tournament_results` — final placements, entered once + - `participant_surface_elos` — tennis surface-specific Elo +- **Per-window** (scoped to a `sports_seasons` row): + - `season_participants` — draftable roster for this window; links to canonical via `participant_id` + - `season_participant_expected_values` — EV under this window's 4-event set + - `season_participant_qualifying_totals` — running QP sum for this window + - `season_participant_results` — final placement for fantasy scoring + - `event_results` — per-scoring-event placements (derived from `tournament_results` via `syncTournamentResults`) + +Enter tournament results at `/admin/tournaments/:id`. The `syncTournamentResults` service (`app/services/sync-tournament-results.ts`) fans out to every window linked via `scoring_events.tournament_id` — each window in its own transaction, so partial failures isolate. A CHECK constraint on `scoring_events` enforces `NOT is_qualifying_event OR tournament_id IS NOT NULL`. + +When admin creates a new qualifying event via the events admin page, `createScoringEvent` auto-provisions the matching canonical tournament. When admin creates a new season_participant, `createParticipant` auto-links to a canonical participant by `(sport_id, name)`. + +Tennis note: Brackt models tennis as two sports (`Tennis - Men`, `Tennis - Women`). Each real-world tournament becomes two canonical rows. Entering women's Wimbledon does not fan out to men's windows — by design. ## Scoring Event Dates (Non-Obvious Gotcha) diff --git a/docs/agents/domain-models.md b/docs/agents/domain-models.md index 222612e..31d9c31 100644 --- a/docs/agents/domain-models.md +++ b/docs/agents/domain-models.md @@ -27,8 +27,19 @@ Season status drives UI visibility: draft order shown in `pre_draft`, draft cont | Model | Description | |---|---| | **Sport** | Base sport definition (e.g. NFL, NBA); type is `team` or `individual` | -| **Sports Season** | A specific season of a sport (e.g. "2024 NFL Season") | -| **Participant** | An athlete or team that can be drafted | +| **Sports Season** | A specific rolling window of a sport (e.g. "Tennis June 2026") | +| **Season Participant** | A draftable roster entry for one sports season — links to a canonical Participant | | **Season Template** | Reusable configuration for creating league seasons | | **Season Sport** | Junction: links a fantasy season to the sports it covers | -| **Participant Result** | Final standings/scores for participants at season end | +| **Season Participant Result** | Final standings/scores for season participants | + +## Canonical Layer (Qualifying-Points Sports) + +Golf, tennis, and CS2 rolling windows share real-world data via a canonical layer. See `docs/agents/database.md` for the schema split. + +| Model | Description | +|---|---| +| **Participant (canonical)** | Real-world player or team (Djokovic, NAVI) — shared across windows | +| **Tournament** | Real-world tournament (Wimbledon 2026) — shared across windows | +| **Tournament Result** | Final placement in a real-world tournament — entered once at `/admin/tournaments/:id`, fanned out to every linked window via `syncTournamentResults` | +| **Participant Surface Elo** | Canonical surface-specific Elo (tennis) — read by the tennis simulator |