feat: add multi-lang support and cohort scripts (#1)
CI / dependency-pin-check-typescript (push) Successful in 4s
CI / dependency-pin-check-python (push) Successful in 3s
CI / typescript (push) Successful in 9m38s
CI / python (push) Successful in 9m23s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m6s

### Explanation

_No response_

### Issue

_No response_

### Attestations

- [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [ ] I have pinned the dependencies to a specific patch version.

### Style

- [ ] I have run the linter and resolved any errors.
- [ ] My pull request uses an appropriate title, matching the conventional commit standards.
- [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Co-authored-by: Hikari <hikari@nhcarrigan.com>
Reviewed-on: #1
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #1.
This commit is contained in:
2026-01-23 20:07:16 -08:00
committed by Naomi Carrigan
parent 38e7f15d93
commit 6b5fa40599
59 changed files with 2249 additions and 48 deletions
+246
View File
@@ -0,0 +1,246 @@
import json
BLOCK_EMOJIS = {"mornings": "🌅", "afternoons": "☀️", "evenings": "🌆", "nights": "🌙"}
def load_all_data():
"""Load all evaluation data files"""
with open("discord_verification.json") as f:
verification = json.load(f)
with open("proficiency_evaluations.json") as f:
proficiency = json.load(f)
with open("availability_analysis.json") as f:
availability = json.load(f)
with open("leadership_candidates.json") as f:
candidates = json.load(f)
with open("leadership_evaluations.json") as f:
leadership = json.load(f)
return verification, proficiency, availability, candidates, leadership
def build_lookup_dicts(verification, proficiency, availability, leadership):
"""Build lookup dictionaries by discord_id"""
verified_usernames = {v[0]: v[1] for v in verification["verified"]}
prof_by_id = {p["discord_id"]: p for p in proficiency}
avail_by_id = {a["discord_id"]: a for a in availability}
lead_by_id = {l["discord_id"]: l for l in leadership}
return verified_usernames, prof_by_id, avail_by_id, lead_by_id
def format_availability_blocks(blocks):
"""Format availability blocks with emojis"""
if not blocks:
return "No consistent availability"
formatted = []
for block in ["mornings", "afternoons", "evenings", "nights"]:
if block in blocks:
formatted.append(f"{BLOCK_EMOJIS[block]} {block.capitalize()}")
return ", ".join(formatted)
def format_tech_stack(tech_stack):
"""Format tech stack list"""
if not tech_stack:
return "Not specified"
return ", ".join(sorted(tech_stack))
def generate_participants_md(
non_leader_ids, verified_usernames, prof_by_id, avail_by_id
):
"""Generate participants.md for non-leaders"""
lines = [
"# Cohort Participants",
"",
f"**Total Participants**: {len(non_leader_ids)}",
"",
"---",
"",
]
beginner_count = 0
intermediate_count = 0
advanced_count = 0
for discord_id in sorted(non_leader_ids):
if discord_id not in verified_usernames:
continue
username = verified_usernames.get(discord_id, "Unknown")
prof = prof_by_id.get(discord_id, {})
avail = avail_by_id.get(discord_id, {})
proficiency = prof.get("final_proficiency", "unknown")
tech_stack = prof.get("tech_stack", [])
blocks = avail.get("available_blocks", [])
notes = prof.get("notes", [])
if proficiency == "beginner":
beginner_count += 1
elif proficiency == "intermediate":
intermediate_count += 1
elif proficiency == "advanced":
advanced_count += 1
lines.append(f"## {discord_id}")
lines.append(f"**Username**: @{username}")
lines.append(f"**Technical Proficiency**: {proficiency.capitalize()}")
lines.append(f"**Tech Stack**: {format_tech_stack(tech_stack)}")
lines.append(f"**Availability**: {format_availability_blocks(blocks)}")
if notes:
lines.append(f"**Notes**: {', '.join(notes)}")
lines.append("")
verified_count = len([d for d in non_leader_ids if d in verified_usernames])
summary = [
"# Cohort Participants",
"",
f"**Total Participants**: {verified_count}",
"",
"### Proficiency Breakdown",
f"- Beginner: {beginner_count}",
f"- Intermediate: {intermediate_count}",
f"- Advanced: {advanced_count}",
"",
"---",
"",
]
return "\n".join(summary + lines[6:])
def leadership_fit_label(score):
"""Convert leadership score to label"""
if score >= 6:
return "Excellent"
elif score >= 4:
return "Good"
elif score >= 2:
return "Adequate"
else:
return "Needs Review"
def generate_leaders_md(
leader_ids, verified_usernames, prof_by_id, avail_by_id, lead_by_id
):
"""Generate leaders.md for leadership candidates"""
verified_leaders = [id for id in leader_ids if id in verified_usernames]
lines = [
"# Cohort Leaders",
"",
f"**Total Leaders**: {len(verified_leaders)}",
"",
"---",
"",
]
sorted_leaders = sorted(
verified_leaders,
key=lambda x: lead_by_id.get(x, {}).get("leadership_score", 0),
reverse=True,
)
for discord_id in sorted_leaders:
username = verified_usernames.get(discord_id, "Unknown")
prof = prof_by_id.get(discord_id, {})
avail = avail_by_id.get(discord_id, {})
lead = lead_by_id.get(discord_id, {})
proficiency = prof.get("final_proficiency", "unknown")
tech_stack = prof.get("tech_stack", [])
blocks = avail.get("available_blocks", [])
leadership_score = lead.get("leadership_score", 0)
leadership_fit = lead.get("leadership_fit", "unknown")
leadership_notes = lead.get("notes", [])
prof_notes = prof.get("notes", [])
lines.append(f"## {discord_id}")
lines.append(f"**Username**: @{username}")
fit = leadership_fit.capitalize()
lines.append(f"**Leadership Fit**: {fit} (Score: {leadership_score})")
lines.append(f"**Technical Proficiency**: {proficiency.capitalize()}")
lines.append(f"**Tech Stack**: {format_tech_stack(tech_stack)}")
lines.append(f"**Availability**: {format_availability_blocks(blocks)}")
if leadership_notes:
lines.append(f"**Leadership Notes**: {', '.join(leadership_notes)}")
if prof_notes:
lines.append(f"**Technical Notes**: {', '.join(prof_notes)}")
lines.append("")
excellent = sum(
1
for id in verified_leaders
if lead_by_id.get(id, {}).get("leadership_fit") == "excellent"
)
good = sum(
1
for id in verified_leaders
if lead_by_id.get(id, {}).get("leadership_fit") == "good"
)
adequate = sum(
1
for id in verified_leaders
if lead_by_id.get(id, {}).get("leadership_fit") == "adequate"
)
summary = [
"# Cohort Leaders",
"",
f"**Total Leaders**: {len(verified_leaders)}",
"",
"### Leadership Fit Breakdown",
f"- Excellent: {excellent}",
f"- Good: {good}",
f"- Adequate: {adequate}",
"",
"---",
"",
]
return "\n".join(summary + lines[6:])
def main():
verification, proficiency, availability, candidates, leadership = load_all_data()
verified_usernames, prof_by_id, avail_by_id, lead_by_id = build_lookup_dicts(
verification, proficiency, availability, leadership
)
leader_ids = set(candidates["leaders"])
non_leader_ids = set(candidates["non_leaders"])
verified_ids = set(verified_usernames.keys())
leader_ids = leader_ids & verified_ids
non_leader_ids = non_leader_ids & verified_ids
participants_md = generate_participants_md(
non_leader_ids, verified_usernames, prof_by_id, avail_by_id
)
with open("participants.md", "w") as f:
f.write(participants_md)
print(f"Generated participants.md with {len(non_leader_ids)} participants")
leaders_md = generate_leaders_md(
leader_ids, verified_usernames, prof_by_id, avail_by_id, lead_by_id
)
with open("leaders.md", "w") as f:
f.write(leaders_md)
print(f"Generated leaders.md with {len(leader_ids)} leaders")
if __name__ == "__main__":
main()