k-skill/docs/features/foresttrip-vacancy.md
Donghun Seol 00a6d9feae fix(foresttrip-vacancy): exclude 예비 rooms, gate useDt strictly, dedup duplicate rooms
월별예약조회 API가 srchDate 단일 일자 요청에도 5일 윈도우를 반환하고,
"예비"로 표기된 운영자 보유분이 raw 응답에 포함되며, 같은 객실이 다른
goodsId로 중복 표시되는 세 가지 문제를 한꺼번에 fix한다.

수정:
- collect_results 안에 strict useDt gate 추가 (today~last_day 범위 밖 행 차단)
- is_reserve_room() helper로 goodsNm에 "예비" 포함 객실 제외
- (forest_id, use_dt, name) 단위 dedup으로 중복 행 제거
- is_available()는 시그니처/로직 변경 없이 booking-state predicate 유지

추가:
- foresttrip-vacancy/tests/ 18개 단위 테스트 (mock + fixture 기반)
- IsReserveRoomTest, IsAvailableTest, CollectResultsFilterTest,
  StrictUseDtGateTest, GroundTruthTest 다섯 클래스
- 거제·구재봉 fixture로 사용자 라이브 검증 결과 회귀 보호
- package.json lint·test 스크립트에 등록

문서:
- SKILL.md: API 5일 윈도우/예비 객실/중복 dedup 자동 처리 명시 + 회복 시나리오 보강
- docs/features/foresttrip-vacancy.md: 기본 흐름 6단계와 주의할 점 보강

사용자 라이브 검증 ground truth (2026-05-12 기준):
- 거제자연휴양림 5/13 ~9개, 5/16 0개, 5/17 19개, 5/23 0개, 5/24 0개
- 구재봉자연휴양림 5/16 1개 (206호 쑥부쟁이방)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:57:25 +09:00

6.8 KiB
Raw Permalink Blame History

자연휴양림 빈 객실 조회 가이드

대상 사이트는 숲나들e 공식 사이트 https://foresttrip.go.kr/index.jsp 이다. 이 기능은 해당 사이트의 자연휴양림 예약 가능 객실 조회 자동화만 수행한다.

이 기능으로 할 수 있는 일

  • 숲나들e/자연휴양림 예약 가능 객실 조회
  • 특정 날짜 또는 여러 날짜 기준 조회
  • 전체 자연휴양림 또는 휴양림명/ID 기준 조회
  • 숙박/야영 카테고리별 조회
  • JSON 또는 사람이 읽기 좋은 텍스트 출력

이 기능은 조회 전용 자동화이다. 예약 신청, 결제, 캡차 처리, 대기열 우회, 반복 스나이핑은 하지 않는다.

먼저 필요한 것

python3 -m pip install playwright
python3 -m playwright install chromium
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --check-deps

--check-deps 는 숲나들e 로그인이나 네트워크 조회를 수행하지 않고, 로컬 Python/Playwright Chromium 준비 상태만 확인한다.

필요한 환경변수

  • KSKILL_FORESTTRIP_ID
  • KSKILL_FORESTTRIP_PASSWORD

선택:

  • 없음

Credential resolution order

  1. 이미 환경변수에 있으면 그대로 사용한다.
  2. 에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면 거기서 꺼내 환경변수로 주입해도 된다.
  3. ~/.config/k-skill/secrets.env (기본 fallback) — plain dotenv 파일, 퍼미션 0600.
  4. 아무것도 없으면 유저에게 물어서 2 또는 3에 저장한다.

helper는 KSKILL_FORESTTRIP_ID, KSKILL_FORESTTRIP_PASSWORD 환경변수를 읽는다. secret vault나 secrets.env 는 에이전트/사용자가 값을 꺼내 실행 환경에 주입하기 위한 저장 위치이며, helper가 임의로 계정 정보를 다른 곳에 저장하지 않는다.

처음 실행 순서

처음 쓰는 사용자는 의존성 확인 후 환경변수를 현재 shell에만 주입해서 1개 휴양림으로 먼저 조회한다.

export KSKILL_FORESTTRIP_ID="your-foresttrip-id"
export KSKILL_FORESTTRIP_PASSWORD="your-foresttrip-password"

python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --check-deps
python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --forest-name 유명산 --text --dates 20260504

성공 여부를 먼저 보려면 전체 조회보다 --forest-name 또는 --forest-id 로 범위를 좁혀 실행한다. JSON 결과가 필요하면 같은 조건에 --json 을 사용한다.

