Repository: JanDeDobbeleer/oh-my-posh
Pull request #4352 (2023-10-16 19:48:28)

feat(NBA): Add a new NBA segment to track schedule + scores for your favorite NBA team
Creating a draft PR as I have not yet implemented the tests for the change. I still have to think through the best way to actually do the testing because the JSON that is retrieved from the endpoint is very large, so I don't necessarily want to replicate the entire JSON... I may have to though.
 
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md].
- [x] The commit message follows the [conventional commits][cc] guidelines.
- [x] Tests for the changes have been added (for bug fixes / features).
- [x] Docs have been added/updated (for bug fixes / features).

### Description

<!--
copilot:summary
-->
### <samp>🤖 Generated by Copilot at e93706e</samp>

This pull request adds a new NBA segment for oh-my-posh, which shows NBA game data in the prompt. It implements the segment logic in `nba.go`, the segment type and creation in `segment.go`, and the segment documentation in `nba.mdx`. It also uses the NBA API and the Docusaurus framework as dependencies.

### How

<!--
copilot:walkthrough
-->
### <samp>🤖 Generated by Copilot at e93706e</samp>

*  Add a new segment type `NBA` to display NBA game data ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4352/files?diff=unified&w=0#diff-cd158856c1b1aa6b2acece8041eb07352e4247b700e0fc62798a6be35d5f1c7dR171-R172), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4352/files?diff=unified&w=0#diff-cd158856c1b1aa6b2acece8041eb07352e4247b700e0fc62798a6be35d5f1c7dR296))
*  Implement the `NBA` struct and its methods in `nba.go` to fetch, parse, and render the NBA API response ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4352/files?diff=unified&w=0#diff-34cae96d58fdd9d69f24bdec1be456f7cd877135dceb025fdc13918b54255666R1-R257))
*  Document the NBA segment and its configuration, properties, and template in `nba.mdx` ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4352/files?diff=unified&w=0#diff-9e6a8c778e3ef0f52088c4a2f1ce7b56ad0f6b6b1acac5e0ccb67ac3446cf941R1-R99))

<!---

Tips:

If you're not comfortable with working with Git,
we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary


joadoumie: I am new to contributing - I made a wide range of changes based on your feedback @JanDeDobbeleer. Thanks again for all your help here. I am very excited to get this thing out the door!
nopeless: What a feature contribution. I am amazed to see such unique ideas really (never imagined id see nba on this).

What motivated you to write this? Just curious
joadoumie: @nopeless I love basketball! And I was inspired by @JanDeDobbeleer and @shanselman's Nightscout segment: https://www.hanselman.com/blog/a-nightscout-segment-for-ohmyposh-shows-my-realtime-blood-sugar-readings-in-my-git-prompt

I figured the shell is a really fun place to be able to surface information I care about and was hoping I wasn't the only person who'd find this interesting :) 

