Commit Graph

27 Commits

Author SHA1 Message Date
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
th-kim0823
e661372f84 feat: 35명 7팀 배정 + 이름 기반 자동 팀 매핑
- assign_teams.py: 부서 다양성 제약(같은 부서 ≤2명) 시드 고정 배정
- participants.json: 이름→팀 매핑 산출물
- app.py: 이름 선택 → 본인 팀 자동 표시 (수동 입력 부정 차단)
- 어드민 참여율 메트릭 + 미투표자 목록

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:02:27 +09:00
th-kim0823
5fe8842e88 feat: 해커톤 투표 앱 초기 구현
- 35명/7팀/3분야(재미·완성도·실용성) 투표
- 본인 팀 제외 자동 처리
- 이름 UNIQUE 중복 방지
- 진행자 어드민 페이지: 1위와 2위 차이만 공개, 하위 팀 표수는 비공개
- sqlite 단일 파일 저장

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