fix: include player_name in /teams/{id}/evolutions response (#115) #119
No reviewers
Labels
No Label
ai-changes-requested
ai-failed
ai-merged
ai-pr-opened
ai-reviewed
ai-reviewing
ai-reviewing
ai-working
bug
enhancement
evolution
performance
phase-0
phase-1a
phase-1b
phase-1c
phase-1d
security
tech-debt
todo
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/paper-dynasty-database#119
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "ai/paper-dynasty-database#115"
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?
Closes #115
Summary
GET /api/v2/teams/{team_id}/evolutionswas missingplayer_namein each item, causing the Discord bot's/evo statusto fall back to "Player #795" instead of the actual name.Changes
app/routers_v2/teams.py—list_team_evolutions:Playerto the.select()and joined it via.switch(EvolutionCardState).join(Player)— eager-loads player data in a single query (no N+1).item["player_name"] = state.player.p_nameto each serialized item.Response shape (after)
Tests
No test suite in this repo. Fix verified by reading back the modified function.
AI Code Review
Files Reviewed
app/routers_v2/teams.py(modified)Findings
Correctness
EvolutionCardStatehasplayer = ForeignKeyField(Player)indb_engine.py, so.switch(EvolutionCardState).join(Player)is the right Peewee idiom after the existing.join(EvolutionTrack). Both FK targets are eager-loaded in a single SQL query — no N+1.state.player.p_nameis the correct attribute name per thePlayermodel definition (p_name = CharField()). The CLAUDE.md critical note ("API parameter for player name isname, NOTp_name") refers to the HTTP request parameter name for player lookup, not the ORM column — this internal use ofp_nameis appropriate.state.player_id(the pre-existing FK integer accessor) is unaffected; Peewee keeps both the raw integer accessor and the relation accessor separate.Edge Cases
p_name: thePlayermodel defines it as a non-nullableCharField(). If somehow a player row is missing (referential integrity broken), Peewee would raise anAttributeErroratstate.player.p_name, which would surface as a 500. This is acceptable for what would be a corrupt DB state, and mirrors how the rest of the codebase handles such cases.Security
Style & Conventions
EvolutionCardStateandEvolutionTrack.next-release, matching the release workflow described in CLAUDE.md.Suggestions
list_team_evolutions(line 1555-1558) still says "Each item in 'items' has the same shape as GET /evolution/cards/{card_id}." This is now slightly inaccurate since the/teams/{id}/evolutionsresponse addsplayer_namewhich the single-card endpoint does not return. Worth updating in a follow-up, but not a blocker.GET /evolution/cards/{card_id}(inevolution.py) calls the same_build_card_state_responsewithout joiningPlayer. If that endpoint ever needsplayer_nametoo, it will need the same treatment. Not a problem introduced by this PR.Verdict: APPROVED
The change is minimal, correct, and solves the stated problem cleanly. The JOIN is a proper eager load,
player_nameis correctly sourced fromp_name, and no existing fields or behavior are disrupted.Automated review by Claude PR Reviewer
AI Code Review
Files Reviewed
app/routers_v2/teams.py(modified)Findings
Correctness
Playeris added to.select()and eagerly loaded via.switch(EvolutionCardState).join(Player). This is the correct multi-table join pattern in Peewee — after joiningEvolutionTrackfromEvolutionCardState,.switch(EvolutionCardState)resets the join pivot back to the base model before chaining.join(Player). The result is a single SQL query with two JOINs rather than N+1 player lookups.state.player.p_nameis valid.EvolutionCardState.playeris declared asForeignKeyField(Player)(db_engine.py line 1228).Player.p_nameis aCharField(db_engine.py line 281). WithPlayerin.select(), Peewee populates the relation without an additional query.item["player_name"] = ...is safe._build_card_state_responsereturns a plaindict, so mutation after the call is fine. The keyplayer_namedoes not conflict with any existing key in that dict (player_id,team_id,current_tier,current_value,fully_evolved,last_evaluated_at,track,next_threshold).team_buy_players) is cosmetic and harmless.Security
[REDACTED]token log pattern is preserved.player_nameis sourced from a DB-backedPlayerrow — no user-supplied data flows into the response field. No credential exposure.Style & Conventions
Player.Playeris already imported at module scope on line 18 (from ..db_engine import (..., Player, ...)). Adding it again inside the lazy import block (from ..db_engine import EvolutionCardState, EvolutionTrack, Player) is harmless — Python deduplicates module-level names — but it is slightly inconsistent. The lazy import block exists specifically forEvolutionCardStateandEvolutionTrack(which are not in the module-level import) to avoid circular-import issues.Playerdoes not need to be there.Suggestions
list_team_evolutionsstill says items have "the same shape as GET /evolution/cards/{card_id}" but the response now includes an additionalplayer_namefield that the single-card endpoint does not return. Updating the docstring to documentplayer_namewould prevent consumer confusion. Not a blocker.Verdict: APPROVED
Correct, focused fix. The multi-table Peewee join is sound,
p_nameis a valid field, and the response mutation is safe. The redundant lazy import ofPlayerand the stale docstring are minor nits that do not affect correctness or security.Automated review by Claude PR Reviewer