diff --git a/.DS_Store b/.DS_Store
index 7961524..bd3a02a 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/Dockerfile b/Dockerfile
index d8d3a18..0700431 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,8 @@ FROM golang:1.24-bullseye AS builder
ENV CGO_ENABLED=1
ENV GOOS=linux
# Changed from Apple Silicon arm64 config.
-ENV GOARCH=amd64
+# ENV GOARCH=amd64
+ENV GOARCH=arm64
WORKDIR /app
diff --git a/README.md b/README.md
index 5d05985..30899d3 100644
--- a/README.md
+++ b/README.md
@@ -29,3 +29,10 @@ swag init -g main.go -o docs
```bash
go run loadtest.go -n 100 -c 10 -url "http://127.0.0.1:8080/api/playeradvancedstats?page=1&pageSize=20" -log results.log -key "xxx"
```
+
+
+### Local Environment
+
+```bash
+docker-compose -f docker-compose.local.yml up --build -d
+```
\ No newline at end of file
diff --git a/config/database.go b/config/database.go
index f2b0111..814e8c2 100644
--- a/config/database.go
+++ b/config/database.go
@@ -1,3 +1,5 @@
+// File: config/database.go
+
package config
import (
@@ -12,7 +14,7 @@ import (
)
func InitDB(shouldMigrate bool) *gorm.DB {
- // CHANGE: Build the DSN from environment variables
+ // ... (DSN setup code is unchanged) ...
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
@@ -21,13 +23,32 @@ func InitDB(shouldMigrate bool) *gorm.DB {
os.Getenv("DB_PORT"),
)
- db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) // <- CHANGE: Use the postgres driver
+ db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
metrics.DBOperationsTotal.WithLabelValues("connect", "database").Inc()
if shouldMigrate {
+ // --- ADD THIS BLOCK TO DROP STALE TABLES ---
+ // Drop tables in reverse order of dependency (children first).
+ // This ensures a clean migration every time the import process runs.
+ // log.Println("⚠️ Dropping existing game-related tables for a clean migration...")
+ // if err := db.Migrator().DropTable(
+ // &models.LineScore{},
+ // &models.PlayerGameBasicStat{},
+ // &models.PlayerGameAdvStat{},
+ // &models.TeamGameBasicStat{},
+ // &models.TeamGameAdvStat{},
+ // &models.Game{}, // Drop parent table last
+ // ); err != nil {
+ // log.Fatalf("failed to drop tables: %v", err)
+ // }
+ // log.Println("✅ Tables dropped successfully.")
+ // --- END OF ADDED BLOCK ---
+
+
+ // Your existing AutoMigrate calls will now work correctly
if err := db.AutoMigrate(&models.PlayerAdvancedStat{}); err != nil {
log.Fatalf("migrate PlayerAdvancedStat: %v", err)
}
@@ -40,6 +61,17 @@ func InitDB(shouldMigrate bool) *gorm.DB {
if err := db.AutoMigrate(&models.APIKey{}); err != nil {
log.Fatalf("migrate APIKey: %v", err)
}
+ // Game Models will be recreated with the correct schema
+ if err := db.AutoMigrate(
+ &models.Game{},
+ &models.LineScore{},
+ &models.PlayerGameBasicStat{},
+ &models.PlayerGameAdvStat{},
+ &models.TeamGameBasicStat{},
+ &models.TeamGameAdvStat{},
+ ); err != nil {
+ log.Fatalf("migrate game models: %v", err)
+ }
metrics.DBOperationsTotal.WithLabelValues("migrate", "database").Inc()
}
diff --git a/docker-compose.local.yml b/docker-compose.local.yml
index 5c32137..847d123 100644
--- a/docker-compose.local.yml
+++ b/docker-compose.local.yml
@@ -1,49 +1,72 @@
-# docker-compose.local.yml
-#
-# Use this file to run local services (API, Nginx, etc.) while
-# connecting to a REMOTE database.
-#
-# This version includes the 'db-init' service. Note that this service
-# will attempt to run its 'import-data' command every time you execute
-# 'docker-compose up'.
+version: '3.8'
services:
- # This service runs the 'import-data' command against the remote database
- # specified in your '.env.local' file. It has no 'depends_on' since there
- # is no local postgres container to wait for.
+ # NEW: Add a dedicated PostgreSQL database service
+ postgres:
+ image: postgres:15-alpine
+ environment:
+ POSTGRES_USER: ${DB_USER}
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ POSTGRES_DB: ${DB_NAME}
+ ports:
+ - '${DB_PORT}:5432' # Expose DB port to host machine for local tools
+ volumes:
+ - postgres_data:/var/lib/postgresql/data # Persist data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ networks:
+ - api.network
+
+ # MODIFIED: This service now runs migrations and imports against the live Postgres DB
+ # "import-data" on initial run to migrate and import data.
db-init:
build: .
- env_file: .env.local
+ env_file: .env
command: ["/nba_go", "import-data"]
+ depends_on:
+ postgres:
+ condition: service_healthy
networks:
- api.network
restart: "no"
- # API services now have no 'depends_on' section.
- # They will start and immediately try to connect to the DB_HOST in your .env file.
+ # MODIFIED: All API services now depend on postgres being healthy.
+ # The volume mount for './data' is no longer needed.
api1:
build: .
- env_file: .env.local
+ env_file: .env
ports:
- '5001:5000'
+ depends_on:
+ postgres:
+ condition: service_healthy
networks:
- api.network
restart: always
api2:
build: .
- env_file: .env.local
+ env_file: .env
ports:
- '5002:5000'
+ depends_on:
+ postgres:
+ condition: service_healthy
networks:
- api.network
restart: always
api3:
build: .
- env_file: .env.local
+ env_file: .env
ports:
- '5003:5000'
+ depends_on:
+ postgres:
+ condition: service_healthy
networks:
- api.network
restart: always
@@ -57,7 +80,7 @@ services:
- api2
- api3
ports:
- - '8080:8080'
+ - '8081:8080'
networks:
- api.network
@@ -76,6 +99,8 @@ services:
networks:
- api.network
restart: unless-stopped
+ logging:
+ driver: "none"
grafana:
image: grafana/grafana:latest
@@ -91,9 +116,11 @@ services:
networks:
- api.network
restart: unless-stopped
+ logging:
+ driver: none
volumes:
- # The 'postgres_data' volume is not needed in this configuration.
+ postgres_data: # New volume for Postgres
prometheus_data:
grafana_data:
diff --git a/docs/.DS_Store b/docs/.DS_Store
new file mode 100644
index 0000000..c6241e5
Binary files /dev/null and b/docs/.DS_Store differ
diff --git a/docs/docs.go b/docs/docs.go
index 2992c7b..22f31b5 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -477,7 +477,7 @@ var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/",
- Schemes: []string{"https"},
+ Schemes: []string{"http"},
Title: "NBA_Go API",
Description: "Stats service, now with public access!",
InfoInstanceName: "swagger",
diff --git a/docs/postman/.DS_Store b/docs/postman/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/docs/postman/.DS_Store differ
diff --git a/docs/postman/NBA Statistics Go.postman_collection.json b/docs/postman/NBA Statistics Go.postman_collection.json
new file mode 100644
index 0000000..117627c
--- /dev/null
+++ b/docs/postman/NBA Statistics Go.postman_collection.json
@@ -0,0 +1,1596 @@
+{
+ "info": {
+ "_postman_id": "f0cb3c13-063b-490a-aa7f-5af135aacbe6",
+ "name": "NBA Statistics Go",
+ "description": "This collection provides access to the NBA_Go API, a service for retrieving comprehensive NBA player statistics. You can fetch total stats, advanced metrics, and detailed shot chart data.\n\n## Authentication\n\nAuthentication is not currently required for public endpoints. Future versions may require an API key to be sent in the `x-api-key` header.\n\n## Pagination\n\nThe stats endpoints (`playertotals` and `playeradvancedstats`) return a `pagination` object with metadata about the current page, page size, total pages, and total results.\n\nThe `playershotchart` endpoint has a fixed page size of 50 results.",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+ "_exporter_id": "25652688",
+ "_collection_link": "https://www.postman.com/solar-meadow-682296/workspace/nba-stats/collection/25652688-f0cb3c13-063b-490a-aa7f-5af135aacbe6?action=share&source=collection_link&creator=25652688"
+ },
+ "item": [
+ {
+ "name": "Player Totals Stats",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "pm.test(\"Response status code is 200\", function () {",
+ " pm.response.to.have.status(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response time is less than 200ms\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response has the required fields\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('object');",
+ " pm.expect(responseData.data).to.be.an('array');",
+ " ",
+ " responseData.data.forEach(function(player) {",
+ " pm.expect(player).to.have.property('ID');",
+ " pm.expect(player).to.have.property('CreatedAt');",
+ " pm.expect(player).to.have.property('UpdatedAt');",
+ " pm.expect(player).to.have.property('DeletedAt');",
+ " pm.expect(player).to.have.property('playerId');",
+ " pm.expect(player).to.have.property('playerName');",
+ " pm.expect(player).to.have.property('position');",
+ " pm.expect(player).to.have.property('age');",
+ " pm.expect(player).to.have.property('games');",
+ " });",
+ "});",
+ "",
+ "",
+ "",
+ "pm.test(\"Pagination object is present and contains expected fields\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('object');",
+ " pm.expect(responseData.pagination).to.exist;",
+ " pm.expect(responseData.pagination).to.have.property('page');",
+ " pm.expect(responseData.pagination).to.have.property('pageSize');",
+ " pm.expect(responseData.pagination).to.have.property('pages');",
+ " pm.expect(responseData.pagination).to.have.property('total');",
+ "});",
+ "var template = `",
+ "",
+ "",
+ "
",
+ " ",
+ " | ID | ",
+ " CreatedAt | ",
+ " UpdatedAt | ",
+ " DeletedAt | ",
+ " id | ",
+ " playerId | ",
+ " playerName | ",
+ " position | ",
+ " age | ",
+ " games | ",
+ " gamesStarted | ",
+ " minutesPg | ",
+ " fieldGoals | ",
+ " fieldAttempts | ",
+ " fieldPercent | ",
+ " threeFg | ",
+ " threeAttempts | ",
+ " threePercent | ",
+ " twoFg | ",
+ " twoAttempts | ",
+ " twoPercent | ",
+ " effectFgPercent | ",
+ " ft | ",
+ " ftAttempts | ",
+ " ftPercent | ",
+ " offensiveRb | ",
+ " defensiveRb | ",
+ " totalRb | ",
+ " assists | ",
+ " steals | ",
+ " blocks | ",
+ " turnovers | ",
+ " personalFouls | ",
+ " points | ",
+ " team | ",
+ " season | ",
+ "
",
+ " ",
+ " {{#each response.data}}",
+ " ",
+ " | {{ID}} | ",
+ " {{CreatedAt}} | ",
+ " {{UpdatedAt}} | ",
+ " {{DeletedAt}} | ",
+ " {{id}} | ",
+ " {{playerId}} | ",
+ " {{playerName}} | ",
+ " {{position}} | ",
+ " {{age}} | ",
+ " {{games}} | ",
+ " {{gamesStarted}} | ",
+ " {{minutesPg}} | ",
+ " {{fieldGoals}} | ",
+ " {{fieldAttempts}} | ",
+ " {{fieldPercent}} | ",
+ " {{threeFg}} | ",
+ " {{threeAttempts}} | ",
+ " {{threePercent}} | ",
+ " {{twoFg}} | ",
+ " {{twoAttempts}} | ",
+ " {{twoPercent}} | ",
+ " {{effectFgPercent}} | ",
+ " {{ft}} | ",
+ " {{ftAttempts}} | ",
+ " {{ftPercent}} | ",
+ " {{offensiveRb}} | ",
+ " {{defensiveRb}} | ",
+ " {{totalRb}} | ",
+ " {{assists}} | ",
+ " {{steals}} | ",
+ " {{blocks}} | ",
+ " {{turnovers}} | ",
+ " {{personalFouls}} | ",
+ " {{points}} | ",
+ " {{team}} | ",
+ " {{season}} | ",
+ "
",
+ " {{/each}}",
+ "
",
+ "`;",
+ "",
+ "function constructVisualizerPayload() {",
+ " return {response: pm.response.json()}",
+ "}",
+ "",
+ "pm.visualizer.set(template, constructVisualizerPayload());"
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playertotals?season=2025&team=HOU&page=1&pageSize=35&isPlayoff=False",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playertotals"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2025",
+ "description": "(Optional) The season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "HOU",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL)."
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "35",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "False",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ },
+ "description": "### Get Player Total Stats\n\nReturns a paginated and filterable list of traditional player statistics (totals) for a given season. You can retrieve either regular season or playoff data.\n\n### Usage\n\nThis endpoint supports filtering by `season`, `team`, and `playerId`. Results can be paginated using `page` and `pageSize`, and sorted using `sortBy` and `ascending`.\n\n#### **Query Parameters**\n\n- **`season`** (integer, optional): The season year (e.g., 2000).\n \n- **`team`** (string, optional): Team abbreviation (e.g., LAL).\n \n- **`playerId`** (string, optional): The player's unique ID (e.g., `greenac01`).\n \n- **`page`** (integer, optional): Page number. Defaults to `1`.\n \n- **`pageSize`** (integer, optional): Number of results per page. Defaults to `20`.\n \n- **`isPlayoff`** (boolean, optional): Set to `true` for playoff stats. The filter is only applied if the parameter is provided.\n \n- **`sortBy`** (string, optional): Field to sort by. Defaults to `points`.\n \n- **`ascending`** (boolean, optional): Use `true` for ascending order. Defaults to `false`.\n \n\n#### **Available** **`sortBy`** **Fields:**\n\n`playerId`, `playerName`, `position`, `age`, `games`, `gamesStarted`, `minutesPg`, `fieldGoals`, `fieldAttempts`, `fieldPercent`, `threeFg`, `threeAttempts`, `threePercent`, `twoFg`, `twoAttempts`, `twoPercent`, `effectFgPercent`, `ft`, `ftAttempts`, `ftPercent`, `offensiveRb`, `defensiveRb`, `totalRb`, `assists`, `steals`, `blocks`, `turnovers`, `personalFouls`, `points`, `team`, `season`"
+ },
+ "response": [
+ {
+ "name": "Player Totals Stats",
+ "originalRequest": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playertotals?season=2025&team=HOU&page=1&pageSize=35&isPlayoff=False",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playertotals"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2025",
+ "description": "(Optional) The season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "HOU",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL)."
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "35",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "False",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ }
+ },
+ "status": "OK",
+ "code": 200,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "Alt-Svc",
+ "value": "h3=\":443\"; ma=2592000"
+ },
+ {
+ "key": "Content-Encoding",
+ "value": "br"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Date",
+ "value": "Wed, 02 Jul 2025 01:16:40 GMT"
+ },
+ {
+ "key": "Server",
+ "value": "nginx/1.28.0"
+ },
+ {
+ "key": "Vary",
+ "value": "Accept-Encoding"
+ },
+ {
+ "key": "Transfer-Encoding",
+ "value": "chunked"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"data\": [\n {\n \"ID\": 1648,\n \"id\": 13,\n \"playerId\": \"greenja05\",\n \"playerName\": \"Jalen Green\",\n \"position\": \"SG\",\n \"age\": 22,\n \"games\": 82,\n \"gamesStarted\": 82,\n \"minutesPg\": 2697,\n \"fieldGoals\": 608,\n \"fieldAttempts\": 1437,\n \"fieldPercent\": 0.423,\n \"threeFg\": 234,\n \"threeAttempts\": 661,\n \"threePercent\": 0.354,\n \"twoFg\": 374,\n \"twoAttempts\": 776,\n \"twoPercent\": 0.482,\n \"effectFgPercent\": 0.505,\n \"ft\": 273,\n \"ftAttempts\": 336,\n \"ftPercent\": 0.813,\n \"offensiveRb\": 45,\n \"defensiveRb\": 332,\n \"totalRb\": 377,\n \"assists\": 282,\n \"steals\": 71,\n \"blocks\": 27,\n \"turnovers\": 203,\n \"personalFouls\": 127,\n \"points\": 1723,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:06:56.81977Z\",\n \"UpdatedAt\": \"2025-06-20T01:06:56.81977Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1664,\n \"id\": 27,\n \"playerId\": \"sengual01\",\n \"playerName\": \"Alperen Şengün\",\n \"position\": \"C\",\n \"age\": 22,\n \"games\": 76,\n \"gamesStarted\": 76,\n \"minutesPg\": 2395,\n \"fieldGoals\": 567,\n \"fieldAttempts\": 1143,\n \"fieldPercent\": 0.496,\n \"threeFg\": 21,\n \"threeAttempts\": 90,\n \"threePercent\": 0.233,\n \"twoFg\": 546,\n \"twoAttempts\": 1053,\n \"twoPercent\": 0.519,\n \"effectFgPercent\": 0.505,\n \"ft\": 296,\n \"ftAttempts\": 428,\n \"ftPercent\": 0.692,\n \"offensiveRb\": 262,\n \"defensiveRb\": 524,\n \"totalRb\": 786,\n \"assists\": 372,\n \"steals\": 84,\n \"blocks\": 61,\n \"turnovers\": 194,\n \"personalFouls\": 209,\n \"points\": 1451,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:06:59.072751Z\",\n \"UpdatedAt\": \"2025-06-20T01:06:59.072751Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1725,\n \"id\": 78,\n \"playerId\": \"brookdi01\",\n \"playerName\": \"Dillon Brooks\",\n \"position\": \"SF\",\n \"age\": 29,\n \"games\": 75,\n \"gamesStarted\": 75,\n \"minutesPg\": 2388,\n \"fieldGoals\": 384,\n \"fieldAttempts\": 895,\n \"fieldPercent\": 0.429,\n \"threeFg\": 186,\n \"threeAttempts\": 469,\n \"threePercent\": 0.397,\n \"twoFg\": 198,\n \"twoAttempts\": 426,\n \"twoPercent\": 0.465,\n \"effectFgPercent\": 0.533,\n \"ft\": 99,\n \"ftAttempts\": 121,\n \"ftPercent\": 0.818,\n \"offensiveRb\": 75,\n \"defensiveRb\": 200,\n \"totalRb\": 275,\n \"assists\": 128,\n \"steals\": 60,\n \"blocks\": 16,\n \"turnovers\": 76,\n \"personalFouls\": 242,\n \"points\": 1053,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:08.04821Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:08.04821Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1741,\n \"id\": 91,\n \"playerId\": \"thompam01\",\n \"playerName\": \"Amen Thompson\",\n \"position\": \"SF\",\n \"age\": 22,\n \"games\": 69,\n \"gamesStarted\": 42,\n \"minutesPg\": 2225,\n \"fieldGoals\": 388,\n \"fieldAttempts\": 697,\n \"fieldPercent\": 0.557,\n \"threeFg\": 25,\n \"threeAttempts\": 91,\n \"threePercent\": 0.275,\n \"twoFg\": 363,\n \"twoAttempts\": 606,\n \"twoPercent\": 0.599,\n \"effectFgPercent\": 0.575,\n \"ft\": 169,\n \"ftAttempts\": 247,\n \"ftPercent\": 0.684,\n \"offensiveRb\": 192,\n \"defensiveRb\": 372,\n \"totalRb\": 564,\n \"assists\": 265,\n \"steals\": 97,\n \"blocks\": 89,\n \"turnovers\": 138,\n \"personalFouls\": 167,\n \"points\": 970,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:10.362164Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:10.362164Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1763,\n \"id\": 109,\n \"playerId\": \"vanvlfr01\",\n \"playerName\": \"Fred VanVleet\",\n \"position\": \"PG\",\n \"age\": 30,\n \"games\": 60,\n \"gamesStarted\": 60,\n \"minutesPg\": 2111,\n \"fieldGoals\": 287,\n \"fieldAttempts\": 759,\n \"fieldPercent\": 0.378,\n \"threeFg\": 159,\n \"threeAttempts\": 461,\n \"threePercent\": 0.345,\n \"twoFg\": 128,\n \"twoAttempts\": 298,\n \"twoPercent\": 0.43,\n \"effectFgPercent\": 0.483,\n \"ft\": 111,\n \"ftAttempts\": 137,\n \"ftPercent\": 0.81,\n \"offensiveRb\": 32,\n \"defensiveRb\": 189,\n \"totalRb\": 221,\n \"assists\": 333,\n \"steals\": 94,\n \"blocks\": 25,\n \"turnovers\": 87,\n \"personalFouls\": 140,\n \"points\": 844,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:13.597703Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:13.597703Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1821,\n \"id\": 155,\n \"playerId\": \"smithja05\",\n \"playerName\": \"Jabari Smith Jr.\",\n \"position\": \"PF\",\n \"age\": 21,\n \"games\": 57,\n \"gamesStarted\": 39,\n \"minutesPg\": 1716,\n \"fieldGoals\": 248,\n \"fieldAttempts\": 566,\n \"fieldPercent\": 0.438,\n \"threeFg\": 98,\n \"threeAttempts\": 277,\n \"threePercent\": 0.354,\n \"twoFg\": 150,\n \"twoAttempts\": 289,\n \"twoPercent\": 0.519,\n \"effectFgPercent\": 0.525,\n \"ft\": 104,\n \"ftAttempts\": 126,\n \"ftPercent\": 0.825,\n \"offensiveRb\": 105,\n \"defensiveRb\": 294,\n \"totalRb\": 399,\n \"assists\": 60,\n \"steals\": 25,\n \"blocks\": 41,\n \"turnovers\": 61,\n \"personalFouls\": 123,\n \"points\": 698,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:21.954232Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:21.954232Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1825,\n \"id\": 159,\n \"playerId\": \"easonta01\",\n \"playerName\": \"Tari Eason\",\n \"position\": \"PF\",\n \"age\": 23,\n \"games\": 57,\n \"gamesStarted\": 16,\n \"minutesPg\": 1420,\n \"fieldGoals\": 272,\n \"fieldAttempts\": 559,\n \"fieldPercent\": 0.487,\n \"threeFg\": 63,\n \"threeAttempts\": 184,\n \"threePercent\": 0.342,\n \"twoFg\": 209,\n \"twoAttempts\": 375,\n \"twoPercent\": 0.557,\n \"effectFgPercent\": 0.543,\n \"ft\": 79,\n \"ftAttempts\": 104,\n \"ftPercent\": 0.76,\n \"offensiveRb\": 128,\n \"defensiveRb\": 234,\n \"totalRb\": 362,\n \"assists\": 83,\n \"steals\": 97,\n \"blocks\": 50,\n \"turnovers\": 65,\n \"personalFouls\": 136,\n \"points\": 686,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:22.520553Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:22.520553Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1919,\n \"id\": 239,\n \"playerId\": \"whitmca01\",\n \"playerName\": \"Cam Whitmore\",\n \"position\": \"SF\",\n \"age\": 20,\n \"games\": 51,\n \"gamesStarted\": 3,\n \"minutesPg\": 827,\n \"fieldGoals\": 179,\n \"fieldAttempts\": 403,\n \"fieldPercent\": 0.444,\n \"threeFg\": 65,\n \"threeAttempts\": 183,\n \"threePercent\": 0.355,\n \"twoFg\": 114,\n \"twoAttempts\": 220,\n \"twoPercent\": 0.518,\n \"effectFgPercent\": 0.525,\n \"ft\": 54,\n \"ftAttempts\": 72,\n \"ftPercent\": 0.75,\n \"offensiveRb\": 36,\n \"defensiveRb\": 115,\n \"totalRb\": 151,\n \"assists\": 49,\n \"steals\": 31,\n \"blocks\": 13,\n \"turnovers\": 46,\n \"personalFouls\": 44,\n \"points\": 477,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:36.322627Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:36.322627Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1992,\n \"id\": 292,\n \"playerId\": \"holidaa01\",\n \"playerName\": \"Aaron Holiday\",\n \"position\": \"PG\",\n \"age\": 28,\n \"games\": 62,\n \"gamesStarted\": 3,\n \"minutesPg\": 792,\n \"fieldGoals\": 117,\n \"fieldAttempts\": 268,\n \"fieldPercent\": 0.437,\n \"threeFg\": 72,\n \"threeAttempts\": 181,\n \"threePercent\": 0.398,\n \"twoFg\": 45,\n \"twoAttempts\": 87,\n \"twoPercent\": 0.517,\n \"effectFgPercent\": 0.571,\n \"ft\": 34,\n \"ftAttempts\": 41,\n \"ftPercent\": 0.829,\n \"offensiveRb\": 13,\n \"defensiveRb\": 65,\n \"totalRb\": 78,\n \"assists\": 83,\n \"steals\": 19,\n \"blocks\": 11,\n \"turnovers\": 37,\n \"personalFouls\": 64,\n \"points\": 340,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:46.800477Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:46.800477Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2066,\n \"id\": 344,\n \"playerId\": \"sheppre01\",\n \"playerName\": \"Reed Sheppard\",\n \"position\": \"PG\",\n \"age\": 20,\n \"games\": 52,\n \"gamesStarted\": 3,\n \"minutesPg\": 654,\n \"fieldGoals\": 84,\n \"fieldAttempts\": 239,\n \"fieldPercent\": 0.351,\n \"threeFg\": 48,\n \"threeAttempts\": 142,\n \"threePercent\": 0.338,\n \"twoFg\": 36,\n \"twoAttempts\": 97,\n \"twoPercent\": 0.371,\n \"effectFgPercent\": 0.452,\n \"ft\": 13,\n \"ftAttempts\": 16,\n \"ftPercent\": 0.813,\n \"offensiveRb\": 16,\n \"defensiveRb\": 62,\n \"totalRb\": 78,\n \"assists\": 75,\n \"steals\": 35,\n \"blocks\": 17,\n \"turnovers\": 37,\n \"personalFouls\": 53,\n \"points\": 229,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:57.495833Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:57.495833Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2068,\n \"id\": 346,\n \"playerId\": \"adamsst01\",\n \"playerName\": \"Steven Adams\",\n \"position\": \"C\",\n \"age\": 31,\n \"games\": 58,\n \"gamesStarted\": 3,\n \"minutesPg\": 794,\n \"fieldGoals\": 91,\n \"fieldAttempts\": 167,\n \"fieldPercent\": 0.545,\n \"threeFg\": 0,\n \"threeAttempts\": 2,\n \"threePercent\": 0,\n \"twoFg\": 91,\n \"twoAttempts\": 165,\n \"twoPercent\": 0.552,\n \"effectFgPercent\": 0.545,\n \"ft\": 43,\n \"ftAttempts\": 93,\n \"ftPercent\": 0.462,\n \"offensiveRb\": 166,\n \"defensiveRb\": 161,\n \"totalRb\": 327,\n \"assists\": 66,\n \"steals\": 22,\n \"blocks\": 28,\n \"turnovers\": 54,\n \"personalFouls\": 60,\n \"points\": 225,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:57.793469Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:57.793469Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2080,\n \"id\": 356,\n \"playerId\": \"landajo01\",\n \"playerName\": \"Jock Landale\",\n \"position\": \"C\",\n \"age\": 29,\n \"games\": 42,\n \"gamesStarted\": 3,\n \"minutesPg\": 500,\n \"fieldGoals\": 81,\n \"fieldAttempts\": 152,\n \"fieldPercent\": 0.533,\n \"threeFg\": 11,\n \"threeAttempts\": 26,\n \"threePercent\": 0.423,\n \"twoFg\": 70,\n \"twoAttempts\": 126,\n \"twoPercent\": 0.556,\n \"effectFgPercent\": 0.569,\n \"ft\": 27,\n \"ftAttempts\": 40,\n \"ftPercent\": 0.675,\n \"offensiveRb\": 56,\n \"defensiveRb\": 81,\n \"totalRb\": 137,\n \"assists\": 37,\n \"steals\": 13,\n \"blocks\": 10,\n \"turnovers\": 21,\n \"personalFouls\": 49,\n \"points\": 200,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:07:59.521078Z\",\n \"UpdatedAt\": \"2025-06-20T01:07:59.521078Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2090,\n \"id\": 364,\n \"playerId\": \"tateja01\",\n \"playerName\": \"Jae'Sean Tate\",\n \"position\": \"SF\",\n \"age\": 29,\n \"games\": 52,\n \"gamesStarted\": 2,\n \"minutesPg\": 588,\n \"fieldGoals\": 70,\n \"fieldAttempts\": 148,\n \"fieldPercent\": 0.473,\n \"threeFg\": 16,\n \"threeAttempts\": 46,\n \"threePercent\": 0.348,\n \"twoFg\": 54,\n \"twoAttempts\": 102,\n \"twoPercent\": 0.529,\n \"effectFgPercent\": 0.527,\n \"ft\": 32,\n \"ftAttempts\": 47,\n \"ftPercent\": 0.681,\n \"offensiveRb\": 50,\n \"defensiveRb\": 67,\n \"totalRb\": 117,\n \"assists\": 47,\n \"steals\": 26,\n \"blocks\": 7,\n \"turnovers\": 22,\n \"personalFouls\": 82,\n \"points\": 188,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:08:00.954562Z\",\n \"UpdatedAt\": \"2025-06-20T01:08:00.954562Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2097,\n \"id\": 371,\n \"playerId\": \"greenje02\",\n \"playerName\": \"Jeff Green\",\n \"position\": \"PF\",\n \"age\": 38,\n \"games\": 32,\n \"gamesStarted\": 3,\n \"minutesPg\": 396,\n \"fieldGoals\": 61,\n \"fieldAttempts\": 121,\n \"fieldPercent\": 0.504,\n \"threeFg\": 29,\n \"threeAttempts\": 79,\n \"threePercent\": 0.367,\n \"twoFg\": 32,\n \"twoAttempts\": 42,\n \"twoPercent\": 0.762,\n \"effectFgPercent\": 0.624,\n \"ft\": 21,\n \"ftAttempts\": 26,\n \"ftPercent\": 0.808,\n \"offensiveRb\": 11,\n \"defensiveRb\": 47,\n \"totalRb\": 58,\n \"assists\": 20,\n \"steals\": 6,\n \"blocks\": 4,\n \"turnovers\": 9,\n \"personalFouls\": 32,\n \"points\": 172,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:08:02.008397Z\",\n \"UpdatedAt\": \"2025-06-20T01:08:02.008397Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2242,\n \"id\": 461,\n \"playerId\": \"willije02\",\n \"playerName\": \"Jeenathan Williams\",\n \"position\": \"SG\",\n \"age\": 25,\n \"games\": 20,\n \"gamesStarted\": 0,\n \"minutesPg\": 147,\n \"fieldGoals\": 27,\n \"fieldAttempts\": 62,\n \"fieldPercent\": 0.435,\n \"threeFg\": 6,\n \"threeAttempts\": 26,\n \"threePercent\": 0.231,\n \"twoFg\": 21,\n \"twoAttempts\": 36,\n \"twoPercent\": 0.583,\n \"effectFgPercent\": 0.484,\n \"ft\": 5,\n \"ftAttempts\": 8,\n \"ftPercent\": 0.625,\n \"offensiveRb\": 5,\n \"defensiveRb\": 8,\n \"totalRb\": 13,\n \"assists\": 9,\n \"steals\": 8,\n \"blocks\": 4,\n \"turnovers\": 14,\n \"personalFouls\": 16,\n \"points\": 65,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:08:23.073002Z\",\n \"UpdatedAt\": \"2025-06-20T01:08:23.073002Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2303,\n \"id\": 508,\n \"playerId\": \"nfalyda01\",\n \"playerName\": \"N'Faly Dante\",\n \"position\": \"C\",\n \"age\": 23,\n \"games\": 4,\n \"gamesStarted\": 0,\n \"minutesPg\": 51,\n \"fieldGoals\": 10,\n \"fieldAttempts\": 13,\n \"fieldPercent\": 0.769,\n \"threeFg\": 0,\n \"threeAttempts\": 0,\n \"threePercent\": 0,\n \"twoFg\": 10,\n \"twoAttempts\": 13,\n \"twoPercent\": 0.769,\n \"effectFgPercent\": 0.769,\n \"ft\": 4,\n \"ftAttempts\": 5,\n \"ftPercent\": 0.8,\n \"offensiveRb\": 6,\n \"defensiveRb\": 15,\n \"totalRb\": 21,\n \"assists\": 2,\n \"steals\": 1,\n \"blocks\": 5,\n \"turnovers\": 2,\n \"personalFouls\": 9,\n \"points\": 24,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:08:31.603297Z\",\n \"UpdatedAt\": \"2025-06-20T01:08:31.603297Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2322,\n \"id\": 525,\n \"playerId\": \"mcveija01\",\n \"playerName\": \"Jack McVeigh\",\n \"position\": \"PF\",\n \"age\": 28,\n \"games\": 9,\n \"gamesStarted\": 0,\n \"minutesPg\": 43,\n \"fieldGoals\": 5,\n \"fieldAttempts\": 17,\n \"fieldPercent\": 0.294,\n \"threeFg\": 4,\n \"threeAttempts\": 13,\n \"threePercent\": 0.308,\n \"twoFg\": 1,\n \"twoAttempts\": 4,\n \"twoPercent\": 0.25,\n \"effectFgPercent\": 0.412,\n \"ft\": 0,\n \"ftAttempts\": 0,\n \"ftPercent\": 0,\n \"offensiveRb\": 2,\n \"defensiveRb\": 3,\n \"totalRb\": 5,\n \"assists\": 1,\n \"steals\": 0,\n \"blocks\": 2,\n \"turnovers\": 2,\n \"personalFouls\": 3,\n \"points\": 14,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:08:34.253789Z\",\n \"UpdatedAt\": \"2025-06-20T01:08:34.253789Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2116,\n \"id\": 385,\n \"playerId\": \"roddyda01\",\n \"playerName\": \"David Roddy\",\n \"position\": \"PF\",\n \"age\": 23,\n \"games\": 3,\n \"gamesStarted\": 0,\n \"minutesPg\": 35,\n \"fieldGoals\": 5,\n \"fieldAttempts\": 13,\n \"fieldPercent\": 0.385,\n \"threeFg\": 1,\n \"threeAttempts\": 7,\n \"threePercent\": 0.143,\n \"twoFg\": 4,\n \"twoAttempts\": 6,\n \"twoPercent\": 0.667,\n \"effectFgPercent\": 0.423,\n \"ft\": 2,\n \"ftAttempts\": 4,\n \"ftPercent\": 0.5,\n \"offensiveRb\": 0,\n \"defensiveRb\": 5,\n \"totalRb\": 5,\n \"assists\": 2,\n \"steals\": 0,\n \"blocks\": 1,\n \"turnovers\": 1,\n \"personalFouls\": 1,\n \"points\": 13,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:08:04.73934Z\",\n \"UpdatedAt\": \"2025-06-20T01:08:04.73934Z\",\n \"DeletedAt\": null\n }\n ],\n \"pagination\": {\n \"page\": 1,\n \"pageSize\": 35,\n \"pages\": 1,\n \"total\": 18\n }\n}"
+ }
+ ]
+ },
+ {
+ "name": "Player Totals Playoffs Stats",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "pm.test(\"Response status code is 200\", function () {",
+ " pm.response.to.have.status(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response time is less than 200ms\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response has the required fields\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('object');",
+ " pm.expect(responseData.data).to.be.an('array');",
+ " ",
+ " responseData.data.forEach(function(player) {",
+ " pm.expect(player).to.have.property('ID');",
+ " pm.expect(player).to.have.property('CreatedAt');",
+ " pm.expect(player).to.have.property('UpdatedAt');",
+ " pm.expect(player).to.have.property('DeletedAt');",
+ " pm.expect(player).to.have.property('playerId');",
+ " pm.expect(player).to.have.property('playerName');",
+ " pm.expect(player).to.have.property('position');",
+ " pm.expect(player).to.have.property('age');",
+ " pm.expect(player).to.have.property('games');",
+ " });",
+ "});",
+ "",
+ "",
+ "",
+ "pm.test(\"Pagination object is present and contains expected fields\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('object');",
+ " pm.expect(responseData.pagination).to.exist;",
+ " pm.expect(responseData.pagination).to.have.property('page');",
+ " pm.expect(responseData.pagination).to.have.property('pageSize');",
+ " pm.expect(responseData.pagination).to.have.property('pages');",
+ " pm.expect(responseData.pagination).to.have.property('total');",
+ "});",
+ "var template = `",
+ "",
+ "",
+ "",
+ " ",
+ " | ID | ",
+ " CreatedAt | ",
+ " UpdatedAt | ",
+ " DeletedAt | ",
+ " id | ",
+ " playerId | ",
+ " playerName | ",
+ " position | ",
+ " age | ",
+ " games | ",
+ " gamesStarted | ",
+ " minutesPg | ",
+ " fieldGoals | ",
+ " fieldAttempts | ",
+ " fieldPercent | ",
+ " threeFg | ",
+ " threeAttempts | ",
+ " threePercent | ",
+ " twoFg | ",
+ " twoAttempts | ",
+ " twoPercent | ",
+ " effectFgPercent | ",
+ " ft | ",
+ " ftAttempts | ",
+ " ftPercent | ",
+ " offensiveRb | ",
+ " defensiveRb | ",
+ " totalRb | ",
+ " assists | ",
+ " steals | ",
+ " blocks | ",
+ " turnovers | ",
+ " personalFouls | ",
+ " points | ",
+ " team | ",
+ " season | ",
+ "
",
+ " ",
+ " {{#each response.data}}",
+ " ",
+ " | {{ID}} | ",
+ " {{CreatedAt}} | ",
+ " {{UpdatedAt}} | ",
+ " {{DeletedAt}} | ",
+ " {{id}} | ",
+ " {{playerId}} | ",
+ " {{playerName}} | ",
+ " {{position}} | ",
+ " {{age}} | ",
+ " {{games}} | ",
+ " {{gamesStarted}} | ",
+ " {{minutesPg}} | ",
+ " {{fieldGoals}} | ",
+ " {{fieldAttempts}} | ",
+ " {{fieldPercent}} | ",
+ " {{threeFg}} | ",
+ " {{threeAttempts}} | ",
+ " {{threePercent}} | ",
+ " {{twoFg}} | ",
+ " {{twoAttempts}} | ",
+ " {{twoPercent}} | ",
+ " {{effectFgPercent}} | ",
+ " {{ft}} | ",
+ " {{ftAttempts}} | ",
+ " {{ftPercent}} | ",
+ " {{offensiveRb}} | ",
+ " {{defensiveRb}} | ",
+ " {{totalRb}} | ",
+ " {{assists}} | ",
+ " {{steals}} | ",
+ " {{blocks}} | ",
+ " {{turnovers}} | ",
+ " {{personalFouls}} | ",
+ " {{points}} | ",
+ " {{team}} | ",
+ " {{season}} | ",
+ "
",
+ " {{/each}}",
+ "
",
+ "`;",
+ "",
+ "function constructVisualizerPayload() {",
+ " return {response: pm.response.json()}",
+ "}",
+ "",
+ "pm.visualizer.set(template, constructVisualizerPayload());"
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playertotals?season=2019&page=1&pageSize=20&sortBy=assists&isPlayoff=true",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playertotals"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2019",
+ "description": "(Optional) The season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "HOU",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL).",
+ "disabled": true
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "20",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "sortBy",
+ "value": "assists",
+ "description": "(Optional) The field to sort the results by. Defaults to 'points'."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "true",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ },
+ "description": "### Get Player Total Stats (Playoffs)\n\nReturns a paginated and filterable list of traditional player statistics (totals) for the playoffs. This is an example request that sets `isPlayoff=true`.\n\n### Usage\n\nThis endpoint supports filtering by `season`, `team`, and `playerId`. Results can be paginated using `page` and `pageSize`, and sorted using `sortBy` and `ascending`.\n\n#### **Query Parameters**\n\n* **`season`** (integer, optional): The season year (e.g., 2000).\n* **`team`** (string, optional): Team abbreviation (e.g., LAL).\n* **`playerId`** (string, optional): The player's unique ID (e.g., `greenac01`).\n* **`page`** (integer, optional): Page number. Defaults to `1`.\n* **`pageSize`** (integer, optional): Number of results per page. Defaults to `20`.\n* **`isPlayoff`** (boolean, optional): Set to `true` for playoff stats. The filter is only applied if the parameter is provided.\n* **`sortBy`** (string, optional): Field to sort by. Defaults to `points`.\n* **`ascending`** (boolean, optional): Use `true` for ascending order. Defaults to `false`.\n\n#### **Available `sortBy` Fields:**\n`playerId`, `playerName`, `position`, `age`, `games`, `gamesStarted`, `minutesPg`, `fieldGoals`, `fieldAttempts`, `fieldPercent`, `threeFg`, `threeAttempts`, `threePercent`, `twoFg`, `twoAttempts`, `twoPercent`, `effectFgPercent`, `ft`, `ftAttempts`, `ftPercent`, `offensiveRb`, `defensiveRb`, `totalRb`, `assists`, `steals`, `blocks`, `turnovers`, `personalFouls`, `points`, `team`, `season`"
+ },
+ "response": [
+ {
+ "name": "Player Totals Playoffs Stats",
+ "originalRequest": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playertotals?season=2019&page=1&pageSize=20&sortBy=assists&isPlayoff=true",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playertotals"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2019",
+ "description": "(Optional) The season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "HOU",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL).",
+ "disabled": true
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "20",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "sortBy",
+ "value": "assists",
+ "description": "(Optional) The field to sort the results by. Defaults to 'points'."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "true",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ }
+ },
+ "status": "OK",
+ "code": 200,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "Alt-Svc",
+ "value": "h3=\":443\"; ma=2592000"
+ },
+ {
+ "key": "Content-Encoding",
+ "value": "br"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Date",
+ "value": "Wed, 02 Jul 2025 01:17:12 GMT"
+ },
+ {
+ "key": "Server",
+ "value": "nginx/1.28.0"
+ },
+ {
+ "key": "Vary",
+ "value": "Accept-Encoding"
+ },
+ {
+ "key": "Transfer-Encoding",
+ "value": "chunked"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"data\": [\n {\n \"ID\": 7355,\n \"id\": 76,\n \"playerId\": \"greendr01\",\n \"playerName\": \"Draymond Green\",\n \"position\": \"PF\",\n \"age\": 28,\n \"games\": 22,\n \"gamesStarted\": 22,\n \"minutesPg\": 851,\n \"fieldGoals\": 114,\n \"fieldAttempts\": 229,\n \"fieldPercent\": 0.498,\n \"threeFg\": 13,\n \"threeAttempts\": 57,\n \"threePercent\": 0.228,\n \"twoFg\": 101,\n \"twoAttempts\": 172,\n \"twoPercent\": 0.587,\n \"effectFgPercent\": 0.526,\n \"ft\": 51,\n \"ftAttempts\": 71,\n \"ftPercent\": 0.718,\n \"offensiveRb\": 41,\n \"defensiveRb\": 182,\n \"totalRb\": 223,\n \"assists\": 187,\n \"steals\": 32,\n \"blocks\": 33,\n \"turnovers\": 83,\n \"personalFouls\": 85,\n \"points\": 292,\n \"team\": \"GSW\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:33.196486Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:33.196486Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7404,\n \"id\": 125,\n \"playerId\": \"lowryky01\",\n \"playerName\": \"Kyle Lowry\",\n \"position\": \"PG\",\n \"age\": 32,\n \"games\": 24,\n \"gamesStarted\": 24,\n \"minutesPg\": 901,\n \"fieldGoals\": 122,\n \"fieldAttempts\": 277,\n \"fieldPercent\": 0.44,\n \"threeFg\": 52,\n \"threeAttempts\": 145,\n \"threePercent\": 0.359,\n \"twoFg\": 70,\n \"twoAttempts\": 132,\n \"twoPercent\": 0.53,\n \"effectFgPercent\": 0.534,\n \"ft\": 65,\n \"ftAttempts\": 81,\n \"ftPercent\": 0.802,\n \"offensiveRb\": 21,\n \"defensiveRb\": 96,\n \"totalRb\": 117,\n \"assists\": 159,\n \"steals\": 31,\n \"blocks\": 7,\n \"turnovers\": 54,\n \"personalFouls\": 96,\n \"points\": 361,\n \"team\": \"TOR\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:42.589209Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:42.589209Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7321,\n \"id\": 42,\n \"playerId\": \"curryst01\",\n \"playerName\": \"Stephen Curry\",\n \"position\": \"PG\",\n \"age\": 30,\n \"games\": 22,\n \"gamesStarted\": 22,\n \"minutesPg\": 846,\n \"fieldGoals\": 190,\n \"fieldAttempts\": 431,\n \"fieldPercent\": 0.441,\n \"threeFg\": 92,\n \"threeAttempts\": 244,\n \"threePercent\": 0.377,\n \"twoFg\": 98,\n \"twoAttempts\": 187,\n \"twoPercent\": 0.524,\n \"effectFgPercent\": 0.548,\n \"ft\": 148,\n \"ftAttempts\": 157,\n \"ftPercent\": 0.943,\n \"offensiveRb\": 17,\n \"defensiveRb\": 115,\n \"totalRb\": 132,\n \"assists\": 126,\n \"steals\": 24,\n \"blocks\": 4,\n \"turnovers\": 66,\n \"personalFouls\": 68,\n \"points\": 620,\n \"team\": \"GSW\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:26.755237Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:26.755237Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7385,\n \"id\": 106,\n \"playerId\": \"jokicni01\",\n \"playerName\": \"Nikola Jokić\",\n \"position\": \"C\",\n \"age\": 23,\n \"games\": 14,\n \"gamesStarted\": 14,\n \"minutesPg\": 557,\n \"fieldGoals\": 132,\n \"fieldAttempts\": 261,\n \"fieldPercent\": 0.506,\n \"threeFg\": 22,\n \"threeAttempts\": 56,\n \"threePercent\": 0.393,\n \"twoFg\": 110,\n \"twoAttempts\": 205,\n \"twoPercent\": 0.537,\n \"effectFgPercent\": 0.548,\n \"ft\": 66,\n \"ftAttempts\": 78,\n \"ftPercent\": 0.846,\n \"offensiveRb\": 54,\n \"defensiveRb\": 128,\n \"totalRb\": 182,\n \"assists\": 118,\n \"steals\": 16,\n \"blocks\": 13,\n \"turnovers\": 36,\n \"personalFouls\": 54,\n \"points\": 352,\n \"team\": \"DEN\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:38.912453Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:38.912453Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7399,\n \"id\": 120,\n \"playerId\": \"lillada01\",\n \"playerName\": \"Damian Lillard\",\n \"position\": \"PG\",\n \"age\": 28,\n \"games\": 16,\n \"gamesStarted\": 16,\n \"minutesPg\": 650,\n \"fieldGoals\": 138,\n \"fieldAttempts\": 330,\n \"fieldPercent\": 0.418,\n \"threeFg\": 59,\n \"threeAttempts\": 158,\n \"threePercent\": 0.373,\n \"twoFg\": 79,\n \"twoAttempts\": 172,\n \"twoPercent\": 0.459,\n \"effectFgPercent\": 0.508,\n \"ft\": 95,\n \"ftAttempts\": 114,\n \"ftPercent\": 0.833,\n \"offensiveRb\": 8,\n \"defensiveRb\": 68,\n \"totalRb\": 76,\n \"assists\": 106,\n \"steals\": 27,\n \"blocks\": 5,\n \"turnovers\": 60,\n \"personalFouls\": 40,\n \"points\": 430,\n \"team\": \"POR\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:41.632205Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:41.632205Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7395,\n \"id\": 116,\n \"playerId\": \"leonaka01\",\n \"playerName\": \"Kawhi Leonard\",\n \"position\": \"SF\",\n \"age\": 27,\n \"games\": 24,\n \"gamesStarted\": 24,\n \"minutesPg\": 939,\n \"fieldGoals\": 243,\n \"fieldAttempts\": 496,\n \"fieldPercent\": 0.49,\n \"threeFg\": 55,\n \"threeAttempts\": 145,\n \"threePercent\": 0.379,\n \"twoFg\": 188,\n \"twoAttempts\": 351,\n \"twoPercent\": 0.536,\n \"effectFgPercent\": 0.545,\n \"ft\": 191,\n \"ftAttempts\": 216,\n \"ftPercent\": 0.884,\n \"offensiveRb\": 54,\n \"defensiveRb\": 164,\n \"totalRb\": 218,\n \"assists\": 94,\n \"steals\": 40,\n \"blocks\": 17,\n \"turnovers\": 74,\n \"personalFouls\": 56,\n \"points\": 732,\n \"team\": \"TOR\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:40.895323Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:40.895323Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7376,\n \"id\": 97,\n \"playerId\": \"iguodan01\",\n \"playerName\": \"Andre Iguodala\",\n \"position\": \"SF\",\n \"age\": 35,\n \"games\": 21,\n \"gamesStarted\": 15,\n \"minutesPg\": 629,\n \"fieldGoals\": 82,\n \"fieldAttempts\": 166,\n \"fieldPercent\": 0.494,\n \"threeFg\": 28,\n \"threeAttempts\": 80,\n \"threePercent\": 0.35,\n \"twoFg\": 54,\n \"twoAttempts\": 86,\n \"twoPercent\": 0.628,\n \"effectFgPercent\": 0.578,\n \"ft\": 14,\n \"ftAttempts\": 37,\n \"ftPercent\": 0.378,\n \"offensiveRb\": 27,\n \"defensiveRb\": 64,\n \"totalRb\": 91,\n \"assists\": 83,\n \"steals\": 24,\n \"blocks\": 23,\n \"turnovers\": 21,\n \"personalFouls\": 43,\n \"points\": 206,\n \"team\": \"GSW\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:37.278972Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:37.278972Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7285,\n \"id\": 6,\n \"playerId\": \"antetgi01\",\n \"playerName\": \"Giannis Antetokounmpo\",\n \"position\": \"PF\",\n \"age\": 24,\n \"games\": 15,\n \"gamesStarted\": 15,\n \"minutesPg\": 514,\n \"fieldGoals\": 129,\n \"fieldAttempts\": 261,\n \"fieldPercent\": 0.494,\n \"threeFg\": 18,\n \"threeAttempts\": 55,\n \"threePercent\": 0.327,\n \"twoFg\": 111,\n \"twoAttempts\": 206,\n \"twoPercent\": 0.539,\n \"effectFgPercent\": 0.529,\n \"ft\": 107,\n \"ftAttempts\": 168,\n \"ftPercent\": 0.637,\n \"offensiveRb\": 36,\n \"defensiveRb\": 147,\n \"totalRb\": 183,\n \"assists\": 73,\n \"steals\": 18,\n \"blocks\": 30,\n \"turnovers\": 50,\n \"personalFouls\": 48,\n \"points\": 383,\n \"team\": \"MIL\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:19.819646Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:19.819646Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7344,\n \"id\": 65,\n \"playerId\": \"gasolma01\",\n \"playerName\": \"Marc Gasol\",\n \"position\": \"C\",\n \"age\": 34,\n \"games\": 24,\n \"gamesStarted\": 24,\n \"minutesPg\": 735,\n \"fieldGoals\": 76,\n \"fieldAttempts\": 180,\n \"fieldPercent\": 0.422,\n \"threeFg\": 34,\n \"threeAttempts\": 89,\n \"threePercent\": 0.382,\n \"twoFg\": 42,\n \"twoAttempts\": 91,\n \"twoPercent\": 0.462,\n \"effectFgPercent\": 0.517,\n \"ft\": 40,\n \"ftAttempts\": 46,\n \"ftPercent\": 0.87,\n \"offensiveRb\": 16,\n \"defensiveRb\": 138,\n \"totalRb\": 154,\n \"assists\": 73,\n \"steals\": 21,\n \"blocks\": 26,\n \"turnovers\": 21,\n \"personalFouls\": 84,\n \"points\": 226,\n \"team\": \"TOR\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:31.095854Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:31.095854Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7359,\n \"id\": 80,\n \"playerId\": \"hardeja01\",\n \"playerName\": \"James Harden\",\n \"position\": \"PG\",\n \"age\": 29,\n \"games\": 11,\n \"gamesStarted\": 11,\n \"minutesPg\": 424,\n \"fieldGoals\": 109,\n \"fieldAttempts\": 264,\n \"fieldPercent\": 0.413,\n \"threeFg\": 48,\n \"threeAttempts\": 137,\n \"threePercent\": 0.35,\n \"twoFg\": 61,\n \"twoAttempts\": 127,\n \"twoPercent\": 0.48,\n \"effectFgPercent\": 0.504,\n \"ft\": 82,\n \"ftAttempts\": 98,\n \"ftPercent\": 0.837,\n \"offensiveRb\": 9,\n \"defensiveRb\": 66,\n \"totalRb\": 75,\n \"assists\": 73,\n \"steals\": 24,\n \"blocks\": 10,\n \"turnovers\": 51,\n \"personalFouls\": 41,\n \"points\": 348,\n \"team\": \"HOU\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:33.976424Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:33.976424Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7460,\n \"id\": 181,\n \"playerId\": \"simmobe01\",\n \"playerName\": \"Ben Simmons\",\n \"position\": \"PG\",\n \"age\": 22,\n \"games\": 12,\n \"gamesStarted\": 12,\n \"minutesPg\": 421,\n \"fieldGoals\": 72,\n \"fieldAttempts\": 116,\n \"fieldPercent\": 0.621,\n \"threeFg\": 0,\n \"threeAttempts\": 0,\n \"threePercent\": 0,\n \"twoFg\": 72,\n \"twoAttempts\": 116,\n \"twoPercent\": 0.621,\n \"effectFgPercent\": 0.621,\n \"ft\": 23,\n \"ftAttempts\": 40,\n \"ftPercent\": 0.575,\n \"offensiveRb\": 23,\n \"defensiveRb\": 61,\n \"totalRb\": 84,\n \"assists\": 72,\n \"steals\": 15,\n \"blocks\": 12,\n \"turnovers\": 32,\n \"personalFouls\": 45,\n \"points\": 167,\n \"team\": \"PHI\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:53.424036Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:53.424036Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7416,\n \"id\": 137,\n \"playerId\": \"middlkh01\",\n \"playerName\": \"Khris Middleton\",\n \"position\": \"SF\",\n \"age\": 27,\n \"games\": 15,\n \"gamesStarted\": 15,\n \"minutesPg\": 515,\n \"fieldGoals\": 84,\n \"fieldAttempts\": 201,\n \"fieldPercent\": 0.418,\n \"threeFg\": 40,\n \"threeAttempts\": 92,\n \"threePercent\": 0.435,\n \"twoFg\": 44,\n \"twoAttempts\": 109,\n \"twoPercent\": 0.404,\n \"effectFgPercent\": 0.517,\n \"ft\": 46,\n \"ftAttempts\": 52,\n \"ftPercent\": 0.885,\n \"offensiveRb\": 9,\n \"defensiveRb\": 86,\n \"totalRb\": 95,\n \"assists\": 66,\n \"steals\": 9,\n \"blocks\": 0,\n \"turnovers\": 29,\n \"personalFouls\": 44,\n \"points\": 254,\n \"team\": \"MIL\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:44.932463Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:44.932463Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7428,\n \"id\": 149,\n \"playerId\": \"murraja01\",\n \"playerName\": \"Jamal Murray\",\n \"position\": \"PG\",\n \"age\": 21,\n \"games\": 14,\n \"gamesStarted\": 14,\n \"minutesPg\": 508,\n \"fieldGoals\": 107,\n \"fieldAttempts\": 252,\n \"fieldPercent\": 0.425,\n \"threeFg\": 28,\n \"threeAttempts\": 83,\n \"threePercent\": 0.337,\n \"twoFg\": 79,\n \"twoAttempts\": 169,\n \"twoPercent\": 0.467,\n \"effectFgPercent\": 0.48,\n \"ft\": 56,\n \"ftAttempts\": 62,\n \"ftPercent\": 0.903,\n \"offensiveRb\": 19,\n \"defensiveRb\": 42,\n \"totalRb\": 61,\n \"assists\": 66,\n \"steals\": 14,\n \"blocks\": 2,\n \"turnovers\": 22,\n \"personalFouls\": 24,\n \"points\": 298,\n \"team\": \"DEN\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:47.284827Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:47.284827Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7459,\n \"id\": 180,\n \"playerId\": \"siakapa01\",\n \"playerName\": \"Pascal Siakam\",\n \"position\": \"PF\",\n \"age\": 24,\n \"games\": 24,\n \"gamesStarted\": 24,\n \"minutesPg\": 891,\n \"fieldGoals\": 180,\n \"fieldAttempts\": 383,\n \"fieldPercent\": 0.47,\n \"threeFg\": 29,\n \"threeAttempts\": 104,\n \"threePercent\": 0.279,\n \"twoFg\": 151,\n \"twoAttempts\": 279,\n \"twoPercent\": 0.541,\n \"effectFgPercent\": 0.508,\n \"ft\": 66,\n \"ftAttempts\": 87,\n \"ftPercent\": 0.759,\n \"offensiveRb\": 40,\n \"defensiveRb\": 131,\n \"totalRb\": 171,\n \"assists\": 66,\n \"steals\": 24,\n \"blocks\": 17,\n \"turnovers\": 33,\n \"personalFouls\": 67,\n \"points\": 455,\n \"team\": \"TOR\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:53.175678Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:53.175678Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7295,\n \"id\": 16,\n \"playerId\": \"bledser01\",\n \"playerName\": \"Eric Bledsoe\",\n \"position\": \"PG\",\n \"age\": 29,\n \"games\": 15,\n \"gamesStarted\": 15,\n \"minutesPg\": 423,\n \"fieldGoals\": 76,\n \"fieldAttempts\": 185,\n \"fieldPercent\": 0.411,\n \"threeFg\": 17,\n \"threeAttempts\": 72,\n \"threePercent\": 0.236,\n \"twoFg\": 59,\n \"twoAttempts\": 113,\n \"twoPercent\": 0.522,\n \"effectFgPercent\": 0.457,\n \"ft\": 36,\n \"ftAttempts\": 51,\n \"ftPercent\": 0.706,\n \"offensiveRb\": 20,\n \"defensiveRb\": 36,\n \"totalRb\": 56,\n \"assists\": 64,\n \"steals\": 16,\n \"blocks\": 6,\n \"turnovers\": 32,\n \"personalFouls\": 28,\n \"points\": 205,\n \"team\": \"MIL\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:21.702841Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:21.702841Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7379,\n \"id\": 100,\n \"playerId\": \"irvinky01\",\n \"playerName\": \"Kyrie Irving\",\n \"position\": \"PG\",\n \"age\": 26,\n \"games\": 9,\n \"gamesStarted\": 9,\n \"minutesPg\": 330,\n \"fieldGoals\": 69,\n \"fieldAttempts\": 179,\n \"fieldPercent\": 0.385,\n \"threeFg\": 18,\n \"threeAttempts\": 58,\n \"threePercent\": 0.31,\n \"twoFg\": 51,\n \"twoAttempts\": 121,\n \"twoPercent\": 0.421,\n \"effectFgPercent\": 0.436,\n \"ft\": 36,\n \"ftAttempts\": 40,\n \"ftPercent\": 0.9,\n \"offensiveRb\": 5,\n \"defensiveRb\": 34,\n \"totalRb\": 39,\n \"assists\": 63,\n \"steals\": 13,\n \"blocks\": 4,\n \"turnovers\": 28,\n \"personalFouls\": 26,\n \"points\": 192,\n \"team\": \"BOS\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:37.814543Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:37.814543Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7305,\n \"id\": 26,\n \"playerId\": \"butleji01\",\n \"playerName\": \"Jimmy Butler\",\n \"position\": \"SF\",\n \"age\": 29,\n \"games\": 12,\n \"gamesStarted\": 12,\n \"minutesPg\": 421,\n \"fieldGoals\": 79,\n \"fieldAttempts\": 175,\n \"fieldPercent\": 0.451,\n \"threeFg\": 12,\n \"threeAttempts\": 45,\n \"threePercent\": 0.267,\n \"twoFg\": 67,\n \"twoAttempts\": 130,\n \"twoPercent\": 0.515,\n \"effectFgPercent\": 0.486,\n \"ft\": 63,\n \"ftAttempts\": 72,\n \"ftPercent\": 0.875,\n \"offensiveRb\": 22,\n \"defensiveRb\": 50,\n \"totalRb\": 72,\n \"assists\": 62,\n \"steals\": 18,\n \"blocks\": 7,\n \"turnovers\": 22,\n \"personalFouls\": 20,\n \"points\": 233,\n \"team\": \"PHI\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:23.708252Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:23.708252Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7479,\n \"id\": 200,\n \"playerId\": \"vanvlfr01\",\n \"playerName\": \"Fred VanVleet\",\n \"position\": \"PG\",\n \"age\": 24,\n \"games\": 24,\n \"gamesStarted\": 0,\n \"minutesPg\": 592,\n \"fieldGoals\": 65,\n \"fieldAttempts\": 166,\n \"fieldPercent\": 0.392,\n \"threeFg\": 38,\n \"threeAttempts\": 98,\n \"threePercent\": 0.388,\n \"twoFg\": 27,\n \"twoAttempts\": 68,\n \"twoPercent\": 0.397,\n \"effectFgPercent\": 0.506,\n \"ft\": 24,\n \"ftAttempts\": 31,\n \"ftPercent\": 0.774,\n \"offensiveRb\": 7,\n \"defensiveRb\": 33,\n \"totalRb\": 40,\n \"assists\": 62,\n \"steals\": 18,\n \"blocks\": 6,\n \"turnovers\": 21,\n \"personalFouls\": 49,\n \"points\": 192,\n \"team\": \"TOR\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:57.203361Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:57.203361Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7439,\n \"id\": 160,\n \"playerId\": \"paulch01\",\n \"playerName\": \"Chris Paul\",\n \"position\": \"PG\",\n \"age\": 33,\n \"games\": 11,\n \"gamesStarted\": 11,\n \"minutesPg\": 397,\n \"fieldGoals\": 66,\n \"fieldAttempts\": 148,\n \"fieldPercent\": 0.446,\n \"threeFg\": 17,\n \"threeAttempts\": 63,\n \"threePercent\": 0.27,\n \"twoFg\": 49,\n \"twoAttempts\": 85,\n \"twoPercent\": 0.576,\n \"effectFgPercent\": 0.503,\n \"ft\": 38,\n \"ftAttempts\": 45,\n \"ftPercent\": 0.844,\n \"offensiveRb\": 14,\n \"defensiveRb\": 56,\n \"totalRb\": 70,\n \"assists\": 60,\n \"steals\": 24,\n \"blocks\": 7,\n \"turnovers\": 37,\n \"personalFouls\": 36,\n \"points\": 187,\n \"team\": \"HOU\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:49.350696Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:49.350696Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 7411,\n \"id\": 132,\n \"playerId\": \"mccolcj01\",\n \"playerName\": \"CJ McCollum\",\n \"position\": \"SG\",\n \"age\": 27,\n \"games\": 16,\n \"gamesStarted\": 16,\n \"minutesPg\": 635,\n \"fieldGoals\": 154,\n \"fieldAttempts\": 350,\n \"fieldPercent\": 0.44,\n \"threeFg\": 46,\n \"threeAttempts\": 117,\n \"threePercent\": 0.393,\n \"twoFg\": 108,\n \"twoAttempts\": 233,\n \"twoPercent\": 0.464,\n \"effectFgPercent\": 0.506,\n \"ft\": 41,\n \"ftAttempts\": 56,\n \"ftPercent\": 0.732,\n \"offensiveRb\": 14,\n \"defensiveRb\": 66,\n \"totalRb\": 80,\n \"assists\": 59,\n \"steals\": 13,\n \"blocks\": 10,\n \"turnovers\": 30,\n \"personalFouls\": 34,\n \"points\": 395,\n \"team\": \"POR\",\n \"season\": 2019,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T02:54:43.98991Z\",\n \"UpdatedAt\": \"2025-06-20T02:54:43.98991Z\",\n \"DeletedAt\": null\n }\n ],\n \"pagination\": {\n \"page\": 1,\n \"pageSize\": 20,\n \"pages\": 11,\n \"total\": 212\n }\n}"
+ }
+ ]
+ },
+ {
+ "name": "Player Advanced Stats",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "pm.test(\"Response status code is 200\", function () {",
+ " pm.response.to.have.status(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response time is within acceptable range\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response schema matches the expected structure\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('object');",
+ " pm.expect(responseData.data).to.be.an('array').that.is.not.empty;",
+ " ",
+ " responseData.data.forEach(function(playerStats) {",
+ " pm.expect(playerStats).to.have.property('ID');",
+ " pm.expect(playerStats).to.have.property('CreatedAt');",
+ " pm.expect(playerStats).to.have.property('UpdatedAt');",
+ " pm.expect(playerStats).to.have.property('playerId');",
+ " pm.expect(playerStats).to.have.property('playerName');",
+ " pm.expect(playerStats).to.have.property('position');",
+ " pm.expect(playerStats).to.have.property('age');",
+ " pm.expect(playerStats).to.have.property('games');",
+ " pm.expect(playerStats).to.have.property('minutesPlayed');",
+ " pm.expect(playerStats).to.have.property('per');",
+ " pm.expect(playerStats).to.have.property('tsPercent');",
+ " pm.expect(playerStats).to.have.property('threePAR');",
+ " pm.expect(playerStats).to.have.property('ftr');",
+ " pm.expect(playerStats).to.have.property('offensiveRBPercent');",
+ " pm.expect(playerStats).to.have.property('defensiveRBPercent');",
+ " pm.expect(playerStats).to.have.property('totalRBPercent');",
+ " pm.expect(playerStats).to.have.property('assistPercent');",
+ " pm.expect(playerStats).to.have.property('stealPercent');",
+ " pm.expect(playerStats).to.have.property('blockPercent');",
+ " pm.expect(playerStats).to.have.property('turnoverPercent');",
+ " pm.expect(playerStats).to.have.property('usagePercent');",
+ " pm.expect(playerStats).to.have.property('offensiveWS');",
+ " pm.expect(playerStats).to.have.property('defensiveWS');",
+ " pm.expect(playerStats).to.have.property('winShares');",
+ " pm.expect(playerStats).to.have.property('winSharesPer');",
+ " pm.expect(playerStats).to.have.property('offensiveBox');",
+ " pm.expect(playerStats).to.have.property('defensiveBox');",
+ " pm.expect(playerStats).to.have.property('box');",
+ " pm.expect(playerStats).to.have.property('vorp');",
+ " pm.expect(playerStats).to.have.property('team');",
+ " pm.expect(playerStats).to.have.property('season');",
+ " });",
+ "});",
+ "pm.test(\"Response time is within acceptable range\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});"
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playeradvancedstats?season=2025&team=HOU&page=1&pageSize=35&sortBy=vorp&ascending=false&isPlayoff=false",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playeradvancedstats"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2025",
+ "description": "(Optional) The NBA season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "HOU",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL)."
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "35",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "sortBy",
+ "value": "vorp",
+ "description": "(Optional) The field to sort the results by. Defaults to 'winShares'."
+ },
+ {
+ "key": "ascending",
+ "value": "false",
+ "description": "(Optional) If true, sorts the results in ascending order. Defaults to false (descending)."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "false",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ },
+ "description": "### Get Player Advanced Stats\n\nReturns a paginated and filterable list of advanced player statistics for a given season. You can retrieve either regular season or playoff data.\n\n### Usage\n\nThis endpoint supports filtering by `season`, `team`, and `playerId`. Results can be paginated using `page` and `pageSize`, and sorted using `sortBy` and `ascending`.\n\n#### **Query Parameters**\n\n* **`season`** (integer, optional): The season year (e.g., 2025).\n* **`team`** (string, optional): Team abbreviation (e.g., MIL).\n* **`playerId`** (string, optional): The player's unique ID (e.g., `greenaj01`).\n* **`page`** (integer, optional): Page number. Defaults to `1`.\n* **`pageSize`** (integer, optional): Number of results per page. Defaults to `20`.\n* **`isPlayoff`** (boolean, optional): Set to `true` for playoff stats, `false` for regular season. The filter is only applied if the parameter is provided.\n* **`sortBy`** (string, optional): Field to sort by. Defaults to `winShares`.\n* **`ascending`** (boolean, optional): Use `true` for ascending order. Defaults to `false`.\n\n#### **Available `sortBy` Fields:**\n`playerId`, `playerName`, `position`, `age`, `games`, `minutesPlayed`, `per`, `tsPercent`, `threePAR`, `ftr`, `offensiveRBPercent`, `defensiveRBPercent`, `totalRBPercent`, `assistPercent`, `stealPercent`, `blockPercent`, `turnoverPercent`, `usagePercent`, `offensiveWS`, `defensiveWS`, `winShares`, `winSharesPer`, `offensiveBox`, `defensiveBox`, `box`, `vorp`, `team`, `season`"
+ },
+ "response": [
+ {
+ "name": "Player Advanced Stats",
+ "originalRequest": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playeradvancedstats?season=2025&team=HOU&page=1&pageSize=35&sortBy=vorp&ascending=false&isPlayoff=false",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playeradvancedstats"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2025",
+ "description": "(Optional) The NBA season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "HOU",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL)."
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "35",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "sortBy",
+ "value": "vorp",
+ "description": "(Optional) The field to sort the results by. Defaults to 'winShares'."
+ },
+ {
+ "key": "ascending",
+ "value": "false",
+ "description": "(Optional) If true, sorts the results in ascending order. Defaults to false (descending)."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "false",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ }
+ },
+ "status": "OK",
+ "code": 200,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "Alt-Svc",
+ "value": "h3=\":443\"; ma=2592000"
+ },
+ {
+ "key": "Content-Encoding",
+ "value": "br"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Date",
+ "value": "Wed, 02 Jul 2025 01:18:43 GMT"
+ },
+ {
+ "key": "Server",
+ "value": "nginx/1.28.0"
+ },
+ {
+ "key": "Vary",
+ "value": "Accept-Encoding"
+ },
+ {
+ "key": "Transfer-Encoding",
+ "value": "chunked"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"data\": [\n {\n \"ID\": 1451,\n \"id\": 35,\n \"playerId\": \"sengual01\",\n \"playerName\": \"Alperen Şengün\",\n \"position\": \"C\",\n \"age\": 22,\n \"games\": 76,\n \"minutesPlayed\": 2395,\n \"per\": 21.4,\n \"tsPercent\": 0.545,\n \"threePAR\": 0.079,\n \"ftr\": 0.374,\n \"offensiveRBPercent\": 11.4,\n \"defensiveRBPercent\": 23.8,\n \"totalRBPercent\": 17.5,\n \"assistPercent\": 24.1,\n \"stealPercent\": 1.7,\n \"blockPercent\": 2.3,\n \"turnoverPercent\": 12.7,\n \"usagePercent\": 26.2,\n \"offensiveWS\": 4.1,\n \"defensiveWS\": 4.2,\n \"winShares\": 8.3,\n \"winSharesPer\": 0.166,\n \"offensiveBox\": 2.6,\n \"defensiveBox\": 1.8,\n \"box\": 4.4,\n \"vorp\": 3.9,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T00:59:56.345088Z\",\n \"UpdatedAt\": \"2025-06-20T00:59:56.345088Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1475,\n \"id\": 57,\n \"playerId\": \"thompam01\",\n \"playerName\": \"Amen Thompson\",\n \"position\": \"SF\",\n \"age\": 22,\n \"games\": 69,\n \"minutesPlayed\": 2225,\n \"per\": 18.7,\n \"tsPercent\": 0.602,\n \"threePAR\": 0.131,\n \"ftr\": 0.354,\n \"offensiveRBPercent\": 9,\n \"defensiveRBPercent\": 18.2,\n \"totalRBPercent\": 13.5,\n \"assistPercent\": 16.9,\n \"stealPercent\": 2.1,\n \"blockPercent\": 3.6,\n \"turnoverPercent\": 14.6,\n \"usagePercent\": 17.5,\n \"offensiveWS\": 4.1,\n \"defensiveWS\": 3.9,\n \"winShares\": 8,\n \"winSharesPer\": 0.172,\n \"offensiveBox\": 1.4,\n \"defensiveBox\": 2.6,\n \"box\": 4.1,\n \"vorp\": 3.4,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T00:59:59.812013Z\",\n \"UpdatedAt\": \"2025-06-20T00:59:59.812013Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1643,\n \"id\": 190,\n \"playerId\": \"easonta01\",\n \"playerName\": \"Tari Eason\",\n \"position\": \"PF\",\n \"age\": 23,\n \"games\": 57,\n \"minutesPlayed\": 1420,\n \"per\": 18.5,\n \"tsPercent\": 0.567,\n \"threePAR\": 0.329,\n \"ftr\": 0.186,\n \"offensiveRBPercent\": 9.4,\n \"defensiveRBPercent\": 17.9,\n \"totalRBPercent\": 13.6,\n \"assistPercent\": 8.5,\n \"stealPercent\": 3.3,\n \"blockPercent\": 3.2,\n \"turnoverPercent\": 9.7,\n \"usagePercent\": 19.4,\n \"offensiveWS\": 2.1,\n \"defensiveWS\": 2.9,\n \"winShares\": 5,\n \"winSharesPer\": 0.169,\n \"offensiveBox\": 1,\n \"defensiveBox\": 2.6,\n \"box\": 3.6,\n \"vorp\": 2,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:25.170944Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:25.170944Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1424,\n \"id\": 10,\n \"playerId\": \"greenja05\",\n \"playerName\": \"Jalen Green\",\n \"position\": \"SG\",\n \"age\": 22,\n \"games\": 82,\n \"minutesPlayed\": 2697,\n \"per\": 15.1,\n \"tsPercent\": 0.544,\n \"threePAR\": 0.46,\n \"ftr\": 0.234,\n \"offensiveRBPercent\": 1.7,\n \"defensiveRBPercent\": 13.4,\n \"totalRBPercent\": 7.4,\n \"assistPercent\": 16,\n \"stealPercent\": 1.3,\n \"blockPercent\": 0.9,\n \"turnoverPercent\": 11.4,\n \"usagePercent\": 27.3,\n \"offensiveWS\": 1.9,\n \"defensiveWS\": 3.2,\n \"winShares\": 5.1,\n \"winSharesPer\": 0.092,\n \"offensiveBox\": 1.1,\n \"defensiveBox\": -0.5,\n \"box\": 0.5,\n \"vorp\": 1.7,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T00:59:52.368605Z\",\n \"UpdatedAt\": \"2025-06-20T00:59:52.368605Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1494,\n \"id\": 74,\n \"playerId\": \"vanvlfr01\",\n \"playerName\": \"Fred VanVleet\",\n \"position\": \"PG\",\n \"age\": 30,\n \"games\": 60,\n \"minutesPlayed\": 2111,\n \"per\": 12.8,\n \"tsPercent\": 0.515,\n \"threePAR\": 0.607,\n \"ftr\": 0.181,\n \"offensiveRBPercent\": 1.6,\n \"defensiveRBPercent\": 9.7,\n \"totalRBPercent\": 5.6,\n \"assistPercent\": 21.2,\n \"stealPercent\": 2.2,\n \"blockPercent\": 1.1,\n \"turnoverPercent\": 9.6,\n \"usagePercent\": 17.7,\n \"offensiveWS\": 2.4,\n \"defensiveWS\": 2.8,\n \"winShares\": 5.2,\n \"winSharesPer\": 0.119,\n \"offensiveBox\": -0.4,\n \"defensiveBox\": 1.3,\n \"box\": 0.9,\n \"vorp\": 1.5,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:02.726841Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:02.726841Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1583,\n \"id\": 144,\n \"playerId\": \"smithja05\",\n \"playerName\": \"Jabari Smith Jr.\",\n \"position\": \"PF\",\n \"age\": 21,\n \"games\": 57,\n \"minutesPlayed\": 1716,\n \"per\": 12.6,\n \"tsPercent\": 0.562,\n \"threePAR\": 0.489,\n \"ftr\": 0.223,\n \"offensiveRBPercent\": 6.4,\n \"defensiveRBPercent\": 18.6,\n \"totalRBPercent\": 12.4,\n \"assistPercent\": 4.7,\n \"stealPercent\": 0.7,\n \"blockPercent\": 2.1,\n \"turnoverPercent\": 8.9,\n \"usagePercent\": 16.4,\n \"offensiveWS\": 1.9,\n \"defensiveWS\": 2.2,\n \"winShares\": 4.2,\n \"winSharesPer\": 0.117,\n \"offensiveBox\": -0.8,\n \"defensiveBox\": 0.1,\n \"box\": -0.7,\n \"vorp\": 0.6,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:16.318994Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:16.318994Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1792,\n \"id\": 301,\n \"playerId\": \"whitmca01\",\n \"playerName\": \"Cam Whitmore\",\n \"position\": \"SF\",\n \"age\": 20,\n \"games\": 51,\n \"minutesPlayed\": 827,\n \"per\": 15.5,\n \"tsPercent\": 0.549,\n \"threePAR\": 0.454,\n \"ftr\": 0.179,\n \"offensiveRBPercent\": 4.5,\n \"defensiveRBPercent\": 15.1,\n \"totalRBPercent\": 9.7,\n \"assistPercent\": 8.9,\n \"stealPercent\": 1.8,\n \"blockPercent\": 1.4,\n \"turnoverPercent\": 9.6,\n \"usagePercent\": 23.9,\n \"offensiveWS\": 0.6,\n \"defensiveWS\": 1.2,\n \"winShares\": 1.8,\n \"winSharesPer\": 0.106,\n \"offensiveBox\": 0.7,\n \"defensiveBox\": -0.4,\n \"box\": 0.3,\n \"vorp\": 0.5,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:46.875219Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:46.875219Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1795,\n \"id\": 304,\n \"playerId\": \"adamsst01\",\n \"playerName\": \"Steven Adams\",\n \"position\": \"C\",\n \"age\": 31,\n \"games\": 58,\n \"minutesPlayed\": 794,\n \"per\": 16.6,\n \"tsPercent\": 0.541,\n \"threePAR\": 0.012,\n \"ftr\": 0.557,\n \"offensiveRBPercent\": 21.8,\n \"defensiveRBPercent\": 22,\n \"totalRBPercent\": 21.9,\n \"assistPercent\": 10.9,\n \"stealPercent\": 1.3,\n \"blockPercent\": 3.2,\n \"turnoverPercent\": 20.6,\n \"usagePercent\": 13.6,\n \"offensiveWS\": 1,\n \"defensiveWS\": 1.3,\n \"winShares\": 2.3,\n \"winSharesPer\": 0.137,\n \"offensiveBox\": 0,\n \"defensiveBox\": 0.3,\n \"box\": 0.3,\n \"vorp\": 0.5,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:47.313648Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:47.313648Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1452,\n \"id\": 36,\n \"playerId\": \"brookdi01\",\n \"playerName\": \"Dillon Brooks\",\n \"position\": \"SF\",\n \"age\": 29,\n \"games\": 75,\n \"minutesPlayed\": 2388,\n \"per\": 11,\n \"tsPercent\": 0.555,\n \"threePAR\": 0.524,\n \"ftr\": 0.135,\n \"offensiveRBPercent\": 3.3,\n \"defensiveRBPercent\": 9.1,\n \"totalRBPercent\": 6.1,\n \"assistPercent\": 7.4,\n \"stealPercent\": 1.2,\n \"blockPercent\": 0.6,\n \"turnoverPercent\": 7.4,\n \"usagePercent\": 17.7,\n \"offensiveWS\": 2.7,\n \"defensiveWS\": 2.5,\n \"winShares\": 5.1,\n \"winSharesPer\": 0.103,\n \"offensiveBox\": -1.4,\n \"defensiveBox\": 0,\n \"box\": -1.4,\n \"vorp\": 0.4,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T00:59:56.492538Z\",\n \"UpdatedAt\": \"2025-06-20T00:59:56.492538Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1796,\n \"id\": 305,\n \"playerId\": \"holidaa01\",\n \"playerName\": \"Aaron Holiday\",\n \"position\": \"PG\",\n \"age\": 28,\n \"games\": 62,\n \"minutesPlayed\": 792,\n \"per\": 12.2,\n \"tsPercent\": 0.594,\n \"threePAR\": 0.675,\n \"ftr\": 0.153,\n \"offensiveRBPercent\": 1.7,\n \"defensiveRBPercent\": 8.9,\n \"totalRBPercent\": 5.2,\n \"assistPercent\": 14.3,\n \"stealPercent\": 1.2,\n \"blockPercent\": 1.2,\n \"turnoverPercent\": 11.5,\n \"usagePercent\": 16.8,\n \"offensiveWS\": 1.2,\n \"defensiveWS\": 0.8,\n \"winShares\": 2,\n \"winSharesPer\": 0.122,\n \"offensiveBox\": -0.4,\n \"defensiveBox\": 0.1,\n \"box\": -0.2,\n \"vorp\": 0.3,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:47.482453Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:47.482453Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1888,\n \"id\": 375,\n \"playerId\": \"landajo01\",\n \"playerName\": \"Jock Landale\",\n \"position\": \"C\",\n \"age\": 29,\n \"games\": 42,\n \"minutesPlayed\": 500,\n \"per\": 16,\n \"tsPercent\": 0.59,\n \"threePAR\": 0.171,\n \"ftr\": 0.263,\n \"offensiveRBPercent\": 11.7,\n \"defensiveRBPercent\": 17.6,\n \"totalRBPercent\": 14.6,\n \"assistPercent\": 10.3,\n \"stealPercent\": 1.3,\n \"blockPercent\": 1.8,\n \"turnoverPercent\": 11,\n \"usagePercent\": 15.7,\n \"offensiveWS\": 1,\n \"defensiveWS\": 0.7,\n \"winShares\": 1.7,\n \"winSharesPer\": 0.159,\n \"offensiveBox\": 0,\n \"defensiveBox\": 0.4,\n \"box\": 0.4,\n \"vorp\": 0.3,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:01:00.821492Z\",\n \"UpdatedAt\": \"2025-06-20T01:01:00.821492Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1862,\n \"id\": 353,\n \"playerId\": \"tateja01\",\n \"playerName\": \"Jae'Sean Tate\",\n \"position\": \"SF\",\n \"age\": 29,\n \"games\": 52,\n \"minutesPlayed\": 588,\n \"per\": 12.3,\n \"tsPercent\": 0.557,\n \"threePAR\": 0.311,\n \"ftr\": 0.318,\n \"offensiveRBPercent\": 8.9,\n \"defensiveRBPercent\": 12.4,\n \"totalRBPercent\": 10.6,\n \"assistPercent\": 10.5,\n \"stealPercent\": 2.2,\n \"blockPercent\": 1.1,\n \"turnoverPercent\": 11.5,\n \"usagePercent\": 13.3,\n \"offensiveWS\": 0.8,\n \"defensiveWS\": 0.8,\n \"winShares\": 1.6,\n \"winSharesPer\": 0.133,\n \"offensiveBox\": -2.1,\n \"defensiveBox\": 1.4,\n \"box\": -0.7,\n \"vorp\": 0.2,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:57.110788Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:57.110788Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1931,\n \"id\": 404,\n \"playerId\": \"greenje02\",\n \"playerName\": \"Jeff Green\",\n \"position\": \"PF\",\n \"age\": 38,\n \"games\": 32,\n \"minutesPlayed\": 396,\n \"per\": 13.6,\n \"tsPercent\": 0.649,\n \"threePAR\": 0.653,\n \"ftr\": 0.215,\n \"offensiveRBPercent\": 2.9,\n \"defensiveRBPercent\": 12.9,\n \"totalRBPercent\": 7.8,\n \"assistPercent\": 7,\n \"stealPercent\": 0.7,\n \"blockPercent\": 0.9,\n \"turnoverPercent\": 6.4,\n \"usagePercent\": 14.7,\n \"offensiveWS\": 0.9,\n \"defensiveWS\": 0.4,\n \"winShares\": 1.3,\n \"winSharesPer\": 0.159,\n \"offensiveBox\": 0.3,\n \"defensiveBox\": 0,\n \"box\": 0.4,\n \"vorp\": 0.2,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:01:07.235443Z\",\n \"UpdatedAt\": \"2025-06-20T01:01:07.235443Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1837,\n \"id\": 336,\n \"playerId\": \"sheppre01\",\n \"playerName\": \"Reed Sheppard\",\n \"position\": \"PG\",\n \"age\": 20,\n \"games\": 52,\n \"minutesPlayed\": 654,\n \"per\": 9.7,\n \"tsPercent\": 0.465,\n \"threePAR\": 0.594,\n \"ftr\": 0.067,\n \"offensiveRBPercent\": 2.6,\n \"defensiveRBPercent\": 10.3,\n \"totalRBPercent\": 6.4,\n \"assistPercent\": 15.2,\n \"stealPercent\": 2.6,\n \"blockPercent\": 2.3,\n \"turnoverPercent\": 13.1,\n \"usagePercent\": 17.8,\n \"offensiveWS\": -0.3,\n \"defensiveWS\": 1,\n \"winShares\": 0.8,\n \"winSharesPer\": 0.055,\n \"offensiveBox\": -3,\n \"defensiveBox\": 1.3,\n \"box\": -1.7,\n \"vorp\": 0.1,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:00:53.498684Z\",\n \"UpdatedAt\": \"2025-06-20T01:00:53.498684Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2101,\n \"id\": 523,\n \"playerId\": \"nfalyda01\",\n \"playerName\": \"N'Faly Dante\",\n \"position\": \"C\",\n \"age\": 23,\n \"games\": 4,\n \"minutesPlayed\": 51,\n \"per\": 24,\n \"tsPercent\": 0.789,\n \"threePAR\": 0,\n \"ftr\": 0.385,\n \"offensiveRBPercent\": 12.3,\n \"defensiveRBPercent\": 32,\n \"totalRBPercent\": 21.9,\n \"assistPercent\": 5.7,\n \"stealPercent\": 1,\n \"blockPercent\": 8.8,\n \"turnoverPercent\": 11.6,\n \"usagePercent\": 13.9,\n \"offensiveWS\": 0.2,\n \"defensiveWS\": 0.1,\n \"winShares\": 0.3,\n \"winSharesPer\": 0.283,\n \"offensiveBox\": 0.7,\n \"defensiveBox\": 1.7,\n \"box\": 2.4,\n \"vorp\": 0.1,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:01:31.754493Z\",\n \"UpdatedAt\": \"2025-06-20T01:01:31.754493Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 1924,\n \"id\": 397,\n \"playerId\": \"roddyda01\",\n \"playerName\": \"David Roddy\",\n \"position\": \"PF\",\n \"age\": 23,\n \"games\": 3,\n \"minutesPlayed\": 35,\n \"per\": 7.2,\n \"tsPercent\": 0.44,\n \"threePAR\": 0.538,\n \"ftr\": 0.308,\n \"offensiveRBPercent\": 0,\n \"defensiveRBPercent\": 15.5,\n \"totalRBPercent\": 7.6,\n \"assistPercent\": 7.7,\n \"stealPercent\": 0,\n \"blockPercent\": 2.6,\n \"turnoverPercent\": 6.3,\n \"usagePercent\": 18.5,\n \"offensiveWS\": 0,\n \"defensiveWS\": 0,\n \"winShares\": 0,\n \"winSharesPer\": 0.007,\n \"offensiveBox\": -5.1,\n \"defensiveBox\": -3.2,\n \"box\": -8.3,\n \"vorp\": -0.1,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:01:06.249889Z\",\n \"UpdatedAt\": \"2025-06-20T01:01:06.249889Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2038,\n \"id\": 472,\n \"playerId\": \"willije02\",\n \"playerName\": \"Jeenathan Williams\",\n \"position\": \"SG\",\n \"age\": 25,\n \"games\": 20,\n \"minutesPlayed\": 147,\n \"per\": 8.6,\n \"tsPercent\": 0.496,\n \"threePAR\": 0.419,\n \"ftr\": 0.129,\n \"offensiveRBPercent\": 3.6,\n \"defensiveRBPercent\": 5.9,\n \"totalRBPercent\": 4.7,\n \"assistPercent\": 8.8,\n \"stealPercent\": 2.6,\n \"blockPercent\": 2.4,\n \"turnoverPercent\": 17.6,\n \"usagePercent\": 22.3,\n \"offensiveWS\": -0.2,\n \"defensiveWS\": 0.2,\n \"winShares\": 0,\n \"winSharesPer\": -0.007,\n \"offensiveBox\": -5.5,\n \"defensiveBox\": -0.2,\n \"box\": -5.7,\n \"vorp\": -0.1,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:01:22.535279Z\",\n \"UpdatedAt\": \"2025-06-20T01:01:22.535279Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2106,\n \"id\": 528,\n \"playerId\": \"mcveija01\",\n \"playerName\": \"Jack McVeigh\",\n \"position\": \"PF\",\n \"age\": 28,\n \"games\": 9,\n \"minutesPlayed\": 43,\n \"per\": 4,\n \"tsPercent\": 0.412,\n \"threePAR\": 0.765,\n \"ftr\": 0,\n \"offensiveRBPercent\": 4.9,\n \"defensiveRBPercent\": 7.6,\n \"totalRBPercent\": 6.2,\n \"assistPercent\": 3,\n \"stealPercent\": 0,\n \"blockPercent\": 4.2,\n \"turnoverPercent\": 10.5,\n \"usagePercent\": 18.2,\n \"offensiveWS\": -0.1,\n \"defensiveWS\": 0,\n \"winShares\": 0,\n \"winSharesPer\": -0.031,\n \"offensiveBox\": -4.8,\n \"defensiveBox\": -3,\n \"box\": -7.7,\n \"vorp\": -0.1,\n \"team\": \"HOU\",\n \"season\": 2025,\n \"isPlayoff\": false,\n \"CreatedAt\": \"2025-06-20T01:01:32.499427Z\",\n \"UpdatedAt\": \"2025-06-20T01:01:32.499427Z\",\n \"DeletedAt\": null\n }\n ],\n \"pagination\": {\n \"total\": 18,\n \"page\": 1,\n \"pageSize\": 35,\n \"pages\": 1\n }\n}"
+ }
+ ]
+ },
+ {
+ "name": "Player Advanced Playoff Stats",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "pm.test(\"Response status code is 200\", function () {",
+ " pm.response.to.have.status(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response time is within acceptable range\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response schema matches the expected structure\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('object');",
+ " pm.expect(responseData.data).to.be.an('array').that.is.not.empty;",
+ " ",
+ " responseData.data.forEach(function(playerStats) {",
+ " pm.expect(playerStats).to.have.property('ID');",
+ " pm.expect(playerStats).to.have.property('CreatedAt');",
+ " pm.expect(playerStats).to.have.property('UpdatedAt');",
+ " pm.expect(playerStats).to.have.property('playerId');",
+ " pm.expect(playerStats).to.have.property('playerName');",
+ " pm.expect(playerStats).to.have.property('position');",
+ " pm.expect(playerStats).to.have.property('age');",
+ " pm.expect(playerStats).to.have.property('games');",
+ " pm.expect(playerStats).to.have.property('minutesPlayed');",
+ " pm.expect(playerStats).to.have.property('per');",
+ " pm.expect(playerStats).to.have.property('tsPercent');",
+ " pm.expect(playerStats).to.have.property('threePAR');",
+ " pm.expect(playerStats).to.have.property('ftr');",
+ " pm.expect(playerStats).to.have.property('offensiveRBPercent');",
+ " pm.expect(playerStats).to.have.property('defensiveRBPercent');",
+ " pm.expect(playerStats).to.have.property('totalRBPercent');",
+ " pm.expect(playerStats).to.have.property('assistPercent');",
+ " pm.expect(playerStats).to.have.property('stealPercent');",
+ " pm.expect(playerStats).to.have.property('blockPercent');",
+ " pm.expect(playerStats).to.have.property('turnoverPercent');",
+ " pm.expect(playerStats).to.have.property('usagePercent');",
+ " pm.expect(playerStats).to.have.property('offensiveWS');",
+ " pm.expect(playerStats).to.have.property('defensiveWS');",
+ " pm.expect(playerStats).to.have.property('winShares');",
+ " pm.expect(playerStats).to.have.property('winSharesPer');",
+ " pm.expect(playerStats).to.have.property('offensiveBox');",
+ " pm.expect(playerStats).to.have.property('defensiveBox');",
+ " pm.expect(playerStats).to.have.property('box');",
+ " pm.expect(playerStats).to.have.property('vorp');",
+ " pm.expect(playerStats).to.have.property('team');",
+ " pm.expect(playerStats).to.have.property('season');",
+ " });",
+ "});",
+ "pm.test(\"Response time is within acceptable range\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});"
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playeradvancedstats?season=2025&team=OKC&page=1&pageSize=35&sortBy=defensiveBox&ascending=false&isPlayoff=true",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playeradvancedstats"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2025",
+ "description": "(Optional) The NBA season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "OKC",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL)."
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "35",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "sortBy",
+ "value": "defensiveBox",
+ "description": "(Optional) The field to sort the results by. Defaults to 'winShares'."
+ },
+ {
+ "key": "ascending",
+ "value": "false",
+ "description": "(Optional) If true, sorts the results in ascending order. Defaults to false (descending)."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "true",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ },
+ "description": "### Get Player Advanced Stats (Playoffs)\n\nReturns a paginated and filterable list of advanced player statistics for the playoffs. This is an example request that sets `isPlayoff=true`.\n\n### Usage\n\nThis endpoint supports filtering by `season`, `team`, and `playerId`. Results can be paginated using `page` and `pageSize`, and sorted using `sortBy` and `ascending`.\n\n#### **Query Parameters**\n\n* **`season`** (integer, optional): The season year (e.g., 2025).\n* **`team`** (string, optional): Team abbreviation (e.g., MIL).\n* **`playerId`** (string, optional): The player's unique ID (e.g., `greenaj01`).\n* **`page`** (integer, optional): Page number. Defaults to `1`.\n* **`pageSize`** (integer, optional): Number of results per page. Defaults to `20`.\n* **`isPlayoff`** (boolean, optional): Set to `true` for playoff stats, `false` for regular season. The filter is only applied if the parameter is provided.\n* **`sortBy`** (string, optional): Field to sort by. Defaults to `winShares`.\n* **`ascending`** (boolean, optional): Use `true` for ascending order. Defaults to `false`.\n\n#### **Available `sortBy` Fields:**\n`playerId`, `playerName`, `position`, `age`, `games`, `minutesPlayed`, `per`, `tsPercent`, `threePAR`, `ftr`, `offensiveRBPercent`, `defensiveRBPercent`, `totalRBPercent`, `assistPercent`, `stealPercent`, `blockPercent`, `turnoverPercent`, `usagePercent`, `offensiveWS`, `defensiveWS`, `winShares`, `winSharesPer`, `offensiveBox`, `defensiveBox`, `box`, `vorp`, `team`, `season`"
+ },
+ "response": [
+ {
+ "name": "Player Advanced Playoff Stats",
+ "originalRequest": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playeradvancedstats?season=2025&team=OKC&page=1&pageSize=35&sortBy=defensiveBox&ascending=false&isPlayoff=true",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playeradvancedstats"
+ ],
+ "query": [
+ {
+ "key": "season",
+ "value": "2025",
+ "description": "(Optional) The NBA season year (e.g., 2025 for the 2024-25 season)."
+ },
+ {
+ "key": "team",
+ "value": "OKC",
+ "description": "(Optional) The three-letter team abbreviation (e.g., LAL, HOU, MIL)."
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1."
+ },
+ {
+ "key": "pageSize",
+ "value": "35",
+ "description": "(Optional) The number of results to return per page. Defaults to 20."
+ },
+ {
+ "key": "sortBy",
+ "value": "defensiveBox",
+ "description": "(Optional) The field to sort the results by. Defaults to 'winShares'."
+ },
+ {
+ "key": "ascending",
+ "value": "false",
+ "description": "(Optional) If true, sorts the results in ascending order. Defaults to false (descending)."
+ },
+ {
+ "key": "isPlayoff",
+ "value": "true",
+ "description": "(Optional) Set to 'true' to retrieve playoff stats, or 'false' for regular season stats."
+ }
+ ]
+ }
+ },
+ "status": "OK",
+ "code": 200,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "Alt-Svc",
+ "value": "h3=\":443\"; ma=2592000"
+ },
+ {
+ "key": "Content-Encoding",
+ "value": "br"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Date",
+ "value": "Wed, 02 Jul 2025 01:18:52 GMT"
+ },
+ {
+ "key": "Server",
+ "value": "nginx/1.28.0"
+ },
+ {
+ "key": "Vary",
+ "value": "Accept-Encoding"
+ },
+ {
+ "key": "Content-Length",
+ "value": "1692"
+ }
+ ],
+ "cookie": [],
+ "body": "{\n \"data\": [\n {\n \"ID\": 2612,\n \"id\": 32,\n \"playerId\": \"carusal01\",\n \"playerName\": \"Alex Caruso\",\n \"position\": \"SG\",\n \"age\": 30,\n \"games\": 23,\n \"minutesPlayed\": 562,\n \"per\": 15.1,\n \"tsPercent\": 0.598,\n \"threePAR\": 0.563,\n \"ftr\": 0.244,\n \"offensiveRBPercent\": 3.3,\n \"defensiveRBPercent\": 8.8,\n \"totalRBPercent\": 6,\n \"assistPercent\": 12.6,\n \"stealPercent\": 3.9,\n \"blockPercent\": 2.3,\n \"turnoverPercent\": 9.7,\n \"usagePercent\": 14.8,\n \"offensiveWS\": 0.9,\n \"defensiveWS\": 1.1,\n \"winShares\": 2,\n \"winSharesPer\": 0.169,\n \"offensiveBox\": 0.4,\n \"defensiveBox\": 4.7,\n \"box\": 5,\n \"vorp\": 1,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:02:53.068472Z\",\n \"UpdatedAt\": \"2025-06-20T01:02:53.068472Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2794,\n \"id\": 214,\n \"playerId\": \"willija07\",\n \"playerName\": \"Jaylin Williams\",\n \"position\": \"PF\",\n \"age\": 22,\n \"games\": 17,\n \"minutesPlayed\": 141,\n \"per\": 9.7,\n \"tsPercent\": 0.565,\n \"threePAR\": 0.714,\n \"ftr\": 0.314,\n \"offensiveRBPercent\": 3.1,\n \"defensiveRBPercent\": 21.8,\n \"totalRBPercent\": 12.3,\n \"assistPercent\": 16.2,\n \"stealPercent\": 2.8,\n \"blockPercent\": 1.4,\n \"turnoverPercent\": 23.1,\n \"usagePercent\": 15.6,\n \"offensiveWS\": 0,\n \"defensiveWS\": 0.3,\n \"winShares\": 0.3,\n \"winSharesPer\": 0.091,\n \"offensiveBox\": -4,\n \"defensiveBox\": 4.4,\n \"box\": 0.4,\n \"vorp\": 0.1,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:19.343959Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:19.343959Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2683,\n \"id\": 103,\n \"playerId\": \"jonesdi01\",\n \"playerName\": \"Dillon Jones\",\n \"position\": \"SF\",\n \"age\": 23,\n \"games\": 10,\n \"minutesPlayed\": 46,\n \"per\": 19.2,\n \"tsPercent\": 0.968,\n \"threePAR\": 0.364,\n \"ftr\": 0.182,\n \"offensiveRBPercent\": 7,\n \"defensiveRBPercent\": 14.3,\n \"totalRBPercent\": 10.6,\n \"assistPercent\": 16.6,\n \"stealPercent\": 1.1,\n \"blockPercent\": 2.2,\n \"turnoverPercent\": 33.6,\n \"usagePercent\": 16.5,\n \"offensiveWS\": 0.1,\n \"defensiveWS\": 0.1,\n \"winShares\": 0.2,\n \"winSharesPer\": 0.179,\n \"offensiveBox\": 3.4,\n \"defensiveBox\": 4,\n \"box\": 7.5,\n \"vorp\": 0.1,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:03.218018Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:03.218018Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2784,\n \"id\": 204,\n \"playerId\": \"wallaca01\",\n \"playerName\": \"Cason Wallace\",\n \"position\": \"SG\",\n \"age\": 21,\n \"games\": 23,\n \"minutesPlayed\": 516,\n \"per\": 10.8,\n \"tsPercent\": 0.52,\n \"threePAR\": 0.521,\n \"ftr\": 0.076,\n \"offensiveRBPercent\": 4.8,\n \"defensiveRBPercent\": 8.1,\n \"totalRBPercent\": 6.4,\n \"assistPercent\": 12.4,\n \"stealPercent\": 3,\n \"blockPercent\": 1.9,\n \"turnoverPercent\": 11.5,\n \"usagePercent\": 11.4,\n \"offensiveWS\": 0.3,\n \"defensiveWS\": 0.8,\n \"winShares\": 1.2,\n \"winSharesPer\": 0.111,\n \"offensiveBox\": -1.7,\n \"defensiveBox\": 3.7,\n \"box\": 2,\n \"vorp\": 0.5,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:17.911668Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:17.911668Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2638,\n \"id\": 58,\n \"playerId\": \"gilgesh01\",\n \"playerName\": \"Shai Gilgeous-Alexander\",\n \"position\": \"PG\",\n \"age\": 26,\n \"games\": 23,\n \"minutesPlayed\": 851,\n \"per\": 25.3,\n \"tsPercent\": 0.574,\n \"threePAR\": 0.224,\n \"ftr\": 0.431,\n \"offensiveRBPercent\": 2.9,\n \"defensiveRBPercent\": 12.9,\n \"totalRBPercent\": 7.9,\n \"assistPercent\": 30.6,\n \"stealPercent\": 2.2,\n \"blockPercent\": 2.4,\n \"turnoverPercent\": 9.1,\n \"usagePercent\": 32.9,\n \"offensiveWS\": 2.6,\n \"defensiveWS\": 1.4,\n \"winShares\": 3.9,\n \"winSharesPer\": 0.222,\n \"offensiveBox\": 5.7,\n \"defensiveBox\": 2.6,\n \"box\": 8.3,\n \"vorp\": 2.2,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:02:56.768423Z\",\n \"UpdatedAt\": \"2025-06-20T01:02:56.768423Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2654,\n \"id\": 74,\n \"playerId\": \"harteis01\",\n \"playerName\": \"Isaiah Hartenstein\",\n \"position\": \"C\",\n \"age\": 26,\n \"games\": 23,\n \"minutesPlayed\": 516,\n \"per\": 18.3,\n \"tsPercent\": 0.632,\n \"threePAR\": 0,\n \"ftr\": 0.224,\n \"offensiveRBPercent\": 13.2,\n \"defensiveRBPercent\": 23.4,\n \"totalRBPercent\": 18.2,\n \"assistPercent\": 14.3,\n \"stealPercent\": 1.8,\n \"blockPercent\": 2.3,\n \"turnoverPercent\": 15.5,\n \"usagePercent\": 14.3,\n \"offensiveWS\": 1.1,\n \"defensiveWS\": 1,\n \"winShares\": 2.1,\n \"winSharesPer\": 0.199,\n \"offensiveBox\": 0.9,\n \"defensiveBox\": 2.6,\n \"box\": 3.5,\n \"vorp\": 0.7,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:02:59.067598Z\",\n \"UpdatedAt\": \"2025-06-20T01:02:59.067598Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2678,\n \"id\": 98,\n \"playerId\": \"joeis01\",\n \"playerName\": \"Isaiah Joe\",\n \"position\": \"SG\",\n \"age\": 25,\n \"games\": 21,\n \"minutesPlayed\": 211,\n \"per\": 17.4,\n \"tsPercent\": 0.676,\n \"threePAR\": 0.767,\n \"ftr\": 0.192,\n \"offensiveRBPercent\": 3.6,\n \"defensiveRBPercent\": 11.4,\n \"totalRBPercent\": 7.5,\n \"assistPercent\": 9.8,\n \"stealPercent\": 1.6,\n \"blockPercent\": 0.9,\n \"turnoverPercent\": 8.1,\n \"usagePercent\": 17.3,\n \"offensiveWS\": 0.6,\n \"defensiveWS\": 0.3,\n \"winShares\": 0.8,\n \"winSharesPer\": 0.192,\n \"offensiveBox\": 3.5,\n \"defensiveBox\": 2.1,\n \"box\": 5.6,\n \"vorp\": 0.4,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:02.486429Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:02.486429Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2793,\n \"id\": 213,\n \"playerId\": \"willija06\",\n \"playerName\": \"Jalen Williams\",\n \"position\": \"SG\",\n \"age\": 23,\n \"games\": 23,\n \"minutesPlayed\": 796,\n \"per\": 19.1,\n \"tsPercent\": 0.544,\n \"threePAR\": 0.29,\n \"ftr\": 0.323,\n \"offensiveRBPercent\": 3.2,\n \"defensiveRBPercent\": 14.2,\n \"totalRBPercent\": 8.7,\n \"assistPercent\": 22.3,\n \"stealPercent\": 2,\n \"blockPercent\": 1.1,\n \"turnoverPercent\": 8.3,\n \"usagePercent\": 26.3,\n \"offensiveWS\": 1.3,\n \"defensiveWS\": 1.2,\n \"winShares\": 2.5,\n \"winSharesPer\": 0.153,\n \"offensiveBox\": 2.5,\n \"defensiveBox\": 1.7,\n \"box\": 4.2,\n \"vorp\": 1.2,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:19.204511Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:19.204511Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2663,\n \"id\": 83,\n \"playerId\": \"holmgch01\",\n \"playerName\": \"Chet Holmgren\",\n \"position\": \"C\",\n \"age\": 22,\n \"games\": 23,\n \"minutesPlayed\": 686,\n \"per\": 18.1,\n \"tsPercent\": 0.565,\n \"threePAR\": 0.342,\n \"ftr\": 0.365,\n \"offensiveRBPercent\": 6.6,\n \"defensiveRBPercent\": 25.1,\n \"totalRBPercent\": 15.8,\n \"assistPercent\": 4.8,\n \"stealPercent\": 1.2,\n \"blockPercent\": 6.3,\n \"turnoverPercent\": 8.3,\n \"usagePercent\": 20.8,\n \"offensiveWS\": 0.9,\n \"defensiveWS\": 1.4,\n \"winShares\": 2.3,\n \"winSharesPer\": 0.161,\n \"offensiveBox\": 0.9,\n \"defensiveBox\": 1.5,\n \"box\": 2.4,\n \"vorp\": 0.8,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:00.356697Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:00.356697Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2627,\n \"id\": 47,\n \"playerId\": \"dortlu01\",\n \"playerName\": \"Luguentz Dort\",\n \"position\": \"SF\",\n \"age\": 25,\n \"games\": 23,\n \"minutesPlayed\": 666,\n \"per\": 8.1,\n \"tsPercent\": 0.524,\n \"threePAR\": 0.854,\n \"ftr\": 0.134,\n \"offensiveRBPercent\": 5.2,\n \"defensiveRBPercent\": 9.4,\n \"totalRBPercent\": 7.3,\n \"assistPercent\": 4,\n \"stealPercent\": 2.1,\n \"blockPercent\": 1.5,\n \"turnoverPercent\": 10.3,\n \"usagePercent\": 12.3,\n \"offensiveWS\": 0.2,\n \"defensiveWS\": 1,\n \"winShares\": 1.1,\n \"winSharesPer\": 0.081,\n \"offensiveBox\": -2.3,\n \"defensiveBox\": 1.4,\n \"box\": -0.9,\n \"vorp\": 0.2,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:02:55.202425Z\",\n \"UpdatedAt\": \"2025-06-20T01:02:55.202425Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2623,\n \"id\": 43,\n \"playerId\": \"diengou01\",\n \"playerName\": \"Ousmane Dieng\",\n \"position\": \"C\",\n \"age\": 21,\n \"games\": 9,\n \"minutesPlayed\": 32,\n \"per\": 16.2,\n \"tsPercent\": 0.643,\n \"threePAR\": 0.7,\n \"ftr\": 0.2,\n \"offensiveRBPercent\": 0,\n \"defensiveRBPercent\": 13.7,\n \"totalRBPercent\": 6.8,\n \"assistPercent\": 12.9,\n \"stealPercent\": 0,\n \"blockPercent\": 3.1,\n \"turnoverPercent\": 0,\n \"usagePercent\": 14.4,\n \"offensiveWS\": 0.1,\n \"defensiveWS\": 0,\n \"winShares\": 0.1,\n \"winSharesPer\": 0.202,\n \"offensiveBox\": 4.9,\n \"defensiveBox\": 1.3,\n \"box\": 6.3,\n \"vorp\": 0.1,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:02:54.622461Z\",\n \"UpdatedAt\": \"2025-06-20T01:02:54.622461Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2796,\n \"id\": 216,\n \"playerId\": \"willike04\",\n \"playerName\": \"Kenrich Williams\",\n \"position\": \"PF\",\n \"age\": 30,\n \"games\": 16,\n \"minutesPlayed\": 137,\n \"per\": 9.9,\n \"tsPercent\": 0.457,\n \"threePAR\": 0.5,\n \"ftr\": 0.15,\n \"offensiveRBPercent\": 6.3,\n \"defensiveRBPercent\": 21.6,\n \"totalRBPercent\": 13.9,\n \"assistPercent\": 7,\n \"stealPercent\": 2.5,\n \"blockPercent\": 0.7,\n \"turnoverPercent\": 14.1,\n \"usagePercent\": 15.4,\n \"offensiveWS\": -0.1,\n \"defensiveWS\": 0.3,\n \"winShares\": 0.1,\n \"winSharesPer\": 0.052,\n \"offensiveBox\": -2.6,\n \"defensiveBox\": 0.8,\n \"box\": -1.8,\n \"vorp\": 0,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:19.635894Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:19.635894Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2791,\n \"id\": 211,\n \"playerId\": \"wiggiaa01\",\n \"playerName\": \"Aaron Wiggins\",\n \"position\": \"SG\",\n \"age\": 26,\n \"games\": 22,\n \"minutesPlayed\": 303,\n \"per\": 11.8,\n \"tsPercent\": 0.522,\n \"threePAR\": 0.58,\n \"ftr\": 0.143,\n \"offensiveRBPercent\": 5,\n \"defensiveRBPercent\": 13,\n \"totalRBPercent\": 9,\n \"assistPercent\": 9.5,\n \"stealPercent\": 1.6,\n \"blockPercent\": 2,\n \"turnoverPercent\": 11.8,\n \"usagePercent\": 20.1,\n \"offensiveWS\": 0,\n \"defensiveWS\": 0.4,\n \"winShares\": 0.4,\n \"winSharesPer\": 0.071,\n \"offensiveBox\": -0.5,\n \"defensiveBox\": 0.6,\n \"box\": 0.1,\n \"vorp\": 0.2,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:18.910441Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:18.910441Z\",\n \"DeletedAt\": null\n },\n {\n \"ID\": 2715,\n \"id\": 135,\n \"playerId\": \"mitchaj01\",\n \"playerName\": \"Ajay Mitchell\",\n \"position\": \"SG\",\n \"age\": 22,\n \"games\": 12,\n \"minutesPlayed\": 84,\n \"per\": 12.2,\n \"tsPercent\": 0.551,\n \"threePAR\": 0.371,\n \"ftr\": 0.143,\n \"offensiveRBPercent\": 3.8,\n \"defensiveRBPercent\": 7.8,\n \"totalRBPercent\": 5.8,\n \"assistPercent\": 16.2,\n \"stealPercent\": 1.2,\n \"blockPercent\": 0,\n \"turnoverPercent\": 13.9,\n \"usagePercent\": 21.8,\n \"offensiveWS\": 0,\n \"defensiveWS\": 0.1,\n \"winShares\": 0.1,\n \"winSharesPer\": 0.067,\n \"offensiveBox\": -1.3,\n \"defensiveBox\": -0.1,\n \"box\": -1.4,\n \"vorp\": 0,\n \"team\": \"OKC\",\n \"season\": 2025,\n \"isPlayoff\": true,\n \"CreatedAt\": \"2025-06-20T01:03:07.867398Z\",\n \"UpdatedAt\": \"2025-06-20T01:03:07.867398Z\",\n \"DeletedAt\": null\n }\n ],\n \"pagination\": {\n \"total\": 14,\n \"page\": 1,\n \"pageSize\": 35,\n \"pages\": 1\n }\n}"
+ }
+ ]
+ },
+ {
+ "name": "Shot Chart",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "// Test to check if the response time is less than 200ms",
+ "pm.test(\"Response time is less than 200ms\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});",
+ "",
+ "pm.test(\"Response status code is 200\", function () {",
+ " pm.expect(pm.response.code).to.eql(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response is an array with at least one element\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ "});",
+ "",
+ "",
+ "pm.test(\"Validate the schema of the objects in the array\", function () {",
+ " const responseData = pm.response.json();",
+ "",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ "",
+ " responseData.forEach(function(item) {",
+ " pm.expect(item).to.be.an('object');",
+ " pm.expect(item).to.have.all.keys('id', 'playerId', 'season', 'date', 'qtr', 'timeRemaining', 'top', 'left', 'playerName', 'result', 'shotType', 'distanceFt', 'lead', 'teamScore', 'opponentTeamScore', 'opponent', 'team', 'ID', 'CreatedAt', 'UpdatedAt', 'DeletedAt');",
+ " ",
+ " pm.expect(item.id).to.be.a('number');",
+ " pm.expect(item.playerId).to.be.a('string');",
+ " pm.expect(item.season).to.be.a('number');",
+ " pm.expect(item.date).to.be.a('string');",
+ " pm.expect(item.qtr).to.be.a('string');",
+ " pm.expect(item.timeRemaining).to.be.a('string');",
+ " pm.expect(item.top).to.be.a('number');",
+ " pm.expect(item.left).to.be.a('number');",
+ " pm.expect(item.playerName).to.be.a('string');",
+ " pm.expect(item.result).to.be.a('boolean');",
+ " pm.expect(item.shotType).to.be.a('string');",
+ " pm.expect(item.distanceFt).to.be.a('number');",
+ " pm.expect(item.lead).to.be.a('boolean');",
+ " pm.expect(item.teamScore).to.be.a('number');",
+ " pm.expect(item.opponentTeamScore).to.be.a('number');",
+ " pm.expect(item.opponent).to.be.a('string');",
+ " pm.expect(item.team).to.be.a('string');",
+ " pm.expect(item.ID).to.be.a('number');",
+ " pm.expect(item.CreatedAt).to.be.a('string');",
+ " pm.expect(item.UpdatedAt).to.be.a('string');",
+ " pm.expect(item.DeletedAt).to.satisfy(val => val === null || typeof val === 'object');",
+ " });",
+ "});",
+ "",
+ "",
+ "pm.test(\"Player ID is a non-empty string\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ " responseData.forEach(function(item) {",
+ " pm.expect(item.playerId).to.exist.and.to.be.a('string').and.to.have.lengthOf.at.least(1, \"Player ID should not be empty\");",
+ " });",
+ "});",
+ "",
+ "",
+ "pm.test(\"Season is a positive integer\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ " responseData.forEach(function(item) {",
+ " pm.expect(item.season).to.be.a('number').and.to.be.above(0);",
+ " });",
+ "});",
+ "",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playershotchart?page=1&playerId=hardeja01&date=Dec%2017%2C2018",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playershotchart"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1. Each page returns a maximum of 50 results."
+ },
+ {
+ "key": "playerId",
+ "value": "hardeja01",
+ "description": "(Optional) The unique ID for the player (e.g., hardeja01)."
+ },
+ {
+ "key": "date",
+ "value": "Dec%2017%2C2018",
+ "description": "(Optional) The date of the game (e.g., 'Dec 17, 2018')."
+ },
+ {
+ "key": "season",
+ "value": "2024",
+ "description": "(Optional) The season year (e.g., 2024 for the 2023-24 season).",
+ "disabled": true
+ }
+ ]
+ },
+ "description": "### Get Player Shot Chart Data\n\nReturns a paginated list of shot chart data points for a player. The data can be filtered by a variety of parameters to narrow down the results.\n\n### Usage\n\nThis endpoint is ideal for visualizing player shooting performance. You can filter by a specific player, season, game date, quarter, and more.\n\n**Note:** Pagination is available via the `page` parameter, but the number of results per page is fixed at **50**.\n\n#### **Query Parameters**\n\n* **`page`** (integer, optional): The page number for pagination. Defaults to `1`.\n* **`playerId`** (string, optional): The player's unique ID (e.g., `hardeja01`).\n* **`season`** (integer, optional): The season year (e.g., `2023`).\n* **`date`** (string, optional): The specific game date (e.g., `Oct 17, 2018`).\n* **`qtr`** (string, optional): The quarter of the game (e.g., `1st Qtr`).\n* **`result`** (boolean, optional): Filter by shot outcome (`true` for made shots, `false` for missed).\n* **`shot_type`** (string, optional): The type of shot (e.g., `2-pointer`, `3-pointer`).\n* **`opponent`** (string, optional): The three-letter abbreviation for the opponent team (e.g., `NOP`)."
+ },
+ "response": [
+ {
+ "name": "Shot Chart",
+ "originalRequest": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playershotchart?page=1&playerId=hardeja01&date=Dec%2017%2C2018",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playershotchart"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1. Each page returns a maximum of 50 results."
+ },
+ {
+ "key": "playerId",
+ "value": "hardeja01",
+ "description": "(Optional) The unique ID for the player (e.g., hardeja01)."
+ },
+ {
+ "key": "date",
+ "value": "Dec%2017%2C2018",
+ "description": "(Optional) The date of the game (e.g., 'Dec 17, 2018')."
+ },
+ {
+ "key": "season",
+ "value": "2024",
+ "description": "(Optional) The season year (e.g., 2024 for the 2023-24 season).",
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "status": "OK",
+ "code": 200,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "Alt-Svc",
+ "value": "h3=\":443\"; ma=2592000"
+ },
+ {
+ "key": "Content-Encoding",
+ "value": "br"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Date",
+ "value": "Wed, 02 Jul 2025 01:18:59 GMT"
+ },
+ {
+ "key": "Server",
+ "value": "nginx/1.28.0"
+ },
+ {
+ "key": "Vary",
+ "value": "Accept-Encoding"
+ },
+ {
+ "key": "Content-Length",
+ "value": "813"
+ }
+ ],
+ "cookie": [],
+ "body": "[\n {\n \"id\": 510,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"11:09\",\n \"top\": 305,\n \"left\": 161,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 3,\n \"opponentTeamScore\": 0,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 511,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"8:27\",\n \"top\": 130,\n \"left\": 291,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 9,\n \"lead\": true,\n \"teamScore\": 8,\n \"opponentTeamScore\": 4,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 512,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"7:54\",\n \"top\": 130,\n \"left\": 263,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 8,\n \"lead\": true,\n \"teamScore\": 10,\n \"opponentTeamScore\": 6,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 513,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"5:45\",\n \"top\": 218,\n \"left\": 53,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 16,\n \"opponentTeamScore\": 12,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 514,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"4:25\",\n \"top\": 323,\n \"left\": 202,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 16,\n \"opponentTeamScore\": 15,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 515,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"4:18\",\n \"top\": 68,\n \"left\": 220,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 3,\n \"lead\": true,\n \"teamScore\": 18,\n \"opponentTeamScore\": 15,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 516,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"3:50\",\n \"top\": 269,\n \"left\": 362,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 18,\n \"opponentTeamScore\": 17,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 517,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"3:15\",\n \"top\": 64,\n \"left\": 233,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 1,\n \"lead\": true,\n \"teamScore\": 20,\n \"opponentTeamScore\": 19,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 518,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"2:42\",\n \"top\": 56,\n \"left\": 232,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 1,\n \"lead\": true,\n \"teamScore\": 22,\n \"opponentTeamScore\": 19,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 519,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"8:05\",\n \"top\": 59,\n \"left\": 222,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 2,\n \"lead\": true,\n \"teamScore\": 32,\n \"opponentTeamScore\": 23,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 520,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"5:37\",\n \"top\": 68,\n \"left\": 218,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 3,\n \"lead\": true,\n \"teamScore\": 37,\n \"opponentTeamScore\": 30,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 521,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"3:44\",\n \"top\": 77,\n \"left\": 226,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 3,\n \"lead\": true,\n \"teamScore\": 44,\n \"opponentTeamScore\": 32,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 522,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"0:48\",\n \"top\": 164,\n \"left\": 217,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 11,\n \"lead\": true,\n \"teamScore\": 50,\n \"opponentTeamScore\": 35,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 523,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"11:22\",\n \"top\": 66,\n \"left\": 243,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 1,\n \"lead\": true,\n \"teamScore\": 52,\n \"opponentTeamScore\": 40,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 524,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"9:30\",\n \"top\": 149,\n \"left\": 242,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 10,\n \"lead\": true,\n \"teamScore\": 52,\n \"opponentTeamScore\": 45,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 525,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"6:26\",\n \"top\": 62,\n \"left\": 246,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 1,\n \"lead\": true,\n \"teamScore\": 59,\n \"opponentTeamScore\": 58,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 526,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"5:59\",\n \"top\": 64,\n \"left\": 232,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 1,\n \"lead\": true,\n \"teamScore\": 62,\n \"opponentTeamScore\": 60,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 527,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"5:40\",\n \"top\": 277,\n \"left\": 111,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 66,\n \"opponentTeamScore\": 60,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 528,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"4:55\",\n \"top\": 78,\n \"left\": 237,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 3,\n \"lead\": true,\n \"teamScore\": 66,\n \"opponentTeamScore\": 62,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 529,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"4:24\",\n \"top\": 61,\n \"left\": 248,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 1,\n \"lead\": true,\n \"teamScore\": 70,\n \"opponentTeamScore\": 64,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 530,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"1:55\",\n \"top\": 299,\n \"left\": 313,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 78,\n \"opponentTeamScore\": 68,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 531,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"11:41\",\n \"top\": 293,\n \"left\": 351,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 83,\n \"opponentTeamScore\": 73,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 532,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"8:42\",\n \"top\": 213,\n \"left\": 48,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 90,\n \"opponentTeamScore\": 79,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 533,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"7:53\",\n \"top\": 108,\n \"left\": 212,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 6,\n \"lead\": true,\n \"teamScore\": 90,\n \"opponentTeamScore\": 81,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 534,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"7:31\",\n \"top\": 350,\n \"left\": 176,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 30,\n \"lead\": true,\n \"teamScore\": 90,\n \"opponentTeamScore\": 81,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 535,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"6:33\",\n \"top\": 133,\n \"left\": 252,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 8,\n \"lead\": true,\n \"teamScore\": 90,\n \"opponentTeamScore\": 83,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 536,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"5:07\",\n \"top\": 90,\n \"left\": 363,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 13,\n \"lead\": true,\n \"teamScore\": 92,\n \"opponentTeamScore\": 88,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 537,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"4:00\",\n \"top\": 325,\n \"left\": 343,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 29,\n \"lead\": true,\n \"teamScore\": 92,\n \"opponentTeamScore\": 88,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 538,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"3:24\",\n \"top\": 58,\n \"left\": 228,\n \"playerName\": \"hardeja01\",\n \"result\": false,\n \"shotType\": \"2-pointer\",\n \"distanceFt\": 1,\n \"lead\": true,\n \"teamScore\": 92,\n \"opponentTeamScore\": 90,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 539,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"1:44\",\n \"top\": 323,\n \"left\": 323,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": true,\n \"teamScore\": 97,\n \"opponentTeamScore\": 94,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 540,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Dec 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"0:13\",\n \"top\": 287,\n \"left\": 135,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 102,\n \"opponentTeamScore\": 97,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n }\n]"
+ }
+ ]
+ },
+ {
+ "name": "Shot Chart 2",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "// Test to check if the response time is less than 200ms",
+ "pm.test(\"Response time is less than 200ms\", function () {",
+ " pm.expect(pm.response.responseTime).to.be.below(200);",
+ "});",
+ "",
+ "pm.test(\"Response status code is 200\", function () {",
+ " pm.expect(pm.response.code).to.eql(200);",
+ "});",
+ "",
+ "",
+ "pm.test(\"Response is an array with at least one element\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ "});",
+ "",
+ "",
+ "pm.test(\"Validate the schema of the objects in the array\", function () {",
+ " const responseData = pm.response.json();",
+ "",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ "",
+ " responseData.forEach(function(item) {",
+ " pm.expect(item).to.be.an('object');",
+ " pm.expect(item).to.have.all.keys('id', 'playerId', 'season', 'date', 'qtr', 'timeRemaining', 'top', 'left', 'playerName', 'result', 'shotType', 'distanceFt', 'lead', 'teamScore', 'opponentTeamScore', 'opponent', 'team', 'ID', 'CreatedAt', 'UpdatedAt', 'DeletedAt');",
+ " ",
+ " pm.expect(item.id).to.be.a('number');",
+ " pm.expect(item.playerId).to.be.a('string');",
+ " pm.expect(item.season).to.be.a('number');",
+ " pm.expect(item.date).to.be.a('string');",
+ " pm.expect(item.qtr).to.be.a('string');",
+ " pm.expect(item.timeRemaining).to.be.a('string');",
+ " pm.expect(item.top).to.be.a('number');",
+ " pm.expect(item.left).to.be.a('number');",
+ " pm.expect(item.playerName).to.be.a('string');",
+ " pm.expect(item.result).to.be.a('boolean');",
+ " pm.expect(item.shotType).to.be.a('string');",
+ " pm.expect(item.distanceFt).to.be.a('number');",
+ " pm.expect(item.lead).to.be.a('boolean');",
+ " pm.expect(item.teamScore).to.be.a('number');",
+ " pm.expect(item.opponentTeamScore).to.be.a('number');",
+ " pm.expect(item.opponent).to.be.a('string');",
+ " pm.expect(item.team).to.be.a('string');",
+ " pm.expect(item.ID).to.be.a('number');",
+ " pm.expect(item.CreatedAt).to.be.a('string');",
+ " pm.expect(item.UpdatedAt).to.be.a('string');",
+ " pm.expect(item.DeletedAt).to.satisfy(val => val === null || typeof val === 'object');",
+ " });",
+ "});",
+ "",
+ "",
+ "pm.test(\"Player ID is a non-empty string\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ " responseData.forEach(function(item) {",
+ " pm.expect(item.playerId).to.exist.and.to.be.a('string').and.to.have.lengthOf.at.least(1, \"Player ID should not be empty\");",
+ " });",
+ "});",
+ "",
+ "",
+ "pm.test(\"Season is a positive integer\", function () {",
+ " const responseData = pm.response.json();",
+ " ",
+ " pm.expect(responseData).to.be.an('array').that.is.not.empty;",
+ " responseData.forEach(function(item) {",
+ " pm.expect(item.season).to.be.a('number').and.to.be.above(0);",
+ " });",
+ "});",
+ "",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playershotchart?page=1&playerId=hardeja01&season=2019&result=true&shot_type=3-pointer",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playershotchart"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1. Each page returns a maximum of 50 results."
+ },
+ {
+ "key": "playerId",
+ "value": "hardeja01",
+ "description": "(Optional) The unique ID for the player (e.g., hardeja01)."
+ },
+ {
+ "key": "season",
+ "value": "2019",
+ "description": "(Optional) The season year (e.g., 2024 for the 2023-24 season)."
+ },
+ {
+ "key": "result",
+ "value": "true",
+ "description": "(Optional) Filter by shot outcome (true for made, false for missed)."
+ },
+ {
+ "key": "shot_type",
+ "value": "3-pointer",
+ "description": "(Optional) The type of shot (e.g., '2-pointer', '3-pointer')."
+ }
+ ]
+ },
+ "description": "### Get Player Shot Chart Data\n\nReturns a paginated list of shot chart data points for a player. The data can be filtered by a variety of parameters to narrow down the results.\n\n### Usage\n\nThis endpoint is ideal for visualizing player shooting performance. You can filter by a specific player, season, game date, quarter, and more.\n\n**Note:** Pagination is available via the `page` parameter, but the number of results per page is fixed at **50**.\n\n#### **Query Parameters**\n\n* **`page`** (integer, optional): The page number for pagination. Defaults to `1`.\n* **`playerId`** (string, optional): The player's unique ID (e.g., `hardeja01`).\n* **`season`** (integer, optional): The season year (e.g., `2023`).\n* **`date`** (string, optional): The specific game date (e.g., `Oct 17, 2018`).\n* **`qtr`** (string, optional): The quarter of the game (e.g., `1st Qtr`).\n* **`result`** (boolean, optional): Filter by shot outcome (`true` for made shots, `false` for missed).\n* **`shot_type`** (string, optional): The type of shot (e.g., `2-pointer`, `3-pointer`).\n* **`opponent`** (string, optional): The three-letter abbreviation for the opponent team (e.g., `NOP`)."
+ },
+ "response": [
+ {
+ "name": "Shot Chart 2",
+ "originalRequest": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "x-api-key",
+ "value": "{{api-key}}",
+ "type": "text",
+ "disabled": true
+ }
+ ],
+ "url": {
+ "raw": "{{base_url}}/api/playershotchart?page=1&playerId=hardeja01&season=2019&result=true&shot_type=3-pointer",
+ "host": [
+ "{{base_url}}"
+ ],
+ "path": [
+ "api",
+ "playershotchart"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "1",
+ "description": "(Optional) The page number for pagination. Defaults to 1. Each page returns a maximum of 50 results."
+ },
+ {
+ "key": "playerId",
+ "value": "hardeja01",
+ "description": "(Optional) The unique ID for the player (e.g., hardeja01)."
+ },
+ {
+ "key": "season",
+ "value": "2019",
+ "description": "(Optional) The season year (e.g., 2024 for the 2023-24 season)."
+ },
+ {
+ "key": "result",
+ "value": "true",
+ "description": "(Optional) Filter by shot outcome (true for made, false for missed)."
+ },
+ {
+ "key": "shot_type",
+ "value": "3-pointer",
+ "description": "(Optional) The type of shot (e.g., '2-pointer', '3-pointer')."
+ }
+ ]
+ }
+ },
+ "status": "OK",
+ "code": 200,
+ "_postman_previewlanguage": "json",
+ "header": [
+ {
+ "key": "Alt-Svc",
+ "value": "h3=\":443\"; ma=2592000"
+ },
+ {
+ "key": "Content-Encoding",
+ "value": "br"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Date",
+ "value": "Wed, 02 Jul 2025 01:19:06 GMT"
+ },
+ {
+ "key": "Server",
+ "value": "nginx/1.28.0"
+ },
+ {
+ "key": "Vary",
+ "value": "Accept-Encoding"
+ },
+ {
+ "key": "Content-Length",
+ "value": "1291"
+ }
+ ],
+ "cookie": [],
+ "body": "[\n {\n \"id\": 4,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 17,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"4:12\",\n \"top\": 284,\n \"left\": 121,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": false,\n \"teamScore\": 18,\n \"opponentTeamScore\": 22,\n \"opponent\": \"NOP\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 12,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"6:18\",\n \"top\": 288,\n \"left\": 318,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": false,\n \"teamScore\": 95,\n \"opponentTeamScore\": 113,\n \"opponent\": \"NOP\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 15,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"3:27\",\n \"top\": 284,\n \"left\": 343,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": false,\n \"teamScore\": 102,\n \"opponentTeamScore\": 121,\n \"opponent\": \"NOP\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 17,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 20,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"7:48\",\n \"top\": 316,\n \"left\": 332,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": true,\n \"teamScore\": 14,\n \"opponentTeamScore\": 10,\n \"opponent\": \"LAL\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 23,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 20,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"6:25\",\n \"top\": 293,\n \"left\": 147,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 47,\n \"opponentTeamScore\": 44,\n \"opponent\": \"LAL\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 28,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 20,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"0:45\",\n \"top\": 321,\n \"left\": 199,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 63,\n \"opponentTeamScore\": 62,\n \"opponent\": \"LAL\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 32,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 20,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"6:24\",\n \"top\": 253,\n \"left\": 409,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 109,\n \"opponentTeamScore\": 104,\n \"opponent\": \"LAL\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 33,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 20,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"1:12\",\n \"top\": 270,\n \"left\": 404,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 120,\n \"opponentTeamScore\": 113,\n \"opponent\": \"LAL\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 43,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 21,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"6:11\",\n \"top\": 327,\n \"left\": 170,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": true,\n \"teamScore\": 42,\n \"opponentTeamScore\": 39,\n \"opponent\": \"LAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 47,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 21,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"1:44\",\n \"top\": 232,\n \"left\": 418,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 51,\n \"opponentTeamScore\": 50,\n \"opponent\": \"LAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 55,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 21,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"2:47\",\n \"top\": 241,\n \"left\": 407,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": false,\n \"teamScore\": 106,\n \"opponentTeamScore\": 110,\n \"opponent\": \"LAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 59,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 21,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"0:29\",\n \"top\": 288,\n \"left\": 162,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": false,\n \"teamScore\": 111,\n \"opponentTeamScore\": 115,\n \"opponent\": \"LAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 62,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 24,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"9:53\",\n \"top\": 271,\n \"left\": 395,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 7,\n \"opponentTeamScore\": 2,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 64,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 24,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"0:51\",\n \"top\": 253,\n \"left\": 95,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 29,\n \"opponentTeamScore\": 24,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 76,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Oct 24,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"5:50\",\n \"top\": 328,\n \"left\": 285,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": false,\n \"teamScore\": 52,\n \"opponentTeamScore\": 64,\n \"opponent\": \"UTA\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 84,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 3,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"3:34\",\n \"top\": 303,\n \"left\": 150,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": false,\n \"teamScore\": 47,\n \"opponentTeamScore\": 47,\n \"opponent\": \"CHI\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 85,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 3,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"2:52\",\n \"top\": 138,\n \"left\": 471,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 50,\n \"opponentTeamScore\": 47,\n \"opponent\": \"CHI\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 89,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 3,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"5:57\",\n \"top\": 313,\n \"left\": 238,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 63,\n \"opponentTeamScore\": 59,\n \"opponent\": \"CHI\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 90,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 3,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"4:47\",\n \"top\": 307,\n \"left\": 323,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 66,\n \"opponentTeamScore\": 59,\n \"opponent\": \"CHI\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 91,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 3,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"3:59\",\n \"top\": 299,\n \"left\": 182,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 71,\n \"opponentTeamScore\": 59,\n \"opponent\": \"CHI\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 98,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 5,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"9:13\",\n \"top\": 321,\n \"left\": 262,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 11,\n \"opponentTeamScore\": 6,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 99,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 5,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"7:27\",\n \"top\": 282,\n \"left\": 104,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 16,\n \"opponentTeamScore\": 10,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 101,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 5,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"4:06\",\n \"top\": 78,\n \"left\": 477,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 24,\n \"lead\": true,\n \"teamScore\": 24,\n \"opponentTeamScore\": 18,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 106,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 5,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"0:09\",\n \"top\": 328,\n \"left\": 309,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": false,\n \"teamScore\": 53,\n \"opponentTeamScore\": 55,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 111,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 5,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"0:36\",\n \"top\": 267,\n \"left\": 77,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 93,\n \"opponentTeamScore\": 90,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 114,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 8,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"6:32\",\n \"top\": 252,\n \"left\": 390,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": false,\n \"teamScore\": 12,\n \"opponentTeamScore\": 15,\n \"opponent\": \"OKC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 120,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 8,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"3:14\",\n \"top\": 292,\n \"left\": 337,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": false,\n \"teamScore\": 43,\n \"opponentTeamScore\": 47,\n \"opponent\": \"OKC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 123,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 8,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"7:03\",\n \"top\": 202,\n \"left\": 445,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": false,\n \"teamScore\": 55,\n \"opponentTeamScore\": 66,\n \"opponent\": \"OKC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 125,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 8,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"8:24\",\n \"top\": 287,\n \"left\": 331,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": false,\n \"teamScore\": 67,\n \"opponentTeamScore\": 86,\n \"opponent\": \"OKC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 140,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 10,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"1:35\",\n \"top\": 313,\n \"left\": 234,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 48,\n \"opponentTeamScore\": 47,\n \"opponent\": \"SAS\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 160,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"5:13\",\n \"top\": 277,\n \"left\": 88,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 20,\n \"opponentTeamScore\": 16,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 163,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"6:26\",\n \"top\": 305,\n \"left\": 151,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 49,\n \"opponentTeamScore\": 39,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 164,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"5:00\",\n \"top\": 289,\n \"left\": 120,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 55,\n \"opponentTeamScore\": 44,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 165,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"2:29\",\n \"top\": 297,\n \"left\": 348,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 67,\n \"opponentTeamScore\": 50,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 166,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"2:07\",\n \"top\": 344,\n \"left\": 238,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 29,\n \"lead\": true,\n \"teamScore\": 70,\n \"opponentTeamScore\": 50,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 170,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"4:27\",\n \"top\": 289,\n \"left\": 105,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 87,\n \"opponentTeamScore\": 74,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 172,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"3:35\",\n \"top\": 310,\n \"left\": 132,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": true,\n \"teamScore\": 91,\n \"opponentTeamScore\": 75,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 174,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 11,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"7:04\",\n \"top\": 313,\n \"left\": 298,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 107,\n \"opponentTeamScore\": 92,\n \"opponent\": \"IND\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 186,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 13,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"3:40\",\n \"top\": 277,\n \"left\": 348,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 79,\n \"opponentTeamScore\": 74,\n \"opponent\": \"DEN\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 190,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 13,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"4:50\",\n \"top\": 248,\n \"left\": 397,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 102,\n \"opponentTeamScore\": 91,\n \"opponent\": \"DEN\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 199,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 15,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"2:50\",\n \"top\": 264,\n \"left\": 376,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 16,\n \"opponentTeamScore\": 15,\n \"opponent\": \"GSW\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 208,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 15,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"0:29\",\n \"top\": 330,\n \"left\": 204,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": true,\n \"teamScore\": 47,\n \"opponentTeamScore\": 39,\n \"opponent\": \"GSW\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 210,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 15,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"11:37\",\n \"top\": 274,\n \"left\": 64,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 28,\n \"lead\": true,\n \"teamScore\": 50,\n \"opponentTeamScore\": 41,\n \"opponent\": \"GSW\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 213,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 15,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"5:30\",\n \"top\": 231,\n \"left\": 38,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 63,\n \"opponentTeamScore\": 47,\n \"opponent\": \"GSW\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 227,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"7:44\",\n \"top\": 313,\n \"left\": 213,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 83,\n \"opponentTeamScore\": 69,\n \"opponent\": \"SAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 229,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"4:24\",\n \"top\": 313,\n \"left\": 299,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 95,\n \"opponentTeamScore\": 74,\n \"opponent\": \"SAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 230,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 17,2018\",\n \"qtr\": \"3rd Qtr\",\n \"timeRemaining\": \"4:06\",\n \"top\": 295,\n \"left\": 369,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 27,\n \"lead\": true,\n \"teamScore\": 98,\n \"opponentTeamScore\": 74,\n \"opponent\": \"SAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 233,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 17,2018\",\n \"qtr\": \"4th Qtr\",\n \"timeRemaining\": \"3:46\",\n \"top\": 154,\n \"left\": 7,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 126,\n \"opponentTeamScore\": 103,\n \"opponent\": \"SAC\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 238,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 21,2018\",\n \"qtr\": \"1st Qtr\",\n \"timeRemaining\": \"4:40\",\n \"top\": 261,\n \"left\": 384,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 25,\n \"lead\": true,\n \"teamScore\": 15,\n \"opponentTeamScore\": 14,\n \"opponent\": \"DET\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n },\n {\n \"id\": 241,\n \"playerId\": \"hardeja01\",\n \"season\": 2019,\n \"date\": \"Nov 21,2018\",\n \"qtr\": \"2nd Qtr\",\n \"timeRemaining\": \"6:38\",\n \"top\": 243,\n \"left\": 415,\n \"playerName\": \"hardeja01\",\n \"result\": true,\n \"shotType\": \"3-pointer\",\n \"distanceFt\": 26,\n \"lead\": true,\n \"teamScore\": 45,\n \"opponentTeamScore\": 44,\n \"opponent\": \"DET\",\n \"team\": \"HOU\",\n \"ID\": 0,\n \"CreatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"UpdatedAt\": \"2025-06-20T22:48:34.764255Z\",\n \"DeletedAt\": null\n }\n]"
+ }
+ ]
+ }
+ ],
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "type": "text/javascript",
+ "packages": {},
+ "exec": [
+ ""
+ ]
+ }
+ },
+ {
+ "listen": "test",
+ "script": {
+ "type": "text/javascript",
+ "packages": {},
+ "exec": [
+ ""
+ ]
+ }
+ }
+ ],
+ "variable": [
+ {
+ "key": "api-key",
+ "value": "",
+ "type": "string",
+ "disabled": true
+ },
+ {
+ "key": "x-secret",
+ "value": "",
+ "type": "string",
+ "disabled": true
+ },
+ {
+ "key": "shot_player",
+ "value": "jokicni01"
+ },
+ {
+ "key": "base_url",
+ "value": "https://api.server.nbaapi.com",
+ "type": "string"
+ },
+ {
+ "key": "base_url_2",
+ "value": "https://nbago.server.nbaapi.com",
+ "type": "string"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/swagger.json b/docs/swagger.json
index 6ee7f8c..7555ca2 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -1,6 +1,6 @@
{
"schemes": [
- "https"
+ "http"
],
"swagger": "2.0",
"info": {
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 8988b31..21705c3 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -303,7 +303,7 @@ paths:
- PlayerTotals
x-order: 1
schemes:
-- https
+- http
swagger: "2.0"
tags:
- name: PlayerTotals
diff --git a/import.go b/import.go
index d994189..13ca0ac 100644
--- a/import.go
+++ b/import.go
@@ -4,14 +4,14 @@ import (
"log"
"time"
- "gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/services"
"github.com/nprasad2077/NBA_Go/utils"
+ "gorm.io/gorm"
)
// importPlayerAdvanced fetches and stores advanced stats for seasons 2017–2025
func importPlayerAdvanced(db *gorm.DB) {
- for season := 1991; season <= 2002; season++ {
+ for season := 2024; season <= 2025; season++ {
if err := services.FetchAndStorePlayerAdvancedScrapedStats(db, season, false); err != nil {
log.Printf("advanced import failed for %d: %v", season, err)
}
@@ -23,42 +23,85 @@ func importPlayerAdvanced(db *gorm.DB) {
// importPlayerAdvancedPlayoffs fetches and stores advanced stats for playoffs seasons 2023–2025
func importPlayerAdvancedPlayoffs(db *gorm.DB) {
- for season := 1991; season <= 2002; season++ {
+ for season := 2024; season <= 2025; season++ {
if err := services.FetchAndStorePlayerAdvancedScrapedStats(db, season, true); err != nil {
log.Printf("advanced import failed for %d: %v", season, err)
}
log.Printf("Advanced Playoffs import for season: %d", season)
time.Sleep(1100 * time.Millisecond)
- utils.SleepWithJitter(1250 * time.Millisecond)
+ utils.SleepWithJitter(1500 * time.Millisecond)
}
}
// importPlayerTotalsScrape fetches & stores scraped regular-season total stats
func importPlayerTotalsScrape(db *gorm.DB) {
- for season := 1991; season <= 2002; season++ {
- if err := services.FetchAndStorePlayerTotalScrapedStats(db, season, false); err != nil {
- log.Printf("scraped totals import failed for %d: %v", season, err)
- }
+ for season := 2024; season <= 2025; season++ {
+ if err := services.FetchAndStorePlayerTotalScrapedStats(db, season, false); err != nil {
+ log.Printf("scraped totals import failed for %d: %v", season, err)
+ }
log.Printf("Player Totals import for season: %d", season)
- time.Sleep(1100 * time.Millisecond)
- utils.SleepWithJitter(1500 * time.Millisecond)
- }
+ time.Sleep(1100 * time.Millisecond)
+ utils.SleepWithJitter(1250 * time.Millisecond)
+ }
}
// importPlayerPlayoffsScrape fetches & stores scraped playoff total stats
func importPlayerTotalsPlayoffsScrape(db *gorm.DB) {
- for season := 1991; season <= 2002; season++ {
- if err := services.FetchAndStorePlayerTotalScrapedStats(db, season, true); err != nil {
- log.Printf("scraped playoffs import failed for %d: %v", season, err)
- }
+ for season := 2024; season <= 2025; season++ {
+ if err := services.FetchAndStorePlayerTotalScrapedStats(db, season, true); err != nil {
+ log.Printf("scraped playoffs import failed for %d: %v", season, err)
+ }
log.Printf("Player Playoffs Totals import for season: %d", season)
- time.Sleep(1100 * time.Millisecond)
- utils.SleepWithJitter(1750 * time.Millisecond)
- }
+ time.Sleep(1100 * time.Millisecond)
+ utils.SleepWithJitter(1700 * time.Millisecond)
+ }
}
+// importGameSchedules fetches and stores game schedules
+func importGameSchedules(db *gorm.DB) {
+ // An NBA season typically runs from October to June
+ months := []string{
+ "october", "november", "december", "january",
+ "february", "march", "april", "may", "june",
+ // "february", "march", "april", "may", "june",
+ }
+ for season := 2018; season <= 2025; season++ {
+ log.Printf("--- Starting Game Schedule Import for Season: %d ---", season)
+ for _, month := range months {
+ // The service will print a warning and skip if a month has no data (e.g. May/June for a season not yet finished)
+ if err := services.FetchAndStoreGameSchedule(db, season, month); err != nil {
+ // Log the error but continue to the next month/season
+ log.Printf("Game schedule import failed for %s %d: %v", month, season, err)
+ }
+ log.Printf("Game schedule import for %s, %d complete.", month, season)
+ // Respectful delay between requests
+ time.Sleep(1100 * time.Millisecond)
+ utils.SleepWithJitter(1800 * time.Millisecond)
+ }
+ log.Printf("--- Finished Game Schedule Import for Season: %d ---", season)
+ }
+}
+
+// importBoxScores fetches and stores all box score data (line scores, player/team stats)
+// for games within a recent date range.
+func importBoxScores(db *gorm.DB) {
+ // Define the date range for the import.
+
+ // The format is: time.Date(year, month, day, hour, min, sec, nsec, location)
+ // to := time.Date(2019, time.June, 14, 0, 0, 0, 0, time.UTC)
+ to := time.Now()
+ from := to.AddDate(-1, 0, 0) // 0 years, -3 months, 0 days
+
+ log.Printf("--- Starting Box Score Data Import from %s to %s ---", from.Format("2006-01-02"), to.Format("2006-01-02"))
+
+ if err := services.FetchAndStoreBoxScoreDataForDateRange(db, from, to); err != nil {
+ log.Fatalf("Box score import failed: %v", err)
+ }
+
+ log.Printf("--- Finished Box Score Data Import ---")
+}
// importPlayerShotChart fetches shot-charts for every known player
// func importPlayerShotChart(db *gorm.DB) {
diff --git a/main.go b/main.go
index ef24fab..af4b231 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,8 @@
// @title NBA_Go API
// @version 1.0
// @description Stats service, now with public access!
-// @schemes https
+// //@schemes https
+// @schemes http
// @BasePath /
//
// @tag.name PlayerTotals
@@ -45,7 +46,7 @@ import (
func main() {
// ——— One-off import-data mode ———
if len(os.Args) > 1 && os.Args[1] == "import-data" {
- // Run all migrations + import steps exactly once
+ // Run all DB migrations + import steps exactly once
db := config.InitDB(true)
importPlayerAdvanced(db)
@@ -60,6 +61,13 @@ func main() {
importPlayerTotalsPlayoffsScrape(db)
log.Println("🎉 Player Playoffs (scraped) Import completed successfully")
+ importGameSchedules(db)
+ log.Println("🎉 Game Imports completed successfully 🏀")
+
+ importBoxScores(db)
+ log.Println("🎉 Related Box Score Imports completed successfully 📦")
+
+
log.Println("🏀 ALL Imports completed successfully ✅ 🙌")
return
}
diff --git a/models/game.go b/models/game.go
new file mode 100644
index 0000000..79261f7
--- /dev/null
+++ b/models/game.go
@@ -0,0 +1,172 @@
+package models
+
+import (
+ "gorm.io/gorm"
+ "time"
+)
+
+type Game struct {
+ ID uint `gorm:"primaryKey" swaggerignore:"true"`
+ GameID string `gorm:"not null;uniqueIndex" json:"gameId"` // The field you want to use
+ Date time.Time `gorm:"not null;index" json:"date"`
+ IsPlayoff bool `gorm:"not null;default:false;index" json:"isPlayoff"`
+ StartTimeET string `json:"startTimeET"`
+ Arena string `json:"arena"`
+ VisitorTeam string `gorm:"not null" json:"visitorTeam"`
+ VisitorPTS int `json:"visitorPts"`
+ HomeTeam string `gorm:"not null" json:"homeTeam"`
+ HomePTS int `json:"homePts"`
+ GameDuration string `json:"gameDuration"`
+ BoxScoreURL string `json:"boxScoreUrl"`
+
+ // Associations (Updated with explicit foreign key tags)
+ LineScores []LineScore `gorm:"foreignKey:GameID;references:GameID" json:"lineScores"`
+ PlayerGameBasicStats []PlayerGameBasicStat `gorm:"foreignKey:GameID;references:GameID" json:"playerGameBasicStats"`
+ PlayerGameAdvStats []PlayerGameAdvStat `gorm:"foreignKey:GameID;references:GameID" json:"playerGameAdvStats"`
+ TeamGameBasicStats []TeamGameBasicStat `gorm:"foreignKey:GameID;references:GameID" json:"teamGameBasicStats"`
+ TeamGameAdvStats []TeamGameAdvStat `gorm:"foreignKey:GameID;references:GameID" json:"teamGameAdvStats"`
+
+ CreatedAt time.Time `swaggerignore:"true"`
+ UpdatedAt time.Time `swaggerignore:"true"`
+ DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true"`
+}
+
+// LineScore holds the scoring for each team by quarter for a specific game.
+type LineScore struct {
+ ID uint `gorm:"primaryKey" swaggerignore:"true"`
+ GameID string `gorm:"not null;uniqueIndex:idx_line_score,priority:1" json:"gameId"`
+ Team string `gorm:"not null;uniqueIndex:idx_line_score,priority:2" json:"team"`
+ Q1 int `json:"q1"`
+ Q2 int `json:"q2"`
+ Q3 int `json:"q3"`
+ Q4 int `json:"q4"`
+ // Overtime scores; will be 0 if the game did not go to the respective OT period.
+ OT1 int `json:"ot1"`
+ OT2 int `json:"ot2"`
+ OT3 int `json:"ot3"`
+ Total int `json:"total"`
+
+ CreatedAt time.Time `swaggerignore:"true"`
+ UpdatedAt time.Time `swaggerignore:"true"`
+ DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true"`
+}
+
+// PlayerGameBasicStat contains the basic box score statistics for a single player in a single game.
+type PlayerGameBasicStat struct {
+ ID uint `gorm:"primaryKey" swaggerignore:"true"`
+ GameID string `gorm:"not null;uniqueIndex:idx_player_game_basic" json:"gameId"`
+ PlayerID string `gorm:"not null;uniqueIndex:idx_player_game_basic" json:"playerId"`
+ PlayerName string `json:"playerName"`
+ Team string `gorm:"not null" json:"team"`
+ Status string `json:"status"` // e.g., "Starter", "Bench", "Did Not Play"
+ MP string `json:"mp"` // Minutes Played, e.g., "38:07"
+ FG int `json:"fg"`
+ FGA int `json:"fga"`
+ FGPercent float64 `json:"fgPercent"`
+ ThreeP int `json:"threeP"`
+ ThreePA int `json:"threePa"`
+ ThreePPercent float64 `json:"threePPercent"`
+ FT int `json:"ft"`
+ FTA int `json:"fta"`
+ FTPercent float64 `json:"ftPercent"`
+ ORB int `json:"orb"`
+ DRB int `json:"drb"`
+ TRB int `json:"trb"`
+ AST int `json:"ast"`
+ STL int `json:"stl"`
+ BLK int `json:"blk"`
+ TOV int `json:"tov"`
+ PF int `json:"pf"`
+ PTS int `json:"pts"`
+ GmSc float64 `json:"gmSc"` // Game Score
+ PlusMinus int `json:"plusMinus"`
+
+ CreatedAt time.Time `swaggerignore:"true"`
+ UpdatedAt time.Time `swaggerignore:"true"`
+ DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true"`
+}
+
+// PlayerGameAdvStat contains the advanced box score statistics for a single player in a single game.
+type PlayerGameAdvStat struct {
+ ID uint `gorm:"primaryKey" swaggerignore:"true"`
+ GameID string `gorm:"not null;uniqueIndex:idx_player_game_adv" json:"gameId"`
+ PlayerID string `gorm:"not null;uniqueIndex:idx_player_game_adv" json:"playerId"`
+ PlayerName string `json:"playerName"`
+ Team string `gorm:"not null" json:"team"`
+ MP string `json:"mp"` // Minutes Played
+ TSPercent float64 `json:"tsPercent"`
+ EFGPercent float64 `json:"efgPercent"`
+ ThreePAr float64 `json:"threePAr"`
+ FTr float64 `json:"fTr"`
+ ORBPercent float64 `json:"orbPercent"`
+ DRBPercent float64 `json:"drbPercent"`
+ TRBPercent float64 `json:"trbPercent"`
+ ASTPercent float64 `json:"astPercent"`
+ STLPercent float64 `json:"stlPercent"`
+ BLKPercent float64 `json:"blkPercent"`
+ TOVPercent float64 `json:"tovPercent"`
+ USGPercent float64 `json:"usgPercent"`
+ ORtg int `json:"oRtg"`
+ DRtg int `json:"dRtg"`
+ BPM float64 `json:"bpm"` // Box Plus/Minus
+
+ CreatedAt time.Time `swaggerignore:"true"`
+ UpdatedAt time.Time `swaggerignore:"true"`
+ DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true"`
+}
+
+// TeamGameBasicStat holds the total basic stats for a team in a single game.
+type TeamGameBasicStat struct {
+ ID uint `gorm:"primaryKey" swaggerignore:"true"`
+ GameID string `gorm:"not null;uniqueIndex:idx_team_game_basic" json:"gameId"`
+ Team string `gorm:"not null;uniqueIndex:idx_team_game_basic" json:"team"`
+ MP int `json:"mp"` // Total minutes, usually 240 for a regulation game
+ FG int `json:"fg"`
+ FGA int `json:"fga"`
+ FGPercent float64 `json:"fgPercent"`
+ ThreeP int `json:"threeP"`
+ ThreePA int `json:"threePa"`
+ ThreePPercent float64 `json:"threePPercent"`
+ FT int `json:"ft"`
+ FTA int `json:"fta"`
+ FTPercent float64 `json:"ftPercent"`
+ ORB int `json:"orb"`
+ DRB int `json:"drb"`
+ TRB int `json:"trb"`
+ AST int `json:"ast"`
+ STL int `json:"stl"`
+ BLK int `json:"blk"`
+ TOV int `json:"tov"`
+ PF int `json:"pf"`
+ PTS int `json:"pts"`
+
+ CreatedAt time.Time `swaggerignore:"true"`
+ UpdatedAt time.Time `swaggerignore:"true"`
+ DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true"`
+}
+
+// TeamGameAdvStat holds the total advanced stats for a team in a single game.
+type TeamGameAdvStat struct {
+ ID uint `gorm:"primaryKey" swaggerignore:"true"`
+ GameID string `gorm:"not null;uniqueIndex:idx_team_game_adv" json:"gameId"`
+ Team string `gorm:"not null;uniqueIndex:idx_team_game_adv" json:"team"`
+ MP int `json:"mp"`
+ TSPercent float64 `json:"tsPercent"`
+ EFGPercent float64 `json:"efgPercent"`
+ ThreePAr float64 `json:"threePAr"`
+ FTr float64 `json:"fTr"`
+ ORBPercent float64 `json:"orbPercent"`
+ DRBPercent float64 `json:"drbPercent"`
+ TRBPercent float64 `json:"trbPercent"`
+ ASTPercent float64 `json:"astPercent"`
+ STLPercent float64 `json:"stlPercent"`
+ BLKPercent float64 `json:"blkPercent"`
+ TOVPercent float64 `json:"tovPercent"`
+ USGPercent float64 `json:"usgPercent"` // Will be ~100.0 for a team
+ ORtg float64 `json:"oRtg"`
+ DRtg float64 `json:"dRtg"`
+
+ CreatedAt time.Time `swaggerignore:"true"`
+ UpdatedAt time.Time `swaggerignore:"true"`
+ DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true"`
+}
diff --git a/services/box_score_scrape_service.go b/services/box_score_scrape_service.go
new file mode 100644
index 0000000..5098263
--- /dev/null
+++ b/services/box_score_scrape_service.go
@@ -0,0 +1,312 @@
+package services
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/nprasad2077/NBA_Go/models"
+ "github.com/nprasad2077/NBA_Go/utils"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+)
+
+const boxScoreURLBase = "https://www.basketball-reference.com"
+
+// uncommentDoc finds and replaces commented out HTML sections.
+func uncommentDoc(doc *goquery.Document) *goquery.Document {
+ doc.Find("*").Contents().FilterFunction(func(i int, s *goquery.Selection) bool {
+ return goquery.NodeName(s) == "#comment"
+ }).Each(func(i int, s *goquery.Selection) {
+ // Use .Data on the underlying html.Node to get the comment content.
+ commentText := s.Nodes[0].Data
+ if strings.Contains(commentText, "= ? AND date < ?", from, to).Find(&games).Error; err != nil {
+ return fmt.Errorf("failed to query games from DB: %w", err)
+ }
+
+ log.Printf("Found %d games to process in the specified date range.", len(games))
+
+ for _, game := range games {
+ log.Printf("Processing game: %s", game.GameID)
+ fullURL := boxScoreURLBase + game.BoxScoreURL
+ if err := scrapeBoxScorePage(db, fullURL, game.GameID); err != nil {
+ // Log the error but continue to the next game
+ log.Printf("Error processing box score for game %s: %v", game.GameID, err)
+ }
+ // Be a good internet citizen and pause between requests.
+ utils.SleepWithJitter(2300 * time.Millisecond)
+ }
+ return nil
+}
+
+// scrapeBoxScorePage handles fetching and parsing a single box score page.
+func scrapeBoxScorePage(db *gorm.DB, url, gameID string) error {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("received non-200 status code: %s", resp.Status)
+ }
+
+ doc, err := goquery.NewDocumentFromReader(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ // 1. Uncomment all tables in the document first.
+ doc = uncommentDoc(doc)
+
+ // 2. Call the dedicated service to handle line scores.
+ if err := FetchAndStoreLineScore(db, doc, gameID); err != nil {
+ log.Printf("Error processing line score for game %s: %v", gameID, err)
+ }
+
+ // 3. The existing box score parser will now work because its tables are visible.
+ if err := parseAndStoreBoxScores(db, doc, gameID); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// parseAndStoreBoxScores finds all basic and advanced box score tables and processes them.
+func parseAndStoreBoxScores(db *gorm.DB, doc *goquery.Document, gameID string) error {
+ var allPlayerBasicStats []models.PlayerGameBasicStat
+ var allPlayerAdvStats []models.PlayerGameAdvStat
+ var allTeamBasicStats []models.TeamGameBasicStat
+ var allTeamAdvStats []models.TeamGameAdvStat
+
+ // Use a CSS attribute selector to find all box score tables for both teams.
+ doc.Find(`table[id^="box-"][id$="-game-basic"], table[id^="box-"][id$="-game-advanced"]`).Each(func(i int, table *goquery.Selection) {
+ tableID, _ := table.Attr("id")
+ isAdvanced := strings.Contains(tableID, "-advanced")
+ teamAbbr := strings.TrimSuffix(strings.TrimPrefix(tableID, "box-"), "-game-basic")
+ teamAbbr = strings.TrimSuffix(teamAbbr, "-game-advanced")
+
+ // Process player rows
+ table.Find("tbody tr").Each(func(j int, row *goquery.Selection) {
+ playerID, exists := row.Find("th").Attr("data-append-csv")
+ if !exists || playerID == "" {
+ return // Not a player row
+ }
+
+ // Handle "Did Not Play" or other statuses
+ reason := row.Find(`td[data-stat="reason"]`)
+ status := "Played"
+ if reason.Length() > 0 {
+ status = reason.Text()
+ }
+
+ if !isAdvanced {
+ stat := parsePlayerBasicStat(row, gameID, playerID, teamAbbr, status)
+ allPlayerBasicStats = append(allPlayerBasicStats, stat)
+ } else {
+ stat := parsePlayerAdvStat(row, gameID, playerID, teamAbbr, status)
+ allPlayerAdvStats = append(allPlayerAdvStats, stat)
+ }
+ })
+
+ // Process team total row
+ table.Find("tfoot tr").Each(func(j int, row *goquery.Selection) {
+ if !isAdvanced {
+ stat := parseTeamBasicStat(row, gameID, teamAbbr)
+ allTeamBasicStats = append(allTeamBasicStats, stat)
+ } else {
+ stat := parseTeamAdvStat(row, gameID, teamAbbr)
+ allTeamAdvStats = append(allTeamAdvStats, stat)
+ }
+ })
+ })
+
+ // Batch upsert all collected stats
+ if err := batchUpsertAll(db, allPlayerBasicStats, allPlayerAdvStats, allTeamBasicStats, allTeamAdvStats); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// --- Parsing Helper Functions ---
+
+func parsePlayerBasicStat(row *goquery.Selection, gameID, playerID, team, status string) models.PlayerGameBasicStat {
+ return models.PlayerGameBasicStat{
+ GameID: gameID,
+ PlayerID: playerID,
+ PlayerName: row.Find(`th[data-stat="player"] a`).Text(),
+ Team: team,
+ Status: status,
+ MP: row.Find(`td[data-stat="mp"]`).Text(),
+ FG: mustAtoi(row.Find(`td[data-stat="fg"]`).Text()),
+ FGA: mustAtoi(row.Find(`td[data-stat="fga"]`).Text()),
+ FGPercent: mustParseFloat(row.Find(`td[data-stat="fg_pct"]`).Text()),
+ ThreeP: mustAtoi(row.Find(`td[data-stat="fg3"]`).Text()),
+ ThreePA: mustAtoi(row.Find(`td[data-stat="fg3a"]`).Text()),
+ ThreePPercent: mustParseFloat(row.Find(`td[data-stat="fg3_pct"]`).Text()),
+ FT: mustAtoi(row.Find(`td[data-stat="ft"]`).Text()),
+ FTA: mustAtoi(row.Find(`td[data-stat="fta"]`).Text()),
+ FTPercent: mustParseFloat(row.Find(`td[data-stat="ft_pct"]`).Text()),
+ ORB: mustAtoi(row.Find(`td[data-stat="orb"]`).Text()),
+ DRB: mustAtoi(row.Find(`td[data-stat="drb"]`).Text()),
+ TRB: mustAtoi(row.Find(`td[data-stat="trb"]`).Text()),
+ AST: mustAtoi(row.Find(`td[data-stat="ast"]`).Text()),
+ STL: mustAtoi(row.Find(`td[data-stat="stl"]`).Text()),
+ BLK: mustAtoi(row.Find(`td[data-stat="blk"]`).Text()),
+ TOV: mustAtoi(row.Find(`td[data-stat="tov"]`).Text()),
+ PF: mustAtoi(row.Find(`td[data-stat="pf"]`).Text()),
+ PTS: mustAtoi(row.Find(`td[data-stat="pts"]`).Text()),
+ GmSc: mustParseFloat(row.Find(`td[data-stat="game_score"]`).Text()),
+ PlusMinus: mustAtoiWithSign(row.Find(`td[data-stat="plus_minus"]`).Text()),
+ }
+}
+
+func parsePlayerAdvStat(row *goquery.Selection, gameID, playerID, team, status string) models.PlayerGameAdvStat {
+ return models.PlayerGameAdvStat{
+ GameID: gameID,
+ PlayerID: playerID,
+ PlayerName: row.Find(`th[data-stat="player"] a`).Text(),
+ Team: team,
+ MP: row.Find(`td[data-stat="mp"]`).Text(),
+ TSPercent: mustParseFloat(row.Find(`td[data-stat="ts_pct"]`).Text()),
+ EFGPercent: mustParseFloat(row.Find(`td[data-stat="efg_pct"]`).Text()),
+ ThreePAr: mustParseFloat(row.Find(`td[data-stat="fg3a_per_fga_pct"]`).Text()),
+ FTr: mustParseFloat(row.Find(`td[data-stat="fta_per_fga_pct"]`).Text()),
+ ORBPercent: mustParseFloat(row.Find(`td[data-stat="orb_pct"]`).Text()),
+ DRBPercent: mustParseFloat(row.Find(`td[data-stat="drb_pct"]`).Text()),
+ TRBPercent: mustParseFloat(row.Find(`td[data-stat="trb_pct"]`).Text()),
+ ASTPercent: mustParseFloat(row.Find(`td[data-stat="ast_pct"]`).Text()),
+ STLPercent: mustParseFloat(row.Find(`td[data-stat="stl_pct"]`).Text()),
+ BLKPercent: mustParseFloat(row.Find(`td[data-stat="blk_pct"]`).Text()),
+ TOVPercent: mustParseFloat(row.Find(`td[data-stat="tov_pct"]`).Text()),
+ USGPercent: mustParseFloat(row.Find(`td[data-stat="usg_pct"]`).Text()),
+ ORtg: mustAtoi(row.Find(`td[data-stat="off_rtg"]`).Text()),
+ DRtg: mustAtoi(row.Find(`td[data-stat="def_rtg"]`).Text()),
+ BPM: mustParseFloat(row.Find(`td[data-stat="bpm"]`).Text()),
+ }
+}
+
+func parseTeamBasicStat(row *goquery.Selection, gameID, team string) models.TeamGameBasicStat {
+ return models.TeamGameBasicStat{
+ GameID: gameID,
+ Team: team,
+ MP: mustAtoi(row.Find(`td[data-stat="mp"]`).Text()),
+ FG: mustAtoi(row.Find(`td[data-stat="fg"]`).Text()),
+ FGA: mustAtoi(row.Find(`td[data-stat="fga"]`).Text()),
+ FGPercent: mustParseFloat(row.Find(`td[data-stat="fg_pct"]`).Text()),
+ ThreeP: mustAtoi(row.Find(`td[data-stat="fg3"]`).Text()),
+ ThreePA: mustAtoi(row.Find(`td[data-stat="fg3a"]`).Text()),
+ ThreePPercent: mustParseFloat(row.Find(`td[data-stat="fg3_pct"]`).Text()),
+ FT: mustAtoi(row.Find(`td[data-stat="ft"]`).Text()),
+ FTA: mustAtoi(row.Find(`td[data-stat="fta"]`).Text()),
+ FTPercent: mustParseFloat(row.Find(`td[data-stat="ft_pct"]`).Text()),
+ ORB: mustAtoi(row.Find(`td[data-stat="orb"]`).Text()),
+ DRB: mustAtoi(row.Find(`td[data-stat="drb"]`).Text()),
+ TRB: mustAtoi(row.Find(`td[data-stat="trb"]`).Text()),
+ AST: mustAtoi(row.Find(`td[data-stat="ast"]`).Text()),
+ STL: mustAtoi(row.Find(`td[data-stat="stl"]`).Text()),
+ BLK: mustAtoi(row.Find(`td[data-stat="blk"]`).Text()),
+ TOV: mustAtoi(row.Find(`td[data-stat="tov"]`).Text()),
+ PF: mustAtoi(row.Find(`td[data-stat="pf"]`).Text()),
+ PTS: mustAtoi(row.Find(`td[data-stat="pts"]`).Text()),
+ }
+}
+
+func parseTeamAdvStat(row *goquery.Selection, gameID, team string) models.TeamGameAdvStat {
+ return models.TeamGameAdvStat{
+ GameID: gameID,
+ Team: team,
+ MP: mustAtoi(row.Find(`td[data-stat="mp"]`).Text()),
+ TSPercent: mustParseFloat(row.Find(`td[data-stat="ts_pct"]`).Text()),
+ EFGPercent: mustParseFloat(row.Find(`td[data-stat="efg_pct"]`).Text()),
+ ThreePAr: mustParseFloat(row.Find(`td[data-stat="fg3a_per_fga_pct"]`).Text()),
+ FTr: mustParseFloat(row.Find(`td[data-stat="fta_per_fga_pct"]`).Text()),
+ ORBPercent: mustParseFloat(row.Find(`td[data-stat="orb_pct"]`).Text()),
+ DRBPercent: mustParseFloat(row.Find(`td[data-stat="drb_pct"]`).Text()),
+ TRBPercent: mustParseFloat(row.Find(`td[data-stat="trb_pct"]`).Text()),
+ ASTPercent: mustParseFloat(row.Find(`td[data-stat="ast_pct"]`).Text()),
+ STLPercent: mustParseFloat(row.Find(`td[data-stat="stl_pct"]`).Text()),
+ BLKPercent: mustParseFloat(row.Find(`td[data-stat="blk_pct"]`).Text()),
+ TOVPercent: mustParseFloat(row.Find(`td[data-stat="tov_pct"]`).Text()),
+ USGPercent: mustParseFloat(row.Find(`td[data-stat="usg_pct"]`).Text()),
+ ORtg: mustParseFloat(row.Find(`td[data-stat="off_rtg"]`).Text()),
+ DRtg: mustParseFloat(row.Find(`td[data-stat="def_rtg"]`).Text()),
+ }
+}
+
+// --- DB and Utility Functions ---
+
+func batchUpsertAll(db *gorm.DB, pbs []models.PlayerGameBasicStat, pas []models.PlayerGameAdvStat, tbs []models.TeamGameBasicStat, tas []models.TeamGameAdvStat) error {
+ if len(pbs) > 0 {
+ if err := db.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "game_id"}, {Name: "player_id"}},
+ DoUpdates: clause.AssignmentColumns(getModelColumns(&models.PlayerGameBasicStat{})),
+ }).Create(&pbs).Error; err != nil {
+ return fmt.Errorf("failed to upsert player basic stats: %w", err)
+ }
+ }
+ if len(pas) > 0 {
+ if err := db.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "game_id"}, {Name: "player_id"}},
+ DoUpdates: clause.AssignmentColumns(getModelColumns(&models.PlayerGameAdvStat{})),
+ }).Create(&pas).Error; err != nil {
+ return fmt.Errorf("failed to upsert player advanced stats: %w", err)
+ }
+ }
+ if len(tbs) > 0 {
+ if err := db.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "game_id"}, {Name: "team"}},
+ DoUpdates: clause.AssignmentColumns(getModelColumns(&models.TeamGameBasicStat{})),
+ }).Create(&tbs).Error; err != nil {
+ return fmt.Errorf("failed to upsert team basic stats: %w", err)
+ }
+ }
+ if len(tas) > 0 {
+ if err := db.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "game_id"}, {Name: "team"}},
+ DoUpdates: clause.AssignmentColumns(getModelColumns(&models.TeamGameAdvStat{})),
+ }).Create(&tas).Error; err != nil {
+ return fmt.Errorf("failed to upsert team advanced stats: %w", err)
+ }
+ }
+ return nil
+}
+
+
+
+// getModelColumns is a placeholder for a more robust reflection-based column name generator.
+// For now, it returns hardcoded lists.
+func getModelColumns(model interface{}) []string {
+ switch model.(type) {
+ case *models.PlayerGameBasicStat:
+ return []string{"player_name", "team", "status", "mp", "fg", "fga", "fg_percent", "three_p", "three_pa", "three_p_percent", "ft", "fta", "ft_percent", "orb", "drb", "trb", "ast", "stl", "blk", "tov", "pf", "pts", "gm_sc", "plus_minus"}
+ case *models.PlayerGameAdvStat:
+ return []string{"player_name", "team", "mp", "ts_percent", "efg_percent", "three_p_ar", "f_tr", "orb_percent", "drb_percent", "trb_percent", "ast_percent", "stl_percent", "blk_percent", "tov_percent", "usg_percent", "o_rtg", "d_rtg", "bpm"}
+ case *models.TeamGameBasicStat:
+ return []string{"mp", "fg", "fga", "fg_percent", "three_p", "three_pa", "three_p_percent", "ft", "fta", "ft_percent", "orb", "drb", "trb", "ast", "stl", "blk", "tov", "pf", "pts"}
+ case *models.TeamGameAdvStat:
+ return []string{"mp", "ts_percent", "efg_percent", "three_p_ar", "f_tr", "orb_percent", "drb_percent", "trb_percent", "ast_percent", "stl_percent", "blk_percent", "tov_percent", "usg_percent", "o_rtg", "d_rtg"}
+ }
+ return []string{}
+}
diff --git a/services/game_scrape_service.go b/services/game_scrape_service.go
new file mode 100644
index 0000000..386db98
--- /dev/null
+++ b/services/game_scrape_service.go
@@ -0,0 +1,190 @@
+// File: services/game_scrape_service.go
+package services
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/nprasad2077/NBA_Go/models"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+)
+
+const gameScheduleURLFmt = "https://www.basketball-reference.com/leagues/NBA_%d_games-%s.html"
+
+// FetchAndStoreGameSchedule scrapes the game schedule for a given season and month.
+// The month should be the full lowercase name, e.g., "october", "november".
+// If db is nil, it will perform a "dry run" and print the parsed data to the console.
+func FetchAndStoreGameSchedule(db *gorm.DB, season int, month string) error {
+ url := fmt.Sprintf(gameScheduleURLFmt, season, month)
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return fmt.Errorf("failed to fetch schedule for %s %d: %w", month, season, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ log.Printf("⚠️ Skipping schedule for %s %d (Status: %s)", month, season, resp.Status)
+ return nil // Not a fatal error, just no data for this month.
+ }
+
+ htmlBytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return fmt.Errorf("failed to read response body for %s %d: %w", month, season, err)
+ }
+
+ doc, err := goquery.NewDocumentFromReader(bytes.NewReader(htmlBytes))
+ if err != nil {
+ return fmt.Errorf("failed to parse HTML for %s %d: %w", month, season, err)
+ }
+
+ table := doc.Find("table#schedule")
+ if table.Length() == 0 {
+ // Sometimes the content is commented out
+ commentNode := doc.Find("#all_schedule").Contents().FilterFunction(func(i int, s *goquery.Selection) bool {
+ return goquery.NodeName(s) == "#comment"
+ })
+ if commentNode.Length() > 0 {
+ commentedHTML := commentNode.Nodes[0].FirstChild.Data
+ innerDoc, err := goquery.NewDocumentFromReader(strings.NewReader(commentedHTML))
+ if err != nil {
+ return fmt.Errorf("failed to parse commented schedule HTML: %w", err)
+ }
+ table = innerDoc.Find("table#schedule")
+ }
+ }
+
+ if table.Length() == 0 {
+ log.Printf("No schedule table found for %s %d.", month, season)
+ return nil
+ }
+
+ var gamesToUpsert []models.Game
+ table.Find("tbody tr").Each(func(i int, row *goquery.Selection) {
+ // Skip table header rows that are sometimes repeated in the body
+ if row.Find("th.poptip").Length() > 1 {
+ return
+ }
+
+ // Skip rows that don't represent games (e.g., placeholder rows)
+ if row.Find(`[data-stat="visitor_team_name"]`).Text() == "" {
+ return
+ }
+
+ var game models.Game
+ var gameID string
+
+ // Extract GameID from the box score link, which is the most reliable unique key
+ boxScoreCell := row.Find(`td[data-stat="box_score_text"] a`)
+ if href, exists := boxScoreCell.Attr("href"); exists {
+ parts := strings.Split(href, "/")
+ fileName := parts[len(parts)-1]
+ gameID = strings.TrimSuffix(fileName, ".html")
+ }
+
+ // If there's no box score link, it's likely a future game, we can skip it or handle differently
+ if gameID == "" {
+ return
+ }
+ game.GameID = gameID
+
+ // Get the date part from the 'csk' attribute for accuracy
+ dateCsk, _ := row.Find(`th[data-stat="date_game"]`).Attr("csk")
+ var datePart string
+ if len(dateCsk) >= 8 {
+ datePart = dateCsk[:8]
+ } else {
+ log.Printf("Could not parse date from invalid 'csk' attribute: %s. Skipping.", dateCsk)
+ return
+ }
+
+ // Get the start time string
+ startTimeET := row.Find(`td[data-stat="game_start_time"]`).Text()
+ if startTimeET == "" {
+ // Skip games without a start time, as they can't be parsed accurately
+ log.Printf("Could not find start time for game %s. Skipping.", gameID)
+ return
+ }
+
+ // Load the US/Eastern timezone to correctly handle ET/EST/EDT
+ eastern, err := time.LoadLocation("America/New_York")
+ if err != nil {
+ log.Printf("FATAL: Could not load America/New_York timezone: %v", err)
+ // This is a system-level error, so we stop the row processing here.
+ // The function will continue and process any games already parsed.
+ return
+ }
+
+ // Combine date and time and parse together in the correct timezone.
+ // The layout "3:04p" handles times like "7:30p".
+ fullDateTimeString := datePart + startTimeET
+ layout := "200601023:04p"
+ gameDate, err := time.ParseInLocation(layout, fullDateTimeString, eastern)
+ if err != nil {
+ log.Printf("Could not parse combined date-time for game %s (value: '%s'): %v. Skipping.", gameID, fullDateTimeString, err)
+ return
+ }
+ game.Date = gameDate
+ game.StartTimeET = startTimeET // Keep the original string as well
+
+ game.VisitorTeam = row.Find(`td[data-stat="visitor_team_name"] a`).Text()
+ game.VisitorPTS = mustAtoi(row.Find(`td[data-stat="visitor_pts"]`).Text())
+ game.HomeTeam = row.Find(`td[data-stat="home_team_name"] a`).Text()
+ game.HomePTS = mustAtoi(row.Find(`td[data-stat="home_pts"]`).Text())
+ game.BoxScoreURL, _ = boxScoreCell.Attr("href")
+ game.GameDuration = row.Find(`td[data-stat="game_duration"]`).Text()
+ game.Arena = row.Find(`td[data-stat="arena_name"]`).Text()
+ game.IsPlayoff = strings.Contains(row.Find(`td[data-stat="game_remarks"]`).Text(), "Playoffs")
+
+ gamesToUpsert = append(gamesToUpsert, game)
+ })
+
+ if len(gamesToUpsert) > 0 {
+ // If the db connection is nil, we're in test/debug mode. Print to console.
+ if db == nil {
+ log.Println("--- RUNNING IN DRY-RUN MODE ---")
+ for _, game := range gamesToUpsert {
+ // Use %+v to print the struct with field names for clarity
+ log.Printf("Game Data: %+v\n", game)
+ }
+ log.Printf("--- WOULD INSERT %d RECORDS ---", len(gamesToUpsert))
+ return nil // End execution for dry-run
+ }
+
+ log.Printf("Attempting to batch upsert %d games for %s %d...", len(gamesToUpsert), month, season)
+
+ if err := db.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "game_id"}},
+ DoUpdates: clause.AssignmentColumns(allGameColumns()),
+ }).Create(&gamesToUpsert).Error; err != nil {
+ log.Printf("Failed to batch upsert games: %v", err)
+ return err
+ }
+ log.Printf("✅ Successfully batch upserted %d game records for %s %d.", len(gamesToUpsert), month, season)
+ } else {
+ log.Printf("No game data found to import for %s %d.", month, season)
+ }
+
+ return nil
+}
+
+// allGameColumns returns a list of all column names in the Game model for the upsert operation.
+// This ensures that if a record exists, all its fields are updated with the new data.
+func allGameColumns() []string {
+ return []string{
+ "date", "is_playoff", "start_time_et", "arena", "visitor_team",
+ "visitor_pts", "home_team", "home_pts", "game_duration", "box_score_url",
+ "updated_at",
+ }
+}
diff --git a/services/helpers.go b/services/helpers.go
index 324676e..96f307c 100644
--- a/services/helpers.go
+++ b/services/helpers.go
@@ -8,6 +8,16 @@ func mustAtoi(s string) int {
return i
}
+// mustAtoiWithSign handles strings that might have a "+" or "-" sign.
+func mustAtoiWithSign(s string) int {
+ if s == "" {
+ return 0
+ }
+ // The strconv.Atoi function handles the sign automatically.
+ i, _ := strconv.Atoi(s)
+ return i
+}
+
// mustParseFloat parses s into a float64, or returns 0.0 on error.
func mustParseFloat(s string) float64 {
f, _ := strconv.ParseFloat(s, 64)
diff --git a/services/line_score_scrape_service.go b/services/line_score_scrape_service.go
new file mode 100644
index 0000000..7f6f4a4
--- /dev/null
+++ b/services/line_score_scrape_service.go
@@ -0,0 +1,54 @@
+// File: services/line_score_scrape_service.go
+package services
+
+import (
+ "log"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/nprasad2077/NBA_Go/models"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+)
+
+// FetchAndStoreLineScore parses the line score table from a goquery document and upserts it to the DB.
+// It assumes the document has already been processed to make commented-out tables visible.
+func FetchAndStoreLineScore(db *gorm.DB, doc *goquery.Document, gameID string) error {
+ var lineScores []models.LineScore
+
+ doc.Find("table#line_score tbody tr").Each(func(i int, row *goquery.Selection) {
+ teamName := row.Find(`th[data-stat="team"] a`).Text()
+ if teamName == "" {
+ return // Skip invalid rows
+ }
+
+ ls := models.LineScore{
+ GameID: gameID,
+ Team: teamName,
+ }
+ ls.Q1 = mustAtoi(row.Find(`td[data-stat="1"]`).Text())
+ ls.Q2 = mustAtoi(row.Find(`td[data-stat="2"]`).Text())
+ ls.Q3 = mustAtoi(row.Find(`td[data-stat="3"]`).Text())
+ ls.Q4 = mustAtoi(row.Find(`td[data-stat="4"]`).Text())
+ ls.OT1 = mustAtoi(row.Find(`td[data-stat="OT1"]`).Text())
+ ls.OT2 = mustAtoi(row.Find(`td[data-stat="OT2"]`).Text())
+ ls.OT3 = mustAtoi(row.Find(`td[data-stat="OT3"]`).Text())
+ ls.Total = mustAtoi(row.Find(`td[data-stat="T"]`).Text())
+ lineScores = append(lineScores, ls)
+ })
+
+ if len(lineScores) == 0 {
+ log.Printf("No line score data found to import for game %s.", gameID)
+ return nil
+ }
+
+ // Upsert the data into the database.
+ if err := db.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "game_id"}, {Name: "team"}},
+ DoUpdates: clause.AssignmentColumns([]string{"q1", "q2", "q3", "q4", "ot1", "ot2", "ot3", "total"}),
+ }).Create(&lineScores).Error; err != nil {
+ return err
+ }
+
+ log.Printf("Successfully processed line scores for game %s.", gameID)
+ return nil
+}
\ No newline at end of file
diff --git a/test/run_scraper.go b/test/run_scraper.go
new file mode 100644
index 0000000..5238f73
--- /dev/null
+++ b/test/run_scraper.go
@@ -0,0 +1,104 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/PuerkitoBio/goquery"
+)
+
+// LineScore struct and mustAtoi helper function remain the same
+type LineScore struct {
+ GameID string
+ Team string
+ Q1, Q2, Q3, Q4 int
+ OT1, OT2, OT3 int
+ Total int
+}
+func mustAtoi(s string) int { i, _ := strconv.Atoi(s); return i }
+
+
+func scrapeAndPrintLineScore(gameURL, gameID string) {
+ // 1. Fetch the page
+ resp, err := http.Get(gameURL)
+ if err != nil {
+ log.Fatalf("Failed to fetch page: %v", err)
+ }
+ defer resp.Body.Close()
+
+ bodyBytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ log.Fatalf("Failed to read response body: %v", err)
+ }
+
+ doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(bodyBytes)))
+ if err != nil {
+ log.Fatalf("Failed to parse HTML: %v", err)
+ }
+
+ var table *goquery.Selection
+
+ // Try to find a visible table first
+ table = doc.Find("table#line_score")
+
+ if table.Length() == 0 {
+ log.Println("Visible table not found. Finding and parsing commented table...")
+
+ commentNode := doc.Find("#all_line_score").Contents().FilterFunction(func(i int, s *goquery.Selection) bool {
+ return goquery.NodeName(s) == "#comment"
+ })
+
+ if commentNode.Length() > 0 {
+ // ✅ THE FIX: Access the .Data field directly from the comment node.
+ commentedHTML := commentNode.Nodes[0].Data
+
+ innerDoc, err := goquery.NewDocumentFromReader(strings.NewReader(commentedHTML))
+ if err != nil {
+ log.Fatalf("❌ Failed to parse the HTML from the comment: %v", err)
+ }
+ table = innerDoc.Find("table#line_score")
+ }
+ }
+
+ if table == nil || table.Length() == 0 {
+ log.Println("❌ No line score table found by any method.")
+ return
+ }
+
+ // 3. Parse the data from the found table
+ var lineScores []LineScore
+ table.Find("tbody tr").Each(func(i int, row *goquery.Selection) {
+ teamName := row.Find(`th[data-stat="team"] a`).Text()
+ if teamName != "" {
+ ls := LineScore{GameID: gameID, Team: teamName}
+ ls.Q1 = mustAtoi(row.Find(`td[data-stat="1"]`).Text())
+ ls.Q2 = mustAtoi(row.Find(`td[data-stat="2"]`).Text())
+ ls.Q3 = mustAtoi(row.Find(`td[data-stat="3"]`).Text())
+ ls.Q4 = mustAtoi(row.Find(`td[data-stat="4"]`).Text())
+ ls.Total = mustAtoi(row.Find(`td[data-stat="T"]`).Text())
+ lineScores = append(lineScores, ls)
+ }
+ })
+
+ // 4. Print the results
+ if len(lineScores) > 0 {
+ log.Printf("✅ Success! Found %d line scores for game %s:", len(lineScores), gameID)
+ for _, ls := range lineScores {
+ log.Printf("%+v\n", ls)
+ }
+ } else {
+ log.Println("❌ Table was found, but failed to parse any rows.")
+ }
+}
+
+func main() {
+ gameID := "202406170BOS"
+ gameURL := fmt.Sprintf("https://www.basketball-reference.com/boxscores/%s.html", gameID)
+ log.Printf("--- Scraping Line Score for Game: %s ---", gameID)
+ log.Printf("--- URL: %s ---", gameURL)
+ scrapeAndPrintLineScore(gameURL, gameID)
+}
\ No newline at end of file