Compensation, endowmnet tweaks. Added About.
This commit is contained in:
parent
a41f78545b
commit
13fb4b8418
13 changed files with 914 additions and 17 deletions
|
|
@ -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"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue