Execute Marcus 2026-04-10 batch: 6 contracts + 9 jersey emails #424

Open
opened 2026-04-10 19:51:48 +00:00 by forgejo_admin · 1 comment

Type

Feature

Lineage

Standalone — discovered during 2026-04-10 Westside Ops session. Coach Marcus sent two messages in WKQ Stakeholders group (plus one DM preview for Marie) naming 9 players needing contract offers and/or jersey emails. All 4 of Lucas's clarifying questions answered. This ticket is the execution tracker for the operational batch — not a code change, but a series of triggered sends through the blast approval gate.

Repo

forgejo_admin/basketball-api

User Story

As Lucas operating Westside Kings & Queens
I want a single tracked item for Marcus's 2026-04-10 batch of 9 players
So that every contract offer and jersey email from this batch is auditable, none are lost if the session ends, and the approval-gate execution is visible on the board

Context

Marcus's original messages and my parsed decisions (from WKQ Stakeholders group, 2026-04-10):

Message 1 (Marcus): "Jersey emails need to be sent out. Also if they need a contract I'll also add that: Alice - jersey email and local 16u local contract; Vince - jersey email and 16u elite contract; Brian rhay - jersey email and 17u select or local contract; David kaneko - jersey email; Jace Bronson - jersey email and 16u local contract"

Message 2 (Marcus, earlier): "Jersey email please send to: Zack bod, Zayvion brown, Kevin porja and also contract for 17u select or 17u local"

DM preview (Marcus): "Also Marie needs a 17u travel contract"

