NBA Relationship Scouting: League-Wide Relationship Discovery and Film Review in Foundry
NBA scouts and basketball operations analysts often have to jump between separate lineup-stat tables to understand how players perform together across different contexts. I built a Foundry-native basketball operations application that treats observed player combinations as first-class relationships rather than isolated rows in lineup-stat tables.
For any selected NBA player, the application lets an analyst:
-
Explore their observed 2-, 3-, 4-, and 5-player relationships
-
Traverse a relationship into the complete five-man lineups that contain it
-
Search the league for statistically parallel relationship profiles
-
Use grounded AIP to identify what deserves further investigation
-
Turn that investigation into a tracked Film Review Request
Stack: Pipeline Builder → Ontology → OSDK React → deterministic analytics → AIP Logic → Actions
Primary grain: one observed player group for one team and season
Relationship universe: 27,903 observed 2-, 3-, 4-, and 5-player groups
Current coverage: 2025–26 season
The application is deliberately scoped as a relationship scouting and discovery system. It does not estimate chemistry, simulate unseen lineups, recommend trades, or claim that statistically parallel groups contain interchangeable players.
Why this problem
NBA lineup statistics are readily available, but they are commonly consumed as separate tables.
A player can appear across dozens of pairs, trios, four-player units, and complete five-player lineups. The statistics describe each combination, but moving between those levels of context quickly becomes cumbersome.
The question I wanted to support was broader:
How does a player perform across different teammate relationships, what larger lineups contain those relationships, and where else in the league do similar relationship profiles appear?
The useful layer is therefore not another NET Rating table. It is the connection between:
Player
↓
Observed relationship
↓
Larger lineup context
↓
League-wide parallel relationship
↓
Film investigation
That led to the central modeling decision:
Make the observed player combination itself an Ontology object.
Data
The build uses NBA shared-court advanced statistics for observed player groups.
The extracted relationship universe contains:
2-player groups: 5,403
3-player groups: 7,500
4-player groups: 7,500
5-player groups: 7,500
────────────────────────
Total: 27,903
Each group contains season-level shared-court evidence including:
-
GP / MIN / POSS
-
OFF Rating
-
DEF Rating
-
NET Rating
-
AST%
-
REB%
-
eFG%
-
TS%
-
Pace
The source group identifier also contains the constituent NBA player IDs. That allowed player membership to be derived directly instead of inferred through name matching.
The primary serving dataset is:
observed_player_groups_base
One row represents one observed player combination for one team and season.
Architecture
The build has three connected paths.
The first creates the canonical relationship layer:
2 / 3 / 4 / 5-player group data
↓
Pipeline Builder
↓
observed_player_groups_base
↓
ObservedPlayerGroup
↓
OSDK application
The second derives explicit membership:
observed_player_groups_base
↓
parse constituent player IDs
↓
group_membership
↓
Player ↔ ObservedPlayerGroup
The third derives larger lineup context:
2 / 3 / 4-player membership
+
5-player membership
↓
team + season + constituent-player matching
↓
group_supporting_lineups
↓
smaller group ↔ supporting five-man group
ObservedPlayerGroup stores the shared-court profile. group_membership resolves who belongs to that relationship. group_supporting_lineups resolves which complete observed five-man lineups contain the smaller group.
The OSDK application consumes these Ontology relationships directly rather than reconstructing the graph in frontend code.
Ontology model
The active Ontology contains four object types:
Player
Team
ObservedPlayerGroup
FilmReviewRequest
The central analytical object is ObservedPlayerGroup.
One object represents:
one observed combination of players, for one team, during one season
For example:
Giannis Antetokounmpo
Ryan Rollins
AJ Green
Milwaukee
2025-26
The important links are:
Player ↔ ObservedPlayerGroup
Team ↔ ObservedPlayerGroup
ObservedPlayerGroup
↕
Supporting 5-Man ObservedPlayerGroup
FilmReviewRequest
↔
ObservedPlayerGroup
The grain is the important part.
A Player object tells me who an athlete is. A Team provides organizational context. Neither represents the shared-court behavior of several players together.
The relationship itself is the analytical record.
Relationship Explorer
The application begins with a league-wide Player search.
Selecting a player resolves that player’s linked ObservedPlayerGroup objects and separates them into:
2 Player
3 Player
4 Player
5 Player
Each relationship surfaces the core evidence:
Relationship
Team
GP
MIN
POSS
NET
OFF
DEF
Selecting one opens a dedicated Relationship Detail page.
A Player becomes the entry point into every observed relationship containing that player.
Relationship detail and supporting context
The Relationship Detail page expands one observed group into its statistical and Ontology context.
The analyst sees:
-
constituent players
-
team and season
-
performance metrics
-
GP / MIN / POSS evidence volume
-
supporting five-man lineups
For a smaller relationship, the supporting-lineup link answers:
What complete observed lineups contained these players together?
For example:
Westbrook + DeRozan + Achiuwa
↓
Westbrook + DeRozan + Achiuwa + Murray + Raynaud
Westbrook + DeRozan + Achiuwa + LaVine + Raynaud
Each supporting lineup is itself another ObservedPlayerGroup, so the analyst can open it and continue investigating.
3-player relationship
↓
supporting 5-man relationship
↓
full relationship profile
This turns what would normally be a static lineup table into a navigable relationship graph.
Smaller player relationships can be expanded into the complete observed five-man contexts that contain them.
Parallel Relationships
The primary analytical extension is Parallel Relationship discovery.
Once an analyst identifies an interesting relationship, the application searches the same-size league-wide relationship universe for statistically nearby profiles.
A two-player relationship is compared only against other two-player relationships. A three-player relationship is compared only against other three-player relationships, and so on.
The comparison uses:
NET Rating
OFF Rating
DEF Rating
AST%
REB%
eFG%
TS%
Pace
Each dimension is standardized across the same-size universe before calculating distance.
Conceptually:
D(g,h) = sqrt((1/k) * Σ(z_g,i - z_h,i)^2)
This keeps metrics with different numerical scales from dominating the comparison.
The application then shows the reference and selected parallel relationship side-by-side, including:
-
raw metric values
-
standardized gap by dimension
-
overall distance
GP, MIN, and POSS are intentionally not similarity dimensions.
They are displayed separately as evidence-volume context.
Two relationships can therefore have similar observed profiles without being treated as equally well-supported samples.
The methodology is intentionally transparent. The analyst can see exactly why one relationship was retrieved as a parallel to another.
Parallel Relationships retrieves same-size observed groups with nearby multivariate shared-court profiles while keeping evidence volume separate from similarity.
Why “parallel relationships”?
The comparison is deliberately between relationship profiles, not between individual players.
A nearby result supports the statement:
These observed shared-court relationships have similar profiles across the selected dimensions.
It does not establish that:
-
the constituent players are interchangeable
-
the players perform equivalent roles
-
the groups have the same chemistry
-
the same basketball mechanisms created the results
Parallel retrieval is therefore a discovery mechanism. It gives the analyst another relationship worth investigating.
AIP Scouting Copilot
The AIP design went through an important iteration.
The first version was correctly grounded but mostly repeated values already visible in the application.
If the interface already shows:
NET 9.2
OFF 120.7
POSS 1,266
an LLM repeating those numbers adds very little.
The more useful question became:
Given what the deterministic system already found, what deserves the analyst’s attention next?
That changed the division of labor.
Before the Copilot runs, the application calculates additional discovery signals deterministically.
For the selected relationship, it derives same-size standing across:
-
NET Rating
-
OFF Rating
-
DEF Rating
-
AST%
-
REB%
-
eFG%
-
TS%
-
Pace
This includes percentile and standardized standing.
For supporting five-man contexts, the application derives:
-
possession-weighted context statistics
-
contextual dispersion
-
highest-volume context
-
volume-aware high and low contexts
-
largest meaningful contextual differences
For a selected parallel relationship it also provides:
-
selected rank and distance
-
closest comparison dimensions
-
largest standardized gaps
-
evidence-volume comparison
The LLM does not calculate or override these values.
It receives only the current investigation context:
selected relationship
constituent players
team / season
raw metrics
GP / MIN / POSS
loaded supporting five-man contexts
derived discovery signals
selected parallel, when applicable
deterministic comparison output
The full league-wide relationship universe is never sent to AIP.
The Copilot then answers a much narrower set of questions.
What Stands Out
Which grounded patterns deserve attention?
What to Investigate Next
Which contexts or differences warrant deeper review?
Film Review Question
What specific qualitative question could film help answer?
Evidence Limitations
What caveats should temper the investigation?
The deterministic layer finds the pattern.
AIP prioritizes the investigation.
The Copilot consumes deterministic discovery signals and turns quantitative patterns into concrete investigation priorities rather than recomputing the underlying analytics.
From discovery to Action
Aggregate statistics can reveal an interesting relationship.
They cannot establish the basketball mechanism producing it.
That creates a natural handoff:
statistical discovery
↓
parallel comparison
↓
AIP investigation guidance
↓
film-review question
↓
FilmReviewRequest
AIP can draft the Film Review Question, but it does not execute the Action.
The analyst still controls:
Priority
Film Scope
Review Question
Notes
and explicitly submits the existing Create Film Review Request Action.
FilmReviewRequest is linked directly to the ObservedPlayerGroup being investigated.
New requests begin with:
status = Requested
and can progress through:
Requested
↓
Preparing
↓
Ready
↓
Reviewed
using the Edit Film Review Request Action.
This closes the workflow:
discover
↓
compare
↓
identify what deserves investigation
↓
request film
↓
track operational state
Statistical discovery becomes a persistent operational workflow through FilmReviewRequest objects linked back to the relationship under investigation.
Validation and iteration
A few issues only became obvious once the application was used against live Ontology data.
Search relevance
The original Player search used fuzzy matching but alphabetically ordered the returned candidates.
That meant obvious searches could rank below weaker fuzzy matches.
The final ranking is:
exact full name
→ exact first / last name
→ prefix
→ substring
→ fuzzy fallback
Relationship-size filtering
The original 2 / 3 / 4 / 5-player interface relied primarily on asynchronous query filtering.
During transitions, cached results from the previous group size could briefly remain visible.
The final application enforces:
displayed group_quantity
=
selected group_quantity
at the render boundary and clears incompatible selections.
AIP usefulness
The first grounded Copilot was descriptive rather than investigative.
Testing showed that the LLM needed derived discovery signals, not simply more raw statistics.
The final design moved relationship standing, context variation, evidence-volume reasoning, and parallel-profile gaps into deterministic analytics while keeping AIP focused on investigation guidance.
That was the iteration that made the AIP layer useful rather than decorative.
Limitations
Season-aggregate grain
ObservedPlayerGroup currently represents season-level shared-court aggregates.
The system knows which relationship deserves investigation, but it does not yet resolve that relationship into the exact:
games
stints
possessions
timestamps
video clips
that produced the aggregate profile.
A Film Review Request therefore identifies what relationship should be investigated and the desired film scope, rather than automatically constructing the underlying clip package.
Single-season coverage
The current relationship universe covers only the 2025–26 season.
Every relationship profile, supporting context, and parallel comparison therefore reflects one season.
The application cannot yet test whether a relationship pattern persists across:
-
multiple seasons
-
different teammates
-
different teams
-
different coaching environments
-
changing roster structures
A relationship that looks distinctive in one season may be highly dependent on its surrounding context.
Multi-season coverage would make it possible to investigate:
Does this relationship pattern persist when the basketball environment changes?
Supporting-lineup coverage
Supporting five-man contexts represent the observed five-player groups available in the current extraction.
They should therefore be interpreted as available observed contexts containing the smaller relationship, not as a guarantee that every underlying lineup configuration is represented.
Extraction bounds
The 3-, 4-, and 5-player pulls each returned 7,500 rows, corresponding to 250 groups per team in this extraction.
The application therefore operates over the extracted relationship universe, rather than claiming complete coverage of every possible observed group.
Descriptive similarity
Parallel Relationships does not:
-
establish causal chemistry
-
predict future performance
-
project unseen lineups
-
adjust for opponent strength
-
prove player interchangeability
-
identify the underlying basketball mechanism
It is a transparent retrieval method for finding relationships worth comparing.
Client-side parallel serving
The same-size relationship universe is currently loaded into the browser before parallel retrieval.
That is workable at the current scale, but a production implementation would move retrieval to a server-side Function, transform, or precomputed nearest-neighbor layer.
Next iteration
The highest-value extension is to move one grain deeper.
A production version could introduce:
ObservedPlayerGroup
↓
GroupGameAppearance
↓
Game
↓
Shared stint / possession
↓
Timestamp
↓
Video clip
That would turn:
Review the best-performing games for this relationship
into a directly executable workflow:
relationship
→ relevant games
→ shared possessions
→ time-coded film
Multi-season coverage would add another important analytical dimension:
Does this relationship pattern persist when teammates, teams, and surrounding context change?
That would make it possible to study relationship persistence and portability rather than only single-season similarity.
A production version would also move parallel retrieval out of the browser while preserving the same transparent comparison methodology.
Final result
The core design idea is simple:
Make the observed player relationship the unit of investigation.
Once that relationship exists as a first-class Ontology object, the analyst can move naturally from:
player
→ relationship
→ larger lineup context
→ league-wide parallel
→ AIP-guided investigation
→ film review
The project deliberately favors inspectable relationship structure, transparent analytics, grounded AIP, and human-in-the-loop investigation over hypothetical fit scores or unsupported chemistry scores or hypothetical fit predictions.
Happy to go deeper on the Pipeline Builder relationship logic, Ontology model, OSDK traversal, parallel-profile methodology, AIP grounding, or Action workflow.





