88 Commits

Author SHA1 Message Date
c964572a9c Replace point ledger with wallet engine and rework reward earning
- 지갑 문서(users/{uid}/wallet/current)와 문서 ID=멱등키 원장(pointLedger/{txId})으로 포인트 엔진 교체 — 기존 balanceAfter 최신 row 조회 방식 폐기
- 모든 포인트 변경은 pointService.applyPointChangesTx 단일 경로로 처리, available+reserved == totalEarned-totalSpent 불변식을 매 커밋 검증
- 출석 리워드 개편: 일일 20P, 연속 5일 +50P(사이클당 1회), 10일 단위 +100P — attendance/state 문서에 스트릭 상태 저장, 주간·월간 보너스 폐기
- 승부예측 일일 리워드 정산 신설: 전체 참여 50P + 성공 100P + 퍼펙트 50P, judgeDay 이후 voteHistory.rewardSettledAt 플래그와 원장 멱등키로 배치 재실행에도 중복 지급 차단
- 관리자 포인트 지급·회수(adminPointService)와 수동 재정산 디버그 라우트(/debug/settle-reward) 추가
- 소비처 없던 티켓 시스템(dailyAllKill·weeklyMaster)과 위클리마스터 판정 흐름 전체 제거 — StatsResponse.tickets 필드 삭제로 클라 응답 스키마 변경
2026-07-16 14:25:42 +09:00
9daba193e4 Add draw prediction support to vote pipeline
- selectedTeamCode에 무승부 예약 코드 'DRAW' 허용 (sideOf가 draw 진영 반환)
- RTDB /votes/{gameId}/counts에 drawCount 추가, 투표 요약 응답에 포함
- 채점 규칙 변경: 무승부 경기는 'DRAW' 투표만 적중, 팀 투표는 오답 (기존: 전원 적중 처리)
- 무승부 경기(completed + winningTeamCode 없음)도 onGameCompleted 트리거에서 즉시 채점 (기존: dailyArchive 리컨실까지 지연)
- 어드민 수동 종료(POST /admin/game/end)에 winningTeamCode: 'DRAW' 지원
- VOTE_FLOW.md 갱신, voteRepository 무승부 투표/카운트 재분배 테스트 추가
2026-07-10 16:51:56 +09:00
de35ef21ce Align final score order with team name order in tool output
- 종료 스코어를 "원정:홈" 위치 표기에서 팀명 순서(우리팀:상대)로 정렬 — 순서 불일치로 모델이 홈 승리(8:3)를 "3:8 패배"로 오독하고 패배 서사까지 지어내던 문제 수정
- formatLineupResult 종료 스코어에 승패 명기 추가(기존엔 승패 표기 없음)
- 원정 관점 회귀 테스트 추가. 프로브 실측: 동일 질문 5회 중 오독 2회 → 0회
2026-07-03 16:42:33 +09:00
148e26d1bb Add head-to-head record lookup to rank snapshot tool
- get_team_rank_snapshot에 vs 인자 추가 — 순위표의 팀간 승패표(vsRecords)에서 두 팀의 올 시즌 맞대결 승패무를 반환
- formatH2hResult 순수 함수 분리 및 단위 테스트 추가
- 오늘 상대가 아닌 임의 팀과의 상대 전적 질문을 도구 1회 호출로 처리(일정→전적 체이닝도 가능). 기존에는 vsRecords가 도구 계층에서 버려져 모델이 접근할 경로가 없었음
2026-07-03 16:41:44 +09:00
9e24611db0 Implement a fallback mechanism for empty model responses after tool calls
- 도구 호출 후 모델이 빈 응답을 반환하는 경우, 검열 문구 대신 재시도를 유도하는 `EMPTY_REPLY_NOTICE`를 표시하도록 로직을 개선했습니다.
- `chatProviderService`에 무텍스트 STOP 응답 방어 로직을 추가하여, 함수 호출 모드를 NONE으로 강제한 후 1회 재시도를 수행하도록 처리했습니다.
- 분석 서비스의 스키마 버전을 2로 올리고, `ChatOutcome`에 `empty` 타입을 추가하여 빈 응답 사례를 필터링과 구분하여 통계에 기록합니다.
2026-07-03 15:32:50 +09:00
4d1a46e587 Implement soft-delete for user accounts and add account purge job
- 회원 탈퇴 시 즉시 삭제 대신 `active: false` 및 `deactivatedAt`을 기록하여 30일간 데이터를 유예하는 소프트 삭제 방식을 도입했습니다.
- 경기 취소 시 투표를 삭제하는 대신 `cancelled: true`로 마킹하여 참여 흔적을 보존하고, 통계 집계에서만 제외하도록 로직을 변경했습니다.
- 유예 기간이 지난 비활성 계정을 영구적으로 파기하는 `accountPurge` 스케줄러를 추가했습니다.
2026-07-02 11:29:40 +09:00
e980410858 Add kboTodayKst helper and support "today" alias in KBO endpoints
- KBO 데이터 갱신 시점(02:00)을 고려하여 03:00 이전 요청 시 전일 데이터를 반환하도록 `kboTodayKst` 유틸리티를 추가했습니다.
- `kboHandlers`에서 `day` 파라미터에 "today" 값을 전달할 경우, 위 컷오프 로직이 적용된 날짜를 자동으로 계산하도록 개선했습니다.
- 신규 테스트 코드를 통해 새벽 시간대 경계값에서도 날짜가 정확하게 계산되는지 검증했습니다.
2026-06-30 09:59:29 +09:00
526a8ef731 Derive today via todayKst in kboRepository
- mergeLiveIntoSchedule·fetchScheduleMonth의 formatYmd(new Date())를 todayKst().replace로 교체 — 서버 TZ 의존 제거, 정규 KST 헬퍼로 통일
- 명시적 날짜용 formatYmd와 경기 시간대 시(hour) 게이트는 그대로 유지
- TODO.md 해당 항목 종결
2026-06-27 00:20:44 +09:00
6d78ef5c7a Consolidate KST date helpers and fix tuesdayOf timezone leak
- dayOfWeekKst → dayOfWeek, addDaysKst → addDays: 결과가 타임존과 무관한 헬퍼라 이름에서 KST 제거(요일·달력 산술은 TZ 불변)
- tuesdayOf: 로컬 Date 생성(new Date(y,m-1,d-offset))을 제거하고 dayOfWeek·addDays 기반으로 재작성 — 런타임 TZ에 따라 하루 어긋나던 잠재 버그 수정(UTC+14에서 검증)
- 호출처(attendance·dailyArchive·chat·judgment·stats 및 테스트) 일괄 갱신
2026-06-27 00:20:35 +09:00
8be33be10f Use todayKst helper instead of manual KST offset in game-detail
- isGameDateInFuture에서 Date.now()+9h 수동 합성 대신 기존 todayKst() 사용
- 서버 TZ=Asia/Seoul 전제로 동작 동등, 코드베이스 시간 처리 컨벤션과 일치
2026-06-26 23:06:18 +09:00
fc285a54c6 Cache KBO live game list with TTL and game-hour gate
- gatherUserContext가 채팅 매 호출마다 오늘 라이브 일정을 fetch하던 것을 60초 인메모리 캐시로 공유 — 같은 시간창 호출이 1회 크롤을 공유
- KST 경기 시간대(14시 이후) 밖이면 라이브 merge를 건너뛰어 하루 대부분의 불필요한 외부 KBO 크롤 제거
- 동시 사용자 증가 시 외부 엔드포인트 부하·응답 레이턴시 완화
2026-06-26 23:06:08 +09:00
603486d629 Support multi-turn and scanner-safe chat probing in debug endpoint
- debug/chatProbe에 messages 배열을 받는 멀티턴 경로 추가 — 전체 대화를 stateless로 리플레이해 atkgen 등 대화형 probe 지원
- 빈/누락 content를 400 대신 200으로 안전 반환 — 외부 보안 스캐너(garak)가 4xx를 치명적 에러로 보고 런 전체를 중단하는 문제 방지
- chatProbeService에 runChatProbeConversation/runMessages 추가(저장·쿼터 없는 읽기 전용 경로 유지)
2026-06-26 23:05:58 +09:00
5f519afcbf Add human-readable labels to chat tool calls for UI rendering.
- `ChatToolCallInfo` 타입에 `label` 필드를 추가하여 클라이언트가 도구 호출을 직관적인 칩 형태로 표시할 수 있도록 개선했습니다.
- `chatToolService`에 도구별 라벨 매핑 테이블(`TOOL_LABELS`)과 이를 적용하는 `withToolLabels` 함수를 구현하여, API 응답 시점에만 라벨이 동적으로 결합되도록 설계했습니다.
- 기존 저장소(Firestore) 로직에는 영향을 주지 않으면서, 메시지 조회 및 전송 결과 반환 시점에 라벨을 주입하여 표현 계층의 유연성을 확보했습니다.
- 테스트 코드를 수정하여 도구 호출 응답 및 이력 조회 시 라벨이 정확하게 포함되는지 검증했습니다.
2026-06-26 09:22:08 +09:00
e466676210 Add anonymous sign-in provider support to user service and types.
- `Provider` 타입에 `anonymous`를 추가하여 비회원 인증 상태를 명시적으로 처리합니다.
- `userService`의 인증 제공자 식별 로직에 `anonymous` 타입을 포함하여 예외 발생을 방지하고 유연한 사용자 관리를 지원합니다.
2026-06-25 17:04:12 +09:00
0f8443bc40 Add client request doc for chat navigation routes
- 짹 채팅 화면 이동(딥링크) 라우트 목록을 클라이언트에 요청하는 브리프 문서 추가
- route 키·라벨·실제 이동 경로·파라미터·인증과 응답 JSON 스키마, 동작 흐름·제약 명시
2026-06-24 14:08:34 +09:00
f79cf6d0a0 Add in-app navigation actions to chat responses
- 모델이 화면 안내 시 [[NAV:route]] 마커를 붙이면 서버가 검증·제거 후 actions[]로 응답·이력에 실어, 클라가 해당 화면으로 이동하는 버튼을 렌더할 수 있게 함
- chatNavService(마커 파서 + APP_ROUTES 12개 라우트·라벨), NavAction/AppRoute 타입, finalizeExchange 저장·멱등 재반환·이력 매핑까지 전 경로 연결
- 위기/필터로 교체된 응답에는 액션을 붙이지 않고, 목록 밖 route는 드롭(모델 환각 방어)
- actions는 route+label만 전달(실제 경로 매핑·이동은 클라가 보유)
- 분석 이벤트에 navRoutes/navCount 추가, chatNavService 단위 테스트 추가
2026-06-24 14:08:34 +09:00
81aa1f2fb6 Implement chat analytics logging to track usage and performance metrics.
- 채팅 서비스의 관측성을 확보하기 위해 `chatAnalyticsService`를 도입하여 주요 이벤트(요청 결과, 토큰 사용량, 지연 시간 등)를 구조화된 로그로 기록합니다.
- 개인정보 보호를 위해 메시지 본문은 제외하고 길잇값과 메타데이터 위주로 수집하며, 비동기 로그 싱크를 통해 서비스 응답 지연을 방지했습니다.
- `sendMessage`의 각 단계(차단, 에러, 정상 응답 등)에 로깅을 통합하고, 테스트 코드를 통해 스키마 준수 및 필드 파생 로직의 정확성을 검증했습니다.
2026-06-24 10:02:51 +09:00
6595523afb Refine chat prompts to prevent live-viewing illusions and lecture-like behavior.
- 실시간 중계 상황을 직접 보고 있는 것처럼 말하는 페르소나의 암시를 제거하고, 라이브 스코어 제공 불가 안내를 명확히 했습니다.
- 'bestfriend'와 'commentator' 스타일 모두에서 사용자를 훈계하거나 건전한 응원을 강요하는 설교적 프롬프트를 배제했습니다.
- 사용자의 승리 주장 시 상황을 의심하거나 초를 치지 않고 자연스럽게 호응하도록 유도하며, 점수판 농담 등 스타일별 리액션 가이드를 구체화했습니다.
- 팀 홈구장 정보를 오늘 경기 장소로 단정하지 않도록 금칙을 추가하고, 관련 테스트 케이스를 보강하여 프롬프트의 일관성을 검증했습니다.
2026-06-24 08:11:18 +09:00
ba09cce185 Harden claim-acceptance against fabricated scores.
- 적대적 QA에서 사용자가 거짓 구체 스코어("어제 10대 0 대승")를 주장하면 그 수치를 사실로 받아 각색하던 문제를 보완했습니다.
- bestfriend·commentator 스타일의 "결과 주장 수용" 항목에, 승패 분위기는 받되 사용자가 댄 구체 스코어·기록 수치는 도구로 확인되지 않으면 단정·반복하지 말고 없는 경기를 각색하지 않도록 명시했습니다.
2026-06-23 13:57:07 +09:00
4248746bc0 Add team persona packs for the remaining 8 KBO clubs.
- KT·NC·SSG·LG·KIA·롯데·삼성·키움 8개 구단의 팀 페르소나 팩을 추가해 DEFAULT_TEAM_PERSONAS가 10개 구단 전체를 커버하도록 했습니다(이전에는 HH·OB만 실팩, 나머지는 generic fallback).
- 모든 팀 블록을 톤-중립으로 작성했습니다: 1인칭·반말/존댓말 샘플·말버릇 없이 팀색(연고·상징·팬 정서·팀색 어휘·라이벌·금기)만 담아, 말투는 스타일 팩이 전적으로 정하도록 했습니다.
- 10개 구단 모두 generic이 아닌 실팩을 쓰는지 검증하는 테스트를 추가했습니다.
2026-06-23 13:46:24 +09:00
772c961383 Give each style a self-reference name instead of "AI 캐릭터".
- StylePack에 name 필드를 추가하고 스타일별 이름을 정했습니다(bestfriend = "베스트프랜드 짹", commentator = "해설가 짹").
- 각 페르소나 아키타입에 이름을 명시하고, AI 정체성 규칙을 "자기 지칭은 페르소나 이름을 쓰되, AI임을 직접 물으면 부정하지 않는다"로 갱신했습니다. 밋밋한 "AI 캐릭터" 단독 지칭을 지양합니다.
2026-06-23 13:33:53 +09:00
9f7666a73f Add style override to the debug chat probe.
- 디버그 chatProbe·chatProbeSheet에 style 쿼리 파라미터를 추가해, prod config(config/chat.stylePack)를 바꾸지 않고도 스타일을 A/B로 검증할 수 있게 했습니다.
- chatProbeService의 buildProbeEnv/runChatProbe/runChatProbeBatch에 styleOverride를 연결하고, 프로브 시트 메타·마크다운에 적용 스타일을 표기했습니다.
2026-06-23 13:27:17 +09:00
4e98532f6c Add commentator style pack and make team personas tone-neutral.
- 정중한 해설위원 톤의 "commentator" 스타일 팩을 추가하고 STYLE_PACKS에 등록했습니다(bestfriend/commentator 2종).
- 팀 페르소나에서 말투 요소를 제거했습니다: 반말 샘플 문장을 걷어내고, 1인칭("나"/"저")을 팀 블록이 아니라 스타일 팩이 정하도록 옮겼습니다. 팀 페르소나는 이제 팀색(연고·정서·라이벌·금기·어휘)만 담습니다.
- 공통 프롬프트의 역할 분리를 명확히 했습니다: 말투·캐릭터·1인칭은 스타일이, 팀색은 팀 페르소나가 정하며 팀 페르소나는 말투를 지정하지 않습니다.
2026-06-23 13:27:17 +09:00
5ca8fc3022 Extract conversation style into a swappable "bestfriend" module.
- 대화 스타일(3부 기본 스타일 + 짹 아키타입)을 chatStyles.ts의 스타일 팩으로 분리하고, 현재 "익명 야구방 상주러" 스타일을 "bestfriend" id로 등록했습니다.
- COMMON 시스템 프롬프트는 1부 안전·데이터, 2부 앱 능력만 고정으로 남기고 3부 자리에 {{styleBaseStyle}} 플레이스홀더를 두어 스타일 팩에서 주입하도록 했습니다.
- ChatConfig에 stylePack 필드(기본 bestfriend)를 추가해 config/chat.stylePack으로 코드 배포 없이 스타일을 교체할 수 있게 했습니다.
- 조립 결과는 기존과 본문 동일(3부 헤더 주석 1줄 외)로 동작 변화 없이 구조만 모듈화했습니다.
2026-06-23 13:04:41 +09:00
448726adb8 Refactor chat persona into a swappable 4-layer system with team packs.
- 공통 프롬프트를 1부 안전·데이터 정책 / 2부 앱 능력 정책(확정 데이터 vs 라이브 데이터 구분) / 3부 기본 대화 스타일의 고정 3층으로 재구성했습니다.
- 캐릭터·말투·팀색을 교체 가능한 4층(짹 페르소나 팩 + 팀 페르소나)으로 분리하고, 친절한 상담형 AI 톤 대신 "익명 야구방 상주러" 반응성 톤으로 전환했습니다.
- 두산(OB)에 실제 팀 페르소나 팩을 추가하고 DEFAULT_TEAM_PERSONAS에 등록해, generic placeholder 대신 팀색이 살아나도록 했습니다.
- 말버릇 "짹" 사용을 희소화하고, 사용자가 주장한 결과는 대화 전제로 수용하되 공식 수치는 도구 확인 없이 단정하지 않도록 했습니다.
2026-06-23 11:58:36 +09:00
e9531edcb5 Update .gitignore to exclude build artifacts, tool states, and temp files.
- 빌드 결과물(lib)과 에이전트 작업 상태(.omc, .omx)를 제외하여 저장소의 청결도를 유지합니다.
- 스크립트 실행 시 생성되는 임시 데이터 폴더(tmp)를 추가하여 불필요한 파일의 버전 관리를 방지했습니다.
- 프로젝트 개발 환경에서 발생하는 에디터 및 OS 관련 부산물을 일괄 정리하여 관리 효율성을 높였습니다.
- 로컬 전용 작업물인 짹 말투 검수 로그/하니스(chat-review) 폴더를 버전 관리에서 제외했습니다.
2026-06-23 10:48:54 +09:00
75a35d965a Tidy up service test fixtures and type assertions.
- scoreboardService·statsService 테스트에서 날짜 리터럴에 DateString 타입 단언을 추가해 타입 안정성을 보정했습니다.
- chatToolService·gameSyncService 테스트의 사소한 픽스처/단언을 정리했습니다.
2026-06-23 10:46:49 +09:00
229a199bbb Optimize chat system prompts and context for tool-based data retrieval.
- 시스템 프롬프트에서 시사적 데이터(경기 결과·일정·통계 등)를 제거하고, 정체성 정보만 남겨 모델의 '도구 우선 사용' 원칙을 강화했습니다.
- 지식 수준별 가이드를 별도 모듈로 분리하여 시스템이 직접 주입하도록 개선함으로써 교차 유저 캐시 히트율을 최적화했습니다.
- 사용자의 감정적 반응에 우선순위를 두도록 대화 지침을 보완하여 AI의 공감 능력을 향상하고 불필요한 정보 제공을 방지했습니다.
2026-06-23 10:46:49 +09:00
40dcb41d99 Add debug endpoints and scripts to evaluate chat tools and models.
특정 uid로 실제 도구·모델 루프를 돌려 호출된 도구·토큰·속도·캐시 히트를
보고하는 /debug/chatToolsSheet, /debug/chatProbe(Sheet) 엔드포인트를 추가한다
(요청별 model 오버라이드로 A/B 가능). prod에 같은 검증을 돌리는
scripts/chat-tools-sheet.ts도 추가한다.

