Implement detailed Phase 5 implementation plan for Expected Value System
- Added comprehensive implementation plan for Phase 5, including executive summary, clarifications, recommended architecture, task breakdown, and performance considerations. - Defined database schema changes for participant probabilities and expected values. - Outlined service layer architecture for probability generation, EV calculation, and probability updates. - Included detailed task breakdown for each sub-phase, focusing on manual entry, futures odds integration, real results integration, projected totals display, draft integration, and daily update job. - Incorporated validation data and calibration goals for NHL futures odds. - Updated planning questions and answers based on previous discussions to refine the approach for EV calculations and autodraft integration.
This commit is contained in:
parent
6ef829f667
commit
41b81771d4
2 changed files with 1181 additions and 0 deletions
856
plans/phase-5-detailed-implementation-plan.md
Normal file
856
plans/phase-5-detailed-implementation-plan.md
Normal file
|
|
@ -0,0 +1,856 @@
|
||||||
|
# Phase 5: Expected Value System - Detailed Implementation Plan
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
Based on your answers, here's the core approach:
|
||||||
|
|
||||||
|
**MVP Strategy**: Start with **one sport** (suggest NFL playoffs) using **Vegas futures odds** as the probability source. This can be reused across all scoring patterns later.
|
||||||
|
|
||||||
|
**Key Principles**:
|
||||||
|
- Different scoring patterns = different probability models
|
||||||
|
- Finished participants ALWAYS have EV = actual points
|
||||||
|
- Store P(1st) through P(8th) once, calculate league-specific EV on demand
|
||||||
|
- Display as "Projected Points" (not "Expected Value")
|
||||||
|
- Never override manual draft queues
|
||||||
|
- Only update active/upcoming seasons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Follow-up Clarifications Needed
|
||||||
|
|
||||||
|
Before I finalize the plan, I need to clarify a few things based on your answers:
|
||||||
|
|
||||||
|
### Clarification 1: Futures Odds Data Shape
|
||||||
|
|
||||||
|
You asked: "Are we thinking using vegas futures, or potentially Elo ratings?"
|
||||||
|
|
||||||
|
**Here's the difference**:
|
||||||
|
|
||||||
|
**Vegas Futures** give us direct probabilities:
|
||||||
|
- Input: "Chiefs +500 to win Super Bowl" → 16.7% probability
|
||||||
|
- We convert all futures odds to probabilities that sum to 100%
|
||||||
|
- Then distribute those probabilities across our 1st-8th placements
|
||||||
|
|
||||||
|
**Elo Ratings** give us relative strength:
|
||||||
|
- Input: "Chiefs Elo: 1850, Bills Elo: 1820"
|
||||||
|
- We run Monte Carlo simulations to generate probabilities
|
||||||
|
- Simulate 100k tournaments, count how often each team finishes 1st-8th
|
||||||
|
|
||||||
|
**My recommendation**: Start with **Vegas Futures** because:
|
||||||
|
1. Direct conversion to probabilities (simpler)
|
||||||
|
2. Can be manually entered if API not available
|
||||||
|
3. Works for all sports (most have championship futures)
|
||||||
|
4. Easier to validate (bookmakers already balanced the probabilities)
|
||||||
|
|
||||||
|
**Question for you**: Does starting with Vegas Futures approach make sense? We can add Elo-based simulation for specific sports (F1, Tennis) in Phase 5.2+.
|
||||||
|
Please start with the vegas futures. However, one thing we can do is use things like the independent chip model and potentially even like the algorithm to calculate ICM in large tournaments. Here are some links:
|
||||||
|
- https://en.wikipedia.org/wiki/Independent_Chip_Model#Model
|
||||||
|
- https://www.holdemresources.net/blog/high-accuracy-mtt-icm/
|
||||||
|
- https://forumserver.twoplustwo.com/15/poker-theory-amp-gto/new-algorithm-calculate-icm-large-tournaments-1098489/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Clarification 2: Monte Carlo Simulation Scope
|
||||||
|
|
||||||
|
You said: "simulate the winning odds in the system for everyone and then apply it accordingly, not per participant"
|
||||||
|
|
||||||
|
**I want to confirm I understand correctly**:
|
||||||
|
- Run ONE simulation of the entire tournament/season (100k iterations)
|
||||||
|
- Count how often each participant finishes 1st, 2nd, ..., 8th
|
||||||
|
- Store those percentages in the database
|
||||||
|
- For each league, multiply those percentages by that league's scoring rules
|
||||||
|
|
||||||
|
**Is this correct?** Or did you mean something different?
|
||||||
|
That's correct!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Clarification 3: Performance & Caching
|
||||||
|
|
||||||
|
You asked: "I don't know if it'll be taxing, what do you think? We could probably cache the results as well I guess?"
|
||||||
|
|
||||||
|
**My analysis**:
|
||||||
|
|
||||||
|
**Good news**: Your approach is already efficient!
|
||||||
|
- Store P(1st)-P(8th) once per participant (8 decimals)
|
||||||
|
- Calculate EV on-demand: `sum(P(placement) * points(placement))` - this is cheap (8 multiplications)
|
||||||
|
- 100 participants × 50 leagues = 5000 EV calcs = ~0.4ms on modern hardware
|
||||||
|
|
||||||
|
**Recommendation**:
|
||||||
|
- **Don't cache individual EVs** (calculation is faster than cache lookup)
|
||||||
|
- **DO pre-calculate projected totals** for standings snapshots (you already agreed to this)
|
||||||
|
- **DO cache probability distributions** (expensive Monte Carlo simulations run weekly, not per-request)
|
||||||
|
|
||||||
|
**Question**: Does this approach make sense?
|
||||||
|
Yes!
|
||||||
|
---
|
||||||
|
|
||||||
|
### Clarification 4: Which Sport for MVP?
|
||||||
|
|
||||||
|
You said "start with one sport" - which one do you prefer?
|
||||||
|
|
||||||
|
**Options ranked by difficulty**:
|
||||||
|
|
||||||
|
1. **NFL Playoffs** (easiest)
|
||||||
|
- Single elimination bracket (already implemented in Phase 2)
|
||||||
|
- Futures odds readily available
|
||||||
|
- Clear 1st-8th placements
|
||||||
|
- 14 teams total, only 8 score fantasy points
|
||||||
|
|
||||||
|
2. **Golf Majors** (medium)
|
||||||
|
- Qualifying points system (implemented in Phase 3)
|
||||||
|
- Need to simulate 4 tournaments
|
||||||
|
- Predict QP totals → convert to fantasy placements
|
||||||
|
- Futures odds available for each major
|
||||||
|
|
||||||
|
3. **F1 Season** (harder)
|
||||||
|
- Season standings (implemented in Phase 3)
|
||||||
|
- Need to simulate ~20 races
|
||||||
|
- Predict championship points → positions
|
||||||
|
- May need Elo or performance models (futures less useful)
|
||||||
|
|
||||||
|
**My recommendation**: Start with **NFL Playoffs** for MVP, then expand to Golf, then F1.
|
||||||
|
|
||||||
|
**Question**: Does NFL Playoffs make sense as the starting sport?
|
||||||
|
Actually can we start with the NHL? Similar setup but I will be working with a league that's currently running so the numbers will be easier to gather.
|
||||||
|
---
|
||||||
|
|
||||||
|
### Clarification 5: Futures Odds Format
|
||||||
|
|
||||||
|
When you manually enter futures odds (or we fetch from API), what format works best?
|
||||||
|
|
||||||
|
**Option A: American Odds**
|
||||||
|
```
|
||||||
|
Chiefs: +500
|
||||||
|
49ers: +600
|
||||||
|
Ravens: +800
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Decimal Odds**
|
||||||
|
```
|
||||||
|
Chiefs: 6.00
|
||||||
|
49ers: 7.00
|
||||||
|
Ravens: 9.00
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option C: Implied Probabilities**
|
||||||
|
```
|
||||||
|
Chiefs: 16.7%
|
||||||
|
49ers: 14.3%
|
||||||
|
Ravens: 11.1%
|
||||||
|
```
|
||||||
|
|
||||||
|
We can convert between all three, but which is easiest for you to source/enter?
|
||||||
|
I'm going to guess the American odds but it would be nice to be able to convert. That's probably not MVP though.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Architecture
|
||||||
|
|
||||||
|
Based on your answers, here's the system design:
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 1. PROBABILITY GENERATION │
|
||||||
|
│ │
|
||||||
|
│ Manual Entry OR API Fetch │
|
||||||
|
│ ↓ │
|
||||||
|
│ Convert to P(1st) - P(8th) │
|
||||||
|
│ ↓ │
|
||||||
|
│ Store in participant_probabilities table │
|
||||||
|
│ (sportsSeasonId + participantId → 8 percentages) │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 2. EV CALCULATION (On-Demand) │
|
||||||
|
│ │
|
||||||
|
│ For each participant in a league: │
|
||||||
|
│ EV = Σ P(placement) × league_points(placement) │
|
||||||
|
│ │
|
||||||
|
│ Example: │
|
||||||
|
│ Chiefs: 20% × 100 + 15% × 70 + ... = 45.5 points │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 3. PROJECTED TOTALS (Pre-calculated with snapshots) │
|
||||||
|
│ │
|
||||||
|
│ For each team in a season: │
|
||||||
|
│ Projected Total = Actual Points + Sum(EVs of unfinished) │
|
||||||
|
│ │
|
||||||
|
│ Stored in team_standings_snapshots table │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Schema Changes
|
||||||
|
|
||||||
|
**New Table: `participant_probabilities`**
|
||||||
|
```typescript
|
||||||
|
participant_probabilities {
|
||||||
|
id: uuid PRIMARY KEY
|
||||||
|
participantId: uuid -> participants.id
|
||||||
|
sportsSeasonId: uuid -> sportsSeasons.id
|
||||||
|
|
||||||
|
// Probability distribution (percentages that sum to 100)
|
||||||
|
probFirst: decimal(5,2) // e.g., 15.50 = 15.5%
|
||||||
|
probSecond: decimal(5,2)
|
||||||
|
probThird: decimal(5,2)
|
||||||
|
probFourth: decimal(5,2)
|
||||||
|
probFifth: decimal(5,2)
|
||||||
|
probSixth: decimal(5,2)
|
||||||
|
probSeventh: decimal(5,2)
|
||||||
|
probEighth: decimal(5,2)
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
source: enum('manual', 'futures_odds', 'elo_simulation', 'performance_model')
|
||||||
|
lastUpdated: timestamp
|
||||||
|
calculatedAt: timestamp
|
||||||
|
|
||||||
|
// Unique constraint
|
||||||
|
UNIQUE(participantId, sportsSeasonId)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why separate from `participant_expected_values`?**
|
||||||
|
- Probabilities are **sports season-specific** (same for all leagues)
|
||||||
|
- EVs are **league-specific** (different scoring rules)
|
||||||
|
- This avoids storing duplicate probability data
|
||||||
|
- Saves ~800 bytes per participant per league
|
||||||
|
|
||||||
|
**Modify: `team_standings_snapshots`**
|
||||||
|
```typescript
|
||||||
|
team_standings_snapshots {
|
||||||
|
// ... existing fields
|
||||||
|
|
||||||
|
// NEW FIELDS:
|
||||||
|
actualPoints: decimal // Points from completed participants
|
||||||
|
projectedPoints: decimal // actualPoints + sum(EVs of unfinished)
|
||||||
|
participantsFinished: integer // Count of finished
|
||||||
|
participantsRemaining: integer // Count not yet finished
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Layer Architecture
|
||||||
|
|
||||||
|
**New Module: `app/services/probability-engine.ts`**
|
||||||
|
```typescript
|
||||||
|
// Core probability generation
|
||||||
|
async function generateProbabilitiesFromFutures(
|
||||||
|
sportsSeasonId: string,
|
||||||
|
futuresOdds: { participantId: string; odds: number }[]
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
|
// Convert odds to probabilities
|
||||||
|
function convertOddsToProbabilities(
|
||||||
|
odds: { participantId: string; odds: number }[],
|
||||||
|
oddsFormat: 'american' | 'decimal'
|
||||||
|
): { participantId: string; probabilities: number[] }[]
|
||||||
|
|
||||||
|
// Monte Carlo simulation (for future phases)
|
||||||
|
async function runMonteCarloSimulation(
|
||||||
|
sportsSeasonId: string,
|
||||||
|
simulations: number = 100000
|
||||||
|
): Promise<Map<string, number[]>>
|
||||||
|
```
|
||||||
|
|
||||||
|
**New Module: `app/services/ev-calculator.ts`**
|
||||||
|
```typescript
|
||||||
|
// Calculate EV for one participant in one league
|
||||||
|
function calculateEV(
|
||||||
|
probabilities: number[], // P(1st) through P(8th)
|
||||||
|
scoringRules: ScoringRules // League's point values
|
||||||
|
): number
|
||||||
|
|
||||||
|
// Calculate EV for all participants in a sports season for a specific league
|
||||||
|
async function calculateAllEVsForLeague(
|
||||||
|
seasonId: string,
|
||||||
|
sportsSeasonId: string
|
||||||
|
): Promise<Map<string, number>> // participantId → EV
|
||||||
|
|
||||||
|
// Get projected total for a team
|
||||||
|
async function calculateProjectedTotal(
|
||||||
|
teamId: string,
|
||||||
|
seasonId: string
|
||||||
|
): Promise<{
|
||||||
|
actualPoints: number;
|
||||||
|
projectedPoints: number;
|
||||||
|
participantsFinished: number;
|
||||||
|
participantsRemaining: number;
|
||||||
|
}>
|
||||||
|
```
|
||||||
|
|
||||||
|
**New Module: `app/services/probability-updater.ts`**
|
||||||
|
```typescript
|
||||||
|
// Update probabilities based on real results
|
||||||
|
async function updateProbabilitiesAfterResult(
|
||||||
|
sportsSeasonId: string
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
|
// Re-simulate remaining events with actual results locked in
|
||||||
|
async function resimulateWithPartialResults(
|
||||||
|
sportsSeasonId: string,
|
||||||
|
completedEvents: string[],
|
||||||
|
remainingEvents: string[]
|
||||||
|
): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 Task Breakdown (Revised)
|
||||||
|
|
||||||
|
### Phase 5.1: Foundation & Manual Entry (MVP)
|
||||||
|
|
||||||
|
**Goal**: Admin can manually enter probabilities, system calculates EVs
|
||||||
|
|
||||||
|
- [ ] **5.1.1** Database schema
|
||||||
|
- [ ] Create `participant_probabilities` table
|
||||||
|
- [ ] Add `actualPoints`, `projectedPoints` to `team_standings_snapshots`
|
||||||
|
- [ ] Create migration
|
||||||
|
- [ ] Add Drizzle schema definitions
|
||||||
|
|
||||||
|
- [ ] **5.1.2** Core calculation functions
|
||||||
|
- [ ] `app/services/ev-calculator.ts`
|
||||||
|
- [ ] `calculateEV(probabilities, scoringRules)` - pure function
|
||||||
|
- [ ] `calculateAllEVsForLeague(seasonId, sportsSeasonId)` - batch calculation
|
||||||
|
- [ ] `calculateProjectedTotal(teamId, seasonId)` - team projection
|
||||||
|
- [ ] Unit tests (15+ tests)
|
||||||
|
- [ ] Test EV calculation with different scoring rules
|
||||||
|
- [ ] Test with finished participants (EV = actual points)
|
||||||
|
- [ ] Test projected totals (actual + EVs)
|
||||||
|
|
||||||
|
- [ ] **5.1.3** Probability storage model
|
||||||
|
- [ ] `app/models/participant-probability.ts`
|
||||||
|
- [ ] `createProbability()` - insert new probabilities
|
||||||
|
- [ ] `updateProbability()` - update existing
|
||||||
|
- [ ] `getProbabilitiesForSportsSeason()` - get all for a sport
|
||||||
|
- [ ] `getProbabilityForParticipant()` - get one participant
|
||||||
|
- [ ] Validation: probabilities sum to 100% (±0.1% tolerance)
|
||||||
|
- [ ] Unit tests (10+ tests)
|
||||||
|
|
||||||
|
- [ ] **5.1.4** Admin UI - Manual probability entry
|
||||||
|
- [ ] Route: `/admin/sports-seasons/:id/probabilities`
|
||||||
|
- [ ] List all participants in sports season
|
||||||
|
- [ ] For each participant: 8 input fields (P(1st) through P(8th))
|
||||||
|
- [ ] Live validation: sum must equal 100%
|
||||||
|
- [ ] Auto-distribute remaining probability
|
||||||
|
- [ ] Save button → `createProbability()` or `updateProbability()`
|
||||||
|
- [ ] "Recalculate EVs" button (manual trigger)
|
||||||
|
|
||||||
|
- [ ] **5.1.5** Admin UI - Bulk import
|
||||||
|
- [ ] CSV upload format:
|
||||||
|
```
|
||||||
|
Participant Name, P(1st), P(2nd), ..., P(8th)
|
||||||
|
Chiefs, 20.5, 15.3, ..., 2.1
|
||||||
|
```
|
||||||
|
- [ ] Validate CSV rows
|
||||||
|
- [ ] Preview before import
|
||||||
|
- [ ] Bulk insert probabilities
|
||||||
|
|
||||||
|
### Phase 5.2: Futures Odds Integration
|
||||||
|
|
||||||
|
**Goal**: Convert Vegas futures to probabilities automatically
|
||||||
|
|
||||||
|
- [ ] **5.2.1** Odds conversion logic
|
||||||
|
- [ ] `app/services/probability-engine.ts`
|
||||||
|
- [ ] `convertAmericanOddsToProbability(odds)` - +500 → 16.7%
|
||||||
|
- [ ] `convertDecimalOddsToProbability(odds)` - 6.00 → 16.7%
|
||||||
|
- [ ] `normalizeProbabilities(probs)` - ensure sum to 100%
|
||||||
|
- [ ] `distributeProbabilitiesToPlacements(winProbs)` - championship win % → P(1st-8th)
|
||||||
|
- [ ] Unit tests (20+ tests)
|
||||||
|
- [ ] Test odds conversions (American, Decimal)
|
||||||
|
- [ ] Test normalization (handle bookmaker vig)
|
||||||
|
- [ ] Test distribution algorithms
|
||||||
|
|
||||||
|
- [ ] **5.2.2** Distribution algorithms
|
||||||
|
|
||||||
|
**For playoffs (single elimination)**:
|
||||||
|
- Championship odds → P(1st)
|
||||||
|
- Simulate bracket → P(2nd), P(3rd/4th), P(5th-8th)
|
||||||
|
|
||||||
|
**For season standings (F1)**:
|
||||||
|
- Championship odds → P(1st)
|
||||||
|
- Apply statistical distribution for P(2nd-8th) based on strength
|
||||||
|
|
||||||
|
**For qualifying points (Golf)**:
|
||||||
|
- Major win odds for each of 4 majors
|
||||||
|
- Simulate QP accumulation
|
||||||
|
- Convert QP standings → P(1st-8th)
|
||||||
|
|
||||||
|
- [ ] **5.2.3** Admin UI - Futures odds entry
|
||||||
|
- [ ] Route: `/admin/sports-seasons/:id/futures-odds`
|
||||||
|
- [ ] For each participant: enter futures odds
|
||||||
|
- [ ] Dropdown: American odds / Decimal odds
|
||||||
|
- [ ] "Generate Probabilities" button
|
||||||
|
- [ ] Runs conversion + distribution algorithm
|
||||||
|
- [ ] Preview P(1st-8th) for each participant
|
||||||
|
- [ ] Confirm → save to `participant_probabilities`
|
||||||
|
|
||||||
|
- [ ] **5.2.4** Sports betting API research & integration (optional)
|
||||||
|
- [ ] Research API options:
|
||||||
|
- [ ] The Odds API (theoddsapi.com) - $79/mo, good coverage
|
||||||
|
- [ ] API-Sports (api-sports.io) - Tiered pricing
|
||||||
|
- [ ] RapidAPI sports betting endpoints
|
||||||
|
- [ ] If API selected:
|
||||||
|
- [ ] Implement API client
|
||||||
|
- [ ] Map API participant names to our participants
|
||||||
|
- [ ] Weekly cron job to fetch and update odds
|
||||||
|
- [ ] Error handling and logging
|
||||||
|
|
||||||
|
### Phase 5.3: Real Results Integration
|
||||||
|
|
||||||
|
**Goal**: Update probabilities when results come in
|
||||||
|
|
||||||
|
- [ ] **5.3.1** Probability update service
|
||||||
|
- [ ] `app/services/probability-updater.ts`
|
||||||
|
- [ ] `updateProbabilitiesAfterResult(sportsSeasonId)`
|
||||||
|
- [ ] For finished participants: set to 100% at their placement, 0% elsewhere
|
||||||
|
- [ ] For unfinished participants: re-run simulation/calculation
|
||||||
|
|
||||||
|
- [ ] **5.3.2** Partial results handling
|
||||||
|
|
||||||
|
**Qualifying Points (Golf/Tennis)**:
|
||||||
|
- [ ] Get actual QP from completed majors
|
||||||
|
- [ ] Simulate remaining majors
|
||||||
|
- [ ] Combine actual + simulated QP → final standings probabilities
|
||||||
|
|
||||||
|
**Playoffs**:
|
||||||
|
- [ ] Eliminated teams → 0% for better placements
|
||||||
|
- [ ] Remaining teams → re-simulate from current bracket state
|
||||||
|
|
||||||
|
**Season Standings (F1)**:
|
||||||
|
- [ ] Get actual championship points so far
|
||||||
|
- [ ] Simulate remaining races
|
||||||
|
- [ ] Combine to get final standings probabilities
|
||||||
|
|
||||||
|
- [ ] **5.3.3** Admin trigger
|
||||||
|
- [ ] "Recalculate Probabilities" button on event completion page
|
||||||
|
- [ ] Runs `updateProbabilitiesAfterResult()`
|
||||||
|
- [ ] Shows before/after probabilities
|
||||||
|
- [ ] Updates all affected participant probabilities
|
||||||
|
|
||||||
|
- [ ] **5.3.4** Automatic triggers (optional)
|
||||||
|
- [ ] When event marked complete → auto-recalculate
|
||||||
|
- [ ] When participant result entered → auto-recalculate
|
||||||
|
- [ ] Background job (if expensive, queue it)
|
||||||
|
|
||||||
|
### Phase 5.4: Projected Totals Display
|
||||||
|
|
||||||
|
**Goal**: Show "Projected Points" to league members
|
||||||
|
|
||||||
|
- [ ] **5.4.1** Update standings snapshot calculation
|
||||||
|
- [ ] Modify `recalculateStandings()` in `app/models/scoring-calculator.ts`
|
||||||
|
- [ ] For each team:
|
||||||
|
- [ ] Calculate `actualPoints` (finished participants only)
|
||||||
|
- [ ] Calculate EVs for unfinished participants
|
||||||
|
- [ ] `projectedPoints = actualPoints + sum(EVs)`
|
||||||
|
- [ ] Store in `team_standings_snapshots`
|
||||||
|
|
||||||
|
- [ ] **5.4.2** Standings page updates
|
||||||
|
- [ ] Route: `/leagues/:leagueId/standings/:seasonId`
|
||||||
|
- [ ] Add "Projected Points" column
|
||||||
|
- [ ] Show actual vs projected
|
||||||
|
- [ ] Visual indicator: "X participants remaining"
|
||||||
|
- [ ] Tooltip: "Projected points = actual points + expected value of remaining participants"
|
||||||
|
|
||||||
|
- [ ] **5.4.3** Team breakdown page updates
|
||||||
|
- [ ] Route: `/leagues/:leagueId/standings/:seasonId/teams/:teamId`
|
||||||
|
- [ ] For each participant:
|
||||||
|
- [ ] If finished: show actual points
|
||||||
|
- [ ] If not finished: show "Projected: X.X points"
|
||||||
|
- [ ] Totals section:
|
||||||
|
- [ ] Actual Points: XXX
|
||||||
|
- [ ] Projected Points: XXX
|
||||||
|
- [ ] Participants Remaining: X
|
||||||
|
|
||||||
|
- [ ] **5.4.4** Participant detail page (new)
|
||||||
|
- [ ] Route: `/leagues/:leagueId/participants/:participantId`
|
||||||
|
- [ ] Show participant info
|
||||||
|
- [ ] Current status (finished or in progress)
|
||||||
|
- [ ] If finished: actual placement and points
|
||||||
|
- [ ] If not finished: projected points (league-specific)
|
||||||
|
- [ ] Ownership: which teams in this league own this participant
|
||||||
|
|
||||||
|
### Phase 5.5: Draft Integration
|
||||||
|
|
||||||
|
**Goal**: Use EV to sort available participants during draft
|
||||||
|
|
||||||
|
- [ ] **5.5.1** Draft participant sorting
|
||||||
|
- [ ] Update `app/models/draft.ts` or create `app/models/draft-rankings.ts`
|
||||||
|
- [ ] `getAvailableParticipantsForDraft(seasonId)` - current function
|
||||||
|
- [ ] Modify to include EV calculation
|
||||||
|
- [ ] Sort by EV (descending) as default order
|
||||||
|
- [ ] Respect manual draft queue FIRST (never override)
|
||||||
|
|
||||||
|
- [ ] **5.5.2** Autodraft logic
|
||||||
|
- [ ] Update `app/services/autodraft.ts` (or wherever autodraft lives)
|
||||||
|
- [ ] When autodraft picks:
|
||||||
|
1. Check manual draft queue first
|
||||||
|
2. If queue empty: pick highest EV available participant
|
||||||
|
3. Account for already-owned participants
|
||||||
|
- [ ] Log autodraft decisions
|
||||||
|
|
||||||
|
- [ ] **5.5.3** Testing autodraft
|
||||||
|
- [ ] Unit tests: autodraft picks highest EV when queue empty
|
||||||
|
- [ ] Unit tests: autodraft respects queue over EV
|
||||||
|
- [ ] Integration test: full draft with autodraft teams
|
||||||
|
|
||||||
|
### Phase 5.6: Daily Update Job
|
||||||
|
|
||||||
|
**Goal**: Keep probabilities and EVs fresh
|
||||||
|
|
||||||
|
- [ ] **5.6.1** Scheduled job setup
|
||||||
|
- [ ] Create `server/probability-updater.ts`
|
||||||
|
- [ ] Daily cron job (runs at 3 AM or configurable)
|
||||||
|
- [ ] Get all active/upcoming seasons
|
||||||
|
- [ ] For each sports season in those seasons:
|
||||||
|
- [ ] If has probabilities source (API): fetch new odds
|
||||||
|
- [ ] Re-calculate probabilities
|
||||||
|
- [ ] Update `participant_probabilities` table
|
||||||
|
|
||||||
|
- [ ] **5.6.2** Logging & monitoring
|
||||||
|
- [ ] Log job start/completion
|
||||||
|
- [ ] Log number of probabilities updated
|
||||||
|
- [ ] Log any errors or API failures
|
||||||
|
- [ ] Alert if job fails
|
||||||
|
|
||||||
|
- [ ] **5.6.3** Manual trigger
|
||||||
|
- [ ] Admin route: `/admin/update-probabilities`
|
||||||
|
- [ ] "Run Update Now" button
|
||||||
|
- [ ] Show progress/results
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sports Betting API Research
|
||||||
|
|
||||||
|
Here are the best options I found for fetching futures odds:
|
||||||
|
|
||||||
|
### Option 1: The Odds API (Recommended)
|
||||||
|
- **Website**: theoddsapi.com
|
||||||
|
- **Pricing**: $79/month for 10,000 requests
|
||||||
|
- **Coverage**: NFL, NBA, MLB, NHL, Golf, Tennis, F1, AFL
|
||||||
|
- **Pros**:
|
||||||
|
- Simple REST API
|
||||||
|
- Futures markets included
|
||||||
|
- Good documentation
|
||||||
|
- Free tier (500 requests/month) for testing
|
||||||
|
- **Cons**:
|
||||||
|
- Need to map their participant IDs to ours
|
||||||
|
- Some niche sports not covered
|
||||||
|
|
||||||
|
### Option 2: API-Sports
|
||||||
|
- **Website**: api-sports.io
|
||||||
|
- **Pricing**: $30-300/month depending on sport
|
||||||
|
- **Coverage**: Individual APIs per sport
|
||||||
|
- **Pros**:
|
||||||
|
- Very comprehensive data
|
||||||
|
- Historical data available
|
||||||
|
- Includes Elo ratings for some sports
|
||||||
|
- **Cons**:
|
||||||
|
- More expensive for multiple sports
|
||||||
|
- Steeper learning curve
|
||||||
|
|
||||||
|
### Option 3: Manual Entry (MVP)
|
||||||
|
- **Pricing**: Free
|
||||||
|
- **Coverage**: Whatever you enter
|
||||||
|
- **Pros**:
|
||||||
|
- Total control
|
||||||
|
- No API dependency
|
||||||
|
- Can use any source (ESPN, Vegas, etc.)
|
||||||
|
- **Cons**:
|
||||||
|
- Time-consuming
|
||||||
|
- Human error risk
|
||||||
|
- No automation
|
||||||
|
|
||||||
|
**My recommendation**: Start with **manual entry** for MVP, add **The Odds API** in Phase 5.2.4 when you're ready to scale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
- [ ] Odds conversion functions (20 tests)
|
||||||
|
- [ ] Probability distribution algorithms (15 tests)
|
||||||
|
- [ ] EV calculation (15 tests)
|
||||||
|
- [ ] Projected totals calculation (10 tests)
|
||||||
|
- [ ] Probability validation (10 tests)
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- [ ] End-to-end: Enter futures odds → generate probabilities → calculate EVs → display projected points (5 tests)
|
||||||
|
- [ ] Real results update flow (5 tests)
|
||||||
|
- [ ] Autodraft with EV ranking (3 tests)
|
||||||
|
|
||||||
|
### Manual Testing Checklist
|
||||||
|
- [ ] Enter probabilities for NFL playoffs manually
|
||||||
|
- [ ] Verify EVs calculate correctly for different league scoring rules
|
||||||
|
- [ ] Enter results, verify probabilities update
|
||||||
|
- [ ] Verify finished participants have EV = actual points
|
||||||
|
- [ ] Test projected totals on standings page
|
||||||
|
- [ ] Test autodraft picks highest EV
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
Based on your answers and my analysis:
|
||||||
|
|
||||||
|
### What's Fast (No Caching Needed)
|
||||||
|
- ✅ EV calculation (8 multiplications) - ~0.05ms per participant
|
||||||
|
- ✅ On-demand calculation is faster than cache lookup
|
||||||
|
- ✅ 100 participants × 50 leagues = 5000 EVs = ~0.25 seconds total
|
||||||
|
|
||||||
|
### What's Slow (Cache It)
|
||||||
|
- ❌ Monte Carlo simulations (100k iterations) - ~5-30 seconds
|
||||||
|
- ❌ API calls to fetch futures odds - ~1-2 seconds
|
||||||
|
- ❌ Database queries to fetch all probabilities - ~50ms
|
||||||
|
|
||||||
|
### Caching Strategy
|
||||||
|
|
||||||
|
**1. Probability Distributions** (stored in DB, not in-memory cache)
|
||||||
|
- Updated weekly via cron job
|
||||||
|
- Invalidate when admin manually updates
|
||||||
|
|
||||||
|
**2. Projected Totals** (pre-calculated in snapshots)
|
||||||
|
- Calculated with daily standings snapshots
|
||||||
|
- Stored in `team_standings_snapshots.projectedPoints`
|
||||||
|
- No real-time calculation needed
|
||||||
|
|
||||||
|
**3. Draft Participant Rankings** (in-memory cache, 5-minute TTL)
|
||||||
|
- Cache sorted list of available participants with EVs
|
||||||
|
- Invalidate when pick is made
|
||||||
|
- Invalidate every 5 minutes (in case probabilities updated)
|
||||||
|
|
||||||
|
### Database Indexes
|
||||||
|
```sql
|
||||||
|
CREATE INDEX idx_participant_probabilities_lookup
|
||||||
|
ON participant_probabilities(participantId, sportsSeasonId);
|
||||||
|
|
||||||
|
CREATE INDEX idx_participant_probabilities_sports_season
|
||||||
|
ON participant_probabilities(sportsSeasonId);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Path: Simple → Complex
|
||||||
|
|
||||||
|
### Phase 5.1 (Week 1-2): Manual Entry MVP
|
||||||
|
- Admin enters probabilities by hand
|
||||||
|
- System calculates EVs
|
||||||
|
- Display projected points
|
||||||
|
- **Deliverable**: Working system for 1 sport (NFL playoffs)
|
||||||
|
|
||||||
|
### Phase 5.2 (Week 3): Futures Odds Conversion
|
||||||
|
- Admin enters futures odds (not probabilities)
|
||||||
|
- System converts to P(1st-8th)
|
||||||
|
- Still manual, but easier than calculating percentages
|
||||||
|
- **Deliverable**: Futures → probabilities working
|
||||||
|
|
||||||
|
### Phase 5.3 (Week 4): Real Results Integration
|
||||||
|
- When results entered, probabilities auto-update
|
||||||
|
- Partial results + simulation for remaining events
|
||||||
|
- **Deliverable**: Dynamic probability updates
|
||||||
|
|
||||||
|
### Phase 5.4 (Week 5): UI Polish
|
||||||
|
- Projected points on all pages
|
||||||
|
- Better visualization
|
||||||
|
- Tooltips and help text
|
||||||
|
- **Deliverable**: User-facing features complete
|
||||||
|
|
||||||
|
### Phase 5.5 (Week 6): Draft Integration
|
||||||
|
- EV-based sorting
|
||||||
|
- Autodraft uses EV
|
||||||
|
- **Deliverable**: Draft room integration
|
||||||
|
|
||||||
|
### Phase 5.6 (Week 7): Automation
|
||||||
|
- API integration for futures odds
|
||||||
|
- Daily update job
|
||||||
|
- Expand to 2nd sport (Golf)
|
||||||
|
- **Deliverable**: Automated system
|
||||||
|
|
||||||
|
### Phase 5.7+ (Future): Advanced Models
|
||||||
|
- Elo-based simulations for F1, Tennis
|
||||||
|
- Performance models
|
||||||
|
- Machine learning predictions
|
||||||
|
- VORP (Value Over Replacement Player)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Data (NHL - November 2025)
|
||||||
|
|
||||||
|
### Futures Odds (Championship)
|
||||||
|
```
|
||||||
|
Colorado Avalanche: +550 (15.4%)
|
||||||
|
Florida Panthers: +800 (11.1%)
|
||||||
|
Vegas Golden Knights: +800 (11.1%)
|
||||||
|
Tampa Bay Lightning: +1000 (9.1%)
|
||||||
|
New Jersey Devils: +1400 (6.7%)
|
||||||
|
Toronto Maple Leafs: +2500 (3.8%)
|
||||||
|
NY Rangers: +4000 (2.4%)
|
||||||
|
Detroit Red Wings: +7500 (1.3%)
|
||||||
|
Vancouver Canucks: +7500 (1.3%)
|
||||||
|
NY Islanders: +15000 (0.66%)
|
||||||
|
Nashville Predators: +40000 (0.24%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Game Lines (November 16, 2025)
|
||||||
|
```
|
||||||
|
Colorado (-270, 73.0%) vs NY Islanders (+220, 31.3%)
|
||||||
|
Tampa Bay (-170, 63.0%) vs Vancouver (+145, 40.8%)
|
||||||
|
Vegas (-130, 56.5%) vs Minnesota (+110, 47.6%)
|
||||||
|
NY Rangers (-145, 59.2%) vs Detroit (+125, 44.4%)
|
||||||
|
Pittsburgh (-125, 55.6%) vs Nashville (+105, 48.8%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Calibration Goals
|
||||||
|
- Predict game lines within ±5% of actual betting odds
|
||||||
|
- Use these 5 matchups to calibrate Elo transformation
|
||||||
|
- Validate that approach works across wide range (favorites vs underdogs)
|
||||||
|
|
||||||
|
## Elo Conversion Algorithm (Empirically Calibrated)
|
||||||
|
|
||||||
|
### Algorithm Overview
|
||||||
|
|
||||||
|
**Problem**: Championship futures are compressed over multiple rounds. We need to "decompress" them to single-game strength.
|
||||||
|
|
||||||
|
**Solution**: Power transformation + Elo mapping
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Step 1: Convert American odds to championship probability
|
||||||
|
function americanOddsToProb(odds: number): number {
|
||||||
|
return 1 / (1 + Math.abs(odds) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Apply power transformation to decompress
|
||||||
|
// Exponent empirically calibrated to match game lines
|
||||||
|
function decompressProbability(
|
||||||
|
championshipProb: number,
|
||||||
|
exponent: number = 0.33 // Cube root, configurable
|
||||||
|
): number {
|
||||||
|
return Math.pow(championshipProb * 100, exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Normalize to Elo scale
|
||||||
|
function mapToElo(
|
||||||
|
strength: number,
|
||||||
|
minStrength: number,
|
||||||
|
maxStrength: number,
|
||||||
|
eloMin: number = 1250,
|
||||||
|
eloMax: number = 1750
|
||||||
|
): number {
|
||||||
|
const normalized = (strength - minStrength) / (maxStrength - minStrength);
|
||||||
|
return eloMin + (normalized * (eloMax - eloMin));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Calculate head-to-head probability
|
||||||
|
function eloWinProbability(eloA: number, eloB: number): number {
|
||||||
|
return 1 / (1 + Math.pow(10, (eloB - eloA) / 400));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Calibration Process
|
||||||
|
|
||||||
|
**Phase 5.1.6**: Build calibration tool
|
||||||
|
- Input: 5+ matchups with futures odds and actual game lines
|
||||||
|
- Adjust `exponent` parameter until predictions match actuals
|
||||||
|
- Target: ±5% average error
|
||||||
|
- Store calibrated parameters per sport
|
||||||
|
|
||||||
|
### Example Calculation
|
||||||
|
|
||||||
|
**Colorado vs NY Islanders:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Futures
|
||||||
|
const colOdds = 550; // 15.4% championship
|
||||||
|
const nyiOdds = 15000; // 0.66% championship
|
||||||
|
|
||||||
|
// Step 1: Championship probabilities
|
||||||
|
const colChamp = 0.154;
|
||||||
|
const nyiChamp = 0.0066;
|
||||||
|
|
||||||
|
// Step 2: Decompress with cube root
|
||||||
|
const colStrength = Math.pow(15.4, 0.33) = 2.49;
|
||||||
|
const nyiStrength = Math.pow(0.66, 0.33) = 0.87;
|
||||||
|
|
||||||
|
// Step 3: Map to Elo (assume min=0.5, max=3.0 for all teams)
|
||||||
|
const colElo = 1250 + ((2.49 - 0.5) / (3.0 - 0.5)) * 500 = 1648;
|
||||||
|
const nyiElo = 1250 + ((0.87 - 0.5) / (3.0 - 0.5)) * 500 = 1324;
|
||||||
|
|
||||||
|
// Step 4: Win probability
|
||||||
|
const eloDiff = 1648 - 1324 = 324;
|
||||||
|
const winProb = 1 / (1 + 10^(-324/400)) = 82.8%
|
||||||
|
|
||||||
|
// Actual line: 73.0%
|
||||||
|
// Error: 9.8% (needs calibration adjustment)
|
||||||
|
```
|
||||||
|
|
||||||
|
By adjusting the exponent from 0.33 to ~0.38, we can get closer to 73%.
|
||||||
|
|
||||||
|
## Implementation Plan Updates
|
||||||
|
|
||||||
|
### Phase 5.1 (MVP - Manual Entry)
|
||||||
|
- No changes (manual probability entry)
|
||||||
|
|
||||||
|
### Phase 5.2 (Elo-Based Simulation)
|
||||||
|
|
||||||
|
**Updated task list:**
|
||||||
|
|
||||||
|
- [ ] **5.2.1** Elo conversion with calibration
|
||||||
|
- [ ] `convertAmericanOddsToProbability(odds)` - +500 → 16.7%
|
||||||
|
- [ ] `decompressProbability(prob, exponent)` - Apply power transformation
|
||||||
|
- [ ] `mapToElo(strength, params)` - Normalize to Elo scale
|
||||||
|
- [ ] `eloWinProbability(eloA, eloB)` - Standard Elo formula
|
||||||
|
- [ ] Make exponent and Elo range configurable per sport
|
||||||
|
- [ ] Unit tests (20+ tests with known values)
|
||||||
|
|
||||||
|
- [ ] **5.2.2** Calibration tool (NEW)
|
||||||
|
- [ ] Admin route: `/admin/sports-seasons/:id/calibrate`
|
||||||
|
- [ ] Enter actual game matchups with betting lines
|
||||||
|
- [ ] Calculate our predicted probabilities
|
||||||
|
- [ ] Show comparison table (predicted vs actual)
|
||||||
|
- [ ] Adjust exponent slider → see predictions update live
|
||||||
|
- [ ] Save calibrated parameters to `sportsSeasons` table
|
||||||
|
- [ ] Display accuracy metrics (avg error, max error)
|
||||||
|
|
||||||
|
- [ ] **5.2.3** Bracket simulator
|
||||||
|
- [ ] `initializeBracket(teams, format)` - NHL/NFL structure
|
||||||
|
- [ ] `simulatePlayoffs(teams, sims)` - Monte Carlo with Elo
|
||||||
|
- [ ] `recordPlacements(bracket)` - Track 1st, 2nd, 3-4, 5-8
|
||||||
|
- [ ] 100,000 simulations (configurable)
|
||||||
|
- [ ] Progress indicator for long simulations
|
||||||
|
|
||||||
|
- [ ] **5.2.4** Admin UI - Futures odds entry
|
||||||
|
- [ ] Route: `/admin/sports-seasons/:id/futures-odds`
|
||||||
|
- [ ] For each participant: enter American odds (+550, etc.)
|
||||||
|
- [ ] "Generate Probabilities" button
|
||||||
|
- [ ] Shows preview: Team → Elo → P(1st-8th)
|
||||||
|
- [ ] Runs simulation with calibrated parameters
|
||||||
|
- [ ] Confirm to save to `participant_probabilities`
|
||||||
|
|
||||||
|
### New Database Fields
|
||||||
|
|
||||||
|
Add to `sportsSeasons` table:
|
||||||
|
```typescript
|
||||||
|
sportsSeasons {
|
||||||
|
// ... existing fields
|
||||||
|
|
||||||
|
// EV Calibration parameters
|
||||||
|
eloCalibrationExponent: decimal(3,2) (nullable) // e.g., 0.33
|
||||||
|
eloMinRating: integer (default: 1250)
|
||||||
|
eloMaxRating: integer (default: 1750)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ready to Start?
|
||||||
|
|
||||||
|
**Summary of Decisions:**
|
||||||
|
✅ **Sport**: NHL Playoffs
|
||||||
|
✅ **Approach**: Elo-based with empirical calibration
|
||||||
|
✅ **Odds format**: American odds (primary), conversion tools for others
|
||||||
|
✅ **Validation data**: 5 NHL matchups from ESPN
|
||||||
|
✅ **Performance**: Pre-calculate projected totals with snapshots
|
||||||
|
✅ **Monte Carlo**: 100k simulations of entire tournament
|
||||||
|
|
||||||
|
**Next step**: I'll start with Phase 5.1.1 (database schema) once you confirm this approach makes sense!
|
||||||
|
|
||||||
|
The key insight: We'll use the NHL validation data to calibrate the power transformation exponent, ensuring our Elo-based predictions match real betting lines accurately.
|
||||||
325
plans/phase-5-expected-value-planning.md
Normal file
325
plans/phase-5-expected-value-planning.md
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
# Phase 5: Expected Value System - Detailed Planning
|
||||||
|
|
||||||
|
## Current Phase 5 Outline (from scoring-system.md)
|
||||||
|
|
||||||
|
**Goal**: Implement EV calculation and autodraft integration
|
||||||
|
|
||||||
|
- [ ] **5.1** Probability distribution storage
|
||||||
|
- [ ] Create data structure for probabilities (8 placements)
|
||||||
|
- [ ] Admin UI for manual entry (temporary)
|
||||||
|
- [ ] Validation (probabilities sum to 100%)
|
||||||
|
|
||||||
|
- [ ] **5.2** EV calculation engine
|
||||||
|
- [ ] Implement `calculateExpectedValue` function
|
||||||
|
- [ ] League-specific calculation based on scoring rules
|
||||||
|
- [ ] Batch calculation for all participants in a season
|
||||||
|
|
||||||
|
- [ ] **5.3** Daily update job
|
||||||
|
- [ ] Create scheduled job/cron
|
||||||
|
- [ ] Fetch/update probability distributions
|
||||||
|
- [ ] Recalculate all EVs
|
||||||
|
- [ ] Log updates
|
||||||
|
|
||||||
|
- [ ] **5.4** Autodraft ranking integration
|
||||||
|
- [ ] Update draft queue ranking based on EV
|
||||||
|
- [ ] Sort available participants by EV
|
||||||
|
- [ ] Autodraft picks highest EV available
|
||||||
|
|
||||||
|
- [ ] **5.5** Projected totals display
|
||||||
|
- [ ] Calculate projected final points (completed + EV of remaining)
|
||||||
|
- [ ] Display on standings table
|
||||||
|
- [ ] Show in team breakdown
|
||||||
|
|
||||||
|
## Context from Previous Q&A
|
||||||
|
|
||||||
|
**Q9:** Where does the probability distribution (P(1st), P(2nd), etc.) come from?
|
||||||
|
- **Answer**: I'd like to come up with a statistical model. We can likely get some futures to help predict, or we can pull in Elo ratings in some situations and then do some monte carlo simulations.
|
||||||
|
|
||||||
|
**Q10:** Does EV update based on real results or external data?
|
||||||
|
- **Answer**: I'd like to update daily based on real results but also model.
|
||||||
|
|
||||||
|
**Requirements**:
|
||||||
|
- League-specific EV (based on that league's scoring rules)
|
||||||
|
- Updates daily during season
|
||||||
|
- Not shown on draft board (used internally for autodraft rankings initially)
|
||||||
|
- Based on probability distribution of placements for each participant
|
||||||
|
|
||||||
|
## Database Schema (Already Defined)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
participant_expected_values {
|
||||||
|
id: uuid PRIMARY KEY
|
||||||
|
participantId: uuid -> participants.id
|
||||||
|
seasonId: uuid -> seasons.id
|
||||||
|
|
||||||
|
// Probability distribution (stored as percentages)
|
||||||
|
probFirst: decimal(5,2) // e.g., 15.50 = 15.5%
|
||||||
|
probSecond: decimal(5,2)
|
||||||
|
probThird: decimal(5,2)
|
||||||
|
probFourth: decimal(5,2)
|
||||||
|
probFifth: decimal(5,2)
|
||||||
|
probSixth: decimal(5,2)
|
||||||
|
probSeventh: decimal(5,2)
|
||||||
|
probEighth: decimal(5,2)
|
||||||
|
|
||||||
|
// Calculated EV
|
||||||
|
expectedValue: decimal // Based on league scoring
|
||||||
|
|
||||||
|
calculatedAt: timestamp
|
||||||
|
updatedAt: timestamp
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planning Questions
|
||||||
|
|
||||||
|
Please answer these questions inline below each one. Add as much detail as you'd like!
|
||||||
|
|
||||||
|
### Section 1: Data Sources & Initial Setup
|
||||||
|
|
||||||
|
**Q1:** For the initial probability distributions (before we have historical data), what's the priority order?
|
||||||
|
- Option A: Start with manual admin entry as MVP, then add external sources later
|
||||||
|
- Option B: Block Phase 5 until we have at least one external data source integrated
|
||||||
|
- Option C: Other approach?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Manual entry is fine, I'm not entirely sure what the data shape should be yet for you though. Are we thinking using vegas futures, or potentially ELo ratings?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q2:** For Elo ratings:
|
||||||
|
- Should we calculate Elo ourselves based on historical results we enter?
|
||||||
|
- Or fetch from an external source?
|
||||||
|
- Do we need Elo for all sports, or just specific ones (e.g., F1, tennis)?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
I would prefer fetching from an external source to start, and it's only in certain sports. Some sports we'll only have Vegas futures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q3:** For futures odds (betting markets):
|
||||||
|
- Should we fetch these automatically via API?
|
||||||
|
- Or allow admins to manually enter them?
|
||||||
|
- How often do futures odds update (daily, weekly)?
|
||||||
|
- Any specific API providers you have in mind? (Or should we research options?)
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Futures odds can just update weekly, really. I don't have any specific API providers in mind, but if you have an idea or can figure one out, I'd love it. If all we can do is manual, I can make that work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Section 2: Statistical Model Design
|
||||||
|
|
||||||
|
**Q4:** Should different scoring patterns use different probability models?
|
||||||
|
- Playoffs: Bracket-based simulation (team strength × matchup probabilities)?
|
||||||
|
- Season standings (F1): Performance model based on qualifying/race history?
|
||||||
|
- Qualifying points (Golf/Tennis): Tournament-by-tournament predictions?
|
||||||
|
- Or one unified model that adapts to all patterns?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Yes, different scoring patterns should use different probability models, you've got the right idea.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q5:** When combining multiple data sources (futures + Elo + recent performance), how should we weight them?
|
||||||
|
- Equal weighting initially?
|
||||||
|
- Configurable weights per sport?
|
||||||
|
- Adaptive weighting based on data quality?
|
||||||
|
- Other approach?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
I don't think we'll be combining them but also it's fine to weigh them equally I guess? But really I'd just expect us to pick one method to use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q6:** For Monte Carlo simulations:
|
||||||
|
- How many simulations per participant? (1000? 10000?)
|
||||||
|
- What parameters do we simulate? (matchup outcomes, performance variance, etc.)
|
||||||
|
- Should simulation complexity be configurable (simple MVP vs full model)?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
10000 at least, maybe 100000? But we should just simulate the winning odds in the system for everyone and then apply it accordingly, not per participant.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Section 3: Real Results Integration
|
||||||
|
|
||||||
|
**Q7:** When real results come in (e.g., team eliminated from playoffs, golfer wins a major), how should probabilities update?
|
||||||
|
- Automatic recalculation based on predefined rules?
|
||||||
|
- Or manual "Recalculate EVs" button after entering results?
|
||||||
|
- Should updates be immediate or batched with daily job?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
I think a manual recalculate EVs button after entering results makes sense but automatic also fine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q8:** For partial results in qualifying sports:
|
||||||
|
- Example: Golfer wins 1 of 4 majors → their P(1st overall) should increase
|
||||||
|
- How much should it increase? Based on QP standings? Statistical model?
|
||||||
|
- Should we show "current ranking EV" vs "final projected EV"?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
We should take the actual scores of the existing majors, and sim the majors that are remaining, and use those two combined to calculate EV.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q9:** For participants already finished:
|
||||||
|
- Do we still store their probability distribution (all 0% except their final placement)?
|
||||||
|
- Or remove them from EV calculations entirely?
|
||||||
|
- Does a finished participant have EV = actual points?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
A finished participant will always have EV = actual points. Any system that doesn't give them actual points based on already awarded points is a flawed system, because past results should be used as part of how EV is calculated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Section 4: Autodraft & Draft Integration
|
||||||
|
|
||||||
|
**Q10:** Should autodraft ALWAYS pick highest EV available?
|
||||||
|
- Or give users options (highest EV, positional needs, etc.)?
|
||||||
|
- Should it respect manual draft queues at all, or completely override?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Highest EV available is fine for now. It absolutely should not override manual draft queues, that is always first and foremost, no matter what.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q11:** During manual drafting, should we show EV to help users?
|
||||||
|
- Display EV next to each available participant on draft board?
|
||||||
|
- EV-based "recommended picks" feature?
|
||||||
|
- Or keep EV hidden (only used for autodraft rankings)?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Let's keep it hidden and only use it for the sorting order. Players with higher EV should be higher on the list, and really we'll start thinking about VORP for sorting instead of EV, but this is the start of it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q12:** How should draft queue ordering work with EV?
|
||||||
|
- Auto-sort queue by EV with manual override?
|
||||||
|
- Show EV in queue UI to help users order manually?
|
||||||
|
- "Optimize my queue by EV" button?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Don't sort draft queue at all by any sort of EV. The draft queue is only controlled by the user. It's a private number in the draft room, really.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Section 5: UI/UX & Display
|
||||||
|
|
||||||
|
**Q13:** What should users see about probabilities/EV?
|
||||||
|
- Just the final EV number?
|
||||||
|
- Full probability distribution (bar chart showing P(1st), P(2nd), etc.)?
|
||||||
|
- Comparison to other participants ("Top 5% EV" badge)?
|
||||||
|
- All of the above?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Only the final EV number makes sense. We're using that to show projected total points for a manager based on all of their EVs + scored points.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q14:** Where should EV be displayed?
|
||||||
|
- Draft board (during draft)?
|
||||||
|
- Standings page (projected totals)?
|
||||||
|
- Team breakdown page?
|
||||||
|
- Participant detail pages?
|
||||||
|
- All of the above?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Standings page, team breakdown page, participant details pages. Not draft board.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q15:** How do we explain EV to casual users?
|
||||||
|
- Tooltip: "Expected Value is the average points this participant is projected to score"?
|
||||||
|
- Help page with examples?
|
||||||
|
- Simple vs Advanced view toggle?
|
||||||
|
- What wording would resonate with your target users?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Call it projected points, that should be good enough.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Section 6: Performance & Scaling
|
||||||
|
|
||||||
|
**Q16:** For the daily update job:
|
||||||
|
- Update ALL participants in ALL seasons every day?
|
||||||
|
- Or only participants in active/upcoming seasons?
|
||||||
|
- Priority tiers (active drafts update hourly, active seasons daily, completed seasons never)?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Only update participants in active/upcoming seasons. There is no point in updating a completed season, we know what the actual points are.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q17:** League-specific EV calculations:
|
||||||
|
- If we have 100 participants × 50 leagues, that's 5000 EV calculations
|
||||||
|
- Should we batch these efficiently?
|
||||||
|
- Cache EVs and only recalculate when scoring rules change or probabilities update?
|
||||||
|
- Any performance concerns we should address upfront?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
So in my head, we'll calculate the P(1st) through P(8th) and store those percentages. And then we can just reference those numbers when calculating EV based on the points scoring for a league. I don't know if it'll be taxing, what do you think? We could probably cache the results as well I guess?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q18:** For projected totals on standings page:
|
||||||
|
- Calculate on-demand when page loads?
|
||||||
|
- Pre-calculate and store with standings snapshots?
|
||||||
|
- Cache with TTL (e.g., 1 hour)?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
I guess precalculate since we're taking the standings snapshots anyhow? What do you think?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Section 7: Phasing & MVP Scope
|
||||||
|
|
||||||
|
**Q19:** Should we MVP Phase 5 with a simpler approach first?
|
||||||
|
- Option A: Start with manual probability entry only, add statistical model later
|
||||||
|
- Option B: Start with one sport (e.g., NFL playoffs) and one model, expand later
|
||||||
|
- Option C: Build full system from the start (all sports, all models)
|
||||||
|
- Option D: Other approach?
|
||||||
|
|
||||||
|
Which approach do you prefer and why?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
Start with one sport and one model and expand later. Let's start with the base case of using futures odds, since we can likely reuse that in all of the models. We can get more refined afterwards.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q20:** For qualifying points sports (golf/tennis) with two-phase scoring:
|
||||||
|
- Should EV reflect "expected QP" → "expected fantasy points"?
|
||||||
|
- Or just "expected final fantasy points" (skipping QP detail)?
|
||||||
|
- Show both "projected QP total" AND "projected fantasy points"?
|
||||||
|
|
||||||
|
**ANSWER:**
|
||||||
|
From the backend, I would imagine EV is going to try to predict QP. On the frontend, we can just show them projected fantasy points.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Questions / Notes
|
||||||
|
|
||||||
|
Please add any additional thoughts, requirements, or questions you have below:
|
||||||
|
|
||||||
|
**YOUR NOTES:**
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Once you've answered these questions:
|
||||||
|
1. Review your answers
|
||||||
|
2. Let me know when ready
|
||||||
|
3. I'll create a detailed Phase 5 implementation plan with:
|
||||||
|
- Refined task breakdown
|
||||||
|
- Data model specifications
|
||||||
|
- API/service layer design
|
||||||
|
- UI component specifications
|
||||||
|
- Testing strategy
|
||||||
|
- Migration path from simple → complex models
|
||||||
Loading…
Add table
Reference in a new issue