added league table

This commit is contained in:
2026-02-21 23:11:57 +11:00
parent daeb19cb82
commit d011c7acb8
6 changed files with 242 additions and 22 deletions

View File

@@ -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).

View File

@@ -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(