무인증 + 임의 uid의 개인 데이터를 노출하는 검수용 임시 엔드포인트다.
2026-06-16 16:52:21 +09:00
d3c6fddb58 Tune the Gemini chat provider and cache-optimize the system prompt.
기본 모델을 gemini-3.1-flash-lite로, 전 모델에 공평한 24초 호출 예산과
2048 토큰 출력 한도로 맞추고, Gemini 3 모델은 thinkingLevel을 쓴다.

개인화 값(닉네임·지식수준)을 공통 프롬프트에서 빼서 모든 유저 공통의
정적 prefix로 만들어 Vertex 교차-유저 implicit caching 히트율을 높인다.
2026-06-16 16:52:05 +09:00
1ab744e660 Add AI chat tools for team standings, attendance, and predictions.
순위(get_team_rank_snapshot)·출석(get_my_attendance_this_month)·예측 복기
(get_prediction_breakdown_date) 도구를 추가하고, 상대 날짜(dayOffset)와
리그 전체 로스터 조회를 지원한다.

모델이 호출한 도구(이름·인자)를 전송 응답과 메시지 이력에 함께 실어
클라이언트가 나중에 활용할 수 있게 한다.
2026-06-16 16:51:53 +09:00
360c0d356e Add AI chat tools to fetch KBO game standouts and refine model providers.
- 특정 경기에서 활약한 선수(WPA 상위)를 조회하는 `get_game_standouts` 도구를 추가하고, 시스템 프롬프트에 과거 기록 검증 로직을 강화했습니다.
- Gemini 3.0 이상 모델의 Thinking 설정을 지원하도록 Provider 계층을 개선하여 모델 세대별 최적의 추론 환경을 구성했습니다.
- 도구 우선 사용 원칙을 구체화하여 AI가 경기 데이터의 사실 관계를 스스로 확인하고 답변하도록 환각 방지 로직을 보완했습니다.
2026-06-16 08:36:16 +09:00
b126c05c82 Add AI chat tools to fetch KBO game lineups and player roster records.
- KBO 경기 라인업(타순) 및 선수 기록을 실시간으로 조회하는 AI 도구(Function Calling) 체계를 구현했습니다.
- Anthropic 및 Google Gemini 모델의 Tool-loop를 지원하도록 Provider 계층을 개선하여, 시점 의존적인 데이터를 모델이 필요할 때만 스스로 조회하도록 최적화했습니다.
- 시스템 프롬프트에 '도구 우선 사용' 원칙을 추가하여 환각(Hallucination) 현상을 방지하고, 미발표 정보나 경기 미진행 시 정확한 사실 확인 로직을 강화했습니다.
2026-06-15 15:44:38 +09:00
b67125f412 Add AI chat persona and infrastructure for the "Jjaek" chatbot.
- AI 챗봇 '짹'의 페르소나 정의 및 시스템 프롬프트 상수를 추가했습니다.
- 채팅 이력, 쿼터 관리, 요청 멱등성 보장을 위한 Firestore 데이터 모델을 구현했습니다.
- 위기 상황 대응(자해·위해 예고 등) 및 입력/출력 필터링 파이프라인을 구축했습니다.
- Gemini 및 Anthropic 모델을 지원하는 AI Provider 추상화 계층을 마련했습니다.
2026-06-15 13:07:58 +09:00
20e3b1cbf0 Add a script to fetch and display KBO team rankings and records.
- KBO 공식 홈페이지에서 연도별 팀 순위 및 상대 전적 데이터를 가져오는 크롤링 스크립트를 구현했습니다.
- ASP.NET의 ViewState 및 EventValidation 필드를 처리하여 동적인 연도 전환 요청을 지원합니다.
- 데이터를 가독성 높은 콘솔 테이블 형태로 출력하거나 JSON 형식으로 변환하는 기능을 제공합니다.
2026-05-29 14:59:50 +09:00
05b774a713 Expand backend-write improvement report for non-expert readability
- improvements-backend-writes.md 각 항목에 배경 단락 및 평이한 설명 추가
- 비전문가도 이해할 수 있도록 비유·용어 괄호 설명 포함
- 문서 상단에 Firestore/RTDB/cron 등 핵심 용어 풀이 추가
2026-05-28 19:51:05 +09:00
7fbc4f9501 Expand backend-read improvement report for non-expert readability
- improvements-backend-reads.md 각 항목에 배경 단락 및 평이한 설명 추가
- 비전문가도 이해할 수 있도록 비유·용어 괄호 설명 포함
- 보류 항목 R2·R3의 사유를 쉬운 말로 풀어 설명
2026-05-28 19:50:32 +09:00
dafaacc446 Add narrative summaries to backend-read improvement report
- improvements-backend-reads.md 각 항목 제목 아래에 서사 요약 blockquote 추가
- R1·R4·R5·R7 항목의 Before→Fix 흐름을 한 문장으로 정리
2026-05-28 19:33:30 +09:00
11d9fa9b5f Add narrative summaries to backend-write improvement report
- improvements-backend-writes.md 각 항목 제목 아래에 서사 요약 blockquote 추가
- W3·W4·W5+R6·W6·Doc 항목의 Before→Fix 흐름을 한 문장으로 정리
2026-05-28 19:33:29 +09:00
d23b0814d9 docs: link before/fix/after improvement reports in index
- README.md에 Before→Fix→After 개선 보고서 3개 링크 추가
- backend-writes, backend-reads, client 개선 보고서를 인덱스에서 참조
2026-05-28 19:14:16 +09:00
42826eafcc Document backend WRITE optimizations (W1-W6, doc-drift)
- 백엔드 WRITE 최적화 적용 결과를 improvements-backend-writes.md에 문서화
- W1~W6 및 문서 드리프트(Doc) 적용 내역 기록
2026-05-28 19:12:52 +09:00
e6de4d1568 Document backend READ optimizations (R1/R4/R5/R7) and deferrals
- 백엔드 READ 최적화 적용 결과를 improvements-backend-reads.md에 문서화
- R1·R4·R5·R7 적용 내역 및 R2·R3 보류 사유 기록
2026-05-28 19:12:44 +09:00
14fd893ad4 Correct CACHING.md gameListCache drift (doc)
- CACHING.md의 gameListCache 관련 잘못된 내용 정정
- 사이즈 cap(100개) 및 RTDB 메트릭 카운터 항목을 실제 코드 동작에 맞게 수정
2026-05-28 17:35:15 +09:00
571c894278 Parallelize multi-year rank fetch (R7)
fetchRankFromKbo awaited each year's getOrFetch sequentially even though
years use independent cache keys. Map to Promise.all so multi-year lookups
run concurrently; order is preserved.
2026-05-28 17:34:44 +09:00
f0b01a2dc0 Field-mask the scoreboard self-user read (R5)
getScoreboard loaded the full user doc just to use 5 fields. Add
getUserForScoreboard using getAll with a fieldMask so only displayName,
photoUrl, tierPoints, favoriteTeamCode, and rankSnapshot are fetched,
cutting per-request bandwidth. The short-lived me-rank cache (meRankCache,
30s) already covers the rank-count side of R5.
2026-05-28 17:34:16 +09:00
fa7767d172 Stop bulk-invalidating game_detail cache in kboRefresh (W6)
The 02:00 cron prefix-deleted all game_detail__ docs, but those already
carry a response-derived dynamic TTL (7d for finished games, 30s live,
etc.). Bulk-wiping them forces a cold-miss stampede of external KBO
detail re-fetches right after the cron. Drop the deletion and let each
key expire on its own TTL. rank__ and schedule_day__ still get the
safety-net invalidation.
2026-05-28 17:32:40 +09:00
92ce4f9153 Trim redundant user reads/writes in userService (W5, R6)
W5: getMe synced photoUrl on every read whenever the token URL string
differed, but providers (Google) rotate query params (=sN-c sizing) for
the same image, causing write churn on a read path. Compare only the URL
before '?' so the write fires only on a genuine photo change.

