Commit Graph

52 Commits

Author SHA1 Message Date
e61e4d833c 문구 부드럽게 수정 2026-04-27 12:56:28 +00:00
th-kim0823
addde1a0ea chore: repo 정리 — entrypoint.sh 추가, runtime state gitignore
- entrypoint.sh: 첫 부팅 시 assign_teams.py 자동 실행 (시드)
- Dockerfile: ENTRYPOINT 적용, DATA_PATH=/app/data/hackathon.json
- hackathon.json (root) 삭제 — data/ 디렉터리로 이전 (gitignore)
- teams.md 추적 해제 (assign_teams.py가 매번 재생성)
- results_*.json + data/ gitignore 추가
- .env.example 삭제 (compose에 ADMIN_TOKEN 박제, .env 불필요)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:31:29 +09:00
th-kim0823
447f067ae9 fix: admin stage 진행 — radio + 적용 버튼 제거 (session_state mismatch 버그)
이전/다음 두 버튼만 유지. radio key 고정으로 stage 변경 후
session_state가 stale → 적용 버튼이 의도치 않게 뜨던 문제 제거.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:23:10 +09:00
th-kim0823
75aa0349ff fix: admin UX — QR placeholder, 투표 상태 분리, 제목 placeholder 다양화, 시상 빈블럭 가드
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 21:18:48 +09:00
th-kim0823
e872b841e3 feat: PUBLIC_BASE_URL default = https://hackerthon.altair823.xyz
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:12:18 +09:00
th-kim0823
02e186a860 fix: UX — start.sh 자동 LAN IP 감지 + topics/vote 레이아웃 조정
- start.sh: 호스트 LAN IP 자동 감지 후 PUBLIC_BASE_URL 세팅, 이제 QR이 172.x 컨테이너 IP 대신 실제 LAN IP를 가리킴
- docker-compose.yml: PUBLIC_BASE_URL 환경변수 pass-through 추가
- app.py: topics min-height 480→360, font-size/line-height 상향, vote counter를 QR 위로 이동, pct 계산 단순화
- README: 실행 섹션 교체 (start.sh 권장, raw/최소 방식 병기)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 21:07:11 +09:00
th-kim0823
02e67baa77 fix: 큰 화면 CSS 라이트 모드 대응
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:56:02 +09:00
th-kim0823
50615e0069 fix: admin URL labels + topics-empty 안내문 + README 테스트 수
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:31:24 +09:00
th-kim0823
a0774ff0d3 fix: assign_teams seed에 current_stage 포함 (디스크 JSON 완전성)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:22:41 +09:00
th-kim0823
56c273779e docs: README — 새 흐름 + URL + topics 설명
Stage 1-6 흐름 갱신 (팀편성 → 안내 → 해킹 → 발표 → 투표 → 시상)
모바일 투표 QR URL 명시 (/?mode=vote)
topics.categories + settings.current_stage 데이터 구조 설명 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:20:19 +09:00
th-kim0823
918fac2742 docs: show-urls.sh — 큰 화면 + 모바일 vote URL 추가 2026-04-27 20:19:25 +09:00
th-kim0823
3373f5729f feat: admin public_base_url override (QR target)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:18:03 +09:00
th-kim0823
c3bbb4e959 feat: admin 주제 편집 — JSON 직접 편집 + 검증
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:15:25 +09:00
th-kim0823
f9cc5be2f0 feat: admin 주제 편집 — form mode (4 카테고리 expander)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:13:24 +09:00
th-kim0823
4c414b37a6 feat: admin stage 진행 section (이전/직접/다음)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:10:59 +09:00
th-kim0823
4cd2484374 feat: stage vote — QR + 카운터 + autorefresh 3초
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:08:41 +09:00
th-kim0823
dd690fcb62 feat: stage topics — 2×2 카테고리 그리드 + 10주제 list + 색상
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:06:18 +09:00
th-kim0823
b27c2a1873 feat: stage intro — 팀편성 4×2 그리드 + 순서/시상 박스 2026-04-27 20:03:15 +09:00
th-kim0823
54fa724420 feat: render_show dispatcher + 큰 화면 CSS, default '/' 변경
- SHOW_CSS 상수 추가 (show-stage-*, show-team-*, show-cat-*, show-vote-* 클래스)
- render_show() dispatcher + 3개 skeleton stage 함수 추가 (T9-T11에서 본구현)
- main() default → render_show(), ?mode=vote → render_voter() 명시 라우팅

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 20:00:34 +09:00
th-kim0823
a353f2f337 feat: voter stage gate (current_stage=vote && voting_open)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:57:38 +09:00
th-kim0823
874de0a46d feat: QR PNG 생성 + vote URL resolver
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:54:31 +09:00
th-kim0823
546bd54700 feat: DEFAULT_TOPICS_SEED + ensure_topics_seeded
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:51:35 +09:00
th-kim0823
6c6929a505 feat: topics 헬퍼 (get_topics, update_topics)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:48:34 +09:00
th-kim0823
70f326eb20 feat: stage 헬퍼 (get_stage, set_stage, can_accept_votes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:47:23 +09:00
th-kim0823
c675b8c297 feat: _empty_state — current_stage + topics 키 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:44:38 +09:00
th-kim0823
3311002d95 deps: qrcode[pil] + streamlit-autorefresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:40:39 +09:00
th-kim0823
d70746cf6a docs: implementation plan — 해커톤 진행 앱 (19 tasks)
TDD bite-sized: deps → schema → helpers → seed → QR/URL → voter gate
→ show dispatcher → 3 stages → admin stage/topics/url → docs → e2e → smoke.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:30:47 +09:00
th-kim0823
54e28e6f1e docs: spec — 해커톤 진행 앱 (intro → topics → vote → ceremony)
Stage 기반 큰 화면 흐름 + 모바일 QR 투표. 어드민에서 stage 컨트롤
+ 주제 4 카테고리 × 10 편집. 기존 voter/admin/ceremony는 그대로.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:26:07 +09:00
th-kim0823
067e25116a feat: admin URL 쉽게 접근 - show-urls.sh + admin 내부 링크
- show-urls.sh: localhost + LAN IP 포함 모든 URL 출력 (참가자/어드민/시상식)
- admin 페이지에 다른 페이지 URL expander 추가 (ceremony 링크 클릭 가능)
- README에 사용법 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:35:15 +09:00
th-kim0823
6e517be918 refactor: DB 제거 → 단일 hackathon.json (JSON only)
DB(sqlite + WAL) 제거. 모든 state를 단일 JSON 파일로 통합.
일회용/내부용이라 유지보수성/확장성보다 단순성 우선.

변경:
- app.py: sqlite3 import 제거. load_data/save_data + threading.RLock + atomic write
  - votes: list of dict
  - titles, tie_breaks, settings: dict
  - people: roster (assign_teams가 채움)
  - 누락 키 자동 보강
- assign_teams.py: hackathon.json 단일 출력. 기존 votes/titles 보존
- Dockerfile/compose: votes.db volume 제거. hackathon.json read-write mount
- tests/e2e.py: 12개 (12/12 통과). load/save/insert_vote/clear_votes/atomic 추가
- README: 새 데이터 구조 문서화
- roster.json/participants.json 제거 (hackathon.json으로 통합)

호스트 편집 워크플로:
- jq/vi로 hackathon.json 직접 편집
- 앱 매 요청 reload — 컨테이너 재시작 불필요

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:25:46 +09:00
th-kim0823
6cfc75e3b8 test: E2E 테스트 10개 (핵심 비즈니스 로직)
tests/e2e.py — 컨테이너에서 직접 실행:
1. roster.json 로드 (34명, 7팀, 한지승 포함, 김태현 제외)
2. 투표 마감 토글
3. 기본 winner (priority 충돌 없음)
4. priority 1팀 모든 분야 1위 → 다음 팀에 자동 이양
5. 동률 status='tie'
6. tie_break 적용 시 status='ok'
7. voter_name UNIQUE 강제
8. 팀 제목 영속
9. archive 파일 생성
10. archive 동률 미해결 시 skip

실행:
  docker cp tests/e2e.py hackathon-vote:/tmp/e2e.py
  docker exec hackathon-vote python3 /tmp/e2e.py

결과: 10/10 통과

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:59:30 +09:00
th-kim0823
bf4d3e73cc feat: roster.json 단일 명단 파일 + 핫리로드
호스트에서 직접 편집 가능한 단일 JSON으로 명단 일원화.
앱이 매 요청마다 디스크에서 reload → 컨테이너 재시작 불필요.

변경:
- roster.json 새 형식: {"people": [{"name", "team", "dept", "senior", "notes"}, ...]}
- assign_teams.py: roster.json + legacy participants.json 둘 다 출력
- app.py: get_participants() / get_teams() 매 호출 reload
  - PARTS = get_participants() / TEAMS = get_teams() 함수 안에서 호출
  - 모듈 레벨 PARTICIPANTS/TEAMS 제거
  - load_roster() roster.json 우선, 없으면 legacy fallback
- docker-compose: roster.json + participants.json 둘 다 mount
- Dockerfile: ROSTER env + roster.json COPY

사용자 워크플로:
- 사람 다른 팀 옮기기: roster.json에서 그 사람 'team' 값만 변경
- 자동 배정 재실행: python3 assign_teams.py

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:43:16 +09:00
th-kim0823
3f40f3f47a feat: 동시성 + UX + 결과 백업 보강
#14 이미 투표한 사람 UX
- selectbox에  마크
- 선택 시 친절한 에러 (재투표 불가 안내, 진행자 문의 가이드)

#15 SQLite WAL 모드
- get_conn에서 PRAGMA journal_mode = WAL
- synchronous = NORMAL (성능 + 안전 균형)
- 동시 read/write 충돌 방지 (35명 동시 제출 안전)
- timeout 10초 (busy 시 retry)

#17 시상 결과 archive
- ceremony 진입 시 1회 winners를 results_<timestamp>.json 저장
- DB 손실 보험. 위치: /data 볼륨

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:42:36 +09:00
th-kim0823
638f0b36c8 feat: 엣지 케이스 5개 처리
1. 투표 마감 락 (settings 테이블 + 어드민 토글)
   - 시상 도중 결과 바뀜 방지
   - ceremony 진입 시 voting_open이면 경고 + 차단
2. 빈 결과 ceremony 차단
   - 투표 0건이면 진입 불가
3. 타임존 KST (Dockerfile tzdata + TZ=Asia/Seoul)
   - 감사 로그 시각 정확
4. CSV UTF-8 BOM
   - Excel에서 한글 정상 표시
5. 사번 입력 안내 강화
   - placeholder + help: 민감정보 입력 금지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:19:12 +09:00
th-kim0823
5189d27261 fix: ceremony에서 동률 부문/후보 정보 숨김 (발표 spoiler 방지)
큰 화면 공유 상황에서 동률 후보가 노출되면 발표 임팩트 ↓.
'시상 준비 중입니다' 단순 안내만 표시. 진행자는 별도 화면에서
어드민 처리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:15:05 +09:00
th-kim0823
f689da3c6e feat: 동률 처리 - 즉석 추첨 또는 수동 선택
- DB tie_breaks 테이블 (category, winner_team, method, decided_at)
- compute_winners()가 동률 시 status='tie' 반환, tied 후보 표시
- 어드민: 동률 부문에 🎲 추첨 버튼 + 수동 선택 라디오 + 결정 취소
- 우승 표시에 결정 방식 태그 (🎲 추첨 / 🖊️ 수동)
- ceremony: 동률 미해결 발견 시 진입 차단, 어드민 처리 유도

흐름:
1. 어드민에서 동률 알림 확인
2. 즉석 추첨 또는 수동 선택으로 결정
3. ceremony 진입하면 정상 reveal

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:12:46 +09:00
th-kim0823
aac609eb59 feat: 사번 입력 + 감사 로그 (사칭 추적)
- 투표 폼에 사번 입력 필수 추가
- DB votes 테이블에 employee_id 컬럼 (마이그레이션 자동)
- 어드민 감사 로그 expander: 시각/이름/사번/본인팀/투표내역 표
- CSV 내려받기 버튼
- 같은 사번이 여러 이름으로 투표 시 자동 의심 마크
- 안내 expander에 사번 입력/추적 설명 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:07:02 +09:00
th-kim0823
602e4779fe feat: 투표 페이지 상단에 투표/수상 로직 안내 expander
투표 방식, 1팀 1상 우선순위 (팜레스트 > 양우산 > 손선풍기),
시상 발표 순서를 첫 화면에 펼침 상태로 표시.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:03:10 +09:00
th-kim0823
1c55b77bc1 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>
2026-04-25 20:00:56 +09:00
th-kim0823
d581f4e8e7 feat: 팜레스트→실용성상, 마무리 문구 '수고하셨습니다!'
- 가격 순 매칭: 팜레스트(최고가)→실용성상, 양우산→완성도상, 손선풍기→재미상
- ceremony 마지막 화면: '모든 시상 완료' → '수고하셨습니다!'

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:57:09 +09:00
th-kim0823
b047c589d8 feat: 시상식 reveal 페이지 (?mode=ceremony)
3 단계 진행:
1. 시상식 시작 화면 → 시작 버튼
2. 부문별 announce (label + 상품) → 🥁🥁🥁 → 우승팀 공개 버튼
3. 우승팀 reveal: gradient gold 큰 폰트 + balloons + fadeIn 애니메이션
4. 다음 부문 → 반복 → 모든 시상 완료 (snow + balloons)

진행자 클릭만으로 진행. session_state로 단계 관리.
CATEGORIES에 상품 매핑 (재미상=손선풍기, 완성도상=팜레스트, 실용성상=양우산).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:52:32 +09:00
th-kim0823
0c73d655a7 feat: .env 기반 ADMIN_TOKEN 관리
- docker-compose가 .env 자동 로드
- .env는 gitignore (token 노출 방지)
- .env.example 템플릿 추가
- README 절차 업데이트

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:45:18 +09:00
th-kim0823
c76ecbaf32 feat: 결과물 제목 라이브 입력 + 투표 라디오 표시
- DB 테이블 team_titles 추가
- 어드민 페이지에 팀별 제목 입력 폼 (저장 즉시 반영)
- 투표 라디오 옵션이 '팀1 — 결과물 제목' 형식으로 표시
- 우승 발표/시상 텍스트에도 제목 포함
- 제목 미입력 시 팀명만 (fallback)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:40:33 +09:00
th-kim0823
64404c27ed feat: Docker 패키징 (로컬 테스트 + 홈서버 배포)
- Dockerfile: python:3.12-slim 베이스, headless streamlit
- docker-compose.yml: ADMIN_TOKEN 환경변수, votes.db 영속 볼륨, participants.json read-only mount
- .dockerignore: 빌드 컨텍스트 최소화

테스트:
- docker compose build OK
- 컨테이너 실행 후 http://localhost:8501 200 OK 확인

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:35:44 +09:00
th-kim0823
68a04d04fe feat: 팀 배정 확정 - teams.md 박제
시드 20260435 기준 최종 7팀 배정 박제.
진행자 인쇄/공유용 마크다운 자동 생성.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:30:21 +09:00
th-kim0823
ed0a2fed86 feat: 김영관 ↔ 이준석 수동 swap (MANUAL_SWAPS 메커니즘 추가)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:26:23 +09:00
th-kim0823
1db7ad397d feat: 한지승 지각 가능성 대비 제약 추가
- 한지승 4명팀 배치 금지 (지각 시 3명 방지)
- 한지승 팀에 다른 시니어 ≥1 필수 (지각 시 시니어 0 방지)
- 출력에  마크 추가

결과: 한지승 → 팀5 (5명), 팀5 다른 시니어 김병훈 동행

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:20:26 +09:00
th-kim0823
aa427d6670 feat: 시니어 균등 분배 + 김태현 진행요원 제외
변경:
- 김태현 PEOPLE에서 제거 (진행요원, 35→34명)
- 팀 사이즈 [5,5,5,5,5,5,4] 가변
- 시니어 명단 10명 정의 (Platform/Data/HPC/System만)
- 알고리즘 일반화: 가변 사이즈 + 부서 균등 + 시니어 균등 + 모든 팀 ≥1 시니어/EffTech
- 시드 재시도로 모든 제약 만족
- 김재현(최주니어) 표시

결과: 시니어 [1,1,1,2,2,1,2]

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:15:12 +09:00
th-kim0823
3453217e62 refactor: 부서별 ceil-aware 균등 분배 알고리즘
기존 round-robin은 운에 따라 부서 분포 max-min=2 발생 (예: HPC 2,1,0,1,1,2,1).
ceil 슬롯을 ceil_count 적은 팀에 우선 배정하여 모든 부서 max-min ≤ 1 보장.

결과:
- EffTech [1,2,2,2,2,2,1]
- System  [2,1,1,1,1,1,1]
- HPC     [1,1,1,1,1,1,2]
- Data    [1,1,1,1,0,1,1]
- Platform [0,0,0,0,1,0,0] (1명)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:08:56 +09:00
th-kim0823
ffce2b9bfc fix: EffTech 신규 합류 화합 위해 모든 팀에 EffTech 1명 이상 필수
기존 제약(같은 부서 ≤2명) 만으로는 EffTech 0명인 팀 발생 (재배정 전 팀2).
신규 부서 ≥1명 제약 추가로 모든 팀이 기존-신규 섞이도록 보장.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:05:20 +09:00