Populate team_name in PracticeScheduleResponse from Team join #341

Closed
opened 2026-04-05 17:37:26 +00:00 by forgejo_admin · 4 comments
Contributor

Type

Bug

User Story

story:WS-S13 As an admin, I want to view and manage the program schedule so that I have a single source of truth for all planned activities.

What Broke

GET /public/schedule returns practices without team_name. The PracticeScheduleResponse Pydantic model in schedule.py has no team_name field. The frontend relies on team_name for grouping practices by team — without it, practices display as individual cards instead of grouped by team.

Repro Steps

  1. curl /public/schedule | jq '.practices[] | {team_id, label}'
  2. All 17 practices have valid team_id values but no team_name in the response
  3. Frontend falls back to grouping by label producing 4 cards instead of 2

Expected Behavior

team_name field added to PracticeScheduleResponse and populated from Team.name via the team_id FK join.

Environment

basketball-api, /public/schedule and /admin/schedule endpoints

Lineage

  • Story: story:WS-S13
  • Arch: arch:basketball-api
  • Blocked by: Nothing
  • Blocks: westside-landing#209 validation (public schedule grouping depends on team_name)

Repo

forgejo_admin/basketball-api

Context

Discovered by Playwright validation agent testing PR #216. The PracticeSchedule model has a team relationship but _practice_to_response() doesn't use it, and PracticeScheduleResponse has no team_name field.

File Targets

  • src/basketball_api/routes/schedule.py lines 39-50: Add team_name: str | None = None to PracticeScheduleResponse
  • src/basketball_api/routes/schedule.py _practice_to_response(): Populate team_name from practice.team.name if practice.team else None
  • src/basketball_api/routes/public.py query: Add joinedload(PracticeSchedule.team) to eager load the team relationship

Acceptance Criteria

  1. PracticeScheduleResponse includes team_name: str | None field
  2. GET /public/schedule returns team_name populated from Team.name for every practice with a team_id
  3. Practices without a team_id return team_name: null
  4. No N+1 queries — team relationship eagerly loaded via joinedload

Test Expectations

  • Existing schedule tests pass
  • New test: practice with team_id returns team_name matching Team.name
  • curl /public/schedule | jq '.practices[0].team_name' returns a non-null string

Constraints

  • Schema change required: add team_name to PracticeScheduleResponse
  • Eager load team relationship to avoid N+1
  • Admin schedule endpoint (/admin/schedule) uses the same _practice_to_response() helper — both benefit from this fix

Checklist

  • Add team_name: str | None = None to PracticeScheduleResponse in schedule.py
  • Populate team_name in _practice_to_response() from practice.team.name
  • Add joinedload(PracticeSchedule.team) to public schedule query in public.py
  • Add joinedload(PracticeSchedule.team) to admin schedule query in schedule.py if not present
  • Run existing tests
  • Add test for team_name population
  • westside-landing#209 / PR #216 (frontend fix depends on non-null team_name)
  • First production bug caught by Playwright validation agent
### Type Bug ### User Story `story:WS-S13` As an admin, I want to view and manage the program schedule so that I have a single source of truth for all planned activities. ### What Broke `GET /public/schedule` returns practices without `team_name`. The `PracticeScheduleResponse` Pydantic model in `schedule.py` has no `team_name` field. The frontend relies on `team_name` for grouping practices by team — without it, practices display as individual cards instead of grouped by team. ### Repro Steps 1. `curl /public/schedule | jq '.practices[] | {team_id, label}'` 2. All 17 practices have valid `team_id` values but no `team_name` in the response 3. Frontend falls back to grouping by `label` producing 4 cards instead of 2 ### Expected Behavior `team_name` field added to `PracticeScheduleResponse` and populated from `Team.name` via the `team_id` FK join. ### Environment basketball-api, `/public/schedule` and `/admin/schedule` endpoints ### Lineage - **Story:** `story:WS-S13` - **Arch:** `arch:basketball-api` - **Blocked by:** Nothing - **Blocks:** westside-landing#209 validation (public schedule grouping depends on team_name) ### Repo `forgejo_admin/basketball-api` ### Context Discovered by Playwright validation agent testing PR #216. The `PracticeSchedule` model has a `team` relationship but `_practice_to_response()` doesn't use it, and `PracticeScheduleResponse` has no `team_name` field. ### File Targets - `src/basketball_api/routes/schedule.py` lines 39-50: Add `team_name: str | None = None` to `PracticeScheduleResponse` - `src/basketball_api/routes/schedule.py` `_practice_to_response()`: Populate `team_name` from `practice.team.name if practice.team else None` - `src/basketball_api/routes/public.py` query: Add `joinedload(PracticeSchedule.team)` to eager load the team relationship ### Acceptance Criteria 1. `PracticeScheduleResponse` includes `team_name: str | None` field 2. `GET /public/schedule` returns `team_name` populated from Team.name for every practice with a team_id 3. Practices without a team_id return `team_name: null` 4. No N+1 queries — team relationship eagerly loaded via joinedload ### Test Expectations - Existing schedule tests pass - New test: practice with team_id returns team_name matching Team.name - `curl /public/schedule | jq '.practices[0].team_name'` returns a non-null string ### Constraints - Schema change required: add `team_name` to `PracticeScheduleResponse` - Eager load team relationship to avoid N+1 - Admin schedule endpoint (`/admin/schedule`) uses the same `_practice_to_response()` helper — both benefit from this fix ### Checklist - [ ] Add `team_name: str | None = None` to `PracticeScheduleResponse` in `schedule.py` - [ ] Populate `team_name` in `_practice_to_response()` from `practice.team.name` - [ ] Add `joinedload(PracticeSchedule.team)` to public schedule query in `public.py` - [ ] Add `joinedload(PracticeSchedule.team)` to admin schedule query in `schedule.py` if not present - [ ] Run existing tests - [ ] Add test for team_name population ### Related - westside-landing#209 / PR #216 (frontend fix depends on non-null team_name) - First production bug caught by Playwright validation agent
Author
Contributor

