19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 | def compute_chair_stats(
all_members: list,
systems_members: list,
security_members: list,
all_results: dict,
conf_to_area: dict,
) -> dict:
"""Compute comprehensive chair statistics.
Parameters
----------
all_members : list
Full member list (output from _compute_member_stats).
systems_members : list
Systems-area member list.
security_members : list
Security-area member list.
all_results : dict
{conf_year: [{name, affiliation, role?}, ...]}
conf_to_area : dict
{conf_year: 'systems'|'security'|'unknown'}
Returns
-------
dict with keys:
- chairs_all: list of chair records (all areas)
- chairs_systems: list of chair records (systems only)
- chairs_security: list of chair records (security only)
- summary: dict of aggregate statistics
- chair_teams: list of per-conference-year chair teams
- pipeline: dict of member-to-chair promotion stats
- retention: dict of retention / repeat-chairing stats
- cross_conference: list of chairs who chaired different series
"""
# ── Extract chairs from each member list ─────────────────────────────────
chairs_all = _extract_chairs(all_members)
chairs_systems = _extract_chairs(systems_members)
chairs_security = _extract_chairs(security_members)
# ── Per-conference-year chair teams ──────────────────────────────────────
chair_teams = _compute_chair_teams(chairs_all)
# ── Member-to-chair pipeline ─────────────────────────────────────────────
pipeline = _compute_pipeline(chairs_all)
# ── Retention / repeat chairing ──────────────────────────────────────────
retention = _compute_retention(chairs_all)
retention_systems = _compute_retention(chairs_systems)
retention_security = _compute_retention(chairs_security)
# ── Cross-conference chairs ──────────────────────────────────────────────
cross_conference = _compute_cross_conference(chairs_all)
# ── Year-over-year trends ────────────────────────────────────────────────
year_trends = _compute_year_trends(chairs_all)
# ── Geographic diversity ─────────────────────────────────────────────────
geographic = _compute_geographic(chairs_all)
# ── Per-area chair teams for avg computation ─────────────────────────────
chair_teams_systems = _compute_chair_teams(chairs_systems)
chair_teams_security = _compute_chair_teams(chairs_security)
# ── Summary ──────────────────────────────────────────────────────────────
summary = {
"total_chairs": len(chairs_all),
"total_chairs_systems": len(chairs_systems),
"total_chairs_security": len(chairs_security),
"repeat_chairs": retention["repeat_count"],
"repeat_chairs_pct": round(100 * retention["repeat_count"] / max(len(chairs_all), 1), 1),
"repeat_chairs_systems": retention_systems["repeat_count"],
"repeat_chairs_security": retention_security["repeat_count"],
"cross_conference_chairs": len(cross_conference),
"pipeline_promoted": pipeline["promoted_count"],
"pipeline_promoted_pct": pipeline["promoted_pct"],
"pipeline_avg_years": pipeline["avg_years_to_chair"],
"avg_chairs_per_edition": round(sum(t["chair_count"] for t in chair_teams) / max(len(chair_teams), 1), 1),
"avg_chairs_per_edition_systems": round(
sum(t["chair_count"] for t in chair_teams_systems) / max(len(chair_teams_systems), 1), 1
),
"avg_chairs_per_edition_security": round(
sum(t["chair_count"] for t in chair_teams_security) / max(len(chair_teams_security), 1), 1
),
"total_countries": geographic["total_countries"],
"total_continents": geographic["total_continents"],
"year_trends": year_trends,
}
logger.info(
f" Chair stats: {len(chairs_all)} chairs "
f"({len(chairs_systems)} sys, {len(chairs_security)} sec), "
f"{retention['repeat_count']} repeat, {len(cross_conference)} cross-conference"
)
return {
"chairs_all": chairs_all,
"summary": summary,
"chair_teams": chair_teams,
"pipeline": pipeline,
"retention": retention,
"cross_conference": cross_conference,
"geographic": geographic,
}
|