Skip to content

Commit e6980ba

Browse files
jeremymanningclaude
andcommitted
use CV as authoritative source for alumni ordering
build_people.py now parses JRM_CV.tex to get the exact order of undergraduate advisees. Alumni are sorted to match CV order (reverse chronological by join date) automatically - no manual spreadsheet ordering needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e442830 commit e6980ba

File tree

2 files changed

+84
-53
lines changed

2 files changed

+84
-53
lines changed

people.html

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -257,32 +257,31 @@ <h3>Undergraduate Researchers</h3>
257257
Annabelle Morrow (2025-2026)<br>
258258
Owen Phillips (2025-2026)<br>
259259
Rodrigo Vega Ayllon (2025)<br>
260-
Chelsea Joe (2024-2026)<br>
261-
Harrison Stropkay (2024-2025)<br>
262260
Miel Wewerka (2024)<br>
263261
Manraaj Singh (2024)<br>
264262
Can Kam (2024)<br>
263+
Chelsea Joe (2024-2026)<br>
265264
Rohan Goyal (2024)<br>
265+
Harrison Stropkay (2024-2025)<br>
266266
Abigayle McCusker (2024)<br>
267267
Torsha Chakraverty (2024)<br>
268268
Chloe Terestchenko (2024)<br>
269269
Ansh Motiani (2024)<br>
270270
Kaitlyn Peng (2024)<br>
271271
Everett Tai (2024)<br>
272272
Andrew Cao (2024)<br>
273-
Jake McDermid (2023-2025)<br>
274273
Michael Chen (2023-2024)<br>
274+
Jake McDermid (2023-2025)<br>
275275
Grady Redding (2023-2024)<br>
276276
DJ Matusz (2023-2024)<br>
277277
Aaryan Agarwal (2023-2024)<br>
278278
Maura Hough (2023-2024)<br>
279279
Emma Reeder (2023-2024)<br>
280-
Megan Liu (2023-2024)<br>
281-
Mira Chiruvolu (2023-2024)<br>
282280
Safwan Rashid (2023)<br>
283281
Francisca Fadairo (2023)<br>
284282
Ameer Talha Yasser (2023)<br>
285283
Yue Zhuo (2023)<br>
284+
Megan Liu (2023-2024)<br>
286285
Charles Baker (2023)<br>
287286
Andrew Shi (2023)<br>
288287
Ash Chinta (2023)<br>
@@ -294,9 +293,10 @@ <h3>Undergraduate Researchers</h3>
294293
Natalie Schreder (2023)<br>
295294
Raselas Dessalegn (2023)<br>
296295
Grace Wang (2023)<br>
296+
Mira Chiruvolu (2023-2024)<br>
297+
Anna Mikhailova (2022)<br>
297298
Ansh Patel (2022-2024)<br>
298299
Ziyan Zhu (2022-2023)<br>
299-
Anna Mikhailova (2022)<br>
300300
Benjamin Lehrburger (2022)<br>
301301
Thomas Corrado (2022)<br>
302302
Samuel Crombie (2022)<br>
@@ -308,51 +308,51 @@ <h3>Undergraduate Researchers</h3>
308308
Zachary Somma (2022)<br>
309309
Dawson Haddox (2022)<br>
310310
Swestha Jain (2022)<br>
311+
Aidan Adams (2021)<br>
312+
Damini Kohli (2021)<br>
311313
Kunal Jha (2021-2024)<br>
312314
Daniel Carstensen (2021-2024)<br>
313315
Brian Chiang (2021-2022)<br>
314-
Aidan Adams (2021)<br>
315-
Damini Kohli (2021)<br>
316316
Daniel Ha (2021)<br>
317-
Tyler Chen (2020-2022)<br>
318-
Chris Jun (2020-2022)<br>
319-
Ethan Adner (2020-2022)<br>
320317
Darren Gu (2020-2021)<br>
318+
Tyler Chen (2020-2022)<br>
321319
Tehut Biru (2020-2021)<br>
322320
Chris Suh (2020-2021)<br>
323-
Chris Long (2020-2021)<br>
324-
Esme Chen (2020-2021)<br>
325321
Helen Liu (2020)<br>
326322
Kelly Rutherford (2020)<br>
323+
Chris Jun (2020-2022)<br>
324+
Ethan Adner (2020-2022)<br>
325+
Chris Long (2020-2021)<br>
326+
Esme Chen (2020-2021)<br>
327327
Luca Lit (2020)<br>
328328
Vivian Tran (2020)<br>
329329
Greg Han (2020)<br>
330330
Austin Zhang (2020)<br>
331331
Chelsea Uddenberg (2020)<br>
332332
Shane Hewitt (2020)<br>
333333
Chetan Palvuluri (2020)<br>
334-
Tudor Muntianu (2019-2021)<br>
335334
Aaron Lee (2019-2020)<br>
336335
Anne George (2019-2020)<br>
337336
Sarah Park (2019-2020)<br>
338337
Shane Park (2019-2020)<br>
339338
William Chen (2019-2020)<br>
340-
Alejandro Martinez (2018-2020)<br>
339+
Tudor Muntianu (2019-2021)<br>
341340
William Baxley (2018-2019)<br>
342341
Ann Carpenter (2018)<br>
343342
Seung Ju Lee (2018)<br>
344343
Mustafa Nasir-Moin (2018)<br>
345344
Iain Sheerin (2018)<br>
346345
Darya Romanova (2018)<br>
346+
Alejandro Martinez (2018-2020)<br>
347347
Rachael Chacko (2018)<br>
348348
Kirsten Soh (2018)<br>
349349
Paxton Fitzpatrick (2017-2019)<br>
350350
Stephen Satterthwaite (2017-2018)<br>
351351
Bryan Bollinger (2017-2018)<br>
352352
Christina Lu (2017)<br>
353353
Armando Oritz (2017)<br>
354-
Madeline Lee (2016-2020)<br>
355354
Campbell Field (2016-2018)<br>
355+
Madeline Lee (2016-2020)<br>
356356
Wei Liang Samuel Ching (2016-2017)<br>
357357
Marisol Tracy (2016-2017)<br>
358358
Allison Frantz (2016-2017)<br>