![image](https://github.com/JanDeDobbeleer/oh-my-posh/assets/98557455/f6e15248-6e71-4eda-9ad7-da009278f5fd)
![image](https://github.com/JanDeDobbeleer/oh-my-posh/assets/98557455/6c76d3e1-40b9-45cd-8f48-ac9e55964f0d)

joadoumie: I have added test cases in nba_test.go. 

I still have to: 

1. Figure out how to best handle the Template situation with all of the different possible variations. 
2. Follow up on other refactoring as needed.

Hopefully getting really close to wrapping it up :) 
JanDeDobbeleer: @joadoumie I will take the last ones, still this week. You've done an amazing job already. I want to go through the code in vscode myself and play around to see what works best.
joadoumie: > @joadoumie I will take the last ones, still this week. You've done an amazing job already. I want to go through the code in vscode myself and play around to see what works best.

@JanDeDobbeleer that sounds great! Thank you so much for pushing this through to the finish line. It was a really fun project to work on and I'd love to keep contributing to this project moving forward. I appreciate all of your help and guidance. I was able to learn a lot :). 
JanDeDobbeleer: @joadoumie I'm done. If you can validate my changes, we have a go.
joadoumie: @JanDeDobbeleer looks great! Thanks for all the changes. Looking through the code gave me some good ideas for the next time I work on a project like this. I'm going to change this to ready for review so you can merge it. Ship it!
JanDeDobbeleer: @joadoumie I forgot to extend the docs, will do that tomorrow. Will ship ASAP!

Patch:
diff --git a/src/engine/segment.go b/src/engine/segment.go
index e4403ea8..fe99ebda 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -170,6 +170,8 @@ const (
 	LUA SegmentType = "lua"
 	// MERCURIAL writes the Mercurial source control information
 	MERCURIAL SegmentType = "mercurial"
+	// NBA writes NBA game data
+	NBA SegmentType = "nba"
 	// NBGV writes the nbgv version information
 	NBGV SegmentType = "nbgv"
 	// NIGHTSCOUT is an open source diabetes system
@@ -296,6 +298,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
 	KUBECTL:         func() SegmentWriter { return &segments.Kubectl{} },
 	LUA:             func() SegmentWriter { return &segments.Lua{} },
 	MERCURIAL:       func() SegmentWriter { return &segments.Mercurial{} },
+	NBA:             func() SegmentWriter { return &segments.Nba{} },
 	NBGV:            func() SegmentWriter { return &segments.Nbgv{} },
 	NIGHTSCOUT:      func() SegmentWriter { return &segments.Nightscout{} },
 	NODE:            func() SegmentWriter { return &segments.Node{} },
diff --git a/src/segments/nba.go b/src/segments/nba.go
new file mode 100644
index 00000000..7ba753b2
--- /dev/null
+++ b/src/segments/nba.go
@@ -0,0 +1,418 @@
+package segments
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+)
+
+// segment struct, makes templating easier
+type Nba struct {
+	props properties.Properties
+	env   platform.Environment
+
+	NBAData
+}
+
+// NBA struct contains parsed API data that care about for the segment
+type NBAData struct {
+	HomeTeam       string
+	AwayTeam       string
+	Time           string
+	GameDate       string
+	StartTimeUTC   string
+	GameStatus     GameStatus // 1 = scheduled, 2 = in progress, 3 = finished
+	HomeScore      int
+	AwayScore      int
+	HomeTeamWins   int
+	HomeTeamLosses int
+	AwayTeamWins   int
+	AwayTeamLosses int
+}
+
+func (nba *NBAData) HasStats() bool {
+	return nba.HomeTeamWins != 0 || nba.HomeTeamLosses != 0 || nba.AwayTeamWins != 0 || nba.AwayTeamLosses != 0
+}
+
+func (nba *NBAData) Started() bool {
+	return nba.GameStatus == InProgress || nba.GameStatus == Finished
+}
+
+const (
+	NBASeason  properties.Property = "season"
+	TeamName   properties.Property = "team"
+	DaysOffset properties.Property = "days_offset"
+
+	ScheduledTemplate  properties.Property = "scheduled_template"
+	InProgressTemplate properties.Property = "in_progress_template"
+	FinishedTemplate   properties.Property = "finished_template"
+
+	NBAScoreURL      string = "https://cdn.nba.com/static/json/liveData/scoreboard/todaysScoreboard_00.json"
+	NBASchedURLPart1 string = "https://stats.nba.com/stats/internationalbroadcasterschedule?LeagueID=00&Season="
+	NBASchedURLPart2 string = "&RegionID=1&EST=Y"
+
+	Unknown = "Unknown"
+)
+
+// Custom type for GameStatus
+type GameStatus int
+
+// Constants for GameStatus values
+const (
+	Scheduled  GameStatus = 1
+	InProgress GameStatus = 2
+	Finished   GameStatus = 3
+	NotFound   GameStatus = 4
+)
+
+// Int() method for GameStatus to get its integer representation
+// This is a helpful method if people want to come up with their own templates
+func (gs GameStatus) Int() int {
+	return int(gs)
+}
+
+func (gs GameStatus) Valid() bool {
+	return gs == Scheduled || gs == InProgress || gs == Finished
+}
+
+func (gs GameStatus) String() string {
+	switch gs {
+	case Scheduled:
+		return "Scheduled"
+	case InProgress:
+		return "In Progress"
+	case Finished:
+		return "Finished"
+	case NotFound:
+		return "Not Found"
+	default:
+		return Unknown
+	}
+}
+
+// All of the structs needed to retrieve data from the live score endpoint
+type ScoreboardResponse struct {
+	Scoreboard Scoreboard `json:"scoreboard"`
+}
+
+type Scoreboard struct {
+	GameDate string `json:"gameDate"`
+	Games    []Game `json:"games"`
+}
+
+type Game struct {
+	GameStatus     int    `json:"gameStatus"`
+	GameStatusText string `json:"gameStatusText"`
+	GameTimeUTC    string `json:"gameTimeUTC"`
+	HomeTeam       Team   `json:"homeTeam"`
+	AwayTeam       Team   `json:"awayTeam"`
+}
+
+type Team struct {
+	TeamTricode string `json:"teamTricode"`
+	Wins        int    `json:"wins"`
+	Losses      int    `json:"losses"`
+	Score       int    `json:"score"`
+}
+
+// All the structs needed to get data from the schedule endpoint
+type ScheduleResponse struct {
+	ResultSets []ResultSet `json:"resultSets"`
+}
+
+type ResultSet struct {
+	CompleteGameList []ScheduledGame `json:"CompleteGameList,omitempty"`
+}
+
+type ScheduledGame struct {
+	VtAbbreviation string `json:"vtAbbreviation"`
+	HtAbbreviation string `json:"htAbbreviation"`
+	Date           string `json:"date"`
+	Time           string `json:"time"`
+}
+
+func (nba *Nba) Template() string {
+	return " \U000F0806 {{ .HomeTeam}}{{ if .HasStats }} ({{.HomeTeamWins}}-{{.HomeTeamLosses}}){{ end }}{{ if .Started }}:{{.HomeScore}}{{ end }} vs {{ .AwayTeam}}{{ if .HasStats }} ({{.AwayTeamWins}}-{{.AwayTeamLosses}}){{ end }}{{ if .Started }}:{{.AwayScore}}{{ end }} | {{ if not .Started }}{{.GameDate}} | {{ end }}{{.Time}} " //nolint:lll
+}
+
+func (nba *Nba) Enabled() bool {
+	data, err := nba.getResult()
+	if err != nil || !data.GameStatus.Valid() {
+		return false
+	}
+
+	nba.NBAData = *data
+
+	return true
+}
+
+// Returns an empty Game Data struct with the GameStatus set to NotFound
+// Helpful for caching the fact that a game was not found for a team
+func (nba *Nba) getGameNotFoundData() string {
+	return `{
+		"HomeTeam":"",
+		"AwayTeam":"",
+		"Time":"",
+		"GameDate":"",
+		"StartTimeUTC":"",
+		"GameStatus":4,
+		"HomeScore":0,
+		"AwayScore":0,
+		"HomeTeamWins":0,
+		"HomeTeamLosses":0,
+		"AwayTeamWins":0,
+		"AwayTeamLosses":0
+	}`
+}
+
+// parses through a set of games from the score endpoint and looks for props.team in away or home team
+func (nba *Nba) findGameScoreByTeamTricode(games []Game, teamTricode string) (*Game, error) {
+	for _, game := range games {
+		if game.HomeTeam.TeamTricode == teamTricode || game.AwayTeam.TeamTricode == teamTricode {
+			return &game, nil
+		}
+	}
+
+	return nil, errors.New("no game score found for team")
+}
+
+// parses through a set of games from the schedule endpoint and looks for props.team in away or home team
+func (nba *Nba) findGameSchedulebyTeamTricode(games []ScheduledGame, teamTricode string) (*ScheduledGame, error) {
+	for _, game := range games {
+		if game.VtAbbreviation == teamTricode || game.HtAbbreviation == teamTricode {
+			return &game, nil
+		}
+	}
+
+	return nil, errors.New("no scheduled game found for team")
+}
+
+// parses the time and date from the schedule endpoint into a UTC time
+func (nba *Nba) parseTimetoUTC(timeEST, date string) string {
+	combinedTime := date + " " + timeEST
+	timeUTC, err := time.Parse("01/02/2006 03:04 PM", combinedTime)
+	if err != nil {
+		return ""
+	}
+
+	return timeUTC.UTC().Format("2006-01-02T15:04:05Z")
+}
+
+// retrieves data from the score endpoint
+func (nba *Nba) retrieveScoreData(teamName string, httpTimeout int) (*NBAData, error) {
+	body, err := nba.env.HTTPRequest(NBAScoreURL, nil, httpTimeout)
+	if err != nil {
+		return nil, err
+	}
+
+	var scoreboardResponse *ScoreboardResponse
+	err = json.Unmarshal(body, &scoreboardResponse)
+	if err != nil {
+		return nil, err
+	}
+
+	gameInfo, err := nba.findGameScoreByTeamTricode(scoreboardResponse.Scoreboard.Games, teamName)
+	if err != nil {
+		return nil, err
+	}
+
+	return &NBAData{
+		AwayTeam:       gameInfo.AwayTeam.TeamTricode,
+		HomeTeam:       gameInfo.HomeTeam.TeamTricode,
+		Time:           gameInfo.GameStatusText,
+		GameDate:       scoreboardResponse.Scoreboard.GameDate,
+		StartTimeUTC:   gameInfo.GameTimeUTC,
+		GameStatus:     GameStatus(gameInfo.GameStatus),
+		HomeScore:      gameInfo.HomeTeam.Score,
+		AwayScore:      gameInfo.AwayTeam.Score,
+		HomeTeamWins:   gameInfo.HomeTeam.Wins,
+		HomeTeamLosses: gameInfo.HomeTeam.Losses,
+		AwayTeamWins:   gameInfo.AwayTeam.Wins,
+		AwayTeamLosses: gameInfo.AwayTeam.Losses,
+	}, nil
+}
+
+// Retrieves the data from the schedule endpoint
+func (nba *Nba) retrieveScheduleData(teamName string, httpTimeout int) (*NBAData, error) {
+	// How many days into the future should we look for a game.
+	numDaysToSearch := nba.props.GetInt(DaysOffset, 8)
+	nbaSeason := nba.props.GetString(NBASeason, "2023")
+	// Get the current date in America/New_York
+	t := time.Now().In(time.FixedZone("America/New_York", -5*60*60))
+
+	// Check to see if a game is scheduled while the numDaysToSearch is greater than 0
+	for numDaysToSearch > 0 {
+		// convert t into the format that the API expects "MM/YYYY/DD"
+		dateStr := fmt.Sprintf("%02d/%02d/%d", t.Month(), t.Day(), t.Year())
+		urlEndpoint := NBASchedURLPart1 + nbaSeason + "&Date=" + dateStr + NBASchedURLPart2
+		body, err := nba.env.HTTPRequest(urlEndpoint, nil, httpTimeout)
+		if err != nil {
+			return nil, err
+		}
+
+		var scheduleResponse *ScheduleResponse
+		err = json.Unmarshal(body, &scheduleResponse)
+		if err != nil {
+			return nil, err
+		}
+
+		// Check if we can find a game for the team
+		gameInfo, err := nba.findGameSchedulebyTeamTricode(scheduleResponse.ResultSets[1].CompleteGameList, teamName)
+		if err != nil {
+			// We didn't find a game for the team on this day, so we need to check the next day
+			t = t.AddDate(0, 0, 1)
+			numDaysToSearch--
+			continue
+		}
+
+		return &NBAData{
+			AwayTeam:       gameInfo.VtAbbreviation,
+			HomeTeam:       gameInfo.HtAbbreviation,
+			Time:           gameInfo.Time + " ET",
+			GameDate:       gameInfo.Date,
+			StartTimeUTC:   nba.parseTimetoUTC(gameInfo.Time, gameInfo.Date),
+			GameStatus:     Scheduled,
+			HomeScore:      0,
+			AwayScore:      0,
+			HomeTeamWins:   0,
+			HomeTeamLosses: 0,
+			AwayTeamWins:   0,
+			AwayTeamLosses: 0,
+		}, nil
+	}
+
+	return nil, errors.New("no scheduled game found for team within DaysOffset days")
+}
+
+// First try to get the data from the score endpoint, if that fails, try the schedule endpoint
+// The score endpoint usually goes live within 12 hours of a game starting
+func (nba *Nba) getAvailableGameData(teamName string, httpTimeout int) (*NBAData, error) {
+	// Get the info from the score endpoint
+	data, err := nba.retrieveScoreData(teamName, httpTimeout)
+	if err == nil {
+		return data, nil
+	}
+
+	// If the score endpoint doesn't have anything get data from the schedule endpoint
+	data, err = nba.retrieveScheduleData(teamName, httpTimeout)
+	if err == nil {
+		return data, nil
+	}
+
+	return nil, err
+}
+
+// Gets the data from the cache if it exists
+func (nba *Nba) getCacheValue(key string) (*NBAData, error) {
+	if val, found := nba.env.Cache().Get(key); found {
+		var nbaData *NBAData
+		err := json.Unmarshal([]byte(val), &nbaData)
+		if err != nil {
+			return nil, err
+		}
+		return nbaData, nil
+	}
+
+	return nil, errors.New("no data in cache")
+}
+
+// Gets the data from the cache for a scheduled game if it exists
+// Checks whether the game should have started and if so, removes the cache entry
+func (nba *Nba) getCachedScheduleValue(key string) (*NBAData, error) {
+	data, err := nba.getCacheValue(key)
+	if err != nil {
+		return nil, errors.New("no data in cache")
+	}
+
+	// check if the game was previously not found and we should wait to check again
+	if data.GameStatus == NotFound {
+		return data, nil
+	}
+
+	// check if the current time is after the start time of the game
+	// if so, we need to refresh the data
+	startTime, err := time.Parse("2006-01-02T15:04:05Z", data.StartTimeUTC)
+	if err != nil {
+		return nil, err
+	}
+
+	if time.Now().UTC().After(startTime) {
+		// remove the cache entry
+		nba.env.Cache().Delete(key)
+		return nil, errors.New("game has already started")
+	}
+
+	return data, nil
+}
+
+func (nba *Nba) getResult() (*NBAData, error) {
+	teamName := nba.props.GetString(TeamName, "")
+
+	cachedScheduleKey := fmt.Sprintf("%s%s", teamName, "schedule")
+	cachedScoreKey := fmt.Sprintf("%s%s", teamName, "score")
+
+	httpTimeout := nba.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
+
+	// How often you want to query the API to get live score information, defaults to 2 minutes
+	cacheScoreTimeout := nba.props.GetInt(properties.CacheTimeout, 2)
+
+	// Cache the schedule information for a day so we don't call the API too often
+	cacheScheduleTimeout := nba.props.GetInt(properties.CacheTimeout, 1440)
+
+	// Cache the fact a game was not found for 30 minutes so we don't call the API too often
+	cacheNotFoundTimeout := nba.props.GetInt(properties.CacheTimeout, 30)
+
+	nba.env.Debug("Validating cache data for " + teamName)
+
+	if cacheScheduleTimeout > 0 {
+		if data, err := nba.getCachedScheduleValue(cachedScheduleKey); err == nil {
+			return data, nil
+		}
+	}
+
+	if cacheScoreTimeout > 0 {
+		if data, err := nba.getCacheValue(cachedScoreKey); err == nil {
+			return data, nil
+		}
+	}
+
+	nba.env.Debug("Fetching available data for " + teamName)
+
+	data, err := nba.getAvailableGameData(teamName, httpTimeout)
+	if err != nil {
+		// cache the fact that we didn't find a game yet for the day for 30m so we don't continuously ping the endpoints
+		nba.env.Cache().Set(cachedScheduleKey, nba.getGameNotFoundData(), cacheNotFoundTimeout)
+		nba.env.Error(errors.Join(err, fmt.Errorf("unable to get data for team %s", teamName)))
+		return nil, err
+	}
+
+	if !data.GameStatus.Valid() {
+		err := fmt.Errorf("%d is not a valid game status", data.GameStatus)
+		nba.env.Error(err)
+		return nil, err
+	}
+
+	if cacheScheduleTimeout > 0 && data.GameStatus == Scheduled {
+		// persist data for team in cache
+		cachedData, _ := json.Marshal(data)
+		nba.env.Cache().Set(cachedScheduleKey, string(cachedData), cacheScheduleTimeout)
+	}
+
+	// if the game is in progress or finished, we can cache the score
+	if cacheScoreTimeout > 0 && data.GameStatus == InProgress || data.GameStatus == Finished {
+		// persist data for team in cache
+		cachedData, _ := json.Marshal(data)
+		nba.env.Cache().Set(cachedScoreKey, string(cachedData), cacheScoreTimeout)
+	}
+
+	return data, nil
+}
+
+func (nba *Nba) Init(props properties.Properties, env platform.Environment) {
+	nba.props = props
+	nba.env = env
+}
diff --git a/src/segments/umbraco.go b/src/segments/umbraco.go
index 73189578..42c592ca 100644
--- a/src/segments/umbraco.go
+++ b/src/segments/umbraco.go
@@ -158,7 +158,7 @@ func (u *Umbraco) TryFindLegacyUmbraco(configPath string) bool {
 			u.Modern = false
 
 			if len(appSetting.Value) == 0 {
-				u.Version = "Unknown"
+				u.Version = Unknown
 			} else {
 				u.Version = appSetting.Value
 			}
diff --git a/website/docs/segments/nba.mdx b/website/docs/segments/nba.mdx
new file mode 100644
index 00000000..49affc8d
--- /dev/null
+++ b/website/docs/segments/nba.mdx
@@ -0,0 +1,85 @@
+---
+id: nba
+title: NBA
+sidebar_label: NBA
+---
+
+## What
+
+The NBA segment allows you to display the scheduling and score information for your
+favorite NBA team!
+
+## Sample Configuration
+
+In order to use the NBA segment, you need to provide a valid team
+[tri-code](https://liaison.reuters.com/tools/sports-team-codes) that you'd
+like to get data for inside of the configuration. For example, if you'd like
+to get information for the Los Angeles Lakers, you'd need to use the "LAL"
+tri-code.
+
+This example uses "LAL" to get information for the Los Angeles Lakers. It also
+sets the foreground and background colors to match the theming for the team.
+If you are interested in getting information about specific foreground and
+background colors you could use for other teams, you can explore some of
+the color schemes [here](https://teamcolorcodes.com/nba-team-color-codes/).
+
+It is recommended that you set the HTTP timeout to a higher value than the
+normal default in case it takes some time to gather the scoreboard information.
+In this case we have the http_timeout set to 1500.
+
+import Config from "@site/src/components/Config.js";
+
+<Config
+  data={{
+    background: "#e9ac2f",
+    foreground: "#8748dc",
+    leading_diamond: "\ue0b6",
+    style: "diamond",
+    trailing_diamond: "\ue0b0",
+    type: "nba",
+    properties: {
+      team: "LAL",
+      http_timeout: 1500,
+    },
+  }}
+/>
+
+## Properties
+
+| Name           | Type     | Description                                                                                                                                        |
+| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `team`         | `string` | tri-code for the NBA team you want to get data for                                                                                                 |
+| `days_offset`  | `int`    | how many days in advance you wish to see that information for, defaults to 8                                                                       |
+| `http_timeout` | `int`    | How long do you want to wait before you want to see your prompt more than your sugar? I figure a half second is a good default - defaults to 500ms |
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+\U000F0806 {{ .HomeTeam}}{{ if .HasStats }} ({{.HomeTeamWins}}-{{.HomeTeamLosses}}){{ end }}{{ if .Started }}:{{.HomeScore}}{{ end }} vs {{ .AwayTeam}}{{ if .HasStats }} ({{.AwayTeamWins}}-{{.AwayTeamLosses}}){{ end }}{{ if .Started }}:{{.AwayScore}}{{ end }} | {{ if not .Started }}{{.GameDate}} | {{ end }}{{.Time}}
+```
+
+:::
+
+### Properties
+
+| Name            | Type      | Description                                                 |
+| --------------- | --------- | ----------------------------------------------------------- |
+| .HomeTeam       | `string`  | home team for the upcoming game                             |
+| .AwayTeam       | `string`  | away team for the upcoming game                             |
+| .Time           | `string`  | time (EST) that the upcoming game will start                |
+| .GameDate       | `string`  | date the game will happen                                   |
+| .StartTimeUTC   | `string`  | time (UTC) the game will start                              |
+| .GameStatus     | `integer` | integer, 1 = scheduled, 2 = in progress, 3 = finished       |
+| .HomeScore      | `int`     | score of the home team                                      |
+| .AwayScore      | `int`     | score of the away team                                      |
+| .HomeTeamWins   | `int`     | number of wins the home team currently has for the season   |
+| .HomeTeamLosses | `int`     | number of losses the home team currently has for the season |
+| .AwayTeamWins   | `int`     | number of wins the away team currently has for the season   |
+| .AwayTeamLosses | `int`     | number of losses the away team currently has for the season |
+| .Started        | `boolean` | if the game was started or not                              |
+| .HasStats       | `boolean` | if the game has game stats or not                           |
+
+[templates]: /docs/configuration/templates
+[nf-search]: https://www.nerdfonts.com/cheat-sheet


Test patch:
diff --git a/src/segments/nba_test.go b/src/segments/nba_test.go
new file mode 100644
index 00000000..dc1e32dd
--- /dev/null
+++ b/src/segments/nba_test.go
@@ -0,0 +1,122 @@
+package segments
+
+import (
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/mock"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+
+	"github.com/stretchr/testify/assert"
+	mock2 "github.com/stretchr/testify/mock"
+)
+
+func getTestData(file string) string {
+	content, _ := os.ReadFile(fmt.Sprintf("../test/%s", file))
+	return string(content)
+}
+
+// create Test segment for NBA segment
+func TestNBASegment(t *testing.T) {
+	jsonScheduleData := getTestData("nba/schedule.json")
+	jsonScoreData := getTestData("nba/score.json")
+
+	cases := []struct {
+		Case            string
+		JSONResponse    string
+		ExpectedString  string
+		ExpectedEnabled bool
+		CacheTimeout    int
+		CacheFoundFail  bool
+		TeamName        string
+		DaysOffset      int
+		Error           error
+	}{
+		{
+			Case:            "Team (Home Team) Scheduled Game",
+			JSONResponse:    jsonScheduleData,
+			TeamName:        "LAL",
+			ExpectedString:  "󰠆 LAL vs PHX | 10/26/2023 | 10:00 PM ET",
+			ExpectedEnabled: true,
+			DaysOffset:      8,
+		},
+		{
+			Case:            "Team (Away Team) Scheduled Game",
+			JSONResponse:    jsonScheduleData,
+			TeamName:        "PHX",
+			ExpectedString:  "󰠆 LAL vs PHX | 10/26/2023 | 10:00 PM ET",
+			DaysOffset:      4,
+			ExpectedEnabled: true,
+		},
+		{
+			Case:            "Team (Home Team) Live Game",
+			JSONResponse:    jsonScoreData,
+			TeamName:        "CHA",
+			ExpectedString:  "󰠆 CHA (1-0):13 vs BOS (0-1):8 | Q1 8:23",
+			ExpectedEnabled: true,
+		},
+		{
+			Case:            "Team (Away Team) Live Game",
+			JSONResponse:    jsonScoreData,
+			TeamName:        "BOS",
+			ExpectedString:  "󰠆 CHA (1-0):13 vs BOS (0-1):8 | Q1 8:23",
+			ExpectedEnabled: true,
+		},
+		{
+			Case:            "Team not Found",
+			JSONResponse:    jsonScheduleData,
+			DaysOffset:      8,
+			TeamName:        "INVALID",
+			ExpectedEnabled: false,
+		},
+	}
+
+	for _, tc := range cases {
+		env := &mock.MockedEnvironment{}
+		props := properties.Map{
+			properties.CacheTimeout: tc.CacheTimeout,
+			TeamName:                tc.TeamName,
+			DaysOffset:              tc.DaysOffset,
+		}
+
+		env.On("Error", mock2.Anything)
+		env.On("Debug", mock2.Anything)
+		env.On("HTTPRequest", NBAScoreURL).Return([]byte(tc.JSONResponse), tc.Error)
+
+		// Add all the daysOffset to the http request responses
+		for i := 0; i < tc.DaysOffset; i++ {
+			currTime := time.Now().In(time.FixedZone("America/New_York", -5*60*60))
+			// add offset days to currTime so we can query for games in the future
+			currTime = currTime.AddDate(0, 0, i)
+			dateStr := fmt.Sprintf("%02d/%02d/%d", currTime.Month(), currTime.Day(), currTime.Year())
+			nbaSeason := fmt.Sprintf("%d", currTime.Year())
+			scheduleURLEndpoint := NBASchedURLPart1 + nbaSeason + "&Date=" + dateStr + NBASchedURLPart2
+			env.On("HTTPRequest", scheduleURLEndpoint).Return([]byte(tc.JSONResponse), tc.Error)
+		}
+
+		nba := &Nba{
+			props: props,
+			env:   env,
+		}
+
+		cachedScheduleKey := fmt.Sprintf("%s%s", tc.TeamName, "schedule")
+		cachedScoreKey := fmt.Sprintf("%s%s", tc.TeamName, "score")
+
+		cache := &mock.MockedCache{}
+		cache.On("Get", cachedScheduleKey).Return(nba.getGameNotFoundData(), tc.CacheFoundFail)
+		cache.On("Get", cachedScoreKey).Return(nba.getGameNotFoundData(), tc.CacheFoundFail)
+		cache.On("Set", cachedScheduleKey, nba.getGameNotFoundData(), tc.CacheTimeout).Return()
+		cache.On("Set", cachedScoreKey, nba.getGameNotFoundData(), tc.CacheTimeout).Return()
+		env.On("Cache").Return(cache)
+
+		enabled := nba.Enabled()
+		assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case)
+		if !enabled {
+			continue
+		}
+
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, nba.Template(), nba), tc.Case)
+	}
+}
diff --git a/src/test/nba/schedule.json b/src/test/nba/schedule.json
new file mode 100644
index 00000000..ce788e3f
--- /dev/null
+++ b/src/test/nba/schedule.json
@@ -0,0 +1,96 @@
+{
+  "resource": "internationalbroadcasterschedule",
+  "parameters": {
+    "LeagueID": "00",
+    "Season": "2023",
+    "RegionID": 1,
+    "Date": "10/26/2023",
+    "EST": "Y"
+  },
+  "resultSets": [
+    {
+      "NextGameList": [
+        {
+          "gameID": "0022300075",
+          "vtCity": "Philadelphia",
+          "vtNickName": "76ers",
+          "vtShortName": "Philadelphia",
+          "vtAbbreviation": "PHI",
+          "htCity": "Milwaukee",
+          "htNickName": "Bucks",
+          "htShortName": "Milwaukee",
+          "htAbbreviation": "MIL",
+          "date": "10/26/2023",
+          "time": "07:30 PM",
+          "day": "Thu",
+          "broadcasters": [
+            {
+              "broadcastID": "10",
+              "broadcasterName": "TNT",
+              "tapeDelayComments": ""
+            }
+          ]
+        },
+        {
+          "gameID": "0022300076",
+          "vtCity": "Phoenix",
+          "vtNickName": "Suns",
+          "vtShortName": "Phoenix",
+          "vtAbbreviation": "PHX",
+          "htCity": "Los Angeles",
+          "htNickName": "Lakers",
+          "htShortName": "L.A. Lakers",
+          "htAbbreviation": "LAL",
+          "date": "10/26/2023",
+          "time": "10:00 PM",
+          "day": "Thu",
+          "broadcasters": [
+            {
+              "broadcastID": "10",
+              "broadcasterName": "TNT",
+              "tapeDelayComments": ""
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "CompleteGameList": [
+        {
+          "gameID": "0022300075",
+          "vtCity": "Philadelphia",
+          "vtNickName": "76ers",
+          "vtShortName": "Philadelphia",
+          "vtAbbreviation": "PHI",
+          "htCity": "Milwaukee",
+          "htNickName": "Bucks",
+          "htShortName": "Milwaukee",
+          "htAbbreviation": "MIL",
+          "date": "10/26/2023",
+          "time": "07:30 PM",
+          "day": "Thu",
+          "broadcastID": "10",
+          "broadcasterName": "TNT",
+          "tapeDelayComments": ""
+        },
+        {
+          "gameID": "0022300076",
+          "vtCity": "Phoenix",
+          "vtNickName": "Suns",
+          "vtShortName": "Phoenix",
+          "vtAbbreviation": "PHX",
+          "htCity": "Los Angeles",
+          "htNickName": "Lakers",
+          "htShortName": "L.A. Lakers",
+          "htAbbreviation": "LAL",
+          "date": "10/26/2023",
+          "time": "10:00 PM",
+          "day": "Thu",
+          "broadcastID": "10",
+          "broadcasterName": "TNT",
+          "tapeDelayComments": ""
+        }
+      ]
+    }
+  ]
+}
diff --git a/src/test/nba/score.json b/src/test/nba/score.json
new file mode 100644
index 00000000..28559ab2
--- /dev/null
+++ b/src/test/nba/score.json
@@ -0,0 +1,699 @@
+{
+  "meta": {
+    "version": 1,
+    "request": "https://nba-prod-us-east-1-mediaops-stats.s3.amazonaws.com/NBA/liveData/scoreboard/todaysScoreboard_00.json",
+    "time": "2023-10-19 01:17:57.1757",
+    "code": 200
+  },
+  "scoreboard": {
+    "gameDate": "2023-10-19",
+    "leagueId": "00",
+    "leagueName": "National Basketball Association",
+    "games": [
+      {
+        "gameId": "0012300060",
+        "gameCode": "20231019/BOSCHA",
+        "gameStatus": 2,
+        "gameStatusText": "Q1 8:23",
+        "period": 0,
+        "gameClock": "Q1 8:23",
+        "gameTimeUTC": "2023-10-19T23:00:00Z",
+        "gameEt": "2023-10-19T19:00:00Z",
+        "regulationPeriods": 4,
+        "ifNecessary": false,
+        "seriesGameNumber": "",
+        "seriesText": "Preseason",
+        "seriesConference": "",
+        "poRoundDesc": "",
+        "gameSubtype": "",
+        "homeTeam": {
+          "teamId": 1610612766,
+          "teamName": "Hornets",
+          "teamCity": "Charlotte",
+          "teamTricode": "CHA",
+          "wins": 1,
+          "losses": 0,
+          "score": 13,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "awayTeam": {
+          "teamId": 1610612738,
+          "teamName": "Celtics",
+          "teamCity": "Boston",
+          "teamTricode": "BOS",
+          "wins": 0,
+          "losses": 1,
+          "score": 8,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "gameLeaders": {
+          "homeLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "CHA",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          },
+          "awayLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "BOS",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          }
+        },
+        "pbOdds": {
+          "team": null,
+          "odds": 0.0,
+          "suspended": 0
+        }
+      },
+      {
+        "gameId": "0012300061",
+        "gameCode": "20231019/MINCHI",
+        "gameStatus": 1,
+        "gameStatusText": "8:00 pm ET",
+        "period": 0,
+        "gameClock": "",
+        "gameTimeUTC": "2023-10-20T00:00:00Z",
+        "gameEt": "2023-10-19T20:00:00Z",
+        "regulationPeriods": 4,
+        "ifNecessary": false,
+        "seriesGameNumber": "",
+        "seriesText": "Preseason",
+        "seriesConference": "",
+        "poRoundDesc": "",
+        "gameSubtype": "",
+        "homeTeam": {
+          "teamId": 1610612741,
+          "teamName": "Bulls",
+          "teamCity": "Chicago",
+          "teamTricode": "CHI",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "awayTeam": {
+          "teamId": 1610612750,
+          "teamName": "Timberwolves",
+          "teamCity": "Minnesota",
+          "teamTricode": "MIN",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "gameLeaders": {
+          "homeLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "CHI",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          },
+          "awayLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "MIN",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          }
+        },
+        "pbOdds": {
+          "team": null,
+          "odds": 0.0,
+          "suspended": 0
+        }
+      },
+      {
+        "gameId": "0012300062",
+        "gameCode": "20231019/DETOKC",
+        "gameStatus": 1,
+        "gameStatusText": "8:00 pm ET",
+        "period": 0,
+        "gameClock": "",
+        "gameTimeUTC": "2023-10-20T00:00:00Z",
+        "gameEt": "2023-10-19T20:00:00Z",
+        "regulationPeriods": 4,
+        "ifNecessary": false,
+        "seriesGameNumber": "",
+        "seriesText": "Preseason",
+        "seriesConference": "",
+        "poRoundDesc": "",
+        "gameSubtype": "",
+        "homeTeam": {
+          "teamId": 1610612760,
+          "teamName": "Thunder",
+          "teamCity": "Oklahoma City",
+          "teamTricode": "OKC",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "awayTeam": {
+          "teamId": 1610612765,
+          "teamName": "Pistons",
+          "teamCity": "Detroit",
+          "teamTricode": "DET",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "gameLeaders": {
+          "homeLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "OKC",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          },
+          "awayLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "DET",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          }
+        },
+        "pbOdds": {
+          "team": null,
+          "odds": 0.0,
+          "suspended": 0
+        }
+      },
+      {
+        "gameId": "0012300063",
+        "gameCode": "20231019/PHXLAL",
+        "gameStatus": 1,
+        "gameStatusText": "10:00 pm ET",
+        "period": 0,
+        "gameClock": "",
+        "gameTimeUTC": "2023-10-20T02:00:00Z",
+        "gameEt": "2023-10-19T22:00:00Z",
+        "regulationPeriods": 4,
+        "ifNecessary": false,
+        "seriesGameNumber": "",
+        "seriesText": "Preseason",
+        "seriesConference": "",
+        "poRoundDesc": "",
+        "gameSubtype": "",
+        "homeTeam": {
+          "teamId": 1610612747,
+          "teamName": "Lakers",
+          "teamCity": "Los Angeles",
+          "teamTricode": "LAL",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "awayTeam": {
+          "teamId": 1610612756,
+          "teamName": "Suns",
+          "teamCity": "Phoenix",
+          "teamTricode": "PHX",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "gameLeaders": {
+          "homeLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "LAL",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          },
+          "awayLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "PHX",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          }
+        },
+        "pbOdds": {
+          "team": null,
+          "odds": 0.0,
+          "suspended": 0
+        }
+      },
+      {
+        "gameId": "0012300064",
+        "gameCode": "20231019/DENLAC",
+        "gameStatus": 1,
+        "gameStatusText": "10:00 pm ET",
+        "period": 0,
+        "gameClock": "",
+        "gameTimeUTC": "2023-10-20T02:00:00Z",
+        "gameEt": "2023-10-19T22:00:00Z",
+        "regulationPeriods": 4,
+        "ifNecessary": false,
+        "seriesGameNumber": "",
+        "seriesText": "Preseason",
+        "seriesConference": "",
+        "poRoundDesc": "",
+        "gameSubtype": "",
+        "homeTeam": {
+          "teamId": 1610612746,
+          "teamName": "Clippers",
+          "teamCity": "LA",
+          "teamTricode": "LAC",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "awayTeam": {
+          "teamId": 1610612743,
+          "teamName": "Nuggets",
+          "teamCity": "Denver",
+          "teamTricode": "DEN",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "gameLeaders": {
+          "homeLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "LAC",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          },
+          "awayLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "DEN",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          }
+        },
+        "pbOdds": {
+          "team": null,
+          "odds": 0.0,
+          "suspended": 0
+        }
+      },
+      {
+        "gameId": "0012300065",
+        "gameCode": "20231019/UTASAC",
+        "gameStatus": 1,
+        "gameStatusText": "10:00 pm ET",
+        "period": 0,
+        "gameClock": "",
+        "gameTimeUTC": "2023-10-20T02:00:00Z",
+        "gameEt": "2023-10-19T22:00:00Z",
+        "regulationPeriods": 4,
+        "ifNecessary": false,
+        "seriesGameNumber": "",
+        "seriesText": "Preseason",
+        "seriesConference": "",
+        "poRoundDesc": "",
+        "gameSubtype": "",
+        "homeTeam": {
+          "teamId": 1610612758,
+          "teamName": "Kings",
+          "teamCity": "Sacramento",
+          "teamTricode": "SAC",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "awayTeam": {
+          "teamId": 1610612762,
+          "teamName": "Jazz",
+          "teamCity": "Utah",
+          "teamTricode": "UTA",
+          "wins": 0,
+          "losses": 0,
+          "score": 0,
+          "seed": null,
+          "inBonus": null,
+          "timeoutsRemaining": 0,
+          "periods": [
+            {
+              "period": 1,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 2,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 3,
+              "periodType": "REGULAR",
+              "score": 0
+            },
+            {
+              "period": 4,
+              "periodType": "REGULAR",
+              "score": 0
+            }
+          ]
+        },
+        "gameLeaders": {
+          "homeLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "SAC",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          },
+          "awayLeaders": {
+            "personId": 0,
+            "name": "",
+            "jerseyNum": "",
+            "position": "",
+            "teamTricode": "UTA",
+            "playerSlug": null,
+            "points": 0,
+            "rebounds": 0,
+            "assists": 0
+          }
+        },
+        "pbOdds": {
+          "team": null,
+          "odds": 0.0,
+          "suspended": 0
+        }
+      }
+    ]
+  }
+}