From 1c55b77bc1a478b931223eb5fb42ec376adfe916 Mon Sep 17 00:00:00 2001
From: th-kim0823
Date: Sat, 25 Apr 2026 20:00:56 +0900
Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20?=
=?UTF-8?q?=EA=B8=B0=EB=B0=98=201=ED=8C=80=201=EC=83=81=20=EC=9E=90?=
=?UTF-8?q?=EB=8F=99=20=EC=A0=81=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
수상 결정 순서:
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)
---
app.py | 84 ++++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 61 insertions(+), 23 deletions(-)
diff --git a/app.py b/app.py
index f284aed..458a8e4 100644
--- a/app.py
+++ b/app.py
@@ -37,6 +37,10 @@ CATEGORIES = [
("utility_team", "🛠 실용성상", "팜레스트 5개"),
]
+# 수상 결정 우선순위 (높을수록 먼저 결정, 후순위 상에서 그 팀 제외)
+# 팜레스트(실용성) > 양우산(완성도) > 손선풍기(재미)
+PRIZE_PRIORITY = ["utility_team", "polish_team", "fun_team"]
+
def get_conn():
conn = sqlite3.connect(DB_PATH)
@@ -90,6 +94,36 @@ def fmt_team(team, titles):
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():
st.title("🗳 해커톤 투표")
st.caption("이름 선택 → 본인 팀 자동 매핑 → 본인 팀 제외 3분야 투표. 한 번만 제출 가능.")
@@ -204,24 +238,29 @@ def render_admin():
st.rerun()
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:
- rows = conn.execute(
- f"SELECT {col} AS team, COUNT(*) AS c FROM votes GROUP BY {col} ORDER BY c DESC, team ASC"
- ).fetchall()
-
- st.markdown(f"### {label}")
+ for col, label, prize in CATEGORIES:
+ rows = rankings[col]
+ st.markdown(f"### {label} ({prize})")
if not rows:
st.caption("표 없음")
continue
- winner_team, winner_votes = rows[0]
- runner_votes = rows[1][1] if len(rows) > 1 else 0
- diff = winner_votes - runner_votes
+ result = winners.get(col)
+ if not result:
+ st.warning("후보 없음 (모두 우선순위 상 수상)")
+ continue
+ winner_team, winner_votes, diff, _ = result
winner_label = fmt_team(winner_team, titles)
st.success(
f"**우승: {winner_label}** — {winner_votes}표 (2위와 {diff}표 차이)"
@@ -230,9 +269,12 @@ def render_admin():
f"- {label} 우승: **{winner_label}** ({winner_votes}표, 2위와 {diff}표 차이)"
)
- with st.expander("전체 분포 (진행자만)"):
+ with st.expander("전체 분포 — raw (제외 적용 전)"):
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.subheader("🎤 시상식 발표용 (복사해서 화면 공유)")
@@ -256,19 +298,15 @@ def render_ceremony():
return
titles = get_titles()
- conn = get_conn()
+ winners, _ = compute_winners()
+ # CATEGORIES 순서로 reveal (손선풍기 → 양우산 → 팜레스트)
results = []
for col, label, prize in CATEGORIES:
- rows = conn.execute(
- f"SELECT {col} AS team, COUNT(*) AS c FROM votes "
- f"GROUP BY {col} ORDER BY c DESC, team ASC"
- ).fetchall()
- 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()
+ result = winners.get(col)
+ if result:
+ winner, votes, diff, _ = result
+ results.append((label, prize, winner, votes, diff))
if "ceremony_step" not in st.session_state:
st.session_state.ceremony_step = 0