R6: createMe/updateMe re-read the user with getUser right after writing.
Synthesize the response instead — updateMe from the already-read doc plus
the applied patch; createMe from the written inputs (createdAt approximated
as now, since the stored value uses serverTimestamp and later reads reflect
it). Removes one Firestore read per onboarding/profile edit.
2026-05-28 17:32:12 +09:00
38a2f78647 Fold rank snapshot into the daily judgment transaction (W4)
dailyArchive wrote users/{uid} twice per cron run: once for rankSnapshot
and once for the judgment. Extract the read-only computeRankSnapshot, then
pass the pre-judgment snapshot into judgeDay so applyDailyJudgmentTx merges
it into the same patch as the judgment write. The snapshot is computed from
current (pre-judgment) tierPoints before the transaction, preserving the
"rank BEFORE judgment" semantics; on the idempotent guard-skip path no
write occurs (and stale re-snapshotting is avoided). snapshotRankForUser is
kept as a thin wrapper for its existing callers/tests.
2026-05-28 17:29:14 +09:00
6fc00b2952 Use exponential backoff for cache-stampede polling (R4)
waitForCache, the schedule month-poll loop, and getOrFetchDynamic all
polled Firestore every fixed 500ms (up to ~50 reads) while waiting for a
lock holder to populate the cache. Replace the fixed interval with a
shared backoffDelayMs helper (300ms base, doubling, 5s cap) bounded by the
same ~25s timeout, cutting per-waiter read amplification by roughly 5x.
Timeout/fallback semantics are unchanged.
2026-05-28 17:25:45 +09:00
ee3badb112 Skip per-game stats invalidation fan-out in archive reconcile (W3)
During dailyArchive, reconcileDayVotes may call processGameEndWithGame
per unjudged game, each invalidating every voter's stats cache. Since
dailyArchive already invalidates each archived user once at the end of
its per-user loop, the per-game fan-out is redundant. Add an opt-in
skipInvalidate flag and use it from reconcile. The live onGameCompleted
trigger path keeps invalidating as before. Stale data is impossible:
getStats also self-invalidates on the daily forDate rollover.
2026-05-28 17:24:29 +09:00