168 lines
5.7 KiB
Python
168 lines
5.7 KiB
Python
"""Tests for dashboard query functions."""
|
|
|
|
from admin_analytics.config import UD_UNITID
|
|
from admin_analytics.dashboard.queries import (
|
|
query_admin_cost_ratio,
|
|
query_expense_breakdown,
|
|
query_admin_per_student,
|
|
query_admin_faculty_ratio,
|
|
query_top_earners,
|
|
query_comp_by_role,
|
|
query_comp_vs_cpi,
|
|
query_staff_composition,
|
|
query_student_staff_ratios,
|
|
query_growth_index,
|
|
query_admin_headcount,
|
|
query_headcount_summary,
|
|
)
|
|
|
|
|
|
def _seed_ipeds(conn):
|
|
"""Insert minimal IPEDS data for 2 years."""
|
|
for year, inst_support, total in [(2020, 100_000, 1_000_000), (2021, 120_000, 1_100_000)]:
|
|
conn.execute(
|
|
"INSERT INTO raw_ipeds_finance (unitid, year, institutional_support_expenses, total_expenses) VALUES (?, ?, ?, ?)",
|
|
[UD_UNITID, year, inst_support, total],
|
|
)
|
|
for year, enrollment in [(2020, 20000), (2021, 21000)]:
|
|
conn.execute(
|
|
"INSERT INTO raw_ipeds_enrollment (unitid, year, total_enrollment) VALUES (?, ?, ?)",
|
|
[UD_UNITID, year, enrollment],
|
|
)
|
|
for year, total, faculty, mgmt in [(2020, 3000, 1500, 500), (2021, 3100, 1550, 520)]:
|
|
conn.execute(
|
|
"INSERT INTO raw_ipeds_staff (unitid, year, total_staff, faculty_total, management_total) VALUES (?, ?, ?, ?, ?)",
|
|
[UD_UNITID, year, total, faculty, mgmt],
|
|
)
|
|
|
|
|
|
def _seed_cpi(conn):
|
|
"""Insert CPI data for 2020-2021."""
|
|
for year, value in [(2020, 258.8), (2021, 270.9)]:
|
|
for month in range(1, 13):
|
|
conn.execute(
|
|
"INSERT INTO raw_cpi_u (year, month, value, series_id) VALUES (?, ?, ?, ?)",
|
|
[year, month, value, "CUUR0000SA0"],
|
|
)
|
|
|
|
|
|
def _seed_990(conn):
|
|
"""Insert minimal 990 filing and Schedule J data."""
|
|
conn.execute(
|
|
"INSERT INTO raw_990_filing (object_id, ein, tax_year, organization_name, total_revenue, total_expenses) "
|
|
"VALUES ('obj1', '516000297', 2021, 'UD Foundation', 50000000, 40000000)"
|
|
)
|
|
conn.execute(
|
|
"INSERT INTO raw_990_schedule_j (object_id, ein, tax_year, person_name, title, "
|
|
"base_compensation, bonus_compensation, other_compensation, deferred_compensation, "
|
|
"nontaxable_benefits, total_compensation) "
|
|
"VALUES ('obj1', '516000297', 2021, 'JOHN DOE', 'PRESIDENT', "
|
|
"500000, 100000, 10000, 20000, 15000, 645000)"
|
|
)
|
|
conn.execute(
|
|
"INSERT INTO raw_990_schedule_j (object_id, ein, tax_year, person_name, title, "
|
|
"base_compensation, bonus_compensation, other_compensation, deferred_compensation, "
|
|
"nontaxable_benefits, total_compensation) "
|
|
"VALUES ('obj1', '516000297', 2021, 'JANE SMITH', 'PROVOST', "
|
|
"400000, 50000, 5000, 15000, 10000, 480000)"
|
|
)
|
|
|
|
|
|
class TestEmptyDatabase:
|
|
def test_admin_cost_ratio_empty(self, db_conn):
|
|
df = query_admin_cost_ratio(db_conn)
|
|
assert df.height == 0
|
|
|
|
def test_top_earners_empty(self, db_conn):
|
|
df = query_top_earners(db_conn)
|
|
assert df.height == 0
|
|
|
|
def test_headcount_empty(self, db_conn):
|
|
df = query_admin_headcount(db_conn)
|
|
assert df.height == 0
|
|
|
|
|
|
class TestAdminCostRatio:
|
|
def test_returns_correct_ratio(self, db_conn):
|
|
_seed_ipeds(db_conn)
|
|
_seed_cpi(db_conn)
|
|
df = query_admin_cost_ratio(db_conn)
|
|
assert df.height == 2
|
|
# 2020: 100000 / 1000000 = 10%
|
|
row_2020 = df.filter(df["year"] == 2020)
|
|
assert row_2020["admin_cost_pct"][0] == 10.0
|
|
|
|
|
|
class TestAdminPerStudent:
|
|
def test_returns_per_student(self, db_conn):
|
|
_seed_ipeds(db_conn)
|
|
_seed_cpi(db_conn)
|
|
df = query_admin_per_student(db_conn)
|
|
assert df.height == 2
|
|
# 2020: 100000 / 20000 = 5
|
|
row_2020 = df.filter(df["year"] == 2020)
|
|
assert row_2020["admin_per_student"][0] == 5.0
|
|
|
|
|
|
class TestAdminFacultyRatio:
|
|
def test_returns_ratio(self, db_conn):
|
|
_seed_ipeds(db_conn)
|
|
df = query_admin_faculty_ratio(db_conn)
|
|
assert df.height == 2
|
|
# 2020: 500 / 1500 = 0.333
|
|
row_2020 = df.filter(df["year"] == 2020)
|
|
assert row_2020["admin_faculty_ratio"][0] == 0.333
|
|
|
|
|
|
class TestTopEarners:
|
|
def test_returns_all(self, db_conn):
|
|
_seed_990(db_conn)
|
|
df = query_top_earners(db_conn)
|
|
assert df.height == 2
|
|
assert "canonical_role" in df.columns
|
|
|
|
def test_filter_by_year(self, db_conn):
|
|
_seed_990(db_conn)
|
|
df = query_top_earners(db_conn, year=2021)
|
|
assert df.height == 2
|
|
df_empty = query_top_earners(db_conn, year=2019)
|
|
assert df_empty.height == 0
|
|
|
|
|
|
class TestCompByRole:
|
|
def test_groups_by_role(self, db_conn):
|
|
_seed_990(db_conn)
|
|
df = query_comp_by_role(db_conn)
|
|
roles = df["canonical_role"].to_list()
|
|
assert "PRESIDENT" in roles
|
|
assert "PROVOST" in roles
|
|
|
|
|
|
class TestCompVsCpi:
|
|
def test_returns_indexed(self, db_conn):
|
|
_seed_990(db_conn)
|
|
_seed_cpi(db_conn)
|
|
df = query_comp_vs_cpi(db_conn)
|
|
assert df.height > 0
|
|
assert "comp_index" in df.columns
|
|
assert "cpi_index" in df.columns
|
|
|
|
|
|
class TestStaffComposition:
|
|
def test_computes_other(self, db_conn):
|
|
_seed_ipeds(db_conn)
|
|
df = query_staff_composition(db_conn)
|
|
assert df.height == 2
|
|
# 2020: 3000 - 1500 - 500 = 1000
|
|
row = df.filter(df["year"] == 2020)
|
|
assert row["other_staff"][0] == 1000
|
|
|
|
|
|
class TestGrowthIndex:
|
|
def test_base_year_100(self, db_conn):
|
|
_seed_ipeds(db_conn)
|
|
df = query_growth_index(db_conn)
|
|
assert df.height == 2
|
|
first = df.filter(df["year"] == 2020)
|
|
assert first["mgmt_index"][0] == 100.0
|
|
assert first["enrollment_index"][0] == 100.0
|