- 지갑 문서(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 필드 삭제로 클라 응답 스키마 변경
- 종료 스코어를 "원정:홈" 위치 표기에서 팀명 순서(우리팀:상대)로 정렬 — 순서 불일치로 모델이 홈 승리(8:3)를 "3:8 패배"로 오독하고 패배 서사까지 지어내던 문제 수정
- formatLineupResult 종료 스코어에 승패 명기 추가(기존엔 승패 표기 없음)
- 원정 관점 회귀 테스트 추가. 프로브 실측: 동일 질문 5회 중 오독 2회 → 0회
- get_team_rank_snapshot에 vs 인자 추가 — 순위표의 팀간 승패표(vsRecords)에서 두 팀의 올 시즌 맞대결 승패무를 반환
- formatH2hResult 순수 함수 분리 및 단위 테스트 추가
- 오늘 상대가 아닌 임의 팀과의 상대 전적 질문을 도구 1회 호출로 처리(일정→전적 체이닝도 가능). 기존에는 vsRecords가 도구 계층에서 버려져 모델이 접근할 경로가 없었음
- 도구 호출 후 모델이 빈 응답을 반환하는 경우, 검열 문구 대신 재시도를 유도하는 `EMPTY_REPLY_NOTICE`를 표시하도록 로직을 개선했습니다.
- `chatProviderService`에 무텍스트 STOP 응답 방어 로직을 추가하여, 함수 호출 모드를 NONE으로 강제한 후 1회 재시도를 수행하도록 처리했습니다.
- 분석 서비스의 스키마 버전을 2로 올리고, `ChatOutcome`에 `empty` 타입을 추가하여 빈 응답 사례를 필터링과 구분하여 통계에 기록합니다.
- 회원 탈퇴 시 즉시 삭제 대신 `active: false` 및 `deactivatedAt`을 기록하여 30일간 데이터를 유예하는 소프트 삭제 방식을 도입했습니다.
- 경기 취소 시 투표를 삭제하는 대신 `cancelled: true`로 마킹하여 참여 흔적을 보존하고, 통계 집계에서만 제외하도록 로직을 변경했습니다.
- 유예 기간이 지난 비활성 계정을 영구적으로 파기하는 `accountPurge` 스케줄러를 추가했습니다.
- `ChatToolCallInfo` 타입에 `label` 필드를 추가하여 클라이언트가 도구 호출을 직관적인 칩 형태로 표시할 수 있도록 개선했습니다.
- `chatToolService`에 도구별 라벨 매핑 테이블(`TOOL_LABELS`)과 이를 적용하는 `withToolLabels` 함수를 구현하여, API 응답 시점에만 라벨이 동적으로 결합되도록 설계했습니다.
- 기존 저장소(Firestore) 로직에는 영향을 주지 않으면서, 메시지 조회 및 전송 결과 반환 시점에 라벨을 주입하여 표현 계층의 유연성을 확보했습니다.
- 테스트 코드를 수정하여 도구 호출 응답 및 이력 조회 시 라벨이 정확하게 포함되는지 검증했습니다.
- 모델이 화면 안내 시 [[NAV:route]] 마커를 붙이면 서버가 검증·제거 후 actions[]로 응답·이력에 실어, 클라가 해당 화면으로 이동하는 버튼을 렌더할 수 있게 함
- chatNavService(마커 파서 + APP_ROUTES 12개 라우트·라벨), NavAction/AppRoute 타입, finalizeExchange 저장·멱등 재반환·이력 매핑까지 전 경로 연결
- 위기/필터로 교체된 응답에는 액션을 붙이지 않고, 목록 밖 route는 드롭(모델 환각 방어)
- actions는 route+label만 전달(실제 경로 매핑·이동은 클라가 보유)
- 분석 이벤트에 navRoutes/navCount 추가, chatNavService 단위 테스트 추가
- 채팅 서비스의 관측성을 확보하기 위해 `chatAnalyticsService`를 도입하여 주요 이벤트(요청 결과, 토큰 사용량, 지연 시간 등)를 구조화된 로그로 기록합니다.
- 개인정보 보호를 위해 메시지 본문은 제외하고 길잇값과 메타데이터 위주로 수집하며, 비동기 로그 싱크를 통해 서비스 응답 지연을 방지했습니다.
- `sendMessage`의 각 단계(차단, 에러, 정상 응답 등)에 로깅을 통합하고, 테스트 코드를 통해 스키마 준수 및 필드 파생 로직의 정확성을 검증했습니다.
- 실시간 중계 상황을 직접 보고 있는 것처럼 말하는 페르소나의 암시를 제거하고, 라이브 스코어 제공 불가 안내를 명확히 했습니다.
- 'bestfriend'와 'commentator' 스타일 모두에서 사용자를 훈계하거나 건전한 응원을 강요하는 설교적 프롬프트를 배제했습니다.
- 사용자의 승리 주장 시 상황을 의심하거나 초를 치지 않고 자연스럽게 호응하도록 유도하며, 점수판 농담 등 스타일별 리액션 가이드를 구체화했습니다.
- 팀 홈구장 정보를 오늘 경기 장소로 단정하지 않도록 금칙을 추가하고, 관련 테스트 케이스를 보강하여 프롬프트의 일관성을 검증했습니다.
- KT·NC·SSG·LG·KIA·롯데·삼성·키움 8개 구단의 팀 페르소나 팩을 추가해 DEFAULT_TEAM_PERSONAS가 10개 구단 전체를 커버하도록 했습니다(이전에는 HH·OB만 실팩, 나머지는 generic fallback).
- 모든 팀 블록을 톤-중립으로 작성했습니다: 1인칭·반말/존댓말 샘플·말버릇 없이 팀색(연고·상징·팬 정서·팀색 어휘·라이벌·금기)만 담아, 말투는 스타일 팩이 전적으로 정하도록 했습니다.
- 10개 구단 모두 generic이 아닌 실팩을 쓰는지 검증하는 테스트를 추가했습니다.
- StylePack에 name 필드를 추가하고 스타일별 이름을 정했습니다(bestfriend = "베스트프랜드 짹", commentator = "해설가 짹").
- 각 페르소나 아키타입에 이름을 명시하고, AI 정체성 규칙을 "자기 지칭은 페르소나 이름을 쓰되, AI임을 직접 물으면 부정하지 않는다"로 갱신했습니다. 밋밋한 "AI 캐릭터" 단독 지칭을 지양합니다.
- 정중한 해설위원 톤의 "commentator" 스타일 팩을 추가하고 STYLE_PACKS에 등록했습니다(bestfriend/commentator 2종).
- 팀 페르소나에서 말투 요소를 제거했습니다: 반말 샘플 문장을 걷어내고, 1인칭("나"/"저")을 팀 블록이 아니라 스타일 팩이 정하도록 옮겼습니다. 팀 페르소나는 이제 팀색(연고·정서·라이벌·금기·어휘)만 담습니다.
- 공통 프롬프트의 역할 분리를 명확히 했습니다: 말투·캐릭터·1인칭은 스타일이, 팀색은 팀 페르소나가 정하며 팀 페르소나는 말투를 지정하지 않습니다.
- 공통 프롬프트를 1부 안전·데이터 정책 / 2부 앱 능력 정책(확정 데이터 vs 라이브 데이터 구분) / 3부 기본 대화 스타일의 고정 3층으로 재구성했습니다.
- 캐릭터·말투·팀색을 교체 가능한 4층(짹 페르소나 팩 + 팀 페르소나)으로 분리하고, 친절한 상담형 AI 톤 대신 "익명 야구방 상주러" 반응성 톤으로 전환했습니다.
- 두산(OB)에 실제 팀 페르소나 팩을 추가하고 DEFAULT_TEAM_PERSONAS에 등록해, generic placeholder 대신 팀색이 살아나도록 했습니다.
- 말버릇 "짹" 사용을 희소화하고, 사용자가 주장한 결과는 대화 전제로 수용하되 공식 수치는 도구 확인 없이 단정하지 않도록 했습니다.
- 시스템 프롬프트에서 시사적 데이터(경기 결과·일정·통계 등)를 제거하고, 정체성 정보만 남겨 모델의 '도구 우선 사용' 원칙을 강화했습니다.
- 지식 수준별 가이드를 별도 모듈로 분리하여 시스템이 직접 주입하도록 개선함으로써 교차 유저 캐시 히트율을 최적화했습니다.
- 사용자의 감정적 반응에 우선순위를 두도록 대화 지침을 보완하여 AI의 공감 능력을 향상하고 불필요한 정보 제공을 방지했습니다.
특정 uid로 실제 도구·모델 루프를 돌려 호출된 도구·토큰·속도·캐시 히트를
보고하는 /debug/chatToolsSheet, /debug/chatProbe(Sheet) 엔드포인트를 추가한다
(요청별 model 오버라이드로 A/B 가능). prod에 같은 검증을 돌리는
scripts/chat-tools-sheet.ts도 추가한다.
무인증 + 임의 uid의 개인 데이터를 노출하는 검수용 임시 엔드포인트다.
기본 모델을 gemini-3.1-flash-lite로, 전 모델에 공평한 24초 호출 예산과
2048 토큰 출력 한도로 맞추고, Gemini 3 모델은 thinkingLevel을 쓴다.
개인화 값(닉네임·지식수준)을 공통 프롬프트에서 빼서 모든 유저 공통의
정적 prefix로 만들어 Vertex 교차-유저 implicit caching 히트율을 높인다.
순위(get_team_rank_snapshot)·출석(get_my_attendance_this_month)·예측 복기
(get_prediction_breakdown_date) 도구를 추가하고, 상대 날짜(dayOffset)와
리그 전체 로스터 조회를 지원한다.
모델이 호출한 도구(이름·인자)를 전송 응답과 메시지 이력에 함께 실어
클라이언트가 나중에 활용할 수 있게 한다.
- 특정 경기에서 활약한 선수(WPA 상위)를 조회하는 `get_game_standouts` 도구를 추가하고, 시스템 프롬프트에 과거 기록 검증 로직을 강화했습니다.
- Gemini 3.0 이상 모델의 Thinking 설정을 지원하도록 Provider 계층을 개선하여 모델 세대별 최적의 추론 환경을 구성했습니다.
- 도구 우선 사용 원칙을 구체화하여 AI가 경기 데이터의 사실 관계를 스스로 확인하고 답변하도록 환각 방지 로직을 보완했습니다.
- KBO 경기 라인업(타순) 및 선수 기록을 실시간으로 조회하는 AI 도구(Function Calling) 체계를 구현했습니다.
- Anthropic 및 Google Gemini 모델의 Tool-loop를 지원하도록 Provider 계층을 개선하여, 시점 의존적인 데이터를 모델이 필요할 때만 스스로 조회하도록 최적화했습니다.
- 시스템 프롬프트에 '도구 우선 사용' 원칙을 추가하여 환각(Hallucination) 현상을 방지하고, 미발표 정보나 경기 미진행 시 정확한 사실 확인 로직을 강화했습니다.
- AI 챗봇 '짹'의 페르소나 정의 및 시스템 프롬프트 상수를 추가했습니다.
- 채팅 이력, 쿼터 관리, 요청 멱등성 보장을 위한 Firestore 데이터 모델을 구현했습니다.
- 위기 상황 대응(자해·위해 예고 등) 및 입력/출력 필터링 파이프라인을 구축했습니다.
- Gemini 및 Anthropic 모델을 지원하는 AI Provider 추상화 계층을 마련했습니다.
- 출석 독려 푸시 알림 기능과 관련 스케줄러(attendanceReminder)를 삭제했습니다.(클라이언트로 이관)
- 유저 데이터에서 알림 슬롯 필드를 제거하고, 출석 시 수행하던 슬롯 계산 및 저장 로직을 제거했습니다.
- 주간 보너스 확인 로직을 리팩토링하고, getMonth 함수 내 비동기 데이터 조회를 병렬화하여 성능을 개선했습니다.
- KBO 경기 상세 정보에 선발 및 예상 라인업 정보를 포함하고 CLI에서 이를 확인할 수 있는 출력 기능을 추가했습니다.
- 경기가 시작되기 전 KBO API가 빈 데이터를 반환하는 상황(code 200)을 예외 처리하고, 경기 상태에 따라 null을 반환하도록 개선했습니다.
- 종료된 경기는 박스스코어에서 선발 명단을 추출하고, 시작 전 경기는 라인업 분석 API를 호출하는 이원화된 데이터 수집 로직을 구현했습니다.
- 예상 라인업이 확정으로 전환되는 시점을 빠르게 반영하기 위해 라인업 출처에 따라 캐시 TTL을 동적으로 조절하는 기능을 도입했습니다.
- 라인업 파싱 및 경기 시작 전후의 데이터 처리 로직에 대한 검증을 위해 서비스 레이어 테스트를 업데이트했습니다.
- 출석 체크 시 데일리 포인트와 주간/월간 보너스를 지급하며, 멱등성 및 클럭 오차 처리를 포함한 로직을 구현했습니다.
- 포인트 이력 관리를 위한 `pointLedger` 컬렉션과 잔액 추적 리포지토리를 추가했습니다.
- 사용자별 알림 슬롯에 맞춰 FCM 출석 독려 푸시를 발송하는 스케줄러와 설정 API를 도입했습니다.
- RTDB의 희소 배열 응답을 7일 기준 배열로 정규화하여 통계 데이터의 일관성을 강화했습니다.
- 출석 시스템 전반에 대한 단위 및 통합 테스트를 추가하여 안정성을 확보했습니다.
- KBO 경기 취소 시 '우천취소' 등 구체적인 사유(cancelReason)를 파싱하여 저장하는 로직을 추가했습니다.
- 기존의 `syncYesterdayFromLive` 함수를 `forceSyncDay`로 이름을 변경하고, 어제 날짜뿐만 아니라 특정 날짜를 강제로 동기화할 수 있도록 기능을 일반화했습니다.
- Firestore 업데이트 전 기존 데이터와 취소 사유를 비교하는 로직을 추가하여 불필요한 쓰기 비용을 최적화했습니다.
- 다양한 취소 상황과 노트 필드 처리 방식에 대한 회귀 테스트 케이스를 추가하여 신뢰성을 확보했습니다.
- 사용자의 마지막 참여일 이후 실제 경기가 있었음에도 참여하지 않은 경우를 감지하여 스트릭을 0으로 초기화하는 지연 보정(lazy correction) 로직을 도입했습니다.
- KBO 휴장일(월요일, 전체 우천 취소 등)을 자동으로 식별하여 실제 경기가 열린 날에만 결석 판정이 내려지도록 `hasMissedGameDayBetween` 검증 기능을 구현했습니다.
- 실시간 라이브 데이터를 기반으로 전일 경기 결과를 확정하는 기능을 추가하여 월간 일정 갱신 과정에서 발생할 수 있는 데이터 누락을 보완했습니다.
- 일일 아카이브 및 경기 동기화 로직을 수동으로 트리거할 수 있는 디버그 엔드포인트를 추가하고, 통계 데이터에 산출 기준일(forDate)을 포함하여 캐시 무효화 정밀도를 개선했습니다.
- KBO 게임센터의 스코어보드, 박스스코어, 키플레이어 데이터를 통합 조회하는 기능을 구현했습니다.
- 경기 상태(종료, 진행 중, 시작 전)에 따라 TTL을 30초에서 7일까지 유연하게 적용하는 동적 캐싱 시스템을 도입했습니다.
- KBO 공식 데이터에서 이닝별 득점이 누락되는 경우를 대비해 네이버 스포츠 API를 통한 데이터 보완 로직을 추가했습니다.
- Firestore의 중첩 배열 저장 제한을 회피하기 위해 테이블 데이터를 객체 배열 구조로 파싱하도록 설계했습니다.
- CLI 상세 조회 명령어와 REST API 엔드포인트를 추가하고, HTML 엔티티 디코딩 및 관련 단위 테스트를 보완했습니다.
- 전체 및 팀별 스코어보드 기능을 구현하고, 순위 변동(delta)을 시각화하기 위한 스냅샷 시스템을 도입했습니다.
- `dailyArchive` 과정에서 사용자의 현재 순위를 `rankSnapshot`으로 기록하여, 다음 판정 시점의 순위 변화량을 정확히 계산합니다.
- 상위권 데이터는 RTDB에 사전 계산(precompute)하여 저장함으로써 대규모 조회 요청에 대응하고, `MemCache`를 통해 API 응답 성능을 최적화했습니다.
- Firestore 복합 색인과 Count Aggregation을 활용하여 동점자 처리가 포함된 랭킹 및 상위 퍼센타일 산출 로직을 구현했습니다.
- 관련 서비스 레이어, 저장소 함수, API 핸들러 및 단위 테스트 코드를 추가했습니다.
- `dailyArchive` 과정에 `judgeDay`를 통합하여 경기 결과에 따른 일간 판정, 스트릭 업데이트, 티어 포인트 가산을 자동화했습니다.
- 주간 모든 경기를 성공적으로 예측했을 때 '주간 마스터 티켓'을 지급하는 `judgeWeekIfNeeded` 로직을 구현했습니다.
- Firestore 트랜잭션을 사용하여 판정 결과 반영의 원자성을 확보하고, 중복 처리를 방지하는 멱등성 가드를 적용했습니다.
- `statsService`가 실시간 계산 대신 사용자 문서에 저장된 스트릭과 티어 정보를 활용하도록 고도화하고 관련 유틸리티와 테스트 코드를 추가했습니다.
- 사용자 프로필 수정을 위한 `PATCH` 핸들러와 `updateMe` 서비스를 구현하여 닉네임, 응원팀, 지식 수준 변경 기능을 추가했습니다.
- HTML 정규식 파싱 오류로 인해 `null`이 반환되던 `gameId` 문제를 해결하기 위해 일정 메타데이터 기반의 ID 생성 로직을 도입했습니다.
- `updateUser`가 `FieldValue`를 수용하도록 개선하여 응원팀 설정 해제(필드 삭제)가 가능하도록 수정했습니다.
- 닉네임 중복 확인 및 변경 규칙 검증을 포함한 유저 서비스 테스트 케이스를 작성하고 `TODO.md` 이슈를 해결했습니다.
- `GameStatus` 타입을 `scheduled`, `live`, `completed`, `cancelled`로 변경하여 상태 관리의 일관성을 높였습니다.
- 사용자 프로필의 `favoriteTeamCode`를 선택 사항으로 변경하고, null 또는 undefined 입력에 대한 처리를 추가했습니다.
- KBO 데이터 병합 과정에 상세 로그를 도입하여 실시간 데이터 매칭 및 외부 API 호출 실패 상황에 대한 가시성을 확보했습니다.
- 상태 값 변경에 따라 영향받는 서비스 로직과 테스트 코드를 일괄 업데이트했습니다.
- 닉네임 선점 및 검증을 위한 예약 시스템을 구현하고 관련 로직을 추가했습니다.
- 유저 데이터 및 하위 컬렉션을 완전히 삭제하는 회원 탈퇴 기능을 구현했습니다.
- 토큰의 프로필 사진 변경 시 유저 정보를 자동으로 동기화하도록 개선했습니다.
- RTDB를 활용하여 닉네임 예약 상태를 관리하고, 회원 가입 시 이를 검증하도록 변경했습니다.