Marcus's answers to my 4 clarifying questions:

  1. "Jace Bronson" = Jacelyn Bronson. She was signed at $160 on 16U Elite Queens but wanted out because she didn't want to travel. Now she wants back at 16U Local level.
  2. Alice → 16U Elite Queens (pivoted from "Local" when told no 16U Local Queens team exists)
  3. Brian → 17U Select Kings (primary), 17U Local Kings (fallback if he doesn't want to travel)
  4. Kevin → 17U Select Kings (primary), 17U Local Kings (fallback if he doesn't want to travel)

Marcus's fee list: Jacelyn $160, Alice $100, Brian $200, Kevin $160. Everyone else defaults to $200 (implicit from the question framing).

File Targets

No code changes. This ticket tracks operational sends through existing basketball-api endpoints:

  • send_contract_offer (or equivalent) — for contract creation + signing link email
  • send_jersey_reminder_email — for jersey payment link email (basketball-api/src/basketball_api/services/email.py:1140)

Files NOT to touch: any source code. This is a pure execution ticket. If we discover a bug mid-batch, file it as a separate ticket.

Acceptance Criteria — Per Player

Contract offers (6 to send):

  • Alice Uwamahoro (player id 202, after dedupe of id 201) → 16U Elite Queens, $100/month — contract_status flips none → offered, token minted, email sent to parent, confirmed in email_log
  • Brian Rhay II (player id 191) → 17U Select Kings, $200/month — same flow
  • Kevin Porja (player id 198) → 17U Select Kings, $160/month — same flow
  • Vince Ifote (player id 189) → 16U Elite Kings, $200/month (default) — same flow, needs parent name cleanup (Vince Ifote Ifote)
  • Marie Angilau-Lea (player id 192) → 17U Elite Queens, $200/month (default) — already rostered on 17U Elite Queens; just needs contract offer, token, email
  • Jacelyn Bronson (player id 97) — SPECIAL CASE: currently signed on 16U Elite Queens at $160. Needs to be moved to 16U Local Queens (depends on basketball-api #422 for team creation) and re-issued a new contract at $160. This is the first signed → move → re-offer flow the system has seen.

Jersey emails (9 to send):

  • Alice Uwamahoro — generic jersey email (no option picked yet)
  • Brian Rhay II — generic jersey email
  • Kevin Porja — generic jersey email
  • Vince Ifote — generic jersey email
  • Marie Angilau-Lea — payment email for the jersey_warmup ($130 package) she already picked, not generic
  • Jacelyn Bronson — generic jersey email
  • David Kaneko (player id 99) — already signed, needs jersey email. His prior warmup order was canceled (never reached Stripe). Generic email.
  • Zachary Bod (player id 143) — already signed on 17U Elite Kings at $180. Generic jersey email.
  • Zayvion Brown (player id 132) — already signed on 17U Local Kings at $200. Generic jersey email.

Per-send approval gate (per feedback_email_blast_nuclear_gate):

  • For each wave: draft template → show Lucas → Lucas approves content → test send to draneylucas@gmail.com → Lucas confirms → Lucas says BLAST → Ava reads back recipient list → Lucas confirms BLAST → send
  • No shortcuts. No batching across waves. Contract offers and jersey emails are two separate waves with two separate approval gates.

Test Expectations

  • After each send: verify email_log row exists with correct recipient + template name
  • After each contract offer: verify players.contract_status = 'offered' and players.contract_token IS NOT NULL for the target player
  • After Jacelyn's special flow: verify her old signed-state artifacts are preserved in whatever audit/history mechanism exists (or flagged as a gap if none)

Constraints

  • Blocking dependencies (tickets that MUST land first):
    • pal-e-deployments #105 — hostPath email templates (enables fast iteration on copy during the draft/approve phase)
    • basketball-api #420 — Alice Uwamahoro dedupe (delete row 201 before sending to row 202)
    • basketball-api #422 — Create 16U Local Queens team (required for Jacelyn's move)
  • Non-blocking dependencies (nice-to-have but can proceed without):
    • basketball-api #418 — Email normalization root cause fix
    • basketball-api #421 — Vince Ifote parent name fix
    • basketball-api #423 — Unknown Player investigation
  • Do NOT send any email without the 7-step blast approval gate
  • Do NOT edit contract data outside of the existing endpoint flows — no direct SQL updates
  • Do NOT touch players in declined state as part of this batch
  • Log every send in the tracker (this ticket body checklist) as it completes

Checklist

  • hostPath email template mount landed (#105)
  • Alice dedupe landed (#420)
  • 16U Local Queens team created (#422)
  • Wave 1: Contract offers for Alice, Brian, Kevin, Vince, Marie approved + sent
  • Wave 2: Jacelyn Bronson special-case move + re-offer handled
  • Wave 3: All 9 jersey emails approved + sent
  • All 6 target players show contract_status = 'offered' in DB
  • All 9 players confirmed in email_log
  • Progress reported back to Marcus in WKQ Stakeholders group
  • westside-basketball — project this affects
  • Blocked by: pal-e-deployments #105, basketball-api #420, basketball-api #422
  • Related data cleanup: basketball-api #418, #421, #423
### Type Feature ### Lineage Standalone — discovered during 2026-04-10 Westside Ops session. Coach Marcus sent two messages in WKQ Stakeholders group (plus one DM preview for Marie) naming 9 players needing contract offers and/or jersey emails. All 4 of Lucas's clarifying questions answered. This ticket is the **execution tracker** for the operational batch — not a code change, but a series of triggered sends through the blast approval gate. ### Repo `forgejo_admin/basketball-api` ### User Story As Lucas operating Westside Kings & Queens I want a single tracked item for Marcus's 2026-04-10 batch of 9 players So that every contract offer and jersey email from this batch is auditable, none are lost if the session ends, and the approval-gate execution is visible on the board ### Context Marcus's original messages and my parsed decisions (from WKQ Stakeholders group, 2026-04-10): **Message 1 (Marcus)**: "Jersey emails need to be sent out. Also if they need a contract I'll also add that: Alice - jersey email and local 16u local contract; Vince - jersey email and 16u elite contract; Brian rhay - jersey email and 17u select or local contract; David kaneko - jersey email; Jace Bronson - jersey email and 16u local contract" **Message 2 (Marcus, earlier)**: "Jersey email please send to: Zack bod, Zayvion brown, Kevin porja and also contract for 17u select or 17u local" **DM preview (Marcus)**: "Also Marie needs a 17u travel contract" **Marcus's answers to my 4 clarifying questions**: 1. "Jace Bronson" = Jacelyn Bronson. She was signed at $160 on 16U Elite Queens but wanted out because she didn't want to travel. Now she wants back at 16U Local level. 2. Alice → 16U Elite Queens (pivoted from "Local" when told no 16U Local Queens team exists) 3. Brian → 17U Select Kings (primary), 17U Local Kings (fallback if he doesn't want to travel) 4. Kevin → 17U Select Kings (primary), 17U Local Kings (fallback if he doesn't want to travel) **Marcus's fee list**: Jacelyn $160, Alice $100, Brian $200, Kevin $160. Everyone else defaults to $200 (implicit from the question framing). ### File Targets No code changes. This ticket tracks **operational sends** through existing basketball-api endpoints: - `send_contract_offer` (or equivalent) — for contract creation + signing link email - `send_jersey_reminder_email` — for jersey payment link email (basketball-api/src/basketball_api/services/email.py:1140) Files NOT to touch: any source code. This is a pure execution ticket. If we discover a bug mid-batch, file it as a separate ticket. ### Acceptance Criteria — Per Player **Contract offers (6 to send)**: - [ ] **Alice Uwamahoro** (player id 202, after dedupe of id 201) → 16U Elite Queens, $100/month — contract_status flips `none → offered`, token minted, email sent to parent, confirmed in `email_log` - [ ] **Brian Rhay II** (player id 191) → 17U Select Kings, $200/month — same flow - [ ] **Kevin Porja** (player id 198) → 17U Select Kings, $160/month — same flow - [ ] **Vince Ifote** (player id 189) → 16U Elite Kings, $200/month (default) — same flow, needs parent name cleanup (Vince Ifote Ifote) - [ ] **Marie Angilau-Lea** (player id 192) → 17U Elite Queens, $200/month (default) — already rostered on 17U Elite Queens; just needs contract offer, token, email - [ ] **Jacelyn Bronson** (player id 97) — SPECIAL CASE: currently `signed` on 16U Elite Queens at $160. Needs to be moved to 16U Local Queens (depends on basketball-api #422 for team creation) and re-issued a new contract at $160. This is the first `signed → move → re-offer` flow the system has seen. **Jersey emails (9 to send)**: - [ ] Alice Uwamahoro — generic jersey email (no option picked yet) - [ ] Brian Rhay II — generic jersey email - [ ] Kevin Porja — generic jersey email - [ ] Vince Ifote — generic jersey email - [ ] Marie Angilau-Lea — **payment email for the jersey_warmup ($130 package) she already picked**, not generic - [ ] Jacelyn Bronson — generic jersey email - [ ] **David Kaneko** (player id 99) — already signed, needs jersey email. His prior warmup order was canceled (never reached Stripe). Generic email. - [ ] **Zachary Bod** (player id 143) — already signed on 17U Elite Kings at $180. Generic jersey email. - [ ] **Zayvion Brown** (player id 132) — already signed on 17U Local Kings at $200. Generic jersey email. **Per-send approval gate** (per `feedback_email_blast_nuclear_gate`): - [ ] For each wave: draft template → show Lucas → Lucas approves content → test send to draneylucas@gmail.com → Lucas confirms → Lucas says BLAST → Ava reads back recipient list → Lucas confirms BLAST → send - [ ] No shortcuts. No batching across waves. Contract offers and jersey emails are two separate waves with two separate approval gates. ### Test Expectations - [ ] After each send: verify `email_log` row exists with correct recipient + template name - [ ] After each contract offer: verify `players.contract_status = 'offered'` and `players.contract_token IS NOT NULL` for the target player - [ ] After Jacelyn's special flow: verify her old signed-state artifacts are preserved in whatever audit/history mechanism exists (or flagged as a gap if none) ### Constraints - **Blocking dependencies** (tickets that MUST land first): - `pal-e-deployments #105` — hostPath email templates (enables fast iteration on copy during the draft/approve phase) - `basketball-api #420` — Alice Uwamahoro dedupe (delete row 201 before sending to row 202) - `basketball-api #422` — Create 16U Local Queens team (required for Jacelyn's move) - **Non-blocking dependencies** (nice-to-have but can proceed without): - `basketball-api #418` — Email normalization root cause fix - `basketball-api #421` — Vince Ifote parent name fix - `basketball-api #423` — Unknown Player investigation - Do NOT send any email without the 7-step blast approval gate - Do NOT edit contract data outside of the existing endpoint flows — no direct SQL updates - Do NOT touch players in `declined` state as part of this batch - Log every send in the tracker (this ticket body checklist) as it completes ### Checklist - [ ] hostPath email template mount landed (#105) - [ ] Alice dedupe landed (#420) - [ ] 16U Local Queens team created (#422) - [ ] Wave 1: Contract offers for Alice, Brian, Kevin, Vince, Marie approved + sent - [ ] Wave 2: Jacelyn Bronson special-case move + re-offer handled - [ ] Wave 3: All 9 jersey emails approved + sent - [ ] All 6 target players show `contract_status = 'offered'` in DB - [ ] All 9 players confirmed in `email_log` - [ ] Progress reported back to Marcus in WKQ Stakeholders group ### Related - `westside-basketball` — project this affects - Blocked by: pal-e-deployments #105, basketball-api #420, basketball-api #422 - Related data cleanup: basketball-api #418, #421, #423
Author
Owner

2026-04-10/11 Execution Status

Completed

Dependency PRs merged and deployed:

  • #427 → migration 040 (16U Local Queens team, team id 12 created) — verified in prod, alembic at 042
  • #426 → migration 041 (contract_audit_log table + POST /admin/contract/offer endpoint) — verified, endpoint returns 401 without JWT, used end-to-end tonight
  • #428 → migration 042 (Alice Uwamahoro dedupe) — verified, player 201 + parent 175 removed (direct SQL cleanup of blocking registration row 185 required first)

6 contract offers minted via POST /admin/contract/offer:

  • Alice Uwamahoro (id 202) — 16U Elite Queens — $100/mo — token X5ANnqX-...
  • Brian Rhay II (id 191) — 17U Select Kings — $200/mo — token HEyBkpwq...
  • Kevin Porja (id 198) — 17U Select Kings — $160/mo — token yJfWaZlN...
  • Vince Ifote (id 189) — 16U Elite Kings — $200/mo — token AVnVe4WB...
  • Marie Angilau-Lea (id 192) — 17U Elite Queens — $200/mo — token Qj4QYtgJ...
  • Jacelyn Bronson (id 97) — 16U Local Queens — $160/mo — token qilIolLm...FIRST-EVER signed→offered tier change on the platform, audit row contract_audit_log.id=1, event_type='tier_change', actor='draneylucas@gmail.com'

Wave 3 blast (contract emails) — SENT to 15 parents via POST /admin/email/blast:

  • Intended: 15 (the 6 above + 9 pre-existing offered players Marcus approved: Boris Ivan, Gauge Donovan, Kiana Sikander, Kristian Webb, Maereg Robinson, Natalie Garcia, Romial Strachan, Terrail Brown Jr., Zion Odejinmi)
  • Actual sent: 18 (15 + 3 unintended to Sandra Apaisa)
  • See incident ticket #445 for full root cause

22-player list approval from Marcus in WKQ Stakeholders:

  • Marcus approved the 22-player list minus: Benjamin Betancourt (deleted — not responding), Jencarlo Diaz + 5 other international players (marked as CUSTOM CONTRACT NEEDED, contract_tokens NULLed)
  • 6 international kids flagged in custom_notes: Gabrielius Peciulis (103), Hasip Yigit (92), Hasip Sarp (91), Jencarlo Diaz (131), Johan Kisuka (142), Seydou Goudiaby (160)
  • Benjamin Betancourt (127) fully deleted from DB along with parent 112 and 9 email_log rows

🟡 In Flight (pending Marcus review)

Apaisa family correction:

  • 3 new Apaisa contracts minted at $50/each via POST /admin/contract/offer?force=true
    • Aleiyah (111) — 17U Elite Queens — token fjOXPKtm...
    • Analeigh (186) — 16U Elite Queens — token WqqJGPbu...
    • Ayvah (112) — 17U Elite Queens — token QLHbMP5K...
  • Correction email drafted using _brand_wrapper() (the real Queens pink architecture, not the generic MJML notification template)
  • Test sent to draneylucas@gmail.com for preview → approved
  • Review copy sent to marcusdraney23@gmail.com + GroupMe heads-up in WKQ Stakeholders
  • Pending Marcus's thumbs-up before sending the real correction to apaisasandra@gmail.com

Deferred / Not This Batch

Wave 4 (jersey emails for 9 players): NOT sent. Marcus's original list included jersey emails for Alice, Brian, Vince, Kevin, Marie, Jacelyn, David Kaneko, Zachary Bod, Zayvion Brown. These have NOT been sent yet. Still a pending action in this umbrella ticket.

Jencarlo Diaz, Benjamin Betancourt: removed from batch scope per Marcus's latest WKQ reply.

📋 Follow-Up Tickets Created

  • #445 — Incident: 3 wrong contract emails to Sandra Apaisa (root cause documented)
  • #446 — Fix AgeGroup enum Python↔Postgres mismatch (latent bug exposed by #422 migration)
  • #447 — email_log.player_id NULL on blast sends (logging bug found during incident forensics)
  • #448 — Add player_ids filter to query_unsigned_contracts + blast endpoint (prevention for #445 class of incident)
  • #434 — Fix 2 pre-existing failing tests on main (separate pre-existing debt, not caused by this batch)

Next Actions (when Marcus approves)

  1. Flip Apaisa correction email TO from marcus → apaisasandra@gmail.com, re-run the pod script, verify email_log
  2. Wave 4 jersey blast — requires explicit re-approval from Marcus + the 7-step blast gate
  3. Advance this ticket backlog → done once all 3 waves are complete AND follow-up validation is posted
## 2026-04-10/11 Execution Status ### ✅ Completed **Dependency PRs merged and deployed:** - #427 → migration 040 (16U Local Queens team, team id 12 created) — verified in prod, alembic at 042 - #426 → migration 041 (contract_audit_log table + POST /admin/contract/offer endpoint) — verified, endpoint returns 401 without JWT, used end-to-end tonight - #428 → migration 042 (Alice Uwamahoro dedupe) — verified, player 201 + parent 175 removed (direct SQL cleanup of blocking registration row 185 required first) **6 contract offers minted via POST /admin/contract/offer:** - Alice Uwamahoro (id 202) — 16U Elite Queens — $100/mo — token `X5ANnqX-...` - Brian Rhay II (id 191) — 17U Select Kings — $200/mo — token `HEyBkpwq...` - Kevin Porja (id 198) — 17U Select Kings — $160/mo — token `yJfWaZlN...` - Vince Ifote (id 189) — 16U Elite Kings — $200/mo — token `AVnVe4WB...` - Marie Angilau-Lea (id 192) — 17U Elite Queens — $200/mo — token `Qj4QYtgJ...` - Jacelyn Bronson (id 97) — 16U Local Queens — $160/mo — token `qilIolLm...` — **FIRST-EVER signed→offered tier change on the platform, audit row contract_audit_log.id=1, event_type='tier_change', actor='draneylucas@gmail.com'** **Wave 3 blast (contract emails) — SENT to 15 parents via POST /admin/email/blast:** - Intended: 15 (the 6 above + 9 pre-existing offered players Marcus approved: Boris Ivan, Gauge Donovan, Kiana Sikander, Kristian Webb, Maereg Robinson, Natalie Garcia, Romial Strachan, Terrail Brown Jr., Zion Odejinmi) - Actual sent: **18** (15 + 3 unintended to Sandra Apaisa) - See incident ticket #445 for full root cause **22-player list approval from Marcus in WKQ Stakeholders:** - Marcus approved the 22-player list minus: Benjamin Betancourt (deleted — not responding), Jencarlo Diaz + 5 other international players (marked as CUSTOM CONTRACT NEEDED, contract_tokens NULLed) - 6 international kids flagged in custom_notes: Gabrielius Peciulis (103), Hasip Yigit (92), Hasip Sarp (91), Jencarlo Diaz (131), Johan Kisuka (142), Seydou Goudiaby (160) - Benjamin Betancourt (127) fully deleted from DB along with parent 112 and 9 email_log rows ### 🟡 In Flight (pending Marcus review) **Apaisa family correction:** - 3 new Apaisa contracts minted at $50/each via POST /admin/contract/offer?force=true - Aleiyah (111) — 17U Elite Queens — token `fjOXPKtm...` - Analeigh (186) — 16U Elite Queens — token `WqqJGPbu...` - Ayvah (112) — 17U Elite Queens — token `QLHbMP5K...` - Correction email drafted using _brand_wrapper() (the real Queens pink architecture, not the generic MJML notification template) - Test sent to draneylucas@gmail.com for preview → approved - Review copy sent to marcusdraney23@gmail.com + GroupMe heads-up in WKQ Stakeholders - Pending Marcus's thumbs-up before sending the real correction to apaisasandra@gmail.com ### ❌ Deferred / Not This Batch **Wave 4 (jersey emails for 9 players)**: NOT sent. Marcus's original list included jersey emails for Alice, Brian, Vince, Kevin, Marie, Jacelyn, David Kaneko, Zachary Bod, Zayvion Brown. These have NOT been sent yet. Still a pending action in this umbrella ticket. **Jencarlo Diaz, Benjamin Betancourt**: removed from batch scope per Marcus's latest WKQ reply. ### 📋 Follow-Up Tickets Created - **#445** — Incident: 3 wrong contract emails to Sandra Apaisa (root cause documented) - **#446** — Fix AgeGroup enum Python↔Postgres mismatch (latent bug exposed by #422 migration) - **#447** — email_log.player_id NULL on blast sends (logging bug found during incident forensics) - **#448** — Add player_ids filter to query_unsigned_contracts + blast endpoint (prevention for #445 class of incident) - **#434** — Fix 2 pre-existing failing tests on main (separate pre-existing debt, not caused by this batch) ### Next Actions (when Marcus approves) 1. Flip Apaisa correction email TO from marcus → apaisasandra@gmail.com, re-run the pod script, verify email_log 2. Wave 4 jersey blast — requires explicit re-approval from Marcus + the 7-step blast gate 3. Advance this ticket backlog → done once all 3 waves are complete AND follow-up validation is posted
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#424
No description provided.