2025-10-28 23:50:50 -07:00
|
|
|
import { database } from "~/database/context";
|
|
|
|
|
import * as schema from "~/database/schema";
|
|
|
|
|
import { eq, and, inArray } from "drizzle-orm";
|
|
|
|
|
import { getScoringRules, calculateFantasyPoints, calculateAveragedPoints } from "./scoring-rules";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Core scoring calculation engine
|
|
|
|
|
* This file contains the logic for calculating fantasy points based on participant results
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export type ScoringPattern =
|
|
|
|
|
| "single_elimination_playoff"
|
|
|
|
|
| "page_playoff"
|
|
|
|
|
| "season_standings"
|
|
|
|
|
| "qualifying_points";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process a playoff event completion and assign final placements
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
*
|
|
|
|
|
* Q19: Admin enters the same placement for all tied participants,
|
|
|
|
|
* system automatically shares positions
|
|
|
|
|
*
|
|
|
|
|
* Q20: Participants eliminated before Elite Eight get 0 points
|
2025-11-04 22:09:44 -08:00
|
|
|
*
|
|
|
|
|
* Phase 2.7: Updated to support template-based brackets with non-scoring rounds
|
2025-10-28 23:50:50 -07:00
|
|
|
*/
|
|
|
|
|
export async function processPlayoffEvent(
|
|
|
|
|
eventId: string,
|
|
|
|
|
providedDb?: ReturnType<typeof database>
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const db = providedDb || database();
|
|
|
|
|
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
// Get the event
|
|
|
|
|
const event = await db.query.scoringEvents.findFirst({
|
|
|
|
|
where: eq(schema.scoringEvents.id, eventId),
|
|
|
|
|
with: {
|
|
|
|
|
sportsSeason: {
|
|
|
|
|
with: {
|
|
|
|
|
seasonSports: {
|
|
|
|
|
with: {
|
|
|
|
|
season: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-10-28 23:50:50 -07:00
|
|
|
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
if (!event) {
|
|
|
|
|
throw new Error(`Event ${eventId} not found`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!event.playoffRound) {
|
|
|
|
|
throw new Error(`Event ${eventId} is not a playoff event`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 22:09:44 -08:00
|
|
|
// Get all matches for this event in the specified round
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
const matches = await db.query.playoffMatches.findMany({
|
2025-11-04 22:09:44 -08:00
|
|
|
where: and(
|
|
|
|
|
eq(schema.playoffMatches.scoringEventId, eventId),
|
|
|
|
|
eq(schema.playoffMatches.round, event.playoffRound)
|
|
|
|
|
),
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
orderBy: (matches, { asc }) => [asc(matches.matchNumber)],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Process based on round
|
|
|
|
|
const round = event.playoffRound;
|
|
|
|
|
|
2025-11-04 22:09:44 -08:00
|
|
|
// Check if this is a scoring round
|
|
|
|
|
// First try to get isScoring from the match (template-based)
|
|
|
|
|
const isScoring = matches[0]?.isScoring ?? true; // Default to true for legacy brackets
|
|
|
|
|
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
// Get season scoring rules for the first affected season
|
|
|
|
|
// (all seasons should have their own rules, but we use the event's sports season to find one)
|
|
|
|
|
const seasonSport = event.sportsSeason.seasonSports[0];
|
|
|
|
|
if (!seasonSport) {
|
|
|
|
|
throw new Error(`No fantasy seasons found for sports season ${event.sportsSeasonId}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const scoringRules = await getScoringRules(seasonSport.seasonId, db);
|
|
|
|
|
if (!scoringRules) {
|
|
|
|
|
throw new Error(`Scoring rules not found for season ${seasonSport.seasonId}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 22:09:44 -08:00
|
|
|
// If this is a non-scoring round, award 0 points (Q20)
|
|
|
|
|
if (!isScoring) {
|
|
|
|
|
for (const match of matches) {
|
|
|
|
|
if (match.loserId) {
|
|
|
|
|
await upsertParticipantResult(
|
|
|
|
|
match.loserId,
|
|
|
|
|
event.sportsSeasonId,
|
|
|
|
|
0, // 0 placement = 0 points for early elimination
|
|
|
|
|
db
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, process scoring rounds based on standard playoff logic
|
|
|
|
|
else if (round === "Finals" || round === "Championship" || round === "Super Bowl" || round === "NBA Finals") {
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
// Winner gets 1st, loser gets 2nd
|
|
|
|
|
const finalMatch = matches[0];
|
|
|
|
|
if (!finalMatch || !finalMatch.winnerId || !finalMatch.loserId) {
|
|
|
|
|
throw new Error("Finals match is not complete");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update or create participant results for winner
|
|
|
|
|
await upsertParticipantResult(
|
|
|
|
|
finalMatch.winnerId,
|
|
|
|
|
event.sportsSeasonId,
|
|
|
|
|
1,
|
|
|
|
|
db
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Update or create participant results for loser
|
|
|
|
|
await upsertParticipantResult(
|
|
|
|
|
finalMatch.loserId,
|
|
|
|
|
event.sportsSeasonId,
|
|
|
|
|
2,
|
|
|
|
|
db
|
|
|
|
|
);
|
2025-11-04 22:09:44 -08:00
|
|
|
} else if (round === "Semifinals" || round === "Final Four" || round === "Conference Finals" || round === "Conference Championship") {
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
// Losers share 3rd/4th
|
|
|
|
|
for (const match of matches) {
|
|
|
|
|
if (match.loserId) {
|
|
|
|
|
await upsertParticipantResult(
|
|
|
|
|
match.loserId,
|
|
|
|
|
event.sportsSeasonId,
|
|
|
|
|
3, // Store as placement 3 (the first shared placement)
|
|
|
|
|
db
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-04 22:09:44 -08:00
|
|
|
} else if (round === "Quarterfinals" || round === "Elite Eight" || round === "Divisional" || round === "Conference Semifinals") {
|
feat: Implement bracket expansion plan to support various tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
2025-11-03 09:36:16 -08:00
|
|
|
// Losers share 5th-8th
|
|
|
|
|
for (const match of matches) {
|
|
|
|
|
if (match.loserId) {
|
|
|
|
|
await upsertParticipantResult(
|
|
|
|
|
match.loserId,
|
|
|
|
|
event.sportsSeasonId,
|
|
|
|
|
5, // Store as placement 5 (the first shared placement)
|
|
|
|
|
db
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Recalculate standings for all affected leagues
|
|
|
|
|
await recalculateAffectedLeagues(event.sportsSeasonId, db);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper to upsert participant result
|
|
|
|
|
*/
|
|
|
|
|
async function upsertParticipantResult(
|
|
|
|
|
participantId: string,
|
|
|
|
|
sportsSeasonId: string,
|
|
|
|
|
finalPosition: number,
|
|
|
|
|
db: ReturnType<typeof database>
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const existing = await db.query.participantResults.findFirst({
|
|
|
|
|
where: and(
|
|
|
|
|
eq(schema.participantResults.participantId, participantId),
|
|
|
|
|
eq(schema.participantResults.sportsSeasonId, sportsSeasonId)
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
await db
|
|
|
|
|
.update(schema.participantResults)
|
|
|
|
|
.set({
|
|
|
|
|
finalPosition,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(eq(schema.participantResults.id, existing.id));
|
|
|
|
|
} else {
|
|
|
|
|
await db.insert(schema.participantResults).values({
|
|
|
|
|
participantId,
|
|
|
|
|
sportsSeasonId,
|
|
|
|
|
finalPosition,
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-10-28 23:50:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process a qualifying event completion and update QP totals
|
|
|
|
|
* Implementation will be added in Phase 3
|
|
|
|
|
*/
|
|
|
|
|
export async function processQualifyingEvent(
|
|
|
|
|
eventId: string,
|
|
|
|
|
providedDb?: ReturnType<typeof database>
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const db = providedDb || database();
|
|
|
|
|
|
|
|
|
|
// TODO: Implement in Phase 3
|
|
|
|
|
// 1. Get event results
|
|
|
|
|
// 2. Award qualifying points based on placement
|
|
|
|
|
// 3. Update participant_qualifying_totals
|
|
|
|
|
// 4. Check if all majors complete
|
|
|
|
|
// 5. Update QP standings displays
|
|
|
|
|
|
|
|
|
|
console.log(`[ScoringCalculator] processQualifyingEvent not yet implemented: ${eventId}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Finalize qualifying points and convert to fantasy placements
|
|
|
|
|
* Called manually by admin when all majors are complete
|
|
|
|
|
* Implementation will be added in Phase 3
|
|
|
|
|
*/
|
|
|
|
|
export async function finalizeQualifyingPoints(
|
|
|
|
|
sportsSeasonId: string,
|
|
|
|
|
providedDb?: ReturnType<typeof database>
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const db = providedDb || database();
|
|
|
|
|
|
|
|
|
|
// TODO: Implement in Phase 3
|
|
|
|
|
// 1. Get all participants ranked by total QP
|
|
|
|
|
// 2. Handle ties in QP (share placements)
|
|
|
|
|
// 3. Assign final placements (1-8) based on QP ranking
|
|
|
|
|
// 4. Update participantResults with final placements
|
|
|
|
|
// 5. Mark sports season as finalized
|
|
|
|
|
// 6. Trigger recalculation for all affected leagues
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`[ScoringCalculator] finalizeQualifyingPoints not yet implemented: ${sportsSeasonId}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process season standings (F1) and assign final placements
|
|
|
|
|
* Implementation will be added in Phase 3
|
|
|
|
|
*/
|
|
|
|
|
export async function processSeasonStandings(
|
|
|
|
|
sportsSeasonId: string,
|
|
|
|
|
providedDb?: ReturnType<typeof database>
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const db = providedDb || database();
|
|
|
|
|
|
|
|
|
|
// TODO: Implement in Phase 3
|
|
|
|
|
// 1. Get final season standings from participant_season_results
|
|
|
|
|
// 2. Assign final placements (1-8) based on standings
|
|
|
|
|
// 3. Update participantResults with final placements
|
|
|
|
|
// 4. Trigger recalculation for all affected leagues
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`[ScoringCalculator] processSeasonStandings not yet implemented: ${sportsSeasonId}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calculate total fantasy points for a team in a specific season
|
|
|
|
|
* Sums points from all drafted participants based on their final placements
|
|
|
|
|
*/
|
|
|
|
|
export async function calculateTeamScore(
|
|
|
|
|
teamId: string,
|
|
|
|
|
seasonId: string,
|
|
|
|
|
providedDb?: ReturnType<typeof database>
|
|
|
|
|
): Promise<{
|
|
|
|
|
totalPoints: number;
|
|
|
|
|
placementCounts: Record<number, number>;
|
|
|
|
|
participantsCompleted: number;
|
|
|
|
|
participantsTotal: number;
|
|
|
|
|
}> {
|
|
|
|
|
const db = providedDb || database();
|
|
|
|
|
|
|
|
|
|
// Get season scoring rules
|
|
|
|
|
const scoringRules = await getScoringRules(seasonId, db);
|
|
|
|
|
if (!scoringRules) {
|
|
|
|
|
throw new Error(`Season ${seasonId} not found`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all draft picks for this team
|
|
|
|
|
const picks = await db.query.draftPicks.findMany({
|
|
|
|
|
where: and(
|
|
|
|
|
eq(schema.draftPicks.teamId, teamId),
|
|
|
|
|
eq(schema.draftPicks.seasonId, seasonId)
|
|
|
|
|
),
|
|
|
|
|
with: {
|
|
|
|
|
participant: {
|
|
|
|
|
with: {
|
|
|
|
|
results: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let totalPoints = 0;
|
|
|
|
|
const placementCounts: Record<number, number> = {
|
|
|
|
|
1: 0,
|
|
|
|
|
2: 0,
|
|
|
|
|
3: 0,
|
|
|
|
|
4: 0,
|
|
|
|
|
5: 0,
|
|
|
|
|
6: 0,
|
|
|
|
|
7: 0,
|
|
|
|
|
8: 0,
|
|
|
|
|
};
|
|
|
|
|
let participantsCompleted = 0;
|
|
|
|
|
|
|
|
|
|
for (const pick of picks) {
|
|
|
|
|
const result = pick.participant.results[0]; // Should only be one result per participant
|
|
|
|
|
|
|
|
|
|
if (result && result.finalPosition) {
|
|
|
|
|
const points = calculateFantasyPoints(result.finalPosition, scoringRules);
|
|
|
|
|
totalPoints += points;
|
|
|
|
|
participantsCompleted++;
|
|
|
|
|
|
|
|
|
|
// Track placement for tiebreakers
|
|
|
|
|
if (result.finalPosition >= 1 && result.finalPosition <= 8) {
|
|
|
|
|
placementCounts[result.finalPosition]++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
totalPoints,
|
|
|
|
|
placementCounts,
|
|
|
|
|
participantsCompleted,
|
|
|
|
|
participantsTotal: picks.length,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Recalculate standings for all teams in a season
|
|
|
|
|
* Called after any scoring event completes or participant results change
|
|
|
|
|
* Implementation will be completed in Phase 4
|
|
|
|
|
*/
|
|
|
|
|
export async function recalculateStandings(
|
|
|
|
|
seasonId: string,
|
|
|
|
|
providedDb?: ReturnType<typeof database>
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const db = providedDb || database();
|
|
|
|
|
|
|
|
|
|
// TODO: Complete implementation in Phase 4
|
|
|
|
|
// For now, calculate basic scores without rankings
|
|
|
|
|
|
|
|
|
|
const teams = await db.query.teams.findMany({
|
|
|
|
|
where: eq(schema.teams.seasonId, seasonId),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const team of teams) {
|
|
|
|
|
const teamScore = await calculateTeamScore(team.id, seasonId, db);
|
|
|
|
|
|
|
|
|
|
// Upsert team standings
|
|
|
|
|
const existing = await db.query.teamStandings.findFirst({
|
|
|
|
|
where: and(
|
|
|
|
|
eq(schema.teamStandings.teamId, team.id),
|
|
|
|
|
eq(schema.teamStandings.seasonId, seasonId)
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
await db
|
|
|
|
|
.update(schema.teamStandings)
|
|
|
|
|
.set({
|
|
|
|
|
totalPoints: teamScore.totalPoints.toString(),
|
|
|
|
|
firstPlaceCount: teamScore.placementCounts[1],
|
|
|
|
|
secondPlaceCount: teamScore.placementCounts[2],
|
|
|
|
|
thirdPlaceCount: teamScore.placementCounts[3],
|
|
|
|
|
fourthPlaceCount: teamScore.placementCounts[4],
|
|
|
|
|
fifthPlaceCount: teamScore.placementCounts[5],
|
|
|
|
|
sixthPlaceCount: teamScore.placementCounts[6],
|
|
|
|
|
seventhPlaceCount: teamScore.placementCounts[7],
|
|
|
|
|
eighthPlaceCount: teamScore.placementCounts[8],
|
|
|
|
|
participantsRemaining:
|
|
|
|
|
teamScore.participantsTotal - teamScore.participantsCompleted,
|
|
|
|
|
calculatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(eq(schema.teamStandings.id, existing.id));
|
|
|
|
|
} else {
|
|
|
|
|
await db.insert(schema.teamStandings).values({
|
|
|
|
|
teamId: team.id,
|
|
|
|
|
seasonId,
|
|
|
|
|
totalPoints: teamScore.totalPoints.toString(),
|
|
|
|
|
currentRank: 1, // Will be updated when we add ranking logic
|
|
|
|
|
firstPlaceCount: teamScore.placementCounts[1],
|
|
|
|
|
secondPlaceCount: teamScore.placementCounts[2],
|
|
|
|
|
thirdPlaceCount: teamScore.placementCounts[3],
|
|
|
|
|
fourthPlaceCount: teamScore.placementCounts[4],
|
|
|
|
|
fifthPlaceCount: teamScore.placementCounts[5],
|
|
|
|
|
sixthPlaceCount: teamScore.placementCounts[6],
|
|
|
|
|
seventhPlaceCount: teamScore.placementCounts[7],
|
|
|
|
|
eighthPlaceCount: teamScore.placementCounts[8],
|
|
|
|
|
participantsRemaining: teamScore.participantsTotal - teamScore.participantsCompleted,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[ScoringCalculator] Recalculated standings for season ${seasonId}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Recalculate standings for all leagues that include a specific sports season
|
|
|
|
|
* Called after a sports season event completes
|
|
|
|
|
*/
|
|
|
|
|
export async function recalculateAffectedLeagues(
|
|
|
|
|
sportsSeasonId: string,
|
|
|
|
|
providedDb?: ReturnType<typeof database>
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const db = providedDb || database();
|
|
|
|
|
|
|
|
|
|
// Get all fantasy seasons that include this sports season
|
|
|
|
|
const seasonSports = await db.query.seasonSports.findMany({
|
|
|
|
|
where: eq(schema.seasonSports.sportsSeasonId, sportsSeasonId),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const seasonIds = seasonSports.map((ss) => ss.seasonId);
|
|
|
|
|
|
|
|
|
|
// Recalculate each affected season
|
|
|
|
|
for (const seasonId of seasonIds) {
|
|
|
|
|
await recalculateStandings(seasonId, db);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`[ScoringCalculator] Recalculated ${seasonIds.length} affected season(s) for sports season ${sportsSeasonId}`
|
|
|
|
|
);
|
|
|
|
|
}
|