make staff-tier category labels reflect the actual --senior-threshold/--support-threshold values
This commit is contained in:
parent
a621b94512
commit
efce6f3dec
2 changed files with 31 additions and 12 deletions
|
|
@ -85,6 +85,24 @@ SENIOR_STAFF_THRESHOLD = 100_000
|
|||
SUPPORT_STAFF_THRESHOLD = 40_000
|
||||
|
||||
|
||||
def _fmt_threshold(v: float) -> str:
|
||||
if v == int(v) and v % 1000 == 0:
|
||||
return f"${int(v) // 1000}k"
|
||||
return f"${v:,.0f}"
|
||||
|
||||
|
||||
def category_labels(senior_threshold: float = SENIOR_STAFF_THRESHOLD,
|
||||
support_threshold: float = SUPPORT_STAFF_THRESHOLD) -> dict:
|
||||
"""CATEGORY_LABELS with the staff-tier labels reflecting the actual
|
||||
thresholds passed to classify_title(), instead of the defaults."""
|
||||
labels = dict(CATEGORY_LABELS)
|
||||
labels["SENIOR_PROF_STAFF"] = f"Senior Prof/Staff (>={_fmt_threshold(senior_threshold)})"
|
||||
labels["PROF_STAFF"] = (f"Professional Staff ({_fmt_threshold(support_threshold)}"
|
||||
f"-{_fmt_threshold(senior_threshold)})")
|
||||
labels["SUPPORT_STAFF"] = f"Support Staff (<{_fmt_threshold(support_threshold)})"
|
||||
return labels
|
||||
|
||||
|
||||
def _rx(*patterns):
|
||||
return [re.compile(p, re.IGNORECASE) for p in patterns]
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ def _group_sort_key(key: tuple) -> tuple:
|
|||
|
||||
|
||||
def process_file(path: Path, campus: str, year: int, writer: csv.writer,
|
||||
senior_threshold: float, support_threshold: float,
|
||||
senior_threshold: float, support_threshold: float, labels: dict,
|
||||
agg: dict | None = None, group_dims: tuple = (),
|
||||
top_earners: dict | None = None, top_n: int = 10,
|
||||
tie_breaker: itertools.count | None = None,
|
||||
|
|
@ -157,7 +157,7 @@ def process_file(path: Path, campus: str, year: int, writer: csv.writer,
|
|||
)
|
||||
writer.writerow([
|
||||
year, campus, parsed["title"], category,
|
||||
classifier.CATEGORY_LABELS[category],
|
||||
labels[category],
|
||||
round(parsed["gross_pay"], 2), round(parsed["regular_pay"], 2),
|
||||
round(parsed["overtime_pay"], 2), round(parsed["other_pay"], 2),
|
||||
parsed["first_name"], parsed["last_name"],
|
||||
|
|
@ -180,7 +180,7 @@ def process_file(path: Path, campus: str, year: int, writer: csv.writer,
|
|||
return count
|
||||
|
||||
|
||||
def write_aggregate_csv(agg: dict, group_dims: tuple, outpath: Path) -> None:
|
||||
def write_aggregate_csv(agg: dict, group_dims: tuple, labels: dict, outpath: Path) -> None:
|
||||
"""Write one row per (group_dims..., category) with employee count,
|
||||
total/mean/median gross pay."""
|
||||
header = list(group_dims) + [
|
||||
|
|
@ -196,14 +196,14 @@ def write_aggregate_csv(agg: dict, group_dims: tuple, outpath: Path) -> None:
|
|||
values = agg[key]
|
||||
total = sum(values)
|
||||
writer.writerow([
|
||||
*prefix, category, classifier.CATEGORY_LABELS[category],
|
||||
*prefix, category, labels[category],
|
||||
len(values), round(total, 2), round(total / len(values), 2),
|
||||
round(statistics.median(values), 2),
|
||||
])
|
||||
log.info("Aggregate CSV -> %s", outpath)
|
||||
|
||||
|
||||
def write_top_earners_csv(top_earners: dict, outpath: Path) -> None:
|
||||
def write_top_earners_csv(top_earners: dict, labels: dict, outpath: Path) -> None:
|
||||
"""Write the top-N earners per (group_dims..., category) key, ranked
|
||||
highest gross_pay first within each group."""
|
||||
header = ["category", "category_label", "rank", "year", "campus", "title",
|
||||
|
|
@ -219,7 +219,7 @@ def write_top_earners_csv(top_earners: dict, outpath: Path) -> None:
|
|||
for rank, (gross_pay, _, row) in enumerate(ranked, start=1):
|
||||
year, campus, title, gross, regular, overtime, other, first, last = row
|
||||
writer.writerow([
|
||||
category, classifier.CATEGORY_LABELS[category], rank,
|
||||
category, labels[category], rank,
|
||||
year, campus, title, round(gross, 2), round(regular, 2),
|
||||
round(overtime, 2), round(other, 2), first, last,
|
||||
])
|
||||
|
|
@ -232,7 +232,7 @@ def _money(v: float) -> str:
|
|||
return f"${v:,.0f}"
|
||||
|
||||
|
||||
def print_console_summary(console_stats: dict) -> None:
|
||||
def print_console_summary(console_stats: dict, labels: dict) -> None:
|
||||
"""Print one formatted category-by-category summary table per year,
|
||||
keyed off (year, category) -> [gross_pay, ...] entries in console_stats."""
|
||||
label_w, col_w = 34, 16
|
||||
|
|
@ -255,7 +255,7 @@ def print_console_summary(console_stats: dict) -> None:
|
|||
median = statistics.median(values) if values else 0.0
|
||||
total_count += count
|
||||
total_pay += total
|
||||
label = classifier.CATEGORY_LABELS[category]
|
||||
label = labels[category]
|
||||
print(f"{label:<{label_w}}{count:>{col_w},}"
|
||||
f"{_money(total):>{col_w}}{_money(median):>{col_w}}")
|
||||
|
||||
|
|
@ -331,6 +331,7 @@ def main():
|
|||
else:
|
||||
log.info("--skip-download set, classifying cached data only.")
|
||||
|
||||
labels = classifier.category_labels(args.senior_threshold, args.support_threshold)
|
||||
group_dims = tuple(dim for dim in ("year", "campus") if dim in args.group_by)
|
||||
agg = {} if args.aggregate_output else None
|
||||
top_earners = {} if args.top_earners_output else None
|
||||
|
|
@ -350,7 +351,7 @@ def main():
|
|||
log.info("No data for %s %s, skipping.", campus, year)
|
||||
continue
|
||||
count = process_file(path, campus, year, writer,
|
||||
args.senior_threshold, args.support_threshold,
|
||||
args.senior_threshold, args.support_threshold, labels,
|
||||
agg=agg, group_dims=group_dims,
|
||||
top_earners=top_earners, top_n=args.top_n,
|
||||
tie_breaker=tie_breaker,
|
||||
|
|
@ -365,13 +366,13 @@ def main():
|
|||
log.info("Total rows written: %d -> %s", total_rows, output_path)
|
||||
|
||||
if agg is not None:
|
||||
write_aggregate_csv(agg, group_dims, Path(args.aggregate_output))
|
||||
write_aggregate_csv(agg, group_dims, labels, Path(args.aggregate_output))
|
||||
|
||||
if top_earners is not None:
|
||||
write_top_earners_csv(top_earners, Path(args.top_earners_output))
|
||||
write_top_earners_csv(top_earners, labels, Path(args.top_earners_output))
|
||||
|
||||
if console_stats is not None:
|
||||
print_console_summary(console_stats)
|
||||
print_console_summary(console_stats, labels)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue