From c76ecbaf32a32108b17ba81e5e5f7688bf25e049 Mon Sep 17 00:00:00 2001
From: th-kim0823
Date: Sat, 25 Apr 2026 19:40:33 +0900
Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B2=B0=EA=B3=BC=EB=AC=BC=20=EC=A0=9C?=
=?UTF-8?q?=EB=AA=A9=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=20=EC=9E=85=EB=A0=A5=20?=
=?UTF-8?q?+=20=ED=88=AC=ED=91=9C=20=EB=9D=BC=EB=94=94=EC=98=A4=20?=
=?UTF-8?q?=ED=91=9C=EC=8B=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- DB 테이블 team_titles 추가
- 어드민 페이지에 팀별 제목 입력 폼 (저장 즉시 반영)
- 투표 라디오 옵션이 '팀1 — 결과물 제목' 형식으로 표시
- 우승 발표/시상 텍스트에도 제목 포함
- 제목 미입력 시 팀명만 (fallback)
Co-Authored-By: Claude Opus 4.7 (1M context)
---
app.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 65 insertions(+), 5 deletions(-)
diff --git a/app.py b/app.py
index f5c07ed..b087dff 100644
--- a/app.py
+++ b/app.py
@@ -53,10 +53,43 @@ def get_conn():
)
"""
)
+ conn.execute(
+ """
+ CREATE TABLE IF NOT EXISTS team_titles (
+ team_name TEXT PRIMARY KEY,
+ title TEXT NOT NULL DEFAULT ''
+ )
+ """
+ )
conn.commit()
return conn
+def get_titles():
+ """팀명 → 결과물 제목 dict. 매 호출 DB 조회 (라이브 반영)."""
+ conn = get_conn()
+ rows = conn.execute("SELECT team_name, title FROM team_titles").fetchall()
+ conn.close()
+ return dict(rows)
+
+
+def set_title(team, title):
+ conn = get_conn()
+ conn.execute(
+ "INSERT INTO team_titles (team_name, title) VALUES (?, ?) "
+ "ON CONFLICT(team_name) DO UPDATE SET title = excluded.title",
+ (team, title.strip()),
+ )
+ conn.commit()
+ conn.close()
+
+
+def fmt_team(team, titles):
+ """팀 라벨 — 제목 있으면 'N팀 — 제목' 없으면 'N팀'."""
+ t = titles.get(team, "")
+ return f"{team} — {t}" if t else team
+
+
def render_voter():
st.title("🗳 해커톤 투표")
st.caption("이름 선택 → 본인 팀 자동 매핑 → 본인 팀 제외 3분야 투표. 한 번만 제출 가능.")
@@ -77,14 +110,21 @@ def render_voter():
return
my_team = PARTICIPANTS[name]
- st.info(f"본인 팀: **{my_team}**")
+ titles = get_titles()
+ st.info(f"본인 팀: **{fmt_team(my_team, titles)}**")
candidates = [t for t in TEAMS if t != my_team]
with st.form("vote", clear_on_submit=False):
st.divider()
picks = {}
for col, label in CATEGORIES:
- picks[col] = st.radio(label, candidates, index=None, key=col)
+ picks[col] = st.radio(
+ label,
+ candidates,
+ index=None,
+ key=col,
+ format_func=lambda t: fmt_team(t, titles),
+ )
submitted = st.form_submit_button("제출")
if submitted:
@@ -144,6 +184,25 @@ def render_admin():
for n in sorted(not_voted):
st.write(f"- {n} ({PARTICIPANTS[n]})")
+ st.divider()
+ st.subheader("📝 팀별 결과물 제목 입력")
+ st.caption("발표 직후 입력하면 투표 페이지에 즉시 반영됩니다.")
+ titles = get_titles()
+ with st.form("titles_form"):
+ new_titles = {}
+ for team in TEAMS:
+ new_titles[team] = st.text_input(
+ team,
+ value=titles.get(team, ""),
+ placeholder="예: 슬랙 멘션 자동 분류기",
+ key=f"title_{team}",
+ )
+ if st.form_submit_button("제목 저장"):
+ for team, title in new_titles.items():
+ set_title(team, title)
+ st.success("제목 저장 완료. 투표 페이지에 반영됨.")
+ st.rerun()
+
st.divider()
st.subheader("📊 분야별 집계")
@@ -163,16 +222,17 @@ def render_admin():
runner_votes = rows[1][1] if len(rows) > 1 else 0
diff = winner_votes - runner_votes
+ winner_label = fmt_team(winner_team, titles)
st.success(
- f"**우승: {winner_team}** — {winner_votes}표 (2위와 {diff}표 차이)"
+ f"**우승: {winner_label}** — {winner_votes}표 (2위와 {diff}표 차이)"
)
public_lines.append(
- f"- {label} 우승: **{winner_team}** ({winner_votes}표, 2위와 {diff}표 차이)"
+ f"- {label} 우승: **{winner_label}** ({winner_votes}표, 2위와 {diff}표 차이)"
)
with st.expander("전체 분포 (진행자만)"):
for team, c in rows:
- st.write(f"- {team}: {c}표")
+ st.write(f"- {fmt_team(team, titles)}: {c}표")
st.divider()
st.subheader("🎤 시상식 발표용 (복사해서 화면 공유)")