feat: add calculateReplacementLevel and calculateVORP to ev-calculator
This commit is contained in:
parent
30903bec19
commit
22ec6381b8
2 changed files with 82 additions and 0 deletions
|
|
@ -4,6 +4,8 @@ import {
|
|||
validateProbabilities,
|
||||
normalizeProbabilities,
|
||||
calculateProjectedTotal,
|
||||
calculateReplacementLevel,
|
||||
calculateVORP,
|
||||
type ScoringRules,
|
||||
type ProbabilityDistribution,
|
||||
} from "../ev-calculator";
|
||||
|
|
@ -359,3 +361,48 @@ describe("calculateProjectedTotal", () => {
|
|||
expect(result.projectedPoints).toBe(156.37); // Rounded
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateReplacementLevel", () => {
|
||||
it("returns average EV of 12th-14th players (indices 11-13) when 14+ players exist", () => {
|
||||
const evs = Array.from({ length: 16 }, (_, i) => 100 - i * 10);
|
||||
// evs = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0, -10, -20, -30, -40, -50]
|
||||
// indices 11-13 = -10, -20, -30 → avg = -20
|
||||
expect(calculateReplacementLevel(evs)).toBeCloseTo(-20);
|
||||
});
|
||||
|
||||
it("returns average EV of 12th-14th when exactly 14 players", () => {
|
||||
const evs = Array.from({ length: 14 }, (_, i) => 100 - i * 5);
|
||||
// evs[11] = 45, evs[12] = 40, evs[13] = 35 → avg = 40
|
||||
expect(calculateReplacementLevel(evs)).toBeCloseTo(40);
|
||||
});
|
||||
|
||||
it("uses available players starting at index 11 when fewer than 14", () => {
|
||||
const evs = [50, 40, 30, 20, 15, 12, 10, 8, 6, 4, 2, 1]; // 12 players
|
||||
// Only index 11: evs[11] = 1 → avg = 1
|
||||
expect(calculateReplacementLevel(evs)).toBeCloseTo(1);
|
||||
});
|
||||
|
||||
it("uses last player when fewer than 12 players", () => {
|
||||
const evs = [50, 40, 30, 20, 10]; // 5 players
|
||||
// Use last player: evs[4] = 10
|
||||
expect(calculateReplacementLevel(evs)).toBeCloseTo(10);
|
||||
});
|
||||
|
||||
it("returns 0 for empty array", () => {
|
||||
expect(calculateReplacementLevel([])).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateVORP", () => {
|
||||
it("returns ev minus replacement level", () => {
|
||||
expect(calculateVORP(75, 40)).toBeCloseTo(35);
|
||||
});
|
||||
|
||||
it("returns negative value when ev is below replacement level", () => {
|
||||
expect(calculateVORP(20, 40)).toBeCloseTo(-20);
|
||||
});
|
||||
|
||||
it("returns 0 when ev equals replacement level", () => {
|
||||
expect(calculateVORP(40, 40)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -168,3 +168,38 @@ export function calculateProjectedTotal(
|
|||
participantsRemaining: remainingParticipantEVs.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the replacement level EV for a sport's season.
|
||||
*
|
||||
* Replacement level = average EV of players ranked 12th–14th (1-indexed).
|
||||
* These are the last picks in a standard 12-team draft, representing the
|
||||
* baseline any manager could pick up off the waiver wire.
|
||||
*
|
||||
* @param sortedEvs - All participant EVs for a sport season, sorted descending
|
||||
* @returns Replacement level EV
|
||||
*/
|
||||
export function calculateReplacementLevel(sortedEvs: number[]): number {
|
||||
if (sortedEvs.length === 0) return 0;
|
||||
|
||||
// Target: average EVs at 1-indexed positions 12-14 (0-indexed: 11-13)
|
||||
const startIdx = Math.min(11, sortedEvs.length - 1);
|
||||
const endIdx = Math.min(13, sortedEvs.length - 1);
|
||||
const slice = sortedEvs.slice(startIdx, endIdx + 1);
|
||||
const avg = slice.reduce((sum, ev) => sum + ev, 0) / slice.length;
|
||||
return avg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Value Over Replacement Player (VORP).
|
||||
*
|
||||
* VORP = participant EV − replacement level EV
|
||||
* Positive = better than replacement; negative = worse than replacement.
|
||||
*
|
||||
* @param ev - Participant's expected value
|
||||
* @param replacementLevel - Replacement level EV from calculateReplacementLevel
|
||||
* @returns VORP value
|
||||
*/
|
||||
export function calculateVORP(ev: number, replacementLevel: number): number {
|
||||
return ev - replacementLevel;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue