AdminAnalytics/src/admin_analytics/dashboard/pages/staffing.py
2026-03-30 19:29:33 -04:00

97 lines
3.1 KiB
Python

"""Page 3: Staffing & Enrollment."""
import duckdb
from dash import html, dcc
import plotly.graph_objects as go
from admin_analytics.dashboard.queries import (
query_staff_composition,
query_student_staff_ratios,
query_growth_index,
)
_NO_DATA = html.Div(
"No IPEDS staff data loaded. Run: admin-analytics ingest ipeds",
style={"textAlign": "center", "padding": "40px", "color": "#888"},
)
def layout(conn: duckdb.DuckDBPyConnection):
staff_df = query_staff_composition(conn)
if staff_df.height == 0:
return _NO_DATA
staff_pd = staff_df.to_pandas()
# Staff composition stacked area
comp_fig = go.Figure()
for col, label, color in [
("faculty_total", "Faculty", "#00539F"),
("management_total", "Management", "#E07A5F"),
("other_staff", "Other Staff", "#7FB069"),
]:
comp_fig.add_trace(go.Scatter(
x=staff_pd["year"], y=staff_pd[col],
mode="lines", name=label,
stackgroup="one",
line={"color": color},
))
comp_fig.update_layout(
title="Staff Composition Over Time",
xaxis_title="Year", yaxis_title="Headcount",
template="plotly_white", height=420,
)
# Student-to-staff ratios
ratio_df = query_student_staff_ratios(conn)
ratio_fig = go.Figure()
if ratio_df.height > 0:
ratio_pd = ratio_df.to_pandas()
ratio_fig.add_trace(go.Scatter(
x=ratio_pd["year"], y=ratio_pd["students_per_staff"],
mode="lines+markers", name="Students per Staff",
line={"color": "#00539F"},
))
ratio_fig.add_trace(go.Scatter(
x=ratio_pd["year"], y=ratio_pd["students_per_faculty"],
mode="lines+markers", name="Students per Faculty",
line={"color": "#FFD200"},
))
ratio_fig.update_layout(
title="Student-to-Staff Ratios",
xaxis_title="Year", yaxis_title="Ratio",
template="plotly_white", height=380,
)
# Growth index
growth_df = query_growth_index(conn)
growth_fig = go.Figure()
if growth_df.height > 0:
growth_pd = growth_df.to_pandas()
growth_fig.add_trace(go.Scatter(
x=growth_pd["year"], y=growth_pd["mgmt_index"],
mode="lines+markers", name="Management Growth",
line={"color": "#E07A5F"},
))
growth_fig.add_trace(go.Scatter(
x=growth_pd["year"], y=growth_pd["enrollment_index"],
mode="lines+markers", name="Enrollment Growth",
line={"color": "#00539F"},
))
growth_fig.add_hline(y=100, line_dash="dot", line_color="#ccc")
growth_fig.update_layout(
title="Management vs Enrollment Growth (Indexed, Base Year = 100)",
xaxis_title="Year", yaxis_title="Index",
template="plotly_white", height=380,
)
return html.Div([
dcc.Graph(figure=comp_fig),
html.Div(
[
html.Div(dcc.Graph(figure=ratio_fig), style={"flex": "1"}),
html.Div(dcc.Graph(figure=growth_fig), style={"flex": "1"}),
],
style={"display": "flex", "gap": "16px"},
),
])