DOCS/journey/troubleshooting/251117_rb8001_calendar_duplicate_check_fix.md
Claude-51124 1b4adb73fc docs: 하루종일 일정 등록 문제 해결 과정 보강
- UNKNOWN으로 분류된 메시지의 컨텍스트 보존 문제 추가
- confirm_conv 찾기 로직 개선 및 하루종일 정보 수집 강화 내용 추가
- skill-calendar 서비스 Google Calendar API date 필드 지원 추가
- 교훈 섹션에 UNKNOWN 분류 및 외부 API 스펙 확인 중요성 추가
2025-11-18 22:52:39 +09:00

11 KiB

rb8001 캘린더 중복 체크 로직 수정

작성일: 2025-11-17

작성자: admin

관련 서비스: rb8001, skill-calendar, Google Calendar API


문제 상황

발견된 이슈

사용자가 구글 캘린더 일정 등록을 시도할 때 다음과 같은 문제가 발생:

  1. 거짓 중복 경고

    • 사용자: "11월 25일 검진 인천 남동구..."
    • RoBeing: "일정을 구글 캘린더에 등록해드릴까요?"
    • 사용자: "ㅇㅇ"
    • RoBeing: "이미 같은 시간대에 등록된 일정이 있습니다"
    • 실제로는 구글 캘린더에 일정이 없음
  2. 중복 응답 문제

    • 같은 메시지가 두 번씩 전송됨
    • 모순된 응답 ("일정이 있다" → "일정이 없다")
  3. 대화 이중 저장

    • PostgreSQL에 같은 대화가 두 번 저장됨
    • 각각 다른 intent로 기록 (calendar_event, calendar_confirm)

원인 분석

1. 로컬 로그 기반 중복 체크의 문제

# calendar_handler.py:100-119 (수정 전)
# 로컬 로그 기반 중복 체크
existing = find_matching_event(user_id, target_date, target_minutes)
if existing:
    # 로컬 DB에만 기록되어 있어도 "중복"으로 판단
    duplicate_events.append(...)
    continue

문제점:

  • 로컬 DB(calendar_event_log 테이블)만 확인
  • 실제 구글 캘린더는 조회하지 않음
  • 로컬 로그와 실제 캘린더가 불일치할 수 있음
    • 일정 등록 실패 후에도 로컬 로그에는 기록
    • 사용자가 구글 캘린더에서 직접 삭제한 경우
    • 대화 이중 저장으로 인한 로컬 로그 중복

2. 대화 이중 저장 문제

데이터베이스 조회 결과:

ID     메시지                      Intent
1777   11월 25일 검진...           calendar_event
1776   11월 25일 검진...           calendar_confirm    ← 중복!
1782   25일 일정 등록해줘           calendar_event
1781   25일 일정 등록해줘           calendar_confirm    ← 중복!

원인:

  • main.py:109-115router.py:582-593에서 이중 저장
  • 각각 다른 intent로 저장되어 컨텍스트 혼란 발생

3. 커밋 히스토리

  • 중복 방지 기능: ffc7406 (2025-11-17 00:27)
  • Claude가 추가한 미문서화 기능
  • 성능 최적화 목적으로 로컬 캐시 우선 확인
  • 하지만 로컬 로그 신뢰성 문제 미고려

해결 방안

1. 실제 구글 캘린더 조회로 변경

# calendar_handler.py:100-119 (수정 후)
# 실제 구글 캘린더 조회로 중복 체크 (로컬 로그는 신뢰할 수 없음)
try:
    start_dt = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
    target_date = start_dt.date()

    # 구글 캘린더에서 해당 날짜의 모든 일정 조회
    day_start = f"{target_date.isoformat()}T00:00:00+09:00"
    day_end = f"{target_date.isoformat()}T23:59:59+09:00"
    remote_events = await calendar_skill.get_events(user_id, day_start, day_end)

    # 같은 시작 시간의 일정이 있는지 확인
    existing = None
    for ev in remote_events:
        ev_start = ev.get("start", {}).get("dateTime")
        if ev_start and ev_start == start_time:
            existing = {"title": ev.get("summary", title), "event_id": ev.get("id")}
            break
except Exception as e:
    logger.warning(f"Failed to check Google Calendar for duplicates on {d}: {e}")
    existing = None

