k-skill/packages/k-skill-proxy
Jeffrey (Dongkyu) Kim 66acdf249a Fix parking lot lookups: force HTTPS, cache full dataset, normalize provider fields
Data.go.kr 이 tn_pubr_prkplce_info_api 를 HTTPS 로만 서비스하고 HTTP 요청은 301 로 리다이렉트하기 때문에 Node fetch 가 `response.ok=false` 로 떨어져 기능이 전체 실패하고 있었다. 이 커밋은 HTTPS 로 직접 호출하도록 수정하면서, 업스트림의 주소/지역 필터가 실제로는 동작하지 않고 페이지당 응답이 1000rows 기준 26s 에 달해 20s fetch timeout 에 꾸준히 걸리던 문제까지 함께 해결한다.

## What changed

- packages/k-skill-proxy/src/parking-lots.js
  - PARKING_LOT_API_URL 을 `http://` → `https://` 로 고정 (root cause).
  - 업스트림 address/geo 필터가 신뢰 불가하므로 full-dataset 을 한 번 로드해 프로세스 메모리에 6시간 TTL 로 캐시하고, 동시 호출자는 in-flight promise 를 공유하도록 한다. nearby 쿼리는 캐시된 행을 좌표 거리로 필터링해 서비스한다.
  - DATASET_PAGE_SIZE=300, fetch timeout 30s 로 페이지당 응답이 20s 를 넘기지 않도록 맞췄다.
- packages/k-skill-proxy/src/server.js
  - 더 이상 의미 없어진 numOfRows / maxPages 쿼리 파라미터를 라우트에서 제거하고, 응답 payload 의 query echo 도 정리했다.
- packages/k-skill-proxy/test/server.test.js
  - 새 캐시 기반 동작을 검증하는 테스트로 교체: (1) full dataset load + 좌표 필터 + 프록시 응답 캐시 재사용, (2) public_only 기본값 및 해제 시 동작, (3) 좌표 검증 실패 400, (4) 업스트림 키 미설정 시 503.
- packages/parking-lot-search/src/index.js
  - OFFICIAL_API_URL 도 HTTPS 로 맞춰 직접 호출 모드 사용자도 같은 버그를 밟지 않게 한다.
- packages/parking-lot-search/src/parse.js
  - 업스트림이 `insttCode` / `insttNm` (camelCase) 를 돌려주는데 parser 가 snake_case (`instt_code`, `instt_nm`) 만 인식해 providerCode/providerName 이 비어 있던 문제를 수정.
