Spike: audit API response contract mismatches with westside-landing frontend #278
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api#278
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type
Spike
Lineage
Related to
forgejo_admin/basketball-api#276andforgejo_admin/westside-landing#203— parent phone bug revealed a systemic contract mismatch pattern between API response shapes and frontend field expectations.Repo
Multiple —
forgejo_admin/basketball-api+forgejo_admin/westside-landingQuestion
How many API response shape mismatches exist between basketball-api endpoints and the westside-landing frontend, and should we fix them at the API layer (flatten responses), the frontend layer (add mappings), or both?
What to Explore
Known mismatches (from #276/#203 investigation):
GET /players/{id}returns nestedparent: { name, phone, email }but frontend expects flatparent_phone,parent_name,parent_emailGET /admin/playerswas missingparent_phone(fixed in #276). QA notedIncompletePlayerItemandSubscriptionListItemhave the same omission.Investigation approach:
basketball-api/src/basketball_api/routes/— identify every model that returns parent, team, or coach datawestside-landingconsumes them (grepplayer.parent_,player.team_,player.coach_in Svelte templates)Specific models to check:
AdminPlayerItem(admin.py) — partially fixed, check remaining gapsIncompletePlayerItem(admin.py) — QA flagged missing parent_phoneSubscriptionListItem(admin.py) — QA flagged missing parent_phonePlayerProfileResponse(players.py) — nested parent objectSuccess Criteria
Time-box
1 session (agent-driven exploration). If more than 10 mismatches found, batch into logical groups for separate fix tickets.
Related
project-westside-basketballforgejo_admin/basketball-api#276— parent phone fix (API side)forgejo_admin/westside-landing#203— parent phone fix (frontend mapping)Scope Review: READY
Review note:
review-738-2026-04-03Spike scope is solid — all 8 template sections complete, all 4 named models verified in codebase, traceability triangle intact (story:WS-S12 confirmed on project page).
[SCOPE]Missing arch note: createarch-basketball-apiin pal-e-docs (documentation gap, not a blocker for spike execution).Spike Results: API Response Contract Mismatch Audit
Investigation covered all Pydantic response models in
basketball-api/src/basketball_api/routes/and all Svelte templates inwestside-app/src/routes/that consume player, parent, team, or coach data.1. Complete Inventory of Response Models with Parent/Team/Coach Data
PlayerProfileResponseplayers.pyGET /players/{id}parent: ParentInfo(nested: id, name, email, phone)team: TeamInfo(nested: id, name, coach_name)AdminPlayerItemadmin.pyGET /admin/playersparent_name,parent_email(flat, no parent_phone)team_name(flat)IncompletePlayerItemadmin.pyGET /admin/players/incompleteparent_email,parent_name(flat, no parent_phone)SubscriptionListItemsubscriptions.pyGET /subscriptions,GET /subscriptions/overviewparent_name,parent_email(flat, no parent_phone)AccountPlayerResponseaccount.pyGET /account/playersteam_name(flat)TeamPlayerItemadmin.pyGET /admin/teamsparent_name(flat, no parent_email, no parent_phone)team_id,team,team_ids,team_names(flat)DashboardTeamItemadmin.pyGET /admin/dashboardcoach_name(flat)TeamResponse/TeamDetailteams.pyGET /teams/{id},GET /teamscoach: CoachBrief(nested: id, name, email)CoachDashboardResponsecoaches_api.pyGET /coaches/meteams: [CoachTeamResponse]with nestedplayers: [CoachPlayerBrief]CoachPlayerBriefcoaches_api.py/coaches/mePlayerInforoster.pyGET /{slug}/rosterparent_name,parent_email,parent_phone(flat)PublicTeamResponsepublic.pyGET /public/teamscoach_name(flat)CoachProfileResponsecoaches_api.pyGET /coaches/{id}teams: [CoachTeamResponse]2. Mismatches Found
MISMATCH 1:
GET /players/{id}— Nested parent/team vs flat field expectations (VISIBLE BUG)API returns:
Frontend expects (players/[id]/+page.svelte):
Visible impact: Player profile page shows empty parent contact, empty team name, empty coach name. The "Team & Coach" card appears empty or falls through to "Not assigned to a team yet" even when the player has a team. Admin Actions card shows no parent phone or email. This is the #276/#203 root cause pattern.
Severity: HIGH — core player profile page is broken for all real API data.
MISMATCH 2:
GET /admin/players— Missingparent_phone(VISIBLE BUG)API returns:
AdminPlayerItemhasparent_nameandparent_emailbut noparent_phone.Frontend expects (admin/players/+page.svelte, line 297-299):
Visible impact: Parent phone never displays in the admin CRM player list. The subtitle row shows parent name but never phone. This was flagged in #276 QA but not fixed.
Severity: MEDIUM — data exists in DB (Parent.phone), just not in the response model.
MISMATCH 3:
GET /account/players— Missingcoach_nameandteam_id(VISIBLE BUG)API returns:
AccountPlayerResponsehasteam_namebut nocoach_name, noteam_id, noheight.Frontend expects (my-players/+page.svelte, lines 109-111):
Also references
player.heighton line 107 which is not in the response model.Visible impact: Parent dashboard player cards never show coach name. Height is always empty in the detail line.
Severity: MEDIUM — parent-facing dashboard is missing key info.
MISMATCH 4:
GET /coaches/me—CoachPlayerBriefmissingparent_phone,school,height(VISIBLE BUG)API returns:
CoachPlayerBriefhas onlyid, name, position, division, graduating_class.Frontend expects (coach/+page.svelte, lines 118-122):
Also line 118:
player.schoolandplayer.height.Visible impact: Coach dashboard roster cards never show parent phone (the phone icon row never renders), school, or height. Coaches need parent phone to contact families — this is a workflow blocker.
Severity: HIGH — coaches cannot contact parents from their dashboard.
MISMATCH 5:
GET /admin/players/incomplete—IncompletePlayerItemmissingparent_phone(LATENT)API returns:
parent_email,parent_name— noparent_phone.Frontend consumer: No dedicated frontend page currently renders this endpoint's data in a way that references
parent_phone, but the pattern is inconsistent withPlayerInfo(roster.py) which does include it. If an incomplete-players admin view is built, it will hit the same gap.Severity: LOW — latent, but should be fixed for consistency.
MISMATCH 6:
GET /subscriptions—SubscriptionListItemmissingparent_phone(LATENT)API returns:
parent_name,parent_email— noparent_phone.Frontend consumer: No dedicated subscription management page exists yet in westside-app.
Severity: LOW — latent, fix for consistency.
MISMATCH 7:
GET /admin/teams—TeamPlayerItemmissingparent_phone,parent_email(LATENT)API returns:
parent_nameonly. Noparent_phoneorparent_email.Frontend (admin/teams/+page.svelte): Currently does not display parent info in the draft board, so no visible bug. But the model is inconsistent — admin contexts generally need parent contact info.
Severity: LOW — no current visible impact.
MISMATCH 8:
GET /coaches/{id}— Frontend expectsteam_name,title,biothat API doesn't return (VISIBLE BUG)API returns:
CoachProfileResponsewithid, name, email, phone, role, onboarding_status, teams[].Frontend expects (coaches/[id]/+page.svelte):
Visible impact: Coach profile page shows empty role label (checks
coach.titlenotcoach.role), no team name in header, no bio, no photo. The teams section works because it iteratescoach.teams[].Severity: MEDIUM — coach profile page partially broken.
MISMATCH 9:
GET /teams/{id}—TeamDetailreturns nestedcoach: CoachBriefbut frontend expects flat + extra fields (PARTIAL MISMATCH)API returns:
Frontend expects (teams/[id]/+page.svelte):
team.coaches(plural array) — API returnsteam.coach(singular object)coach.role— API'sCoachBriefhas norolefieldcoach.phone— API'sCoachBriefhas nophonefieldplayer.height— API'sPlayerBriefhas noheightfieldplayer.jersey_number— API'sPlayerBriefhas nojersey_numberfieldVisible impact: Team detail page shows no coaches (iterates
team.coacheswhich is undefined), player cards missing height/jersey. The coaches card renders "0 coaches" and an empty list.Severity: HIGH — team page coaches section completely broken. Players section partially broken (missing height, jersey).
3. Recommendations
Strategy: Fix at API layer (flatten + enrich responses)
The frontend was designed mobile-first with flat field expectations. The API should match. Rationale:
player.parent_phone,player.team_name,player.coach_name— changing all templates is higher riskplayer.parentas an objectPer-mismatch recommendations:
parent_name,parent_phone,parent_email,team_name,team_id,coach_name. Keep nested objects for backwards compat.parent_phone: str | Noneto model + querycoach_name,team_id,heightfieldsparent_phone,school,heightfieldsparent_phone: str | Noneparent_phone: str | Noneparent_phone,parent_emailphoto_url, considerbio. Frontend: usecoach.rolenotcoach.title. Usecoach.teams[0]?.nameforteam_name.coaches: list[CoachBrief]instead ofcoach: CoachBrief. Addphone,roletoCoachBrief. Addheight,jersey_numbertoPlayerBrief.4. Suggested Follow-Up Ticket Groupings
Ticket Group A — Player Profile Flatten (HIGH, fixes #276 pattern completely)
PlayerProfileResponseto includeparent_name,parent_phone,parent_email,team_name,team_id,coach_nameplayers.pyonlyTicket Group B — Admin CRM Missing Fields (MEDIUM)
parent_phonetoAdminPlayerItem,IncompletePlayerItem,TeamPlayerItemparent_emailtoTeamPlayerItemadmin.pyonlyTicket Group C — Parent Dashboard Enrichment (MEDIUM)
coach_name,team_id,heighttoAccountPlayerResponseaccount.pyonlyTicket Group D — Coach Dashboard Parent Contact (HIGH)
parent_phone,school(current_school),heighttoCoachPlayerBriefcoaches_api.pyonlyTicket Group E — Team Detail Page Contract (HIGH)
TeamDetail.coachfrom singular tocoaches: list[]role,phonetoCoachBriefheight,jersey_numbertoPlayerBriefteams.py+teams/[id]/+page.svelteTicket Group F — Coach Profile Page Contract (MEDIUM)
coach.title->coach.roleteam_namefromcoach.teams[0]?.namephoto_url,biotoCoachProfileResponse(if DB supports it)coaches_api.py+coaches/[id]/+page.svelteTicket Group G — Subscription List Consistency (LOW)
parent_phonetoSubscriptionListItemsubscriptions.pyonlySummary