AdminAnalytics/README.md
emfurst 30b89e2fc4 Correct IRS 990 filer description: UD files its own 990
The docs said the UD Foundation was the 990 source. The configured EINs
are UD itself (all Schedule J rows) and the UD Research Foundation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9jG4CUFcH6n4vFvgtwndY
2026-10-17 12:20:45 -04:00

135 lines
6.4 KiB
Markdown

# Admin Analytics
University of Delaware administrative cost benchmarking using public data (IRS 990, IPEDS, EADA, BLS CPI-U). Ingests data into a local DuckDB database and serves an interactive Dash dashboard for analysis.
## Scope
This project is currently scoped to the **University of Delaware** as a single institution. It tracks:
- **Executive compensation** from IRS 990 Schedule J filings by the University of Delaware (EIN 516000297) and UD Research Foundation (EIN 516017306)
- **Administrative cost ratios** from IPEDS finance surveys (expenses by function, staffing levels, enrollment)
- **Endowment performance** and **philanthropic giving** from IPEDS F2 (FASB) financial data
- **Intercollegiate athletics** spending from Equity in Athletics Disclosure Act (EADA) filings, set against university-wide spending and athletics leadership compensation
- **Administrative headcount** via web scraping, currently focused on the **College of Engineering line management** (COE Central, department offices) and the Provost's Office
### Changing the target institution
The institution scope is controlled by constants in `src/admin_analytics/config.py`:
- `UD_UNITID = 130943` -- IPEDS institution identifier. Change this to target a different institution. Look up UNITIDs at the [IPEDS Data Center](https://nces.ed.gov/ipeds/use-the-data).
- `UD_EINS = [516000297, 516017306]` -- IRS Employer Identification Numbers for 990 filings. Update these to the EINs of the target institution's nonprofit entities.
All IPEDS and EADA loaders accept a `unitid_filter` parameter (EADA is keyed by the same UNITID). The scraper URLs in `src/admin_analytics/scraper/directory.py` are UD-specific and would need to be updated for a different institution.
Multi-institution comparisons (AAU peers, Carnegie peers) are planned for a future phase.
## Prerequisites
- Python 3.11+
- [uv](https://docs.astral.sh/uv/) package manager
- Playwright browsers (only needed for the `scrape` command)
## Setup
```bash
# Clone and install
git clone <repo-url>
cd AdminAnalytics
uv sync
# Install Playwright browsers (optional, only for scraping)
uv run playwright install chromium
```
## Ingesting Data
Load data from public sources into the local DuckDB database (`data/admin_analytics.duckdb`).
```bash
# Ingest everything (IPEDS + IRS 990 + CPI + EADA + scraper)
uv run admin-analytics ingest all
# Or ingest individual sources
uv run admin-analytics ingest ipeds --year-range 2005-2024
uv run admin-analytics ingest irs990 --year-range 2019-2025
uv run admin-analytics ingest cpi
uv run admin-analytics ingest eada --year-range 2007-2025
uv run admin-analytics ingest scrape
```
Use `--force` on any command to re-download files that already exist locally.
Downloaded files are stored in `data/raw/` (gitignored).
## Launching the Dashboard
```bash
uv run admin-analytics dashboard
```
Opens at [http://localhost:8050](http://localhost:8050). Use `--port` to change the port, or `--host 0.0.0.0` for network access (e.g. over Tailscale).
For a production server, run it under gunicorn instead of Dash's development server:
```bash
uv run gunicorn -w 2 -b 127.0.0.1:8050 admin_analytics.dashboard.wsgi:server
```
Each worker opens its own read-only DuckDB connection. Don't use `--preload`, which would share one connection across forked workers.
The dashboard must be restarted to pick up newly ingested data (DuckDB opens in read-only mode to avoid lock conflicts).
The dashboard has eight tabs:
- **Executive Compensation** -- top earners from IRS 990 Schedule J, President and top-10 CAGR, trends by role, compensation breakdown by component, growth vs CPI-U (tax years 2015-2024)
- **Admin Cost Overview** -- admin cost ratios, expense breakdown by function, cost per student, admin-to-faculty ratio (IPEDS data, 2005-2024)
- **Staffing & Enrollment** -- staff composition, student-to-staff ratios, management vs faculty vs enrollment growth (indexed)
- **Endowment** -- endowment value trends, CAGR, investment return rate, CIO compensation vs endowment growth (IPEDS F2)
- **Philanthropy** -- total private gifts and grants, gift allocation, President and VP Development compensation growth vs fundraising (IPEDS F2 and IRS 990)
- **Athletics** -- athletics expenses by football, basketball, other sports, and spending not allocated by sport (FY2007-2025, FBS transition marked); growth vs university and instruction spending; cost per full-time undergraduate; share of university expenses; head coach and athletic director compensation (EADA, IPEDS, IRS 990)
- **Current Headcount** -- scraped UD staff directory data with overhead/non-overhead classification by unit
- **About** -- data sources, methodology, and limitations
## Validating Data
Check row counts, NULL rates, year coverage, and cross-source consistency:
```bash
uv run admin-analytics validate
```
## Running Tests
```bash
uv sync --group dev
uv run pytest
```
## Project Structure
```
src/admin_analytics/
cli.py # CLI entry point (typer)
config.py # Constants (UD identifiers, URLs, paths)
db/ # DuckDB schema and connection
ipeds/ # IPEDS download, parsing, loading
irs990/ # IRS 990 XML download, parsing, title normalization
bls/ # BLS CPI-U download and loading
eada/ # EADA athletics download and loading
scraper/ # UD staff directory scraper and classifier
dashboard/ # Dash app, queries, page layouts
validation.py # Data validation queries
data/raw/ # Downloaded files (gitignored)
docs/data_dictionary.md # Schema documentation
tests/ # pytest test suite
```
## Data Sources
| Source | What it provides | Years |
|--------|-----------------|-------|
| [IPEDS](https://nces.ed.gov/ipeds/) | Institutional directory, expenses by function, staffing, enrollment | 2005-2024 |
| [IRS 990 e-file](https://www.irs.gov/charities-non-profits/form-990-series-downloads) | Filings by the University of Delaware and UD Research Foundation; executive compensation (Schedule J, from UD's own filings) | Tax years 2015-2025 |
| [EADA](https://ope.ed.gov/athletics/) | Athletics expenses by sport, participants, coaching salaries, full-time undergraduate enrollment | FY2007-2025 (academic years 2006-07 to 2024-25) |
| [BLS CPI-U](https://www.bls.gov/cpi/) | Consumer Price Index for inflation adjustment | Full history |
| UD staff directories | Admin office headcounts and overhead classification | Current snapshot |