NBA Analytics Project — Shot & Player Summary System

Abstract

This project implements a full-stack analytics workflow that mirrors a production-grade NBA data pipeline. The challenge: design a normalized PostgreSQL database, build a reliable ingestion process, expose structured player-summary analytics via a Django API, and then construct an Angular frontend that visualizes player shooting performance using accurate court-relative coordinates.

My solution integrates a well-designed relational schema, an idempotent ETL pipeline, a clean API architecture, and a modular Angular component that renders both summary stats and shot-chart overlays. The overall system reflects scalable engineering practices—including data validation, model-driven backend development, reusable TypeScript interfaces, and UI components aligned with modern frontend design principles.

Project Details

The assignment focused on three major deliverables:

1. Backend Engineering

  • Normalize and store raw NBA-style player + shot data in PostgreSQL.
  • Implement a repeatable ingestion script that loads raw datasets while preventing duplicates.
  • Create an API endpoint (PlayerSummary) that returns consolidated player information.
  • Export the final database state using pg_dump.

2. Frontend Engineering

  • Build an Angular component that consumes /api/v1/playerSummary/{playerID}.
  • Visualize player shot charts using basket-relative coordinate systems.
  • Create a TypeScript interface mapping exactly to the API response.
  • Capture and upload UI screenshots demonstrating the component.

3. Environment Setup

  • Configure PostgreSQL schemas and roles.
  • Run Django backend using Pyenv + virtual environment.
  • Start Angular frontend (Node 16 + Angular CLI 12).

My implementation unifies these steps into a robust, testable pipeline where each layer—the database, backend services, and UI—integrates seamlessly.

Backend Implementation

Database Architecture

The architecture includes four main tables: Player, Team, Game, and PlayerStats.

  • Player — This table stores information about individual players, with fields for a unique identifier (id) and the player's name (name).
  • Team — This table holds data about different teams, also with an id and name field, ensuring each team is uniquely identifiable.
  • Game — This This table records details of games played, including a unique id, the date of the game, and foreign key relationships to identify the home_team and away_team. The foreign key constraints ensure referential integrity by linking each game to two entries in the Team table.
  • PlayerStats — This table captures detailed statistics for players in each game. It includes a foreign key to the Player table (player), a foreign key to the Team table (team), and another to the Game table (game), linking player performance to specific games and teams. Additional fields record whether the player was a starter (is_starter), minutes played, points scored, assists, various types of rebounds, steals, blocks, turnovers, fouls, and shooting statistics (including free throws, two-pointers, and three-pointers). The shots field is a JSONField designed to store complex data about each shot taken during a game. This architecture effectively organizes and relates data necessary for tracking player performance and game outcomes within a sports context.

The schema enforces referential integrity using foreign keys and follows 3NF principles. It minimizes redundancy while supporting efficient player-level and game-level analytics.

ER Diagram (Players, Games, Shots)

Ingestion & ETL Process

The ingestion script (Python + Django ORM) performs:

  • Parsing raw data files from backend/raw_data
  • Deduplication based on composite shot identifiers
  • Atomic inserts using database transactions
  • Idempotent re-runs that do not re-insert existing records
  • Validation and logging for debugging or data quality checks

After the ingestion process, I exported the full database state with:

pg_dump -U okcapplicant okc > dbexport.pgsql

PlayerSummary API

The PlayerSummary API aggregates player identity info, game-level performance, and all shot coordinates into a single response. It mimics the structure provided in the sample_response.json. This endpoint powers the frontend shot chart and statistical summaries.

Frontend Implementation

Player Summary Interface

Inside frontend/src/app/player-summary/, I designed a comprehensive TypeScript interface that models the full JSON response:

  • Player metadata
  • Array of games played
  • Shot attempts with { x, y, made, gameId }
  • Computed stats such as FG%, makes, misses, and game-by-game totals

Strong typing ensures that frontend logic, rendering, and API consumption remain consistent and safe.

UI & Shot Chart Rendering

The Angular player-summary component:

  • Extracts the playerID from the route
  • Fetches player summary data asynchronously
  • Renders a player header with stats
  • Displays a game summary table
  • Draws a shot chart using coordinate transformations relative to the basket

The visualization logic converts feet-based coordinates into pixel space, maintains correct court orientation, and overlays makes/misses on top of a scalable court diagram.

User Interface

Additional UX Enhancements

  • Typed Angular services for communication with the backend
  • SCSS-based modular styling
  • RXJS Observables for reactive UI updates
  • Error messaging for invalid or missing playerIDs

Overall, the frontend produces a clean, fast, interactive representation of a player's shooting data.

Tech Stack

  • PostgreSQL
  • Django
  • Angular
  • TypeScript