if existing:
    duplicate_events.append(...)
    continue

개선점:

  • 실제 구글 캘린더 API 호출
  • 진짜 중복만 감지
  • 로컬 로그 불일치 문제 해결

2. 성능 고려사항

우려: 매번 API 호출 시 지연/비용 증가 대응:

  • 중복 체크는 일정 생성 시에만 발생 (빈도 낮음)
  • 사용자 경험 > 성능 최적화 (거짓 중복 방지가 우선)
  • 필요시 Redis 캐싱 추가 고려

하루종일 일정 등록 지원 (2025-11-18 추가)

문제 상황

  • "12월 25일 크리스마스 일정 등록해줘" → "시간은 하루종일" → "ㅇㅇ" → 일정 등록 실패
  • "시간은 하루 종일"이 UNKNOWN으로 분류되어 calendar_confirm intent가 없음
  • confirm_conv를 찾을 때 "12월 25일 크리스마스 일정 등록해줘" 메시지만 선택되어 하루종일 정보 누락
  • skill-calendar 서비스에 is_all_day 파라미터가 없어서 Google Calendar API에 dateTime 필드를 보내 400 Bad Request 발생

원인 분석

  1. 의도 분류 실패: calendar_handler.py:42-48에서 calendar_confirm intent만 찾아서 실패 시 recent[0] 사용, 하루종일 정보가 없는 메시지 선택
  2. 하루종일 정보 수집 실패: calendar_handler.py:70-88에서 confirm_convllm_response에만 의존, 최근 대화에서 하루종일 정보를 별도로 수집하지 않음
  3. Google Calendar API 형식 오류: skill-calendar/services/google_calendar_service.py:162-172에서 하루종일일 때도 dateTime 필드 사용, 실제로는 date 필드 필요

해결 방안

  1. confirm_conv 찾기 로직 개선 (calendar_handler.py:42-69)

    • 우선순위: 1) calendar_confirm intent, 2) "하루 종일" 포함 메시지, 3) 최신 대화
    • 최근 대화에서 "하루 종일" 포함 메시지를 별도로 추적하여 all_day_conv로 저장
  2. 하루종일 정보 수집 강화 (calendar_handler.py:70-88)

    • confirm_convllm_response에 하루종일이 없으면 최근 5개 대화에서 별도 수집
    • all_day_info 변수로 저장하여 time_range 추출 시 활용
  3. 슬롯 추출 패턴 확장 (router.py:348-360)

    • 시간 패턴에 "하루종일", "종일", "all day" 추가
    • 하루종일 표현을 "하루종일"로 정규화하여 슬롯에 저장
  4. 시간 파싱 로직 개선 (calendar_handler.py:591-629)

    • parse_time_range 함수에 하루종일 처리 로직 추가
    • all-day 이벤트는 date 필드 사용 (예: "2025-12-25" → "2025-12-26")
    • 반환값에 is_all_day 플래그 추가
  5. skill-calendar 서비스 지원 추가 (skill-calendar/routers/calendar.py:16-25, skill-calendar/services/google_calendar_service.py:115-186)

    • CreateEventRequestis_all_day 필드 추가
    • GoogleCalendarService.create_eventis_all_day 파라미터 추가
    • 하루종일일 때 date 필드 사용 (일반 이벤트는 dateTime 필드)
  6. 중복 체크 로직 개선 (calendar_handler.py:191-196)

    • all-day 이벤트는 date 필드로 비교
    • 일반 이벤트는 dateTime 필드로 비교

구현 완료

  • 85eee34 (2025-11-18): confirm_conv 찾기 로직 개선 - 하루종일 메시지 우선 사용
  • 7bb3524 (2025-11-18): 최근 대화에서 하루종일 정보 수집 로직 추가
  • 9df9e9e (2025-11-18): generator expression에서 re 모듈 스코프 문제 해결
  • 9d150c3 (2025-11-18): 함수 내부 중복 import re 제거
  • 2554576 (2025-11-18): skill-calendar 서비스에 하루종일 이벤트 지원 추가
  • d1b546a (2025-11-18): skill-calendar 인덴테이션 오류 수정
  • 배포: Gitea Actions 자동 배포 완료, rb8001 및 skill-calendar 컨테이너 재시작 확인

