Ground waiver bids in league history - #282
Conversation
Use recency-weighted historical FAAB spend per projected point from linked Sleeper seasons to produce deterministic, budget-aware waiver recommendations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new caching and pricing paths introduce avoidable memory growth and unnecessary repeated linear scans that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds deterministic, history-grounded waiver bid recommendations by deriving a “market” dollars-per-projected-point rate from completed Sleeper FAAB waiver transactions across linked league history, then applying that rate (position-aware when enough samples exist) to current waiver candidates and remaining budget caps.
Changes:
- Introduces historical waiver bid guidance computation + caching, and injects the resulting guidance into both the AI prompt/input and the final waiver suggestions.
- Extends the Sleeper league model to include
previous_league_idand adds market-sample + weighted-median pricing logic. - Adds regression tests covering prior-season pricing and sparse/position-specific/budget-capped markets.
File summaries
| File | Description |
|---|---|
| packages/backend/src/fantasy/fantasy.service.ts | Computes/caches historical waiver market samples, derives suggested bids, and applies them to AI + waiver suggestions. |
| packages/backend/src/fantasy/fantasy.service.spec.ts | Adds/updates tests validating historical pricing behavior and bid application. |
| packages/backend/src/fantasy/fantasy.model.ts | Adds previous_league_id and introduces WaiverBidGuidance model. |
Review details
Suppressed comments (1)
packages/backend/src/fantasy/fantasy.service.ts:387
waiverMarketCacheentries are never removed when expired, so stale league/week samples can accumulate and grow memory usage over time. Delete the cache entry when it is found but expired before recomputing samples.
const cacheKey = `${league.league_id}:${state.season}:${state.week}`;
const cached = this.waiverMarketCache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) {
return this.buildWaiverBidGuidance(cached.samples, league, currentProjections, candidates, remainingBudget);
}
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const key = `${season}:${week}`; | ||
| const cached = this.projectionCache.get(key); | ||
| if (cached && cached.expiresAt > Date.now()) return Promise.resolve(cached.projections); |
| const allRate = this.weightedMedian(samples); | ||
| const suggestedBids = | ||
| allRate === null | ||
| ? {} | ||
| : Object.fromEntries( | ||
| candidates.flatMap((candidate) => { | ||
| const projection = currentProjections.find((item) => item.player_id === candidate.id); | ||
| const points = this.projectedPoints(projection?.stats, league.scoring_settings); | ||
| if (points === null || points <= 0) return []; | ||
| const positionSamples = samples.filter((sample) => sample.position === candidate.position); | ||
| const rate = this.weightedMedian(positionSamples.length >= 5 ? positionSamples : samples) ?? allRate; | ||
| const budget = Math.max(1, league.settings?.waiver_budget ?? 100); | ||
| const marketBid = Math.max(1, Math.round(rate * points * budget)); | ||
| return [[candidate.id, Math.min(remainingBudget, marketBid)]]; | ||
| }), | ||
| ); | ||
| return { sampleSize: samples.length, suggestedBids }; |
Summary
Testing
npm run test:coverage:all