Canonical D3 school identity clearinghouse — one lookup for every name variant
Project description
d3-schools
Canonical D3 school identity clearinghouse. Pass in any name variant, get back whatever format you need.
Team identity data for D3 athletics is fragmented across Massey Ratings, D3hoops.com, NCAA stats, and Snyder efficiency ratings -- each source uses its own names for the same school. This package bundles canonical data for all ~440 NCAA Division III schools and provides O(1) lookups between any naming convention.
Installation
pip install d3-schools
From source
git clone https://github.com/scottpeterson/d3-schools.git
cd d3-schools
pip install -e .
Requirements
- Python >= 3.9
- No runtime dependencies (stdlib only)
Quick Start
from d3_schools import SchoolRegistry
registry = SchoolRegistry()
# Find a school by any name from any source
school = registry.find("Hope")
school.display_name # "Hope"
school.conference # "MIAA"
school.region # 7
school.is_active # True
# Translate between naming formats
registry.resolve("Anderson_IN", output="display") # "Anderson"
registry.resolve("Hope", output="ncaa_id") # "286"
registry.resolve("wash. u.", output="display") # "WashU"
# Massey ID bridge (sport-scoped -- WBB bundled)
registry.get_by_massey_id("148", year="2026") # School(ncaa_id='286', name='Hope')
Data Model
School (frozen dataclass)
| Field | Type | Description |
|---|---|---|
ncaa_id |
str |
Stable NCAA ID (primary key, persists across years) |
variants |
Dict[str, str] |
Open namespace of name variants (see below) |
conference |
str |
Conference abbreviation (e.g., "MIAA"). Empty if no longer D3. |
region |
int |
NCAA region (1--10). 0 if no longer D3. |
gender |
str |
"Coed", "Women", or "Men" |
Properties:
| Property | Type | Description |
|---|---|---|
display_name |
str |
Human-readable name. Fallback: display -> massey cleanup -> "School {ncaa_id}" |
name |
str |
Alias for display_name |
is_active |
bool |
True if region > 0 (currently in D3) |
Methods:
| Method | Returns | Description |
|---|---|---|
get(variant) |
Optional[str] |
Get a specific name variant, or None |
Bundled Variant Types
| Variant Key | Source | Example (Hope College) |
|---|---|---|
display |
Scott Peterson Display format | "Hope" |
massey |
Massey Ratings format | "Hope" |
d3hoops |
D3hoops.com format | "Hope" |
stats_ncaa |
stats.NCAA.org format | "Hope" |
ncaa_manual |
NCAA Championship Manual (full official) | "Hope College" |
snyder |
Matt Snyder Display format | "Hope" |
The variant namespace is open -- adding a column to schools.csv creates a new variant automatically (zero code changes).
Region (IntEnum)
from d3_schools import Region
Region.REGION_1 # 1
Region.REGION_10 # 10
CONFERENCES (frozenset)
from d3_schools import CONFERENCES
"MIAA" in CONFERENCES # True
All ~42 known D3 conferences as of the 2025--26 season.
Inactive Schools
Schools with region=0 and empty conference are no longer NCAA D3 members (e.g., Ferrum, Birmingham-Southern, Cazenovia). They remain in the registry for historical lookups.
school = registry.find("Ferrum")
school.is_active # False
school.region # 0
school.conference # ""
API Reference
SchoolRegistry
Constructor
SchoolRegistry(data_dir: Optional[Path] = None)
Loads bundled CSV data and builds O(1) indexes. Pass data_dir to use custom data files instead of the bundled defaults.
Core Lookups
registry.get(ncaa_id: str) -> Optional[School]
Look up a school by NCAA ID (primary key).
registry.get_by_massey_id(massey_id: str, year: str) -> Optional[School]
Look up a school by Massey ID for a given year. Massey IDs are sport+gender specific (WBB bundled). Accepts both "2026" and "wbb:2026" as the year key.
Find (any name in -> School out)
registry.find(name: str, source: str = "") -> Optional[School]
Find a school by any name variant. Returns None if the name is ambiguous (matches multiple schools) or not found. Pass source to restrict the search to a single variant namespace.
registry.find("Hope") # -> School
registry.find("Trinity (TX)") # -> School
registry.find("Trinity (Texas)", source="d3hoops") # -> School (scoped)
registry.find_all(name: str) -> List[School]
Find all schools matching a name variant. Useful for ambiguous names like "Trinity".
registry.find_all("Trinity (CT)") # -> [School(...)]
Resolve (any name in -> desired format out)
registry.resolve(name: str, output: str = "display", source: str = "") -> Optional[str]
Resolve any name variant to a desired output format. Combines find() + get() in one call.
| Parameter | Description |
|---|---|
name |
Input name (any variant from any source) |
output |
Desired output format: "display", "d3hoops", "snyder", "ncaa_id", "ncaa_manual", etc. |
source |
Optional: restrict input matching to one variant namespace |
registry.resolve("Anderson_IN", output="display") # "Anderson"
registry.resolve("Hope", output="ncaa_id") # "286"
registry.resolve("Hope", output="ncaa_manual") # "Hope College"
registry.resolve("wash. u.", output="display") # "WashU"
registry.resolve("Trinity (Texas)", source="d3hoops",
output="ncaa_id") # "715"
Massey ID Bridge
Massey IDs are year-specific and sport+gender-specific. The bundled data includes WBB (Women's Basketball) mappings for 2024, 2025, and 2026.
registry.massey_id_to_ncaa_id(massey_id: str, year: str) -> Optional[str]
registry.ncaa_id_to_massey_id(ncaa_id: str, year: str) -> Optional[str]
registry.massey_id_to_ncaa_id("148", year="2026") # "286"
registry.ncaa_id_to_massey_id("286", year="2026") # "148"
Both "2026" and "wbb:2026" are accepted as year keys.
Backward-Compatible Dictionaries
For migrating existing code that expects {id: name} or {name: id} dicts:
registry.id_to_name(id_type: str = "massey", year: str = "", variant: str = "display") -> Dict[str, str]
registry.name_to_id(id_type: str = "massey", year: str = "", variant: str = "display") -> Dict[str, str]
id_type |
Requires year |
Result |
|---|---|---|
"massey" |
Yes | {"148": "Hope", "1": "Adrian", ...} |
"ncaa" |
No | {"286": "Hope", "1": "Adrian", ...} |
id_map = registry.id_to_name(id_type="massey", year="2026")
id_map["148"] # "Hope"
name_map = registry.name_to_id(id_type="massey", year="2026")
name_map["Hope"] # "148"
Metadata Queries
registry.all_schools() -> List[School]
registry.schools_in_conference(conference: str) -> List[School]
registry.variant_types -> List[str] # ["display", "massey", "d3hoops", ...]
len(registry) # 440
"286" in registry # True
registry["286"] # School(ncaa_id='286', name='Hope')
Overlay (custom/private variants)
Add custom name variants without modifying the bundled data:
registry.load_overlay(path: str) -> None
The overlay CSV needs an ncaa_id column plus any custom columns:
ncaa_id,nickname,broadcast_name
286,Flying Dutch,Hope (MI)
1,Bulldogs,Adrian (MI)
registry.load_overlay("my_custom_names.csv")
registry.find("Flying Dutch") # -> School(ncaa_id='286')
registry.resolve("Hope", output="nickname") # "Flying Dutch"
Exceptions
| Exception | Raised When |
|---|---|
SchoolNotFoundError |
registry["999"] -- NCAA ID not found |
AmbiguousMatchError |
(Available for consumer use; find() returns None instead) |
DataLoadingError |
Bundled CSV files are missing or malformed |
All inherit from D3SchoolsError.
from d3_schools import SchoolNotFoundError
try:
school = registry["999"]
except SchoolNotFoundError as e:
print(e.query) # "999"
Bundled Data
d3_schools/data/
schools.csv # 440 schools, 6 variant columns + metadata
aliases.csv # 144 many-to-one aliases (d3hoops + shortcodes)
massey/
wbb/ # Women's Basketball (sport+gender scoped)
2024.csv # 423 Massey ID -> NCAA ID mappings
2025.csv # 417 mappings
2026.csv # 413 mappings
schools.csv format
ncaa_id,display,massey,d3hoops,stats_ncaa,ncaa_manual,snyder,conference,region,gender
286,Hope,Hope,Hope,Hope,Hope College,Hope,MIAA,7,Coed
Reserved metadata columns (ncaa_id, conference, region, gender) are not treated as name variants. Everything else is auto-discovered as a variant type from the CSV headers at load time.
aliases.csv format
alias_value,variant_type,ncaa_id
wash. u.,d3hoops,379
trin,shortcode,716
massey/wbb/{year}.csv format
massey_id,ncaa_id
148,286
1,1
Adding New Variant Types
- Add a column to
schools.csv(e.g.,jacob_schauer) - Fill in names for each school
SchoolRegistryauto-discovers it -- no code changes needed- All API methods work with it immediately:
registry.resolve("Hope", output="jacob_schauer")
registry.find("Andy", source="jacob_schauer")
Data Update Workflow
When school data changes (new season, conference realignment, etc.):
- Update
schools.csv(add/modify rows) - Add
massey/wbb/{year}.csvfor new season - Bump version in
pyproject.toml pip install --upgrade d3-schools
Regenerating data from d3-bball-npi sources
python scripts/generate_schools_csv.py /path/to/d3-bball-npi
python scripts/generate_aliases_csv.py
python scripts/generate_massey_year.py /path/to/d3-bball-npi
python scripts/validate_data.py
Development
git clone https://github.com/scottpeterson/d3-schools.git
cd d3-schools
pip install -e ".[dev]"
pytest tests/ -v # run tests (78 tests)
Running Tests
pytest tests/ -v # all tests
pytest tests/test_registry.py -v # registry tests only
pytest tests/ --cov=d3_schools # with coverage
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file d3_schools-0.3.0.tar.gz.
File metadata
- Download URL: d3_schools-0.3.0.tar.gz
- Upload date:
- Size: 75.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dea41db95c70ae01dbb1410f218ef438262057cfe3cd0ea3d606717dafef2469
|
|
| MD5 |
49afcecf53b7fa0d1894af4a5249cde4
|
|
| BLAKE2b-256 |
6bf4d809dd26b6e6a1fcb532d81d09557fbc8bd4786cafe1a01407ab5be17231
|
File details
Details for the file d3_schools-0.3.0-py3-none-any.whl.
File metadata
- Download URL: d3_schools-0.3.0-py3-none-any.whl
- Upload date:
- Size: 31.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf8879decd58e9528cc878c7b36ae2d7f60377da5e2c59dad550339a306e6d63
|
|
| MD5 |
9ebedd0fb60810a3427f598cc40364c0
|
|
| BLAKE2b-256 |
145c66726aec936266fff815b5c72d0495c163111c56ba654d434867522007ae
|