|
| 1 | +from openpyxl import Workbook |
| 2 | +from openpyxl.styles import Font |
| 3 | +from openpyxl.utils import get_column_letter |
1 | 4 | import os |
2 | 5 | import csv |
3 | 6 | import random |
4 | 7 | from collections import defaultdict |
5 | | -from openpyxl import Workbook |
6 | | -from openpyxl.styles import Alignment, Font |
7 | | -from openpyxl.utils import get_column_letter |
8 | | -from openpyxl.worksheet.datavalidation import DataValidation |
9 | 8 |
|
10 | | -# Load deduplicated submissions |
| 9 | +# === Load data === |
11 | 10 | with open("ambassador/ambassador_submissions_deduped.csv", newline='', encoding='utf-8') as f: |
12 | 11 | reader = csv.DictReader(f) |
13 | 12 | submissions = list(reader) |
14 | 13 |
|
15 | | -# Define reviewers |
16 | 14 | reviewers = [f"Reviewer {i}" for i in range(1, 8)] |
17 | 15 |
|
18 | 16 | # Define rubric |
|
41 | 39 | ("Motivation and Vision", "Vision", "Proposed goals or initiatives that align with the mission of the PyTorch Foundation?") |
42 | 40 | ] |
43 | 41 |
|
44 | | -# Limit categories for summary |
45 | | -summary_categories = [ |
46 | | - "Technical Expertise", |
47 | | - "Open Source Contributions", |
48 | | - "Thought Leadership and Technical Writing", |
49 | | - "Community Engagement and Evangelism", |
50 | | - "Online Influence and Reach", |
51 | | - "Alignment and Values", |
52 | | - "Motivation and Vision" |
53 | | -] |
| 42 | +# Define category mapping |
| 43 | +categories = ["Technical Expertise", "Open Source Contributions", "Thought Leadership and Technical Writing", |
| 44 | + "Community Engagement and Evangelism", "Online Influence and Reach", "Alignment and Values", |
| 45 | + "Motivation and Vision"] |
54 | 46 |
|
55 | | -# Output folder |
56 | | -output_folder = "ambassador/reviewer_sheets_excel" |
57 | | -os.makedirs(output_folder, exist_ok=True) |
| 47 | +# Build a lookup for rubric indexes |
| 48 | +category_map = defaultdict(list) |
| 49 | +for i, (cat, _, _) in enumerate(rubric): |
| 50 | + category_map[cat].append(i) |
58 | 51 |
|
59 | | -# Assign reviewers |
| 52 | +# Random reviewer assignment |
60 | 53 | assignments = [] |
61 | 54 | reviewer_counts = defaultdict(int) |
62 | 55 | for submission in submissions: |
63 | | - assigned = random.sample(sorted(reviewers, key=lambda r: reviewer_counts[r])[:4], 2) |
| 56 | + sorted_reviewers = sorted(reviewers, key=lambda r: reviewer_counts[r]) |
| 57 | + assigned = random.sample(sorted_reviewers[:4], 2) |
64 | 58 | for reviewer in assigned: |
65 | 59 | reviewer_counts[reviewer] += 1 |
66 | 60 | assignments.append((submission, reviewer)) |
67 | 61 |
|
68 | | -# Generate reviewer sheets |
| 62 | +# Output directory |
| 63 | +output_dir = "ambassador/reviewer_sheets_excel" |
| 64 | +os.makedirs(output_dir, exist_ok=True) |
| 65 | + |
| 66 | +# Generate Excel with 2 sheets per reviewer |
69 | 67 | for reviewer in reviewers: |
70 | 68 | wb = Workbook() |
71 | 69 | ws = wb.active |
72 | 70 | ws.title = "Review Sheet" |
73 | 71 |
|
74 | | - summary_ws = wb.create_sheet("Score Summary") |
75 | | - |
76 | | - # Review Sheet Header |
77 | | - headers = [ |
78 | | - "Submission ID", "First Name", "Last Name", "Submission Summary", |
79 | | - "Reviewer's Comment", "Category", "Subcategory", "Question", "Score" |
80 | | - ] |
81 | | - ws.append(headers) |
82 | | - for col in range(1, len(headers)+1): |
83 | | - ws.cell(row=1, column=col).font = Font(bold=True) |
84 | | - |
85 | | - # Dropdown for score |
86 | | - dv = DataValidation(type="list", formula1='"Yes,No,N/A"', allow_blank=True) |
87 | | - ws.add_data_validation(dv) |
| 72 | + # Summary worksheet |
| 73 | + summary_ws = wb.create_sheet(title="Score Summary") |
| 74 | + summary_headers = ["Submission ID", "First Name", "Last Name"] + categories + ["Final Score"] |
| 75 | + summary_ws.append(summary_headers) |
| 76 | + for col in summary_ws[1]: |
| 77 | + col.font = Font(bold=True) |
88 | 78 |
|
89 | 79 | row_idx = 2 |
90 | | - candidate_ranges = [] |
| 80 | + summary_row = 2 |
91 | 81 |
|
92 | 82 | for submission, assigned_reviewer in assignments: |
93 | 83 | if assigned_reviewer != reviewer: |
94 | 84 | continue |
95 | 85 |
|
96 | | - sid = submission["Issue #"] |
97 | | - name = submission["Nominee Name"].split() |
98 | | - fname = name[0] |
99 | | - lname = name[-1] if len(name) > 1 else "" |
100 | | - summary = f"""Contributions:\n{submission.get("Contributions", "")} |
101 | | -
|
102 | | -Ambassador Pitch:\n{submission.get("Ambassador Pitch", "")} |
| 86 | + issue_id = submission["Issue #"] |
| 87 | + name_parts = submission["Nominee Name"].split() |
| 88 | + first_name = name_parts[0] if name_parts else "" |
| 89 | + last_name = name_parts[-1] if len(name_parts) > 1 else "" |
103 | 90 |
|
104 | | -Additional Notes:\n{submission.get("Extra Notes", "")}""" |
| 91 | + category_rows = defaultdict(list) |
| 92 | + start_row = row_idx |
105 | 93 |
|
106 | | - start = row_idx |
107 | 94 | for cat, subcat, question in rubric: |
108 | | - ws.append([sid, fname, lname, summary, "", cat, subcat, question, ""]) |
| 95 | + ws.append([ |
| 96 | + issue_id, first_name, last_name, "", "", cat, subcat, question, "" |
| 97 | + ]) |
| 98 | + category_rows[cat].append(row_idx) |
109 | 99 | row_idx += 1 |
110 | | - end = row_idx - 1 |
111 | | - candidate_ranges.append((sid, fname, lname, start, end)) |
112 | | - |
113 | | - # Merge |
114 | | - for col in [1,2,3,4]: |
115 | | - ws.merge_cells(start_row=start, end_row=end, start_column=col, end_column=col) |
116 | | - cell = ws.cell(row=start, column=col) |
117 | | - cell.alignment = Alignment(vertical="top", wrap_text=True) |
118 | | - |
119 | | - # Apply dropdowns |
120 | | - for r in range(start, end + 1): |
121 | | - dv.add(ws[f"I{r}"]) |
122 | | - |
123 | | - # Auto column widths |
124 | | - for col in ws.columns: |
125 | | - max_len = max((len(str(cell.value)) if cell.value else 0) for cell in col) |
126 | | - ws.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 5, 50) |
127 | | - |
128 | | - # Score Summary Sheet Header |
129 | | - summary_ws.append(["Submission ID", "First Name", "Last Name"] + summary_categories + ["Final Score"]) |
130 | | - for col in range(1, summary_ws.max_column + 1): |
131 | | - summary_ws.cell(row=1, column=col).font = Font(bold=True) |
132 | | - |
133 | | - # Fill Score Summary |
134 | | - for sid, fname, lname, start, end in candidate_ranges: |
135 | | - category_rows = defaultdict(list) |
136 | | - for r in range(start, end + 1): |
137 | | - category = ws.cell(row=r, column=6).value |
138 | | - if category in summary_categories: |
139 | | - category_rows[category].append(r) |
140 | 100 |
|
141 | | - formula_cells = [] |
142 | | - for cat in summary_categories: |
| 101 | + # Write summary formulas |
| 102 | + formulas = [] |
| 103 | + for cat in categories: |
143 | 104 | if cat in category_rows: |
144 | 105 | rng = category_rows[cat] |
145 | | - formula = f"SUMPRODUCT(--(Review Sheet!I{rng[0]}:I{rng[-1]}=\"Yes\"))" |
| 106 | + formula = f"=SUMPRODUCT(--('{ws.title}'!I{rng[0]}:I{rng[-1]}=\"Yes\"))" |
| 107 | + formulas.append(formula) |
146 | 108 | else: |
147 | | - formula = "0" |
148 | | - formula_cells.append(f"={formula}") |
| 109 | + formulas.append("") |
| 110 | + |
| 111 | + total_formula = f"=SUM({','.join([chr(68+i) + str(summary_row) for i in range(3, 3+len(categories))])})" |
149 | 112 |
|
150 | | - total_formula = f"=SUM({','.join([f'{get_column_letter(i+4)}{summary_ws.max_row+1}' for i in range(len(formula_cells))])})" |
151 | | - summary_ws.append([sid, fname, lname] + formula_cells + [total_formula]) |
| 113 | + summary_ws.append([issue_id, first_name, last_name] + formulas + [total_formula]) |
| 114 | + summary_row += 1 |
152 | 115 |
|
153 | | - # Save workbook |
154 | | - path = os.path.join(output_folder, f"{reviewer.replace(' ', '_').lower()}_sheet.xlsx") |
155 | | - wb.save(path) |
| 116 | + # Save file |
| 117 | + reviewer_file = os.path.join(output_dir, f"{reviewer.replace(' ', '_').lower()}_sheet.xlsx") |
| 118 | + wb.save(reviewer_file) |
156 | 119 |
|
157 | | -print("✅ Reviewer sheets generated with aligned score summary.") |
| 120 | +print("✅ All reviewer sheets generated with corrected formulas and aligned categories.") |
0 commit comments