"""Tests for EADA athletics download, loader, and dashboard queries.""" import io import zipfile import httpx import pandas as pd import respx import admin_analytics.config as config from admin_analytics.config import UD_UNITID from admin_analytics.dashboard.queries import ( query_athletics_comp, query_athletics_expenses, query_athletics_vs_university, ) from admin_analytics.eada.download import download_all from admin_analytics.eada.loader import load_eada OTHER_UNITID = 999999 def _write_year(year_dir, inst_name="instLevel.xlsx", school_name="Schools.xlsx", lower=False): """Write minimal EADA institution and per-sport spreadsheets.""" year_dir.mkdir(parents=True, exist_ok=True) inst = pd.DataFrame({ "unitid": [UD_UNITID, OTHER_UNITID], "classification_name": ["NCAA Division I-FCS", "NCAA Division II"], "EFTotalCount": [18000, 5000], "STUDENTAID_TOTAL": [15_000_000, 1_000_000], "HDCOACH_SALARY_MEN": [240_000.0, 80_000.0], "UNDUP_CT_PARTIC_MEN": [300, 100], "UNDUP_CT_PARTIC_WOMEN": [320, 110], "TOT_EXPENSE_ALL_NOTALLOC": [14_000_000, 2_000_000], "GRND_TOTAL_REVENUE": [48_000_000, 9_000_000], "GRND_TOTAL_EXPENSE": [48_000_000, 9_000_000], }) if lower: inst.columns = [c.lower() for c in inst.columns] inst.to_excel(year_dir / inst_name, index=False) pd.DataFrame({ "unitid": [UD_UNITID, UD_UNITID, UD_UNITID, OTHER_UNITID], "SUM_PARTIC_MEN": [110, 15, 30, 50], "SUM_PARTIC_WOMEN": [None, 16, 28, 40], "TOTAL_REVENUE_ALL": [9_000_000, 6_000_000, 2_000_000, 1_000_000], "TOTAL_EXPENSE_ALL": [9_000_000, 6_000_000, 2_000_000, 1_000_000], "Sports": ["Football", "Basketball", "Soccer", "Football"], }).to_excel(year_dir / school_name, index=False) class TestLoader: def test_loads_ud_only(self, db_conn, tmp_path, monkeypatch): monkeypatch.setattr(config, "EADA_DATA_DIR", tmp_path) _write_year(tmp_path / "2024") totals = load_eada(db_conn, range(2024, 2025)) assert totals == {"institution": 1, "sport": 3} row = db_conn.execute( "SELECT total_expenses, unallocated_expenses, participants_men, " "ft_undergrad_enrollment, classification FROM raw_eada_institution" ).fetchone() assert row == (48_000_000, 14_000_000, 300, 18000, "NCAA Division I-FCS") def test_resolves_column_and_filename_variants(self, db_conn, tmp_path, monkeypatch): monkeypatch.setattr(config, "EADA_DATA_DIR", tmp_path) _write_year(tmp_path / "2010", inst_name="instLevelData.xlsx", school_name="schools.xlsx", lower=True) totals = load_eada(db_conn, range(2010, 2011)) assert totals["institution"] == 1 assert db_conn.execute( "SELECT total_expenses FROM raw_eada_institution WHERE year = 2010" ).fetchone()[0] == 48_000_000 def test_reload_replaces_year(self, db_conn, tmp_path, monkeypatch): monkeypatch.setattr(config, "EADA_DATA_DIR", tmp_path) _write_year(tmp_path / "2024") load_eada(db_conn, range(2024, 2025)) load_eada(db_conn, range(2024, 2025)) assert db_conn.execute("SELECT COUNT(*) FROM raw_eada_sport").fetchone()[0] == 3 def test_missing_year_skipped(self, db_conn, tmp_path, monkeypatch): monkeypatch.setattr(config, "EADA_DATA_DIR", tmp_path) assert load_eada(db_conn, range(2024, 2025)) == {"institution": 0, "sport": 0} class TestDownload: @respx.mock def test_downloads_spreadsheets_only(self, tmp_path, monkeypatch): monkeypatch.setattr(config, "EADA_DATA_DIR", tmp_path) respx.get(f"{config.EADA_API_URL}/fileList").mock(return_value=httpx.Response(200, json=[ {"Year": 2024, "FileName": "EADA_2023-2024.zip"}, {"Year": 2024, "FileName": "EADA_All_Data_Combined_2023-2024_SAS_SPSS_EXCEL.zip"}, ])) buf = io.BytesIO() with zipfile.ZipFile(buf, "w") as zf: zf.writestr("instLevel.xlsx", b"x") zf.writestr("schools.sav", b"x") route = respx.get(f"{config.EADA_API_URL}/file").mock( return_value=httpx.Response(200, content=buf.getvalue()) ) download_all(range(2024, 2025)) assert route.calls.last.request.url.params["fileName"] == "EADA_2023-2024.zip" assert sorted(p.name for p in (tmp_path / "2024").iterdir()) == ["instLevel.xlsx"] def _seed(conn): for year, total, unalloc in [(2023, 40_000_000, 10_000_000), (2024, 50_000_000, 20_000_000)]: conn.execute( "INSERT INTO raw_eada_institution (unitid, year, total_expenses, unallocated_expenses, " "participants_men, participants_women, ft_undergrad_enrollment) VALUES (?, ?, ?, ?, 300, 300, 20000)", [UD_UNITID, year, total, unalloc], ) for sport, amount in [("Football", 10_000_000), ("Basketball", 5_000_000), ("Soccer", 3_000_000)]: conn.execute( "INSERT INTO raw_eada_sport (unitid, year, sport, total_expenses) VALUES (?, ?, ?, ?)", [UD_UNITID, year, sport, amount], ) conn.execute( "INSERT INTO raw_ipeds_finance (unitid, year, total_expenses, instruction_expenses, " "institutional_support_expenses) VALUES (?, 2023, 1000000000, 400000000, 100000000)", [UD_UNITID], ) for year, value in [(2023, 300.0), (2024, 310.0)]: conn.execute( "INSERT INTO raw_cpi_u (year, month, value, series_id) VALUES (?, 1, ?, 'CUUR0000SA0')", [year, value], ) class TestQueries: def test_expenses_split_and_per_student(self, db_conn): _seed(db_conn) df = query_athletics_expenses(db_conn) assert df["year"].to_list() == [2023, 2024] latest = df.row(1, named=True) assert latest["football"] == 10_000_000 assert latest["other_sports"] == 3_000_000 assert latest["per_student"] == 2500 # Latest year is the CPI base, so adjusted equals nominal assert latest["total_expenses_cpi"] == 50_000_000 def test_vs_university_only_overlapping_years(self, db_conn): _seed(db_conn) df = query_athletics_vs_university(db_conn) assert df["year"].to_list() == [2023] assert df["athletics_pct"][0] == 4.0 assert df["athletics_index"][0] == 100.0 assert query_athletics_vs_university(db_conn, since=2024).height == 0 def test_comp_matches_abbreviated_titles(self, db_conn): for year, name, title, comp in [ (2022, "A", "DIR, INTERCOLLEGIATE ATHLC & REC SR", 700_000), (2023, "A", "DIRECTOR, INTERCOLLEGIATE ATHELTICS & RECREATION", 710_000), (2023, "B", "HEAD FOOTBALL COACH", 680_000), (2023, "C", "HEAD FOOTBALL COACH TO 1/1/23", 500_000), (2023, "D", "FORMER ATHLETIC DIRECTOR", 400_000), (2023, "E", "PROVOST", 800_000), ]: db_conn.execute( "INSERT INTO raw_990_schedule_j (tax_year, person_name, title, total_compensation) " "VALUES (?, ?, ?, ?)", [year, name, title, comp], ) df = query_athletics_comp(db_conn) assert set(zip(df["tax_year"], df["role"], df["person_name"])) == { (2022, "Athletic Director", "A"), (2023, "Athletic Director", "A"), (2023, "Head Football Coach", "B"), } def test_empty(self, db_conn): assert query_athletics_expenses(db_conn).height == 0 assert query_athletics_comp(db_conn).height == 0