shot chart service and models update

This commit is contained in:
Ravi Prasad
2025-05-13 22:10:42 -05:00
parent 5f67c07579
commit cdf5cf4cd2
14 changed files with 3117 additions and 115 deletions
+70 -58
View File
@@ -1,76 +1,88 @@
// File: services/player_shot_chart_fetch_service.go
package services
import (
"encoding/json"
"fmt"
"log"
"time"
"encoding/json"
"fmt"
"log"
"time"
"github.com/nprasad2077/NBA_Go/models"
"github.com/nprasad2077/NBA_Go/utils"
"github.com/nprasad2077/NBA_Go/utils/metrics"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/nprasad2077/NBA_Go/models"
"github.com/nprasad2077/NBA_Go/utils"
"github.com/nprasad2077/NBA_Go/utils/metrics"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// FetchAndStoreShotChartForPlayer fetches a single player's shot chart, parses
// each shot's date into a season, and upserts into DB.
func FetchAndStoreShotChartForPlayer(db *gorm.DB, playerId string) error {
metrics.DBOperationsTotal.WithLabelValues("fetch", "player_shot_chart").Inc()
// FetchAndStoreShotChartForPlayer pulls the public NBAAPI JSON for a single
// player, derives the season from the shot date, and upserts into SQLite.
// Dupes are prevented by the unique index:
//
// (player_id, season, date, qtr, time_remaining, top, left)
func FetchAndStoreShotChartForPlayer(db *gorm.DB, playerID string) error {
metrics.DBOperationsTotal.WithLabelValues("fetch", "player_shot_chart").Inc()
url := fmt.Sprintf("http://rest.nbaapi.com/api/ShotChartData/playerid/%s", playerId)
body, err := utils.GetJSON(url)
if err != nil {
return err
}
url := fmt.Sprintf("http://rest.nbaapi.com/api/ShotChartData/playerid/%s", playerID)
body, err := utils.GetJSON(url)
if err != nil {
return err
}
var shots []models.PlayerShotChart
if err := json.Unmarshal(body, &shots); err != nil {
return err
}
var shots []models.PlayerShotChart
if err := json.Unmarshal(body, &shots); err != nil {
return err
}
for _, shot := range shots {
// parse date "Feb 11, 2023" → time.Time
if t, err := time.Parse("Jan 2, 2006", shot.Date); err == nil {
shot.Season = t.Year()
}
for _, shot := range shots {
// "Feb 11, 2023" → season 2023
if t, err := time.Parse("Jan 2, 2006", shot.Date); err == nil {
shot.Season = t.Year()
}
err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "player_id"}, {Name: "external_id"}},
DoUpdates: clause.AssignmentColumns([]string{
"player_name", "top", "left", "date", "qtr",
"time_remaining", "result", "shot_type", "distance_ft",
"lead", "team_score", "opponent_team_score", "opponent",
"team", "season",
}),
}).Create(&shot).Error
err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "player_id"},
{Name: "season"},
{Name: "date"},
{Name: "qtr"},
{Name: "time_remaining"},
{Name: "top"},
{Name: "left"},
},
// update mutable fields; leave the identity columns untouched
DoUpdates: clause.AssignmentColumns([]string{
"player_name", "result", "shot_type", "distance_ft",
"lead", "team_score", "opponent_team_score",
"opponent", "team",
}),
}).Create(&shot).Error
metrics.DBOperationsTotal.WithLabelValues("store", "player_shot_chart").Inc()
metrics.DBOperationsTotal.WithLabelValues("store", "player_shot_chart").Inc()
if err != nil {
log.Printf("Failed to upsert shot chart for %s id=%d: %v",
shot.PlayerID, shot.ExternalID, err)
}
}
return nil
if err != nil {
log.Printf("❌ upsert failed for %s on %s (%s %s): %v",
shot.PlayerID, shot.Date, shot.Quarter, shot.TimeRemaining, err)
}
}
return nil
}
// FetchAndStoreAllPlayerShotCharts loads every distinct playerId from your stats
// tables and invokes the per-player fetch.
// FetchAndStoreAllPlayerShotCharts enumerates every distinct player_id in your
// totals table, then calls the perplayer loader. It sleeps ~1.1s between
// requests to stay well under publicAPI rate limits.
func FetchAndStoreAllPlayerShotCharts(db *gorm.DB) error {
var playerIds []string
var playerIDs []string
// gather from total stats (could also include advanced stats)
db.Model(&models.PlayerTotalStat{}).
Distinct("player_id").
Pluck("player_id", &playerIds)
// collect IDs (add other stats tables if needed)
db.Model(&models.PlayerTotalStat{}).
Distinct("player_id").
Pluck("player_id", &playerIDs)
for _, pid := range playerIds {
if err := FetchAndStoreShotChartForPlayer(db, pid); err != nil {
log.Printf("Error importing shot chart for %s: %v", pid, err)
}
// throttle
time.Sleep(1100 * time.Millisecond)
}
return nil
for _, pid := range playerIDs {
if err := FetchAndStoreShotChartForPlayer(db, pid); err != nil {
log.Printf("Error importing shot chart for %s: %v", pid, err)
}
time.Sleep(1100 * time.Millisecond) // throttle
}
return nil
}