검증

  • "12월 25일 크리스마스 일정 등록해줘" → "시간은 하루종일" → "ㅇㅇ" → 일정 등록 성공
  • Google Calendar에서 all-day 이벤트로 정상 표시 확인
  • 로그 확인: [Calendar] Found all-day message in recent conversation, [Calendar] Using all_day_info from recent conversations, Created event rd6u0uhuqhdcvse3adovmjfkac

대화 이중 저장 문제 (별도 해결 필요)

현재 상황

  • main.py에서 저장: execution_plan의 intent 사용
  • router.py에서 저장: task_type을 intent로 사용
  • 결과: 같은 대화가 다른 intent로 두 번 저장

해결 방안 (미적용)

router.py:582의 대화 저장을 제거하거나 조건부 실행:

# 제안: frontend 채널은 main.py에서만 저장
if result.get("success") and result.get("content") and channel != "frontend":
    await self._save_conversation(...)

현재 미적용 이유:

  • Slack 채널 등 다른 채널 영향도 확인 필요
  • 별도 이슈로 추적 권장

테스트 시나리오

1. 정상 등록

  • 사용자: "11월 25일 오전 9시 회의"
  • 구글 캘린더 확인: 일정 없음
  • 예상: 일정 등록 성공

2. 중복 감지

  • 사용자: "11월 25일 오전 9시 회의" (같은 일정 재등록)
  • 구글 캘린더 확인: 일정 있음
  • 예상: "이미 같은 시간대에 등록된 일정이 있습니다" 메시지

3. 로컬 로그 불일치

  • 로컬 DB에 기록 있지만 구글 캘린더에는 없는 경우
  • 예상: 정상 등록 (로컬 로그 무시)

모니터링

로그 확인

# 중복 체크 로그
docker logs rb8001 | grep "duplicate check"

# Google Calendar API 호출 로그
docker logs rb8001 | grep "get_events"

성능 모니터링

  • 일정 생성 응답 시간 추적
  • Google Calendar API 호출 빈도/실패율
  • 필요시 캐싱 레이어 추가

교훈

1. 로컬 캐시의 한계

  • 문제: 성능 최적화를 위한 로컬 캐시가 오히려 오류 발생
  • 교훈: 캐시는 신뢰할 수 있을 때만 사용. Source of Truth는 실제 데이터
  • 적용: 중복 체크는 반드시 실제 구글 캘린더 조회

2. 미문서화 기능의 위험

  • 문제: Claude가 추가한 기능이 문서화되지 않음
  • 결과: 의도치 않은 동작, 디버깅 어려움
  • 교훈: 모든 기능은 즉시 문서화 필수

3. 이중 저장 패턴

  • 의도: ChromaDB + PostgreSQL 이중 저장은 정상 (각각 다른 목적)
  • 문제: 같은 DB에 같은 데이터 중복 저장은 버그
  • 교훈: 저장 로직은 단일 진입점 유지

4. UNKNOWN으로 분류된 메시지의 컨텍스트 보존

  • 문제: "시간은 하루 종일"이 UNKNOWN으로 분류되어 calendar_confirm intent가 없었고, confirm_conv를 찾을 때 하루종일 정보가 없는 메시지만 선택됨
  • 교훈: 의도 분류가 실패해도 최근 대화에서 관련 정보(하루종일, 날짜 등)를 별도로 수집하여 보존해야 함. confirm_conv를 찾을 때 intent뿐만 아니라 메시지 내용도 고려해야 함

5. 외부 API 스펙 확인의 중요성

  • 문제: Google Calendar API의 하루종일 이벤트 형식(date 필드 vs dateTime 필드)을 확인하지 않고 구현
  • 교훈: 외부 API 통합 시 공식 문서를 먼저 확인하고 웹 검색으로 검증해야 함. 추측으로 구현하면 400 Bad Request 같은 에러 발생

관련 파일

  • /home/admin/ivada_project/rb8001/app/router/calendar_handler.py (수정됨)
  • /home/admin/ivada_project/rb8001/app/skills/calendar_skill.py
  • /home/admin/ivada_project/rb8001/app/state/calendar_event_repository.py

관련 문서


문서 끝