▍ humdrum codex / sportsball
license AGPL-3.0
3.1 KB raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package model

// LeagueID identifies a supported league.
type LeagueID string

const (
	WorldCup LeagueID = "worldcup"
	MLB      LeagueID = "mlb"
	NBA      LeagueID = "nba"
	WNBA     LeagueID = "wnba"
	NHL      LeagueID = "nhl"
	NFL      LeagueID = "nfl"
)

// League is static metadata for a supported competition.
type League struct {
	ID    LeagueID
	Name  string // display name, e.g. "World Cup"
	Abbr  string // short tag, e.g. "WC"
	Sport string // ESPN sport path segment, e.g. "baseball"
	Path  string // ESPN league path segment, e.g. "mlb"
	Color string // accent hex (no '#') for UI theming
	Icon  string // single-rune emoji/glyph

	// Fetch window in days, tuned to game cadence: daily sports (MLB) need
	// only a couple days to surface last/next; weekly sports (NFL, soccer)
	// need ~10. Zero means fall back to DefaultWindowBack/Forward.
	WindowBack    int
	WindowForward int

	// SeasonDays is the ± window (days) used to decide whether the league is
	// "in season" for auto-hide (TASK-019): a league with no games within this
	// span of today is hidden until it returns. Quadrennial events (World Cup,
	// Olympics) use a wide span so they surface ahead of time. 0 → DefaultSeasonDays.
	SeasonDays int
}

// Leagues is the ordered, canonical set the app polls and displays.
// Order here is the display/cycle order in the dashboard. World Cup and
// MLB lead because they are the current in-season priorities.
// Windows are tuned to surface roughly each team's last game and next game,
// not a whole stretch of fixtures: just wide enough to span one game-to-game
// gap each way for the league's cadence (daily sports stay tight; weekly NFL
// needs ~a week each side). Wider windows pull a "ton more" than last/next.
var Leagues = []League{
	{WorldCup, "World Cup", "WC", "soccer", "fifa.world", "6CABDD", "⚽", 7, 7, 90},
	{MLB, "MLB", "MLB", "baseball", "mlb", "C8102E", "⚾", 2, 3, 0},
	{NBA, "NBA", "NBA", "basketball", "nba", "C9082F", "🏀", 3, 4, 0},
	{WNBA, "WNBA", "WNBA", "basketball", "wnba", "FF6F1E", "🏀", 3, 4, 0},
	{NHL, "NHL", "NHL", "hockey", "nhl", "6B7280", "🏒", 3, 4, 0},
	{NFL, "NFL", "NFL", "football", "nfl", "013369", "🏈", 8, 8, 0},
}

// Default fetch window (days) for leagues that don't specify one.
const (
	DefaultWindowBack    = 4
	DefaultWindowForward = 5
	// DefaultSeasonDays is the ± in-season detection span for leagues that
	// don't set SeasonDays — roughly a month covers normal between-game gaps.
	DefaultSeasonDays = 30
)

// Window returns the league's fetch window in days, applying defaults.
func (l League) Window() (back, forward int) {
	back, forward = l.WindowBack, l.WindowForward
	if back == 0 {
		back = DefaultWindowBack
	}
	if forward == 0 {
		forward = DefaultWindowForward
	}
	return back, forward
}

// SeasonWindow returns the league's in-season detection span in days.
func (l League) SeasonWindow() int {
	if l.SeasonDays == 0 {
		return DefaultSeasonDays
	}
	return l.SeasonDays
}

// LeagueByID returns the League metadata for id, ok=false if unknown.
func LeagueByID(id LeagueID) (League, bool) {
	for _, l := range Leagues {
		if l.ID == id {
			return l, true
		}
	}
	return League{}, false
}