Skip to main content

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

  1. Add a column to schools.csv (e.g., jacob_schauer)
  2. Fill in names for each school
  3. SchoolRegistry auto-discovers it -- no code changes needed
  4. 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.):

  1. Update schools.csv (add/modify rows)
  2. Add massey/wbb/{year}.csv for new season
  3. Bump version in pyproject.toml
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

d3_schools-0.2.0.tar.gz (75.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

d3_schools-0.2.0-py3-none-any.whl (31.9 kB view details)

Uploaded Python 3

File details

Details for the file d3_schools-0.2.0.tar.gz.

File metadata

  • Download URL: d3_schools-0.2.0.tar.gz
  • Upload date:
  • Size: 75.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.2

File hashes

Hashes for d3_schools-0.2.0.tar.gz
Algorithm Hash digest
SHA256 06bf6584474f20f744f7a15dea76636d94baccf1795d5d376ef9cf76f1de3172
MD5 ba41108d7cf93c0a93a99d9b17d655f3
BLAKE2b-256 ac25158cda6d84cc961a648395d7bc061352605285c47a6bed707336396dca9d

See more details on using hashes here.

File details

Details for the file d3_schools-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: d3_schools-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 31.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.2

File hashes

Hashes for d3_schools-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad6b31a85823c0f3fa9f7db2387a22e5284424d55b9c6acaeec5a937820bd8bf
MD5 be4edafdbe0153b1e6e96260b4066697
BLAKE2b-256 191140ce9d9809e01d24f98ada90a7b6f2b5a314d489872b03ed6cca03af239a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page