Portal Class Rankings
> library("chan.portal")
## Ranking teams by net portal impact
Methodology
Each player's portal impact is a function of projected production and expected role:
impact = max(AdjBPMproj, 0) · (MPG / 40)  +  0.5 · (MPG / 40)
AdjBPMproj is the Bayesian next-season projection when available — a hierarchical model with age-dependent shrinkage and an RSCI-rank covariate, so young high-recruits don't regress to the position mean. When no projection exists (unresolved commitments, data gaps), the prior-season AdjBPM is used as a fallback. Players matched across seasons via a (name · prior-team) key with a single-candidate name-only fallback. Sub-replacement production floored at zero: a negative-AdjBPM bench piece isn't credited as a gain when signed, nor counted as a loss when departing. Replacement cost (0.5 · MPG/40): every minute a departing player soaks up must be filled by someone, so even losing a replacement-level body has a small positive cost. Mirrored on the arrival side so high-MPG additions get credit for the minutes they absorb. Net = Σ arrival impacts − Σ departure impacts. Teams are ranked on Net; toggle the buttons below for Gain-only, Loss-only, or raw Volume. Click any row for the full per-player breakdown. Known limitations: the score is absolute — it does not account for positional fit, roster redundancy, or destination-team context (a +5 player fills different holes at Duke vs. Rider). AdjBPM already adjusts for opponent strength but not teammate competition for usage, so transfers moving up or down the SOS ladder are not explicitly reweighted.
> portal %>% arrange(desc(net_bpm))
Rk Team Conf In Out Gain Loss Net Key Addition Key Loss