added league table
This commit is contained in:
@@ -127,6 +127,22 @@ func GetFixture(ctx context.Context, tx bun.Tx, id int) (*Fixture, error) {
|
||||
Get(ctx)
|
||||
}
|
||||
|
||||
// GetAllocatedFixtures returns all fixtures with a game_week assigned for a season+league.
|
||||
func GetAllocatedFixtures(ctx context.Context, tx bun.Tx, seasonID, leagueID int) ([]*Fixture, error) {
|
||||
fixtures, err := GetList[Fixture](tx).
|
||||
Where("season_id = ?", seasonID).
|
||||
Where("league_id = ?", leagueID).
|
||||
Where("game_week IS NOT NULL").
|
||||
Order("game_week ASC", "round ASC", "id ASC").
|
||||
Relation("HomeTeam").
|
||||
Relation("AwayTeam").
|
||||
GetAll(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetList")
|
||||
}
|
||||
return fixtures, nil
|
||||
}
|
||||
|
||||
func GetFixturesForTeam(ctx context.Context, tx bun.Tx, seasonID, leagueID, teamID int) ([]*Fixture, error) {
|
||||
fixtures, err := GetList[Fixture](tx).
|
||||
Where("season_id = ?", seasonID).
|
||||
|
||||
@@ -2,6 +2,8 @@ package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -327,15 +329,27 @@ func GetAggregatedPlayerStatsForTeam(
|
||||
|
||||
// TeamRecord holds win/loss/draw record and goal totals for a team.
|
||||
type TeamRecord struct {
|
||||
Played int
|
||||
Wins int
|
||||
Losses int
|
||||
Draws int
|
||||
GoalsFor int
|
||||
GoalsAgainst int
|
||||
Played int
|
||||
Wins int
|
||||
OvertimeWins int
|
||||
OvertimeLosses int
|
||||
Losses int
|
||||
Draws int
|
||||
GoalsFor int
|
||||
GoalsAgainst int
|
||||
Points int
|
||||
}
|
||||
|
||||
// ComputeTeamRecord calculates W-L-D and GF/GA from fixtures and results.
|
||||
// Point values for the leaderboard scoring system.
|
||||
const (
|
||||
PointsWin = 3
|
||||
PointsOvertimeWin = 2
|
||||
PointsOvertimeLoss = 1
|
||||
PointsLoss = 0
|
||||
)
|
||||
|
||||
// ComputeTeamRecord calculates W-OTW-OTL-L, GF/GA, and points from fixtures and results.
|
||||
// Points: Win=3, OT Win=2, OT Loss=1, Loss=0.
|
||||
func ComputeTeamRecord(teamID int, fixtures []*Fixture, resultMap map[int]*FixtureResult) *TeamRecord {
|
||||
rec := &TeamRecord{}
|
||||
for _, f := range fixtures {
|
||||
@@ -354,17 +368,80 @@ func ComputeTeamRecord(teamID int, fixtures []*Fixture, resultMap map[int]*Fixtu
|
||||
}
|
||||
won := (isHome && res.Winner == "home") || (!isHome && res.Winner == "away")
|
||||
lost := (isHome && res.Winner == "away") || (!isHome && res.Winner == "home")
|
||||
if won {
|
||||
isOT := strings.EqualFold(res.EndReason, "Overtime")
|
||||
|
||||
switch {
|
||||
case won && isOT:
|
||||
rec.OvertimeWins++
|
||||
rec.Points += PointsOvertimeWin
|
||||
case won:
|
||||
rec.Wins++
|
||||
} else if lost {
|
||||
rec.Points += PointsWin
|
||||
case lost && isOT:
|
||||
rec.OvertimeLosses++
|
||||
rec.Points += PointsOvertimeLoss
|
||||
case lost:
|
||||
rec.Losses++
|
||||
} else {
|
||||
rec.Points += PointsLoss
|
||||
default:
|
||||
rec.Draws++
|
||||
}
|
||||
}
|
||||
return rec
|
||||
}
|
||||
|
||||
// LeaderboardEntry represents a single team's standing in the league table.
|
||||
type LeaderboardEntry struct {
|
||||
Position int
|
||||
Team *Team
|
||||
Record *TeamRecord
|
||||
}
|
||||
|
||||
// ComputeLeaderboard builds a sorted leaderboard from teams, fixtures, and results.
|
||||
// Teams are sorted by: Points DESC, Goal Differential DESC, Goals For DESC, Name ASC.
|
||||
func ComputeLeaderboard(teams []*Team, fixtures []*Fixture, resultMap map[int]*FixtureResult) []*LeaderboardEntry {
|
||||
entries := make([]*LeaderboardEntry, 0, len(teams))
|
||||
|
||||
// Build a map of team ID -> fixtures involving that team
|
||||
teamFixtures := make(map[int][]*Fixture)
|
||||
for _, f := range fixtures {
|
||||
teamFixtures[f.HomeTeamID] = append(teamFixtures[f.HomeTeamID], f)
|
||||
teamFixtures[f.AwayTeamID] = append(teamFixtures[f.AwayTeamID], f)
|
||||
}
|
||||
|
||||
for _, team := range teams {
|
||||
record := ComputeTeamRecord(team.ID, teamFixtures[team.ID], resultMap)
|
||||
entries = append(entries, &LeaderboardEntry{
|
||||
Team: team,
|
||||
Record: record,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort: Points DESC, then goal diff DESC, then GF DESC, then name ASC
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
ri, rj := entries[i].Record, entries[j].Record
|
||||
if ri.Points != rj.Points {
|
||||
return ri.Points > rj.Points
|
||||
}
|
||||
diffI := ri.GoalsFor - ri.GoalsAgainst
|
||||
diffJ := rj.GoalsFor - rj.GoalsAgainst
|
||||
if diffI != diffJ {
|
||||
return diffI > diffJ
|
||||
}
|
||||
if ri.GoalsFor != rj.GoalsFor {
|
||||
return ri.GoalsFor > rj.GoalsFor
|
||||
}
|
||||
return entries[i].Team.Name < entries[j].Team.Name
|
||||
})
|
||||
|
||||
// Assign positions
|
||||
for i := range entries {
|
||||
entries[i].Position = i + 1
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// GetFixtureTeamRosters returns all team players with participation status for a fixture.
|
||||
// Returns: map["home"|"away"] -> []*PlayerWithPlayStatus
|
||||
func GetFixtureTeamRosters(
|
||||
|
||||
Reference in New Issue
Block a user