scripts/build_people.py

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,53 @@
77

88
import re
99
from pathlib import Path
10-
from typing import List, Dict, Any
10+
from typing import List, Dict, Any, Optional
1111
import openpyxl
1212

1313
from utils import inject_content
1414
from citation_utils import resolve_link
1515

1616

17+
def parse_cv_undergrad_order(cv_path: Path) -> List[str]:
18+
"""Parse the CV to get the order of undergraduate advisees.
19+
20+
The CV lists undergrads in reverse chronological order by join date
21+
(most recent joiner first). This order is authoritative.
22+
23+
Args:
24+
cv_path: Path to JRM_CV.tex
25+
26+
Returns:
27+
List of names in CV order (first = highest priority)
28+
"""
29+
if not cv_path.exists():
30+
return []
31+
32+
content = cv_path.read_text(encoding="utf-8")
33+
34+
# Find the Undergraduate Advisees section
35+
match = re.search(
36+
r"\\textit\{Undergraduate Advisees\}.*?\\begin\{etaremune\}(.*?)\\end\{etaremune\}",
37+
content,
38+
re.DOTALL
39+
)
40+
if not match:
41+
return []
42+
43+
section = match.group(1)
44+
45+
# Extract names from \item entries
46+
# Format: \item Name[*]? (years)
47+
names = []
48+
for item_match in re.finditer(r"\\item\s+(.+?)\s*\(", section):
49+
name = item_match.group(1).strip()
50+
# Remove asterisk (senior thesis marker)
51+
name = name.rstrip("*").strip()
52+
names.append(name)
53+
54+
return names
55+
56+
1757
def parse_links_field(links_str: str) -> str:
1858
"""Parse links field into HTML.
1959
@@ -326,51 +366,35 @@ def generate_undergrad_entry(alum: Dict[str, Any]) -> str:
326366
return f"{name}{paren_display}"
327367

328368

329-
def get_start_year(years_str: str) -> int:
330-
"""Extract start year from years string for sorting.
331-
332-
Args:
333-
years_str: Years string like '2024-2026', '2025', or '2023-2025'
334-
335-
Returns:
336-
Start year as integer (defaults to 0 if unparseable)
337-
"""
338-
if not years_str:
339-
return 0
340-
years_str = str(years_str).strip()
341-
# Handle "2024-2026" format - extract first year
342-
if "-" in years_str:
343-
try:
344-
return int(years_str.split("-")[0])
345-
except ValueError:
346-
return 0
347-
# Handle single year "2025"
348-
try:
349-
return int(years_str)
350-
except ValueError:
351-
return 0
352-
353-
354-
def generate_undergrad_list_content(alumni: List[Dict[str, Any]]) -> str:
369+
def generate_undergrad_list_content(
370+
alumni: List[Dict[str, Any]], cv_order: Optional[List[str]] = None
371+
) -> str:
355372
"""Generate HTML content for undergraduate alumni list.
356373
357-
Alumni are sorted by start year (descending), matching CV order.
374+
Alumni are sorted to match CV order (reverse chronological by join date).
358375
359376
Args:
360377
alumni: List of alumni dictionaries
378+
cv_order: Optional list of names in CV order (from parse_cv_undergrad_order)
361379
362380
Returns:
363381
HTML string with alumni entries separated by <br>
364382
"""
365383
if not alumni:
366384
return ""
367385

368-
# Sort by start year descending (most recent first)
369-
# Use stable sort to preserve spreadsheet order within same start year (matches CV)
370-
sorted_alumni = sorted(
371-
alumni,
372-
key=lambda a: -get_start_year(a.get("years", ""))
373-
)
386+
# Create position map from CV order (lower = appears first)
387+
cv_position = {}
388+
if cv_order:
389+
for i, name in enumerate(cv_order):
390+
cv_position[name] = i
391+
392+
def sort_key(a):
393+
name = a.get("name", "")
394+
# Use CV position if available, otherwise put at end
395+
return cv_position.get(name, 99999)
396+
397+
sorted_alumni = sorted(alumni, key=sort_key)
374398

375399
entries = [generate_undergrad_entry(a) for a in sorted_alumni]
376400
return "<br>\n ".join(entries)
@@ -419,17 +443,23 @@ def generate_collaborators_content(collaborators: List[Dict[str, Any]]) -> str:
419443
return "\n ".join(entries)
420444

421445

422-
def build_people(data_path: Path, template_path: Path, output_path: Path) -> None:
446+
def build_people(data_path: Path, template_path: Path, output_path: Path, cv_path: Optional[Path] = None) -> None:
423447
"""Build people.html from data and template.
424448
425449
Args:
426450
data_path: Path to people.xlsx
427451
template_path: Path to template HTML file
428452
output_path: Path for generated HTML file
453+
cv_path: Optional path to JRM_CV.tex for ordering undergrad alumni
429454
"""
430455
# Load data
431456
data = load_people(data_path)
432457

458+
# Get CV order for undergrad alumni
459+
cv_order = []
460+
if cv_path:
461+
cv_order = parse_cv_undergrad_order(cv_path)
462+
433463
# Generate content for each section
434464
director_content = ""
435465
if data.get("director"):
@@ -448,7 +478,7 @@ def build_people(data_path: Path, template_path: Path, output_path: Path) -> Non
448478
data.get("alumni_managers", [])
449479
),
450480
"ALUMNI_UNDERGRADS_CONTENT": generate_undergrad_list_content(
451-
data.get("alumni_undergrads", [])
481+
data.get("alumni_undergrads", []), cv_order
452482
),
453483
"COLLABORATORS_CONTENT": generate_collaborators_content(
454484
data.get("collaborators", [])
@@ -469,8 +499,9 @@ def main():
469499
data_path = project_root / "data" / "people.xlsx"
470500
template_path = project_root / "templates" / "people.html"
471501
output_path = project_root / "people.html"
502+
cv_path = project_root / "documents" / "JRM_CV.tex"
472503

473-
build_people(data_path, template_path, output_path)
504+
build_people(data_path, template_path, output_path, cv_path)
474505

475506

476507
if __name__ == "__main__":

0 commit comments

Comments
 (0)