feat: 우선순위 기반 1팀 1상 자동 적용

수상 결정 순서:
1. 실용성상(팜레스트, 최고가) → 1위 결정
2. 완성도상(양우산) → 1번 수상자 제외 후 1위
3. 재미상(손선풍기) → 1, 2번 수상자 제외 후 1위

발표(reveal) 순서는 그대로 손선풍기 → 양우산 → 팜레스트 (긴장감).

compute_winners() 헬퍼로 admin/ceremony 둘 다 동일 로직.
admin 분포 expander에 '상위상수상으로 제외' 마커.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-04-25 20:00:56 +09:00
parent d581f4e8e7
commit 1c55b77bc1

84
app.py
View File

@@ -37,6 +37,10 @@ CATEGORIES = [
("utility_team", "🛠 실용성상", "팜레스트 5개"), ("utility_team", "🛠 실용성상", "팜레스트 5개"),
] ]
# 수상 결정 우선순위 (높을수록 먼저 결정, 후순위 상에서 그 팀 제외)
# 팜레스트(실용성) > 양우산(완성도) > 손선풍기(재미)
PRIZE_PRIORITY = ["utility_team", "polish_team", "fun_team"]
def get_conn(): def get_conn():
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
@@ -90,6 +94,36 @@ def fmt_team(team, titles):
return f"{team}{t}" if t else team return f"{team}{t}" if t else team
def compute_winners():
"""
우선순위 기반 1팀 1상 보장.
PRIZE_PRIORITY 순으로 결정, 이미 수상한 팀은 후순위 상에서 제외.
return: dict[col] = (winner_team, winner_votes, diff_with_2nd, all_rows_excluded)
"""
conn = get_conn()
rankings = {}
for col, _, _ in CATEGORIES:
rankings[col] = conn.execute(
f"SELECT {col} AS team, COUNT(*) AS c FROM votes "
f"GROUP BY {col} ORDER BY c DESC, team ASC"
).fetchall()
conn.close()
winners = {}
excluded = set()
for col in PRIZE_PRIORITY:
rows = rankings[col]
filtered = [(t, c) for t, c in rows if t not in excluded]
if not filtered:
winners[col] = None
continue
winner, votes = filtered[0]
runner = filtered[1][1] if len(filtered) > 1 else 0
winners[col] = (winner, votes, votes - runner, filtered)
excluded.add(winner)
return winners, rankings
def render_voter(): def render_voter():
st.title("🗳 해커톤 투표") st.title("🗳 해커톤 투표")
st.caption("이름 선택 → 본인 팀 자동 매핑 → 본인 팀 제외 3분야 투표. 한 번만 제출 가능.") st.caption("이름 선택 → 본인 팀 자동 매핑 → 본인 팀 제외 3분야 투표. 한 번만 제출 가능.")
@@ -204,24 +238,29 @@ def render_admin():
st.rerun() st.rerun()
st.divider() st.divider()
st.subheader("📊 분야별 집계") st.subheader("📊 분야별 집계 (우선순위 적용 — 1팀 1상)")
st.caption(
"수상 결정 순서: 팜레스트(실용성) → 양우산(완성도) → 손선풍기(재미). "
"이미 받은 팀은 후순위 상에서 제외."
)
public_lines = [] # 시상식 발표용 (하위 비공개) winners, rankings = compute_winners()
awarded_teams = {w[0] for w in winners.values() if w}
public_lines = []
for col, label, _ in CATEGORIES: for col, label, prize in CATEGORIES:
rows = conn.execute( rows = rankings[col]
f"SELECT {col} AS team, COUNT(*) AS c FROM votes GROUP BY {col} ORDER BY c DESC, team ASC" st.markdown(f"### {label} ({prize})")
).fetchall()
st.markdown(f"### {label}")
if not rows: if not rows:
st.caption("표 없음") st.caption("표 없음")
continue continue
winner_team, winner_votes = rows[0] result = winners.get(col)
runner_votes = rows[1][1] if len(rows) > 1 else 0 if not result:
diff = winner_votes - runner_votes st.warning("후보 없음 (모두 우선순위 상 수상)")
continue
winner_team, winner_votes, diff, _ = result
winner_label = fmt_team(winner_team, titles) winner_label = fmt_team(winner_team, titles)
st.success( st.success(
f"**우승: {winner_label}** — {winner_votes}표 (2위와 {diff}표 차이)" f"**우승: {winner_label}** — {winner_votes}표 (2위와 {diff}표 차이)"
@@ -230,9 +269,12 @@ def render_admin():
f"- {label} 우승: **{winner_label}** ({winner_votes}표, 2위와 {diff}표 차이)" f"- {label} 우승: **{winner_label}** ({winner_votes}표, 2위와 {diff}표 차이)"
) )
with st.expander("전체 분포 (진행자만)"): with st.expander("전체 분포 — raw (제외 적용 전)"):
for team, c in rows: for team, c in rows:
st.write(f"- {fmt_team(team, titles)}: {c}") marker = ""
if team in awarded_teams and team != winner_team:
marker = " 🚫상위상수상으로 제외"
st.write(f"- {fmt_team(team, titles)}: {c}{marker}")
st.divider() st.divider()
st.subheader("🎤 시상식 발표용 (복사해서 화면 공유)") st.subheader("🎤 시상식 발표용 (복사해서 화면 공유)")
@@ -256,19 +298,15 @@ def render_ceremony():
return return
titles = get_titles() titles = get_titles()
conn = get_conn() winners, _ = compute_winners()
# CATEGORIES 순서로 reveal (손선풍기 → 양우산 → 팜레스트)
results = [] results = []
for col, label, prize in CATEGORIES: for col, label, prize in CATEGORIES:
rows = conn.execute( result = winners.get(col)
f"SELECT {col} AS team, COUNT(*) AS c FROM votes " if result:
f"GROUP BY {col} ORDER BY c DESC, team ASC" winner, votes, diff, _ = result
).fetchall() results.append((label, prize, winner, votes, diff))
if rows:
winner, votes = rows[0]
runner = rows[1][1] if len(rows) > 1 else 0
results.append((label, prize, winner, votes, votes - runner))
conn.close()
if "ceremony_step" not in st.session_state: if "ceremony_step" not in st.session_state:
st.session_state.ceremony_step = 0 st.session_state.ceremony_step = 0