입력값

  • 날짜: YYYYMMDD
  • 여러 날짜: YYYYMMDD,YYYYMMDD
  • 조회 범위: 전체 자연휴양림, 휴양림 ID, 휴양림명 부분 일치
  • 카테고리:
    • 01: 숙박
    • 02: 야영/캠핑
    • 01,02: 숙박 + 야영/캠핑
  • 고급 옵션:
    • --week-range N: --dates 를 생략했을 때만 오늘부터 N주 조회
    • --concurrency N: 병렬 조회 worker 수, 1-5 범위
    • --session-cache PATH: 로그인 세션 캐시 경로 override

기본 흐름

  1. KSKILL_FORESTTRIP_ID, KSKILL_FORESTTRIP_PASSWORD 를 확보한다.
  2. 필요한 경우 python3 -m pip install playwrightpython3 -m playwright install chromium 을 실행한다.
  3. helper로 read-only 월별예약조회 endpoint를 실행한다.
  4. helper가 로그인 세션, CSRF, 공식 휴양림 ID 목록을 확보한다.
  5. 날짜, 휴양림명, 객실/시설명, 숙박/야영 구분, 정원 중심으로 요약한다.
  6. 응답 정제: API가 srchDate 기준 최대 5일 윈도우를 반환할 수 있어 helper가 요청 범위 밖 useDt, 운영자 보유분("예비" 포함 객실), 같은 객실 중복 행을 자동 제거한다.

2026-04-29 확인 기준, 로그인 없이 월별예약조회 화면에 접근하면 401 Unauthorized가 반환되고, 조회 endpoint는 JSON 대신 안내 HTML을 반환한다. 따라서 현재 구현은 로그인 세션/CSRF 확보를 필수 전제로 둔다.

검증 방식

메인테이너가 별도 숲나들e 계정을 새로 만들 필요는 없다.

  • CI/리뷰 검증: ./scripts/validate-skills.sh, python3 -m py_compile ..., --help, --check-deps 로 진행한다.
  • 실제 조회 검증: 기여자 또는 이미 숲나들e 계정을 가진 사용자가 개인 계정으로 선택 실행한다.
  • PR에는 실제 조회 결과의 forests_scanned, fetch_failures, filter_hits 같은 비민감 요약값만 기록하고, 계정 정보와 세션 쿠키는 공유하지 않는다.

예시

전체 자연휴양림에서 하루 조회:

python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504

JSON으로 조회:

python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --json --dates 20260504

여러 날짜 조회:

python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504,20260505

야영/캠핑만 조회:

python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504 --categories 02

휴양림명으로 좁혀 조회:

python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --forest-name 유명산 --text --dates 20260504

로그인 세션 캐시를 무시하고 새로 조회:

python3 foresttrip-vacancy/scripts/run_foresttrip_vacancy.py --all --text --dates 20260504 --refresh-session

주의할 점

  • 예약 자동화가 아니다.
  • 결제, 캡차 처리, 대기열 우회는 하지 않는다.
  • aggressive polling은 피한다.
  • 조회 결과는 시점 차이로 숲나들e 화면과 달라질 수 있다.
  • 로그인 실패 시 계정 정보 또는 숲나들e 정책 변경을 먼저 확인한다.
  • API가 요청 날짜보다 넓은 5일 윈도우를 반환해도 출력에는 요청 범위(todaylast_day) 안의 행만 포함된다.
  • "예비" 표기가 있는 객실은 사용자 예약 화면에 노출되지 않는 운영자 보유분이라 결과에서 자동 제외된다.

흔한 문제 해결

  • Playwright browser missing: python3 -m playwright install chromium 을 실행한다.
  • Missing KSKILL_FORESTTRIP_ID 또는 Missing KSKILL_FORESTTRIP_PASSWORD: 환경변수가 현재 shell에 주입됐는지 확인한다.
  • 로그인 실패: 숲나들e 웹사이트에서 같은 계정으로 직접 로그인되는지 먼저 확인한다.
  • 날짜/카테고리/출력 옵션 오류: helper가 로그인 전에 argparse error로 중단하므로 메시지에 맞춰 값을 고친다.
  • JSON 대신 HTML 안내 페이지가 반환됨: 세션/CSRF가 없거나 만료된 상태일 수 있으므로 --refresh-session 으로 1회 재조회한다.
  • 일부 휴양림 fetch failure: 성공한 결과와 실패 개수를 함께 보고하고, 반복 polling으로 보정하지 않는다.