From ee97ab4c7f479a19f8c42e676907f7eefb7da48d Mon Sep 17 00:00:00 2001 From: Steve Freeman Date: Fri, 11 Sep 2026 17:19:20 -0400 Subject: [PATCH 1/2] fix(fantasy): always use FantasyCalc redraft rankings, not dynasty Per user feedback, marketValue/positionRank should reflect current-season redraft value for trade and waiver fairness, not long-term dynasty valuation. resolveLeagueFormat previously derived isDynasty from the Sleeper league's keeper/dynasty settings, so dynasty and keeper leagues were scored against FantasyCalc's dynasty rankings. isDynasty is now always false regardless of league settings, so the FantasyCalc API is queried for redraft values/ranks for every league. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/fantasy/fantasy.service.spec.ts | 8 ++++---- .../backend/src/fantasy/fantasy.service.ts | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/fantasy/fantasy.service.spec.ts b/packages/backend/src/fantasy/fantasy.service.spec.ts index 131062d5..e3e6624c 100644 --- a/packages/backend/src/fantasy/fantasy.service.spec.ts +++ b/packages/backend/src/fantasy/fantasy.service.spec.ts @@ -1466,7 +1466,7 @@ describe('FantasyService', () => { roster_positions: ['QB', 'SUPER_FLEX', 'RB'], scoring_settings: { rec: 1 }, }), - ).toEqual({ isDynasty: true, numQbs: 2, ppr: 1 }); + ).toEqual({ isDynasty: false, numQbs: 2, ppr: 1 }); expect( internals.resolveLeagueFormat({ @@ -1480,7 +1480,7 @@ describe('FantasyService', () => { roster_positions: ['QB', 'QB', 'SUPER_FLEX', 'RB'], scoring_settings: { rec: 0.25 }, }), - ).toEqual({ isDynasty: true, numQbs: 2, ppr: 0.5 }); + ).toEqual({ isDynasty: false, numQbs: 2, ppr: 0.5 }); }); it('fetches and caches FantasyCalc player values scaled to the league format', async () => { @@ -1528,7 +1528,7 @@ describe('FantasyService', () => { expect(Axios.get).toHaveBeenCalledWith( 'https://api.fantasycalc.com/values/current', - expect.objectContaining({ params: { isDynasty: true, numQbs: 1, numTeams: 10, ppr: 0.5 }, timeout: 2000 }), + expect.objectContaining({ params: { isDynasty: false, numQbs: 1, numTeams: 10, ppr: 0.5 }, timeout: 2000 }), ); expect(values.get('p1')).toEqual({ sleeperId: 'p1', @@ -1548,7 +1548,7 @@ describe('FantasyService', () => { const twelveTeamValues = await internals.getFantasyCalcValues(twelveTeamLeague); expect(Axios.get).toHaveBeenCalledWith( 'https://api.fantasycalc.com/values/current', - expect.objectContaining({ params: { isDynasty: true, numQbs: 1, numTeams: 12, ppr: 0.5 }, timeout: 2000 }), + expect.objectContaining({ params: { isDynasty: false, numQbs: 1, numTeams: 12, ppr: 0.5 }, timeout: 2000 }), ); expect(twelveTeamValues.get('p1')?.value).toBe(9100); }); diff --git a/packages/backend/src/fantasy/fantasy.service.ts b/packages/backend/src/fantasy/fantasy.service.ts index f250f13a..27f949fc 100644 --- a/packages/backend/src/fantasy/fantasy.service.ts +++ b/packages/backend/src/fantasy/fantasy.service.ts @@ -406,9 +406,10 @@ export class FantasyService { const numQbs = requestedQbs >= 2 ? 2 : 1; const requestedPpr = league.scoring_settings?.rec; const ppr = requestedPpr === undefined ? 0.5 : requestedPpr >= 0.75 ? 1 : requestedPpr >= 0.25 ? 0.5 : 0; - // Sleeper league format: 0 = redraft, 1 = keeper, 2 = dynasty. Keeper leagues value long-term assets - // similarly to dynasty, so both are treated as dynasty for valuation purposes. - const isDynasty = (league.settings?.type ?? 0) >= 1; + // Trade/waiver fairness cares about a player's current-season value, not long-term dynasty + // valuation, so FantasyCalc is always queried for redraft rankings regardless of the Sleeper + // league's keeper/dynasty settings. + const isDynasty = false; return { isDynasty, numQbs, ppr }; } @@ -424,9 +425,11 @@ export class FantasyService { /** * Fetches consensus player trade values from the FantasyCalc API (https://fantasycalc.com/api-docs), - * matched to the league's format (dynasty/redraft, QB count, PPR). Values are keyed by Sleeper player ID - * so they can be merged directly onto `FantasyPlayer` records. Failures are non-fatal: trade/waiver - * recommendations still work without market values, just with less precise fairness signal. + * always using redraft rankings scaled to the league's QB count and PPR (dynasty valuation is + * intentionally not used, since trade/waiver fairness cares about current-season value). Values + * are keyed by Sleeper player ID so they can be merged directly onto `FantasyPlayer` records. + * Failures are non-fatal: trade/waiver recommendations still work without market values, just + * with less precise fairness signal. */ private async getFantasyCalcValues(league: SleeperLeague): Promise> { const { isDynasty, numQbs, ppr } = this.resolveLeagueFormat(league); @@ -1364,8 +1367,8 @@ export class FantasyService { instructions: 'You are a fantasy football analyst. Return only valid JSON with keys teamHealth, tradeInsights, suggestions, waiverSuggestions, and lineupSummary. ' + 'teamHealth must assess the user roster relative to the supplied league with an integer percentage from 0 to 100 and a concise summary. ' + - "Each player object includes a marketValue (a FantasyCalc consensus trade value already scaled to this league's " + - 'format, dynasty vs redraft, QB count, and PPR - higher is more valuable) and a positionRank (rank among players ' + + "Each player object includes a marketValue (a FantasyCalc consensus redraft trade value already scaled to this league's " + + 'QB count and PPR format - higher is more valuable) and a positionRank (rank among players ' + 'at the same position, 1 is best). A null marketValue means the player is unranked by FantasyCalc or the market-value ' + 'feed was unavailable for this request; in either case fall back to position, roster needs, and matchup context instead ' + 'of value. ' + From a3431bf2deaf01ae3d9c7c7f1ab16e00353c886a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:24:34 +0000 Subject: [PATCH 2/2] Clarify FantasyPlayer marketValue redraft documentation Co-authored-by: sfreeman422 <16405652+sfreeman422@users.noreply.github.com> --- packages/backend/src/fantasy/fantasy.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/fantasy/fantasy.model.ts b/packages/backend/src/fantasy/fantasy.model.ts index d619e2bf..93ba6daa 100644 --- a/packages/backend/src/fantasy/fantasy.model.ts +++ b/packages/backend/src/fantasy/fantasy.model.ts @@ -97,7 +97,7 @@ export interface FantasyPlayer { team: string | null; injuryStatus: string | null; fantasyPositions: string[]; - /** Consensus market value from FantasyCalc for the league's format (dynasty/redraft, QB count, PPR). Null if unranked. */ + /** Consensus market value from FantasyCalc redraft rankings for the league's QB count and PPR. Null if unranked. */ marketValue: number | null; /** FantasyCalc rank among players at the same position. Null if unranked. */ positionRank: number | null;