feat: add incomplete_profiles query to email_queries.py #309
No reviewers
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
ldraney/basketball-api!309
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "308-incomplete-profiles-query"
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?
Summary
Adds
query_incomplete_profilesto the email query registry, enabling blast emails to parents whose players have incomplete profiles (missing photo_url, height, position, or date_of_birth).Changes
src/basketball_api/services/email_queries.py-- Addedquery_incomplete_profilesfunction following thequery_unsigned_contractspattern. Returns per-player rows withto,parent_id,parent_name,player_name,team_name,missing_fields, andprofile_url. Registered as"incomplete_profiles"inQUERY_REGISTRY. Importedsettingsfrombasketball_api.configforfrontend_url.tests/test_email_blast.py-- Added 6 tests inTestQueryIncompleteProfiles: returns players with missing fields, includes profile_url with token, excludes complete profiles, handles partial missing fields, test_email filtering, and registry registration.Test Plan
pytest tests/test_email_blast.py -vReview Checklist
Related Notes
None.
Related
Closes #308
QA Review
Pattern Compliance
query_incomplete_profilesfollowsquery_unsigned_contractsexactly: same function signature (db,tenant,test_email), same join/options/filter chain, same result dict structure, sametest_emailfiltering logic.Fields Checked
photo_url,height,position,date_of_birth-- matches issue spec. Jersey number/size correctly excluded (admin-assigned).Profile URL
Uses
settings.frontend_urlfrombasketball_api.configwith/register?token={parent.registration_token}-- matches issue spec.Registry
Registered as
"incomplete_profiles"inQUERY_REGISTRY-- correct.Falsy Check
not getattr(player, f)treatsNoneas missing. These are nullable columns that default toNonewhen unset, so this is correct. Empty string would also be treated as missing, which is reasonable behavior.Join Semantics
Inner join on
player_teamsmeans unrostered players are excluded -- correct, only rostered players should receive profile completion emails.Tests
6 tests covering: happy path with all fields missing, profile_url with token, complete profile exclusion, partial missing fields, test_email filtering, and registry registration. Good coverage.
Nits
None.
VERDICT: APPROVED
PR #309 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy
The new
query_incomplete_profilesfunction follows the establishedquery_unsigned_contractspattern closely. The query structure is sound: joinsPlayertoplayer_teams, eager-loadsparentandteamsviajoinedload, filters by tenant, and post-processes in Python to identify missing fields. The_INCOMPLETE_PROFILE_FIELDStuple is a clean constant. Thetest_emailkeyword-only parameter follows the same filtering contract as the existing query.SQLAlchemy patterns:
joinedloadforPlayer.parentandPlayer.teamsavoids N+1 queries. The.distinct()is appropriate given the join to the association table. Session management follows the existing pattern (caller-owned session).Type hints / PEP 484: Function signature is properly typed with
list[dict]return andstr | Nonefortest_email. Docstring is thorough (PEP 257 compliant).Test coverage: 6 new tests covering: happy path (missing all fields), profile URL correctness, exclusion of complete profiles, partial missing fields,
test_emailfiltering, and registry registration. This is solid coverage.BLOCKERS
None.
NITS
registration_tokencould be None (/home/ldraney/basketball-api/src/basketball_api/models.py:183):Parent.registration_tokenisMapped[str | None]. If a parent has no token, the profile URL becomes.../register?token=None-- a broken link. Other query endpoints inadmin.py(lines 578, 867) explicitly filter.filter(Parent.registration_token.isnot(None)). Consider adding a guard:This is a pre-existing pattern gap (the same unguarded usage exists in
email.py:92), so not a blocker for this PR, but worth hardening.Missing fields display names: The
missing_fieldsvalue uses raw column names (photo_url,date_of_birth). If these appear in user-facing emails, a display-name mapping (e.g.,"photo_url" -> "Photo","date_of_birth" -> "Date of Birth") would be friendlier. This is a template/UX concern rather than a query concern -- the template can handle the mapping.No test for parentless edge case: While
parent_idis non-nullable (Mapped[int]), a test confirming behavior when a player has no team roster entry (noplayer_teamsrow) would strengthen coverage. Currently, such players are implicitly excluded by the join, which is correct but undocumented.SOP COMPLIANCE
308-incomplete-profiles-queryPROCESS OBSERVATIONS
Clean, focused PR. 255 additions, 0 deletions, 2 files. The query-per-email-type pattern scales well for the blast system. Deployment risk is low -- this adds a new registry entry without modifying existing code paths.
VERDICT: APPROVED