--- tags: [calendar, scheduler, holiday, blackout, plans] --- # Calendar Skill 기반 자동 휴일 감지 행동 제어 시스템 구축 **날짜**: 2026-02-14 **작성자**: happybell80 **상태**: 완료 (2026-03-10 기준) **관련 서비스**: rb8001, skill-calendar, robeing-monitor **상위 원칙**: [문서 작성 원칙](../../../../0_VALUE/20_Governance/writing-principles.md) ## 관련 문서 - [Calendar Skill 자동휴일감지 행동제어 원인확정 리서치](../research/260310_calendar_skill_자동휴일감지_행동제어_원인확정_리서치.md) - [Calendar Skill 자동휴일감지 행동제어 구현 및 검증](../worklog/260310_calendar_skill_자동휴일감지_행동제어_구현및검증.md) --- ## 1. 문제 - 스케줄러는 `mon-fri` cron 기준으로만 실행되어 공휴일을 배제하지 못한다. - 연휴 중에도 평일 잡이 실행되어 운영 정책과 충돌한다. - 현재 연휴 제어는 수동 비활성화에 의존한다. ## 1-1. 원인 확정 - `scheduled_jobs`에 휴일/블랙아웃 정책 저장층이 없다. - `skill-calendar`에 휴일 판정 API가 없다. - `rb8001` 잡 실행 전에 공통 실행 가드가 없다. - `robeing-monitor`의 `schedule_type`, `schedule_days`는 실제 DB에 저장되지 않는다. ## 2. 목표 - 목표 1: 상시 규칙으로 워킹데이(공휴일 제외) 실행을 자동화한다. - 목표 2: 특정 기간(예: 설 연휴) 블랙아웃으로 즉시 실행 차단을 지원한다. - 목표 3: 운영 API에서 상태 조회와 제어 이력을 확인한다. ## 2-1. 고정 결정 - 대한민국 공휴일 1차 SSOT는 **공공데이터포털 한국천문연구원 `특일 정보` OpenAPI**로 고정한다. - 1차 운영 범위는 `country=KR`, `timezone=Asia/Seoul`로 고정한다. - 잡 실행 정책 저장 위치는 `scheduled_jobs.schedule_policy JSONB`로 고정한다. - 실행 차단 위치는 각 `_run_*_with_logging` 진입점 앞 공통 가드 헬퍼로 고정한다. - `db_loader`는 정책 전달까지만 담당하고, 실행 여부 판정은 하지 않는다. - 초기 정책 모드는 `workday` 1종으로 고정하고, 다국가/다정책 일반화는 이번 범위에 넣지 않는다. - 공휴일 원격 조회 실패 시에는 기본 실행으로 넘기지 않고, 캐시된 최근 성공값이 없으면 `skip + reason=calendar_unavailable`로 고정한다. ## 3. 해결방안 - `skill-calendar`에 휴일 판정 API를 추가한다. - `rb8001` 스케줄러 실행 경로에 공통 가드를 추가한다. - DB에 스케줄 정책 필드를 추가하고 잡별 정책을 저장한다. - `robeing-monitor` 설정 API에서 `schedule_type`, `schedule_days`를 실제 저장/조회로 전환한다. ## 4. 아키텍처 - 휴일 소스: 한국천문연구원 `특일 정보` OpenAPI + 수동 블랙아웃 기간. - 판정 서비스: `skill-calendar`가 날짜별 `is_workday`, `is_blackout`, `reason`을 반환. - 실행 가드: `rb8001` 잡 실행 직전 판정 API 호출 후 실행/스킵 결정. - 운영 가시성: 스킵 로그를 activity 로그에 저장하고 모니터링 API로 조회. ## 5. Phase 작업 ### Phase 1: 데이터 모델 - `scheduled_jobs`에 `schedule_policy` JSONB 컬럼을 추가한다. - 기본 정책을 `{"mode":"workday","country":"KR","timezone":"Asia/Seoul","blackout_ranges":[]}`로 저장한다. - Repository CRUD에서 `schedule_policy` 읽기/쓰기/수정 경로를 추가한다. ### Phase 2: skill-calendar 휴일 판정 - `skill-calendar`에 `GET /api/workday/check` 엔드포인트를 추가한다. - 입력: `date`, `timezone`, `country`, `blackout_ranges`. - 출력: `is_workday`, `is_blackout`, `should_run`, `reason`. - 응답 계약은 아래 최소 형태로 고정한다. - `{"date":"YYYY-MM-DD","country":"KR","timezone":"Asia/Seoul","is_workday":bool,"is_blackout":bool,"should_run":bool,"reason":"holiday|blackout|workday|weekend"}` - 결과 캐시를 1일 단위로 저장해 반복 조회 부하를 줄인다. - `reason` 확장값은 `holiday|blackout|workday|weekend|calendar_unavailable`까지만 허용한다. ### Phase 3: rb8001 실행 가드 - `db_loader`에서 잡 등록 시 `schedule_policy`를 실행 함수 인자로 전달한다. - 각 `_run_*_with_logging` 진입점에서 공통 가드 함수를 먼저 호출한다. - `should_run=false`면 작업 본문을 실행하지 않고 스킵 로그를 남긴다. - 스킵 로그 최소 필드는 아래로 고정한다. - `job_name`, `job_type`, `scheduled_at`, `policy_mode`, `date`, `country`, `timezone`, `should_run`, `reason` ### Phase 4: 운영 API/관리 UI 정합화 - `rb8001 /api/scheduler/jobs` 응답에 `schedule_policy`를 포함한다. - `rb8001 /api/scheduler/jobs/{name}` PATCH에 `schedule_policy` 업데이트를 포함한다. - `robeing-monitor` preferences API에서 `schedule_type`, `schedule_days`를 DB 반영한다. - `robeing-monitor`는 `schedule_type=workday`와 `blackout_ranges`만 노출하고, 현재 범위 밖인 일반 반복규칙 편집은 넣지 않는다. ### Phase 5: 테스트/검증 - 단위 테스트: 휴일/평일/블랙아웃 케이스 판정 테스트. - 통합 테스트: 잡 실행 시 스킵/실행 분기 테스트. - 운영 검증: 연휴 날짜(2026-02-16~2026-02-18) `should_run=false` 확인. - 고정 테스트 케이스: - `2026-02-16` ~ `2026-02-18` -> `should_run=false` - 일반 평일 1건 -> `should_run=true` - 토요일 1건 -> `should_run=false` - 수동 블랙아웃 날짜 1건 -> `should_run=false` - 완료 판정 기준: - `scheduled_jobs.schedule_policy` 저장/조회가 API와 DB에서 모두 확인될 것 - `/api/workday/check`가 고정 테스트 케이스 4종을 통과할 것 - `_run_*_with_logging` 경로에서 `should_run=false` 스킵 로그가 남을 것 - `robeing-monitor`에서 설정한 정책이 실제 스케줄 실행 결과에 반영될 것 ## 6. 기대효과 - 연휴/공휴일 기간의 자동 실행 충돌을 제거한다. - 수동 비활성화 절차를 상시 자동 제어로 대체한다. - 정책 기반 운영으로 스케줄 변경 이력과 실행 근거를 명확히 유지한다. ## 7. 완료 처리 - 구현 및 검증 결과는 [Calendar Skill 자동휴일감지 행동제어 구현 및 검증](../worklog/260310_calendar_skill_자동휴일감지_행동제어_구현및검증.md)에 기록한다. - 이후 운영 중 새 문제는 별도 `troubleshooting`으로 분리한다.