- packages/parking-lot-search/test/* 및 fixtures
  - HTTPS URL 매칭으로 업데이트하고, insttCode/insttNm 회귀 테스트를 fixture/assertion 에 추가.
- docs/features/parking-lot-search.md, parking-lot-search/SKILL.md, packages/parking-lot-search/README.md
  - 공식 endpoint 표기를 HTTPS 로 통일.
- .changeset/parking-lot-https-fix.md
  - parking-lot-search 패키지 patch 릴리즈 노트 추가.

## How it was verified

- `npm run ci` (lint + typecheck + tests + pack:dry-run) 통과.
- 로컬에서 실제 `DATA_GO_KR_API_KEY` 로 k-skill-proxy 를 기동해 live 호출 검증:
  - 광화문 (37.573713, 126.978338) cold cache: 30s 내 전체 18,868 rows 로드, 2km 내 47개 공영주차장 반환 (세종로 414m, 서린노외 456m 등).
  - 강남역 (37.497952, 127.027621) warm cache: 31ms 응답, 1.5km 내 13개 반환 (역삼문화공원 380m, 역삼푸른솔도서관 421m 등).
- 업스트림 직접 HTTPS 호출로 `resultCode=00 NORMAL_SERVICE` 정상 동작 확인.
2026-04-22 00:46:04 +09:00
..
src Fix parking lot lookups: force HTTPS, cache full dataset, normalize provider fields 2026-04-22 00:46:04 +09:00
test Fix parking lot lookups: force HTTPS, cache full dataset, normalize provider fields 2026-04-22 00:46:04 +09:00
package.json Sync dev → main: MFDS proxy fixes, cache hardening, HWP kordoc, KRX degraded handling + new skills (#152) 2026-04-21 09:53:03 +09:00
README.md Sync dev → main: MFDS proxy fixes, cache hardening, HWP kordoc, KRX degraded handling + new skills (#152) 2026-04-21 09:53:03 +09:00

k-skill-proxy

k-skill용 Fastify 기반 프록시 서버입니다. AirKorea 미세먼지 조회, 기상청 단기예보, 서울 지하철 실시간 도착정보, 한강홍수통제소 수위 정보를 감싸고, 이후 무료/공공 API adapter를 추가하는 베이스로 씁니다.

현재 제공 엔드포인트

  • GET /health
  • GET /v1/fine-dust/report
  • GET /v1/korea-weather/forecast
  • GET /v1/seoul-subway/arrival
  • GET /v1/han-river/water-level
  • GET /v1/household-waste/info — 생활쓰레기 배출정보(DATA_GO_KR_API_KEY; pageNo=1, numOfRows=100 필수)
  • GET /v1/parking-lots/search — 전국주차장정보표준데이터 기반 근처 공영주차장 검색(DATA_GO_KR_API_KEY)
  • GET /v1/neis/school-search — 나이스 학교기본정보(교육청명·학교명 검색)
  • GET /v1/neis/school-meal — 나이스 급식식단정보(일자별 메뉴)
  • GET /v1/mfds/drug-safety/lookup — 식약처 의약품개요정보(e약은요) + 안전상비의약품 정보(DATA_GO_KR_API_KEY)
  • GET /v1/mfds/food-safety/search — 식약처 부적합 식품 + 식품안전나라 회수 정보(DATA_GO_KR_API_KEY, 선택적 FOODSAFETYKOREA_API_KEY)
  • GET /v1/korean-stock/search
  • GET /v1/korean-stock/base-info
  • GET /v1/korean-stock/trade-info
  • GET /v1/naver-shopping/search — 네이버 검색 Open API 쇼핑 검색 우선, 키가 없으면 네이버 쇼핑 공개 BFF JSON 기반 상품/가격 후보 조회
  • GET /v1/data4library/library-search — 도서관 정보나루 정보공개 도서관 조회(DATA4LIBRARY_AUTH_KEY)
  • GET /v1/data4library/book-search — 도서관 정보나루 도서 검색(DATA4LIBRARY_AUTH_KEY)
  • GET /v1/data4library/book-detail — 도서관 정보나루 도서 상세 조회(DATA4LIBRARY_AUTH_KEY)
  • GET /v1/data4library/libraries-by-book — 도서 소장 도서관 조회(DATA4LIBRARY_AUTH_KEY)
  • GET /v1/data4library/book-exists — 도서관별 도서 소장여부(DATA4LIBRARY_AUTH_KEY)

환경변수

  • AIR_KOREA_OPEN_API_KEY — 프록시 서버 쪽 AirKorea upstream key
  • KMA_OPEN_API_KEY — 프록시 서버 쪽 기상청 단기예보 upstream key
  • SEOUL_OPEN_API_KEY — 프록시 서버 쪽 서울 열린데이터 광장 upstream key
  • HRFCO_OPEN_API_KEY — 프록시 서버 쪽 한강홍수통제소 upstream key
  • KEDU_INFO_KEY — 프록시 서버 쪽 나이스(NEIS) 교육정보 개방 포털 Open API 인증키 (school-search, school-meal)
  • DATA4LIBRARY_AUTH_KEY — 프록시 서버 쪽 도서관 정보나루 Open API 인증키 (data4library/*)
  • FOODSAFETYKOREA_API_KEY — 프록시 서버 쪽 식품안전나라 회수정보 live key (mfds/food-safety/search; 없으면 sample feed fallback)
  • KRX_API_KEY — 프록시 서버 쪽 KRX Open API upstream key
  • NAVER_SEARCH_CLIENT_ID, NAVER_SEARCH_CLIENT_SECRET — 선택: 네이버 검색 Open API 쇼핑 검색(shop.json) 키. 설정되면 네이버 쇼핑 route가 bot-block 위험이 낮은 공식 API를 우선 사용하고, 없으면 공개 BFF JSON(ns-portal.shopping.naver.com/api/v2/shopping-paged-slot) 파서로 fallback. 공식 API는 review 정렬을 지원하지 않아 meta.sort_applied: "unsupported"로 표시한다. no-key fallback은 page를 BFF에 전달해 해당 페이지를 고르고, price_asc/price_dsc/review는 선택 페이지 안에서 로컬 정렬하며, datemeta.sort_applied: "unsupported"로 표시
  • KSKILL_PROXY_HOST — 기본 127.0.0.1
  • KSKILL_PROXY_PORT — 기본 4020
  • KSKILL_PROXY_CACHE_TTL_MS — 기본 300000
  • KSKILL_PROXY_RATE_LIMIT_WINDOW_MS — 기본 60000
  • KSKILL_PROXY_RATE_LIMIT_MAX — 기본 60
  • DATA_GO_KR_API_KEY - 공공데이터포털 에서 쓰이는 API 인증키 (household-waste, parking-lots, real-estate, mfds-drug-safety, mfds-food-safety)

기본 정책은 무료 API 공개 프록시 = 무인증 이다. 대신 endpoint scope 를 좁게 유지하고, cache + rate limit 으로 남용을 늦춘다.

로컬 실행

node packages/k-skill-proxy/src/server.js

환경변수(AIR_KOREA_OPEN_API_KEY 등)가 이미 설정되어 있거나 ~/.config/k-skill/secrets.env를 source한 상태에서 실행한다.

서울 지하철 도착정보 예시:

curl -fsS --get 'http://127.0.0.1:4020/v1/seoul-subway/arrival' \
  --data-urlencode 'stationName=강남'

한국 날씨 예시:

curl -fsS --get 'http://127.0.0.1:4020/v1/korea-weather/forecast' \
  --data-urlencode 'lat=37.5665' \
  --data-urlencode 'lon=126.9780'

한강 수위 정보 예시:

curl -fsS --get 'http://127.0.0.1:4020/v1/han-river/water-level' \
  --data-urlencode 'stationName=한강대교'

나이스 학교 검색·급식 식단 예시 (KEDU_INFO_KEY 필요). 급식은 교육청 코드(ATPT_OFCDC_SC_CODE)와 학교 코드(SD_SCHUL_CODE)가 필요하므로 보통 아래 순서로 호출한다.

학교 검색:

curl -fsS --get 'http://127.0.0.1:4020/v1/neis/school-search' \
  --data-urlencode 'educationOffice=서울특별시교육청' \
  --data-urlencode 'schoolName=미래초등학교'

급식 식단:

curl -fsS --get 'http://127.0.0.1:4020/v1/neis/school-meal' \
  --data-urlencode 'educationOfficeCode=B10' \
  --data-urlencode 'schoolCode=7010123' \
  --data-urlencode 'mealDate=20260410'

생활쓰레기 배출정보 예시 (DATA_GO_KR_API_KEY 필요). pageNo·numOfRows는 반드시 1·100:

curl -fsS --get 'http://127.0.0.1:4020/v1/household-waste/info' \
  --data-urlencode 'cond[SGG_NM::LIKE]=강남구' \
  --data-urlencode 'pageNo=1' \
  --data-urlencode 'numOfRows=100'

공영주차장 검색 예시 (DATA_GO_KR_API_KEY 필요):

curl -fsS --get 'http://127.0.0.1:4020/v1/parking-lots/search' \
  --data-urlencode 'latitude=37.573713' \
  --data-urlencode 'longitude=126.978338' \
  --data-urlencode 'address_hint=서울특별시 종로구' \
  --data-urlencode 'limit=3' \
  --data-urlencode 'radius=1500'

의약품 안전 체크 예시 (DATA_GO_KR_API_KEY 필요):

curl -fsS --get 'http://127.0.0.1:4020/v1/mfds/drug-safety/lookup' \
  --data-urlencode 'itemName=타이레놀' \
  --data-urlencode 'itemName=판콜' \
  --data-urlencode 'limit=5'

식품 안전 체크 예시 (DATA_GO_KR_API_KEY 필요, FOODSAFETYKOREA_API_KEY 없으면 회수 정보는 sample fallback):

curl -fsS --get 'http://127.0.0.1:4020/v1/mfds/food-safety/search' \
  --data-urlencode 'query=김밥' \
  --data-urlencode 'limit=5'

네이버 쇼핑 가격비교 예시 (NAVER_SEARCH_CLIENT_ID/NAVER_SEARCH_CLIENT_SECRET이 있으면 공식 Search API를 우선 사용):

curl -fsS --get 'http://127.0.0.1:4020/v1/naver-shopping/search' \
  --data-urlencode 'q=에어팟 프로 2세대' \
  --data-urlencode 'limit=10'

도서관 정보나루 도서 검색 예시 (DATA4LIBRARY_AUTH_KEY 필요):

curl -fsS --get 'http://127.0.0.1:4020/v1/data4library/book-search' \
  --data-urlencode 'keyword=역사' \
  --data-urlencode 'pageNo=1' \
  --data-urlencode 'pageSize=10'

도서관 정보나루 상세/소장 확인 예시:

curl -fsS --get 'http://127.0.0.1:4020/v1/data4library/book-detail' \
  --data-urlencode 'isbn13=9788971998557' \
  --data-urlencode 'loaninfoYN=Y'

curl -fsS --get 'http://127.0.0.1:4020/v1/data4library/book-exists' \
  --data-urlencode 'libraryCode=111001' \
  --data-urlencode 'isbn13=9788971998557'

한국 주식 검색 예시:

curl -fsS --get 'http://127.0.0.1:4020/v1/korean-stock/search' \
  --data-urlencode 'q=삼성전자' \
  --data-urlencode 'bas_dd=20260408'

프록시는 내부적으로 waterlevel/info.json 으로 관측소를 해석하고, waterlevel/list/10M/{WLOBSCD}.json 으로 최신 수위/유량을 조회합니다. 한국 주식 route는 KRX Open API에 AUTH_KEY 헤더를 서버 쪽에서만 주입합니다.

PM2 실행

루트의 ecosystem.config.cjs + scripts/run-k-skill-proxy.sh 조합을 사용하면 재부팅 이후에도 같은 환경변수로 다시 올라옵니다.