- 한지승 4명팀 배치 금지 (지각 시 3명 방지) - 한지승 팀에 다른 시니어 ≥1 필수 (지각 시 시니어 0 방지) - 출력에 ⏰ 마크 추가 결과: 한지승 → 팀5 (5명), 팀5 다른 시니어 김병훈 동행 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
223 lines
7.4 KiB
Python
223 lines
7.4 KiB
Python
"""
|
|
참가자 34명 → 7팀 배정 (5명 6팀 + 4명 1팀).
|
|
- 부서별 균등 분배 (max - min ≤ 1)
|
|
- 시니어 균등 분배 (max - min ≤ 1, 모든 팀 ≥1)
|
|
- 김태현은 진행요원 (참가 X)
|
|
- AI Efficiency Tech는 시니어/주니어 정보 없음 → 균등 분배 대상 외
|
|
|
|
결과: participants.json (이름→팀 매핑) + 콘솔 출력.
|
|
"""
|
|
import json
|
|
import random
|
|
from collections import Counter, defaultdict
|
|
from pathlib import Path
|
|
|
|
# 시니어 명단 (Platform/Data/HPC/System만 알고 있음)
|
|
SENIORS = {
|
|
"한지승", "손현준", "강승형", "변수민", # Platform/Data
|
|
"김정명", "김영관", "전효준", # HPC
|
|
"박영훈", "서희", "김병훈", # System
|
|
}
|
|
|
|
PEOPLE = [
|
|
# (name, dept)
|
|
("한지승", "MLOps Platform"),
|
|
("변수민", "MLOps Data"),
|
|
("박재호", "MLOps Data"),
|
|
("강승형", "MLOps Data"),
|
|
("손현준", "MLOps Data"),
|
|
("김동국", "MLOps Data"),
|
|
# 김태현 진행요원 (제외)
|
|
("김재현", "MLOps HPC"),
|
|
("이준석", "MLOps HPC"),
|
|
("오근현", "MLOps HPC"),
|
|
("김정명", "MLOps HPC"),
|
|
("김영관", "MLOps HPC"),
|
|
("유용혁", "MLOps HPC"),
|
|
("최호진", "MLOps HPC"),
|
|
("전효준", "MLOps HPC"),
|
|
("김병훈", "MLOps System"),
|
|
("이지환", "MLOps System"),
|
|
("서희", "MLOps System"),
|
|
("정채윤", "MLOps System"),
|
|
("장혁진", "MLOps System"),
|
|
("장다현", "MLOps System"),
|
|
("박영훈", "MLOps System"),
|
|
("길주현", "MLOps System"),
|
|
("조민정", "AI Efficiency Tech"),
|
|
("김민섭", "AI Efficiency Tech"),
|
|
("김호승", "AI Efficiency Tech"),
|
|
("서한배", "AI Efficiency Tech"),
|
|
("심성환", "AI Efficiency Tech"),
|
|
("유준희", "AI Efficiency Tech"),
|
|
("이성재", "AI Efficiency Tech"),
|
|
("이재광", "AI Efficiency Tech"),
|
|
("이정태", "AI Efficiency Tech"),
|
|
("이준형", "AI Efficiency Tech"),
|
|
("정현준", "AI Efficiency Tech"),
|
|
("유지원", "AI Efficiency Tech"),
|
|
]
|
|
|
|
NUM_TEAMS = 7
|
|
# 34명 / 7팀 = 5명 6팀 + 4명 1팀
|
|
TEAM_SIZES = [5] * 6 + [4]
|
|
SEED = 20260428 # 행사일 시드 (재현 가능)
|
|
|
|
|
|
def assign_one(seed):
|
|
"""
|
|
가변 팀 사이즈 균등 분배.
|
|
|
|
각 사람을 한 명씩 할당:
|
|
- 우선순위 1: 남은 슬롯 많은 팀
|
|
- 우선순위 2: 이 부서 인원 적게 받은 팀
|
|
- tie: random
|
|
|
|
효과:
|
|
- 팀 사이즈 정확히 TEAM_SIZES (가변)
|
|
- 부서별 max - min ≤ 1 (자연스럽게)
|
|
- 시니어 균등은 시드 재시도로 검증
|
|
"""
|
|
rng = random.Random(seed)
|
|
by_dept = defaultdict(list)
|
|
for name, dept in PEOPLE:
|
|
by_dept[dept].append(name)
|
|
|
|
for d in by_dept:
|
|
rng.shuffle(by_dept[d])
|
|
|
|
teams = [[] for _ in range(NUM_TEAMS)]
|
|
remaining = TEAM_SIZES[:]
|
|
|
|
depts_sorted = sorted(by_dept.keys(), key=lambda d: -len(by_dept[d]))
|
|
|
|
for dept in depts_sorted:
|
|
for person in by_dept[dept]:
|
|
dept_counts = [Counter(d for _, d in t).get(dept, 0) for t in teams]
|
|
priority = sorted(
|
|
range(NUM_TEAMS),
|
|
key=lambda i: (
|
|
-remaining[i],
|
|
dept_counts[i],
|
|
rng.random(),
|
|
),
|
|
)
|
|
for ti in priority:
|
|
if remaining[ti] > 0:
|
|
teams[ti].append((person, dept))
|
|
remaining[ti] -= 1
|
|
break
|
|
|
|
return teams
|
|
|
|
|
|
def validate(teams):
|
|
"""모든 제약 검증."""
|
|
sizes = [len(t) for t in teams]
|
|
if sorted(sizes, reverse=True) != sorted(TEAM_SIZES, reverse=True):
|
|
return False, "팀 사이즈 불일치"
|
|
|
|
# 부서별 max - min ≤ 1
|
|
all_depts = {d for _, d in PEOPLE}
|
|
for dept in all_depts:
|
|
per_team = [Counter(d for _, d in t).get(dept, 0) for t in teams]
|
|
if max(per_team) - min(per_team) > 1:
|
|
return False, f"부서 {dept} 분포 불균등: {per_team}"
|
|
|
|
# EffTech 모든 팀 ≥1
|
|
for ti, team in enumerate(teams):
|
|
eff = sum(1 for _, d in team if d == "AI Efficiency Tech")
|
|
if eff < 1:
|
|
return False, f"팀{ti+1} EffTech 0명"
|
|
|
|
# 시니어 검증
|
|
senior_per_team = [
|
|
sum(1 for n, _ in t if n in SENIORS) for t in teams
|
|
]
|
|
if min(senior_per_team) < 1:
|
|
return False, f"시니어 0명 팀 존재: {senior_per_team}"
|
|
if max(senior_per_team) - min(senior_per_team) > 1:
|
|
return False, f"시니어 분포 불균등: {senior_per_team}"
|
|
|
|
# 한지승(지각 가능) 특수 제약
|
|
# 1. 4명 팀에 배치 금지 (지각 시 3명 → 너무 적음)
|
|
# 2. 한지승 팀에 다른 시니어 ≥1명 (지각 시 시니어 0 방지)
|
|
for team in teams:
|
|
names = [n for n, _ in team]
|
|
if "한지승" in names:
|
|
if len(team) < 5:
|
|
return False, "한지승 4명팀 배치 (지각 시 3명)"
|
|
other_seniors = sum(1 for n in names if n in SENIORS and n != "한지승")
|
|
if other_seniors < 1:
|
|
return False, "한지승 팀에 다른 시니어 0명 (지각 시 시니어 0)"
|
|
|
|
return True, "OK"
|
|
|
|
|
|
def assign(base_seed):
|
|
"""시드 재시도로 모든 제약 만족하는 배정 찾기."""
|
|
for offset in range(10000):
|
|
seed = base_seed + offset
|
|
teams = assign_one(seed)
|
|
ok, msg = validate(teams)
|
|
if ok:
|
|
return teams, seed, offset + 1
|
|
raise RuntimeError("10000회 시도에도 모든 제약 만족 실패")
|
|
|
|
|
|
def main():
|
|
assert len(PEOPLE) == sum(TEAM_SIZES), (
|
|
f"인원수 불일치: {len(PEOPLE)}명, 기대 {sum(TEAM_SIZES)}명"
|
|
)
|
|
|
|
teams, seed_used, attempts = assign(SEED)
|
|
print(f"# 시드 {seed_used} (시도 {attempts}회), 팀 사이즈 {TEAM_SIZES}\n")
|
|
|
|
# 부서별 분포 검증
|
|
dept_dist = defaultdict(list)
|
|
for team in teams:
|
|
team_counts = Counter(d for _, d in team)
|
|
for dept in {d for _, d in PEOPLE}:
|
|
dept_dist[dept].append(team_counts.get(dept, 0))
|
|
|
|
print("# 부서별 팀 분배 (max - min ≤ 1 = 균등)")
|
|
for dept, dist in sorted(dept_dist.items(), key=lambda x: -sum(x[1])):
|
|
print(f" {dept}: {dist} (총 {sum(dist)}명)")
|
|
|
|
senior_dist = [sum(1 for n, _ in t if n in SENIORS) for t in teams]
|
|
print(f"\n# 시니어 분배: {senior_dist} (총 {sum(senior_dist)}명, 모든 팀 ≥1)")
|
|
print()
|
|
|
|
mapping = {}
|
|
for i, team in enumerate(teams, 1):
|
|
team_name = f"팀{i}"
|
|
dept_counts = Counter(d for _, d in team)
|
|
dept_summary = ", ".join(
|
|
f"{d.replace('MLOps ', '').replace('AI Efficiency Tech', 'AI Eff')} {c}"
|
|
for d, c in dept_counts.items()
|
|
)
|
|
n_senior = sum(1 for n, _ in team if n in SENIORS)
|
|
size = len(team)
|
|
late_note = " ⏰한지승 지각시 -1" if any(n == "한지승" for n, _ in team) else ""
|
|
print(f"## {team_name} ({size}명, 시니어 {n_senior}, {dept_summary}){late_note}")
|
|
for name, dept in team:
|
|
tag = " ⭐시니어" if name in SENIORS else ""
|
|
if name == "김재현":
|
|
tag += " 🌱최주니어"
|
|
if name == "한지승":
|
|
tag += " ⏰지각가능"
|
|
print(f" - {name} ({dept}){tag}")
|
|
mapping[name] = team_name
|
|
print()
|
|
|
|
out = Path(__file__).parent / "participants.json"
|
|
out.write_text(
|
|
json.dumps(mapping, ensure_ascii=False, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
print(f"저장: {out}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|