Compensation, endowmnet tweaks. Added About.

This commit is contained in:
emfurst 2026-03-31 08:03:58 -04:00
commit 13fb4b8418
13 changed files with 914 additions and 17 deletions

View file

@ -10,6 +10,9 @@ from admin_analytics.dashboard.queries import (
query_top_earners,
query_comp_by_role,
query_comp_vs_cpi,
query_comp_cagr,
query_aggregate_comp,
query_aggregate_comp_cagr,
)
_NO_DATA = html.Div(
@ -21,6 +24,24 @@ _NO_DATA = html.Div(
_KEY_ROLES = ["PRESIDENT", "PROVOST", "VP_FINANCE", "VP_RESEARCH", "VP_ADVANCEMENT", "CFO"]
def _kpi_card(title: str, value: str, subtitle: str = "") -> html.Div:
return html.Div(
[
html.H4(title, style={"margin": "0", "color": "#666", "fontSize": "14px"}),
html.H2(value, style={"margin": "5px 0", "color": "#00539F"}),
html.P(subtitle, style={"margin": "0", "color": "#999", "fontSize": "12px"}),
],
style={
"flex": "1",
"padding": "20px",
"backgroundColor": "#f8f9fa",
"borderRadius": "8px",
"textAlign": "center",
"margin": "0 8px",
},
)
def layout(conn: duckdb.DuckDBPyConnection):
all_earners = query_top_earners(conn)
if all_earners.height == 0:
@ -31,6 +52,34 @@ def layout(conn: duckdb.DuckDBPyConnection):
{"label": str(y), "value": y} for y in years
]
# KPI cards
cagr = query_comp_cagr(conn)
agg_cagr = query_aggregate_comp_cagr(conn)
kpi_cards = []
if cagr:
kpi_cards.append(_kpi_card(
"President Compensation",
f"${cagr['end_comp']:,}",
f"Tax year {cagr['end_year']}",
))
kpi_cards.append(_kpi_card(
"President CAGR",
f"{cagr['cagr_pct']}%",
f"Annualized growth, {cagr['start_year']}-{cagr['end_year']}",
))
if agg_cagr:
kpi_cards.append(_kpi_card(
"Top-10 Total Compensation",
f"${agg_cagr['end_comp']:,}",
f"Tax year {agg_cagr['end_year']}",
))
kpi_cards.append(_kpi_card(
"Top-10 CAGR",
f"{agg_cagr['cagr_pct']}%",
f"Annualized growth, {agg_cagr['start_year']}-{agg_cagr['end_year']}",
))
kpi_row = html.Div(kpi_cards, style={"display": "flex", "marginBottom": "24px"}) if kpi_cards else html.Div()
# Compensation by role trend
role_df = query_comp_by_role(conn)
role_fig = go.Figure()
@ -61,18 +110,24 @@ def layout(conn: duckdb.DuckDBPyConnection):
mode="lines+markers", name="Top Compensation",
line={"color": "#00539F"},
))
cpi_fig.add_trace(go.Scatter(
x=cpi_pd["year"], y=cpi_pd["agg_index"],
mode="lines+markers", name="Top-10 Aggregate",
line={"color": "#E07A5F"},
))
cpi_fig.add_trace(go.Scatter(
x=cpi_pd["year"], y=cpi_pd["cpi_index"],
mode="lines+markers", name="CPI-U",
line={"color": "#FFD200", "dash": "dash"},
))
cpi_fig.update_layout(
title="Top Compensation vs CPI-U (Indexed, Base Year = 100)",
title="Compensation vs CPI-U (Indexed, Base Year = 100)",
xaxis_title="Year", yaxis_title="Index",
template="plotly_white", height=380,
)
return html.Div([
kpi_row,
html.Div(
[
html.Label("Filter by Tax Year: ", style={"fontWeight": "bold"}),
@ -136,8 +191,17 @@ def register_callbacks(app: dash.Dash, conn: duckdb.DuckDBPyConnection) -> None:
breakdown_fig = go.Figure()
if earners.height > 0:
ep = earners.to_pandas().head(10) # top 10 by total comp
short_names = [n.split(",")[0][:20] if "," in n else n.split()[-1][:20]
for n in ep["person_name"]]
_SUFFIXES = {"JR", "SR", "II", "III", "IV", "JR.", "SR."}
def _short_name(n):
if "," in n:
return n.split(",")[0][:20]
parts = n.split()
while len(parts) > 1 and parts[-1].upper().rstrip(".") in _SUFFIXES:
parts.pop()
return parts[-1][:20] if parts else n[:20]
short_names = [_short_name(n) for n in ep["person_name"]]
for comp_type, label, color in [
("base_compensation", "Base", "#00539F"),
("bonus_compensation", "Bonus", "#FFD200"),