Schedule data model + migration + seed (practices, events) #232

Closed
opened 2026-03-28 22:37:53 +00:00 by forgejo_admin · 1 comment

Type

Feature

Lineage

Child of forgejo_admin/basketball-api#230 (decomposed parent).

Repo

forgejo_admin/basketball-api

User Story

story:WS-S13 As an admin, I want schedule data stored in the database so that practices and events are managed programmatically instead of hardcoded HTML.

Context

No schedule entities exist in the domain model. This ticket adds the data layer only — models, migration, and seed data. API endpoints are a separate ticket (#233).

Key design decisions (from review of #230):

  • Reuse existing Division(boys/girls) enum — Kings=boys, Queens=girls. No new enum needed.
  • Two tables: practice_schedules (recurring weekly) and events (date-specific). Different data shapes warrant separate tables.
  • String(10) for time fields ("14:00" format) — avoids Time type serialization complexity.
  • Self-referencing FK on events.parent_event_id for tournament→game nesting.

File Targets

Files to create or modify:

  • src/basketball_api/models.py — add EventType enum, PracticeSchedule model, Event model. Add relationships to Tenant model.
  • alembic/versions/xxx_add_schedule_tables.py — migration creating both tables
  • scripts/seed_schedule.py — populate current hardcoded schedule data

Pattern reference (do NOT modify):

  • src/basketball_api/models.py lines 1-50 for import/enum style
  • src/basketball_api/models.py lines 146-160 for Tenant relationship pattern
  • src/basketball_api/models.py lines 327-346 for Team model (FK target)

Files NOT to touch:

  • src/basketball_api/routes/* — endpoints are a separate ticket
  • src/basketball_api/schemas.py — does not exist, not part of this ticket

Acceptance Criteria

  • EventType enum added with values: tournament, game, camp, tryout, other
  • PracticeSchedule model created with columns: id, tenant_id (FK), team_id (FK nullable), division (Enum(Division) nullable), label (String 200), day_of_week (Integer 0-6), start_time (String 10), end_time (String 10), location (String 300), notes (Text nullable), is_active (Boolean default true), created_at
  • Event model created with columns: id, tenant_id (FK), title (String 300), event_type (Enum(EventType)), division (Enum(Division) nullable), start_date (Date), end_date (Date nullable), location (String 300 nullable), team_id (FK nullable), parent_event_id (FK self-ref nullable), opponent (String 200 nullable), notes (Text nullable), created_at
  • Alembic migration runs cleanly (upgrade and downgrade)
  • Seed script populates: 8 Kings practice slots, 3 Kings tournaments, 5 Queens tournaments
  • Existing tests still pass

Test Expectations

  • Migration test: upgrade creates tables, downgrade drops them
  • Model test: can create PracticeSchedule and Event instances with required fields
  • Seed test: script is idempotent (running twice doesn't duplicate)
  • Run command: pytest tests/ -v

Constraints

  • Follow Mapped[type] = mapped_column() pattern used throughout models.py
  • Use server_default=func.now() for created_at (not Python default)
  • Use server_default=text("true") for is_active (matches is_public pattern on Player)
  • Tenant FK with index=True (matches every other model)
  • Add practice_schedules and events relationships to Tenant model

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • Migration tested (up and down)
  • Parent: forgejo_admin/basketball-api#230
  • Sibling: forgejo_admin/basketball-api#233 (API endpoints)
  • project-westside-basketball
  • arch-domain-westside-basketball — update after merge
### Type Feature ### Lineage Child of `forgejo_admin/basketball-api#230` (decomposed parent). ### Repo `forgejo_admin/basketball-api` ### User Story `story:WS-S13` As an admin, I want schedule data stored in the database so that practices and events are managed programmatically instead of hardcoded HTML. ### Context No schedule entities exist in the domain model. This ticket adds the data layer only — models, migration, and seed data. API endpoints are a separate ticket (#233). **Key design decisions (from review of #230):** - Reuse existing `Division(boys/girls)` enum — Kings=boys, Queens=girls. No new enum needed. - Two tables: `practice_schedules` (recurring weekly) and `events` (date-specific). Different data shapes warrant separate tables. - `String(10)` for time fields ("14:00" format) — avoids Time type serialization complexity. - Self-referencing FK on `events.parent_event_id` for tournament→game nesting. ### File Targets Files to create or modify: - `src/basketball_api/models.py` — add `EventType` enum, `PracticeSchedule` model, `Event` model. Add relationships to `Tenant` model. - `alembic/versions/xxx_add_schedule_tables.py` — migration creating both tables - `scripts/seed_schedule.py` — populate current hardcoded schedule data Pattern reference (do NOT modify): - `src/basketball_api/models.py` lines 1-50 for import/enum style - `src/basketball_api/models.py` lines 146-160 for Tenant relationship pattern - `src/basketball_api/models.py` lines 327-346 for Team model (FK target) Files NOT to touch: - `src/basketball_api/routes/*` — endpoints are a separate ticket - `src/basketball_api/schemas.py` — does not exist, not part of this ticket ### Acceptance Criteria - [ ] `EventType` enum added with values: tournament, game, camp, tryout, other - [ ] `PracticeSchedule` model created with columns: id, tenant_id (FK), team_id (FK nullable), division (Enum(Division) nullable), label (String 200), day_of_week (Integer 0-6), start_time (String 10), end_time (String 10), location (String 300), notes (Text nullable), is_active (Boolean default true), created_at - [ ] `Event` model created with columns: id, tenant_id (FK), title (String 300), event_type (Enum(EventType)), division (Enum(Division) nullable), start_date (Date), end_date (Date nullable), location (String 300 nullable), team_id (FK nullable), parent_event_id (FK self-ref nullable), opponent (String 200 nullable), notes (Text nullable), created_at - [ ] Alembic migration runs cleanly (upgrade and downgrade) - [ ] Seed script populates: 8 Kings practice slots, 3 Kings tournaments, 5 Queens tournaments - [ ] Existing tests still pass ### Test Expectations - [ ] Migration test: upgrade creates tables, downgrade drops them - [ ] Model test: can create PracticeSchedule and Event instances with required fields - [ ] Seed test: script is idempotent (running twice doesn't duplicate) - Run command: `pytest tests/ -v` ### Constraints - Follow `Mapped[type] = mapped_column()` pattern used throughout models.py - Use `server_default=func.now()` for created_at (not Python default) - Use `server_default=text("true")` for is_active (matches is_public pattern on Player) - Tenant FK with `index=True` (matches every other model) - Add `practice_schedules` and `events` relationships to Tenant model ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes - [ ] Migration tested (up and down) ### Related - Parent: `forgejo_admin/basketball-api#230` - Sibling: `forgejo_admin/basketball-api#233` (API endpoints) - `project-westside-basketball` - `arch-domain-westside-basketball` — update after merge
Author
Owner

Scope Review: READY

Review note: review-629-2026-03-28

All template sections present, traceability triangle complete (story:WS-S13, arch:basketball-api, Forgejo #232). All file targets verified against codebase — line references accurate, patterns confirmed, schemas.py absence confirmed.

Minor nit (non-blocking): Constraints section says server_default=text("true") for is_active "matches is_public pattern on Player" — but Player.is_public defaults to text("false"). The actual match is Product.active (line 382). Consider updating the reference text.

## Scope Review: READY Review note: `review-629-2026-03-28` All template sections present, traceability triangle complete (story:WS-S13, arch:basketball-api, Forgejo #232). All file targets verified against codebase — line references accurate, patterns confirmed, schemas.py absence confirmed. **Minor nit (non-blocking):** Constraints section says `server_default=text("true")` for is_active "matches is_public pattern on Player" — but `Player.is_public` defaults to `text("false")`. The actual match is `Product.active` (line 382). Consider updating the reference text.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/basketball-api#232
No description provided.