Scope Review: NEEDS_REFINEMENT

Review note: review-827-2026-04-04

PracticeScheduleResponse schema does not have a team_name field — the Constraints section incorrectly says "just populate the existing field." The schema must be changed to add team_name: str | None = None.

  • [BODY] Fix Constraints: schema change IS required (add team_name field to PracticeScheduleResponse)
  • [BODY] Add explicit file target for schema change at schedule.py line 49
  • [BODY] Add checklist item: "Add team_name: str | None = None to PracticeScheduleResponse"
  • [SCOPE] Create architecture note arch-basketball-api (missing from pal-e-docs)
## Scope Review: NEEDS_REFINEMENT Review note: `review-827-2026-04-04` `PracticeScheduleResponse` schema does not have a `team_name` field — the Constraints section incorrectly says "just populate the existing field." The schema must be changed to add `team_name: str | None = None`. - **[BODY]** Fix Constraints: schema change IS required (add `team_name` field to `PracticeScheduleResponse`) - **[BODY]** Add explicit file target for schema change at `schedule.py` line 49 - **[BODY]** Add checklist item: "Add `team_name: str | None = None` to `PracticeScheduleResponse`" - **[SCOPE]** Create architecture note `arch-basketball-api` (missing from pal-e-docs)
Author
Contributor

Scope refinement (2026-04-05)

Addressed review-827 findings:

  1. Schema change acknowledged: PracticeScheduleResponse needs team_name: str | None = None added — corrected in Constraints and Checklist
  2. File targets explicit: schedule.py lines 39-50 (schema) + _practice_to_response() (population) + public.py (joinedload)
  3. Blast radius noted: EventResponse has same pattern (no team_name) — separate ticket if needed
### Scope refinement (2026-04-05) Addressed review-827 findings: 1. **Schema change acknowledged:** `PracticeScheduleResponse` needs `team_name: str | None = None` added — corrected in Constraints and Checklist 2. **File targets explicit:** `schedule.py` lines 39-50 (schema) + `_practice_to_response()` (population) + `public.py` (joinedload) 3. **Blast radius noted:** `EventResponse` has same pattern (no team_name) — separate ticket if needed
Author
Contributor

Scope Review: READY

Review note: review-827-2026-04-04
Re-review passed. All three refinement issues from initial review are resolved: schema change acknowledged, file targets have explicit line numbers, checklist includes schema step. Ticket is actionable for agent dispatch.

  • [SCOPE] arch-basketball-api note still missing in pal-e-docs (non-blocking, pre-existing gap)
## Scope Review: READY Review note: `review-827-2026-04-04` Re-review passed. All three refinement issues from initial review are resolved: schema change acknowledged, file targets have explicit line numbers, checklist includes schema step. Ticket is actionable for agent dispatch. - [SCOPE] arch-basketball-api note still missing in pal-e-docs (non-blocking, pre-existing gap)
Author
Contributor

Validation: PASS

Tiers executed: Tier 1 (local), Tier 3 (production)
Validation note: validation-341-2026-04-04

7 checks: 7 PASS, 0 FAIL

# Criterion Result
1 `team_name: str None` field in PracticeScheduleResponse
2 /public/schedule returns team_name from Team.name (17/17 practices) PASS
3 Null-safe: ps.team.name if ps.team else None PASS
4 No N+1: joinedload(PracticeSchedule.team) in public.py + schedule.py PASS
5 Schedule tests: 39 passed in 3.95s PASS
6 Woodpecker pipeline #349: success PASS
7 Pod running, 0 restarts, image bbe2de0 matches merge commit PASS

Regression check: /public/teams 200, /public/schedule 200, /docs 200. 684 non-email tests pass.

Discovered (pre-existing): 6 email test files have broken imports from prior refactors — not caused by this PR.

## Validation: PASS Tiers executed: Tier 1 (local), Tier 3 (production) Validation note: `validation-341-2026-04-04` **7 checks: 7 PASS, 0 FAIL** | # | Criterion | Result | |---|-----------|--------| | 1 | `team_name: str | None` field in PracticeScheduleResponse | PASS | | 2 | `/public/schedule` returns team_name from Team.name (17/17 practices) | PASS | | 3 | Null-safe: `ps.team.name if ps.team else None` | PASS | | 4 | No N+1: `joinedload(PracticeSchedule.team)` in public.py + schedule.py | PASS | | 5 | Schedule tests: 39 passed in 3.95s | PASS | | 6 | Woodpecker pipeline #349: success | PASS | | 7 | Pod running, 0 restarts, image `bbe2de0` matches merge commit | PASS | **Regression check:** `/public/teams` 200, `/public/schedule` 200, `/docs` 200. 684 non-email tests pass. **Discovered (pre-existing):** 6 email test files have broken imports from prior refactors — not caused by this PR.
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
ldraney/basketball-api#341
No description provided.