147 lines
3.6 KiB
TypeScript
147 lines
3.6 KiB
TypeScript
import { database } from "~/database/context";
|
|
import * as schema from "~/database/schema";
|
|
import { eq } from "drizzle-orm";
|
|
|
|
/**
|
|
* Scoring rules configuration for a season
|
|
* These define the points awarded for each placement (1st through 8th)
|
|
*/
|
|
export interface ScoringRules {
|
|
pointsFor1st: number;
|
|
pointsFor2nd: number;
|
|
pointsFor3rd: number;
|
|
pointsFor4th: number;
|
|
pointsFor5th: number;
|
|
pointsFor6th: number;
|
|
pointsFor7th: number;
|
|
pointsFor8th: number;
|
|
}
|
|
|
|
/**
|
|
* Default scoring rules used when creating new seasons
|
|
*/
|
|
export const DEFAULT_SCORING_RULES: ScoringRules = {
|
|
pointsFor1st: 100,
|
|
pointsFor2nd: 70,
|
|
pointsFor3rd: 50,
|
|
pointsFor4th: 40,
|
|
pointsFor5th: 25,
|
|
pointsFor6th: 25,
|
|
pointsFor7th: 15,
|
|
pointsFor8th: 15,
|
|
};
|
|
|
|
/**
|
|
* Get scoring rules for a season
|
|
*/
|
|
export async function getScoringRules(
|
|
seasonId: string,
|
|
providedDb?: ReturnType<typeof database>
|
|
): Promise<ScoringRules | null> {
|
|
const db = providedDb || database();
|
|
|
|
const season = await db.query.seasons.findFirst({
|
|
where: eq(schema.seasons.id, seasonId),
|
|
});
|
|
|
|
if (!season) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
pointsFor1st: season.pointsFor1st,
|
|
pointsFor2nd: season.pointsFor2nd,
|
|
pointsFor3rd: season.pointsFor3rd,
|
|
pointsFor4th: season.pointsFor4th,
|
|
pointsFor5th: season.pointsFor5th,
|
|
pointsFor6th: season.pointsFor6th,
|
|
pointsFor7th: season.pointsFor7th,
|
|
pointsFor8th: season.pointsFor8th,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update scoring rules for a season
|
|
*/
|
|
export async function updateScoringRules(
|
|
seasonId: string,
|
|
rules: Partial<ScoringRules>,
|
|
providedDb?: ReturnType<typeof database>
|
|
): Promise<ScoringRules> {
|
|
const db = providedDb || database();
|
|
|
|
const [updated] = await db
|
|
.update(schema.seasons)
|
|
.set(rules)
|
|
.where(eq(schema.seasons.id, seasonId))
|
|
.returning({
|
|
pointsFor1st: schema.seasons.pointsFor1st,
|
|
pointsFor2nd: schema.seasons.pointsFor2nd,
|
|
pointsFor3rd: schema.seasons.pointsFor3rd,
|
|
pointsFor4th: schema.seasons.pointsFor4th,
|
|
pointsFor5th: schema.seasons.pointsFor5th,
|
|
pointsFor6th: schema.seasons.pointsFor6th,
|
|
pointsFor7th: schema.seasons.pointsFor7th,
|
|
pointsFor8th: schema.seasons.pointsFor8th,
|
|
});
|
|
|
|
return updated;
|
|
}
|
|
|
|
/**
|
|
* Calculate fantasy points for a given placement based on season scoring rules
|
|
*/
|
|
export function calculateFantasyPoints(
|
|
placement: number,
|
|
rules: ScoringRules
|
|
): number {
|
|
const pointsMap: Record<number, number> = {
|
|
1: rules.pointsFor1st,
|
|
2: rules.pointsFor2nd,
|
|
3: rules.pointsFor3rd,
|
|
4: rules.pointsFor4th,
|
|
5: rules.pointsFor5th,
|
|
6: rules.pointsFor6th,
|
|
7: rules.pointsFor7th,
|
|
8: rules.pointsFor8th,
|
|
};
|
|
|
|
return pointsMap[placement] || 0;
|
|
}
|
|
|
|
/**
|
|
* Calculate averaged points for shared placements (e.g., playoff ties)
|
|
* Used when multiple participants share positions
|
|
*
|
|
* Example: 4 teams lose in quarterfinals, they share positions 5-8
|
|
* Average = (25 + 25 + 15 + 15) / 4 = 20 points each
|
|
*/
|
|
export function calculateAveragedPoints(
|
|
placements: number[],
|
|
rules: ScoringRules
|
|
): number {
|
|
if (placements.length === 0) return 0;
|
|
|
|
const total = placements.reduce((sum, placement) => {
|
|
return sum + calculateFantasyPoints(placement, rules);
|
|
}, 0);
|
|
|
|
return total / placements.length;
|
|
}
|
|
|
|
/**
|
|
* Get points array as a simple ordered list [1st, 2nd, 3rd, ..., 8th]
|
|
* Useful for display purposes
|
|
*/
|
|
export function getScoringRulesArray(rules: ScoringRules): number[] {
|
|
return [
|
|
rules.pointsFor1st,
|
|
rules.pointsFor2nd,
|
|
rules.pointsFor3rd,
|
|
rules.pointsFor4th,
|
|
rules.pointsFor5th,
|
|
rules.pointsFor6th,
|
|
rules.pointsFor7th,
|
|
rules.pointsFor8th,
|
|
];
|
|
}
|