docs: 착수 전 조사와 공공데이터 점검 결과 정리

개발 전에 확정해야 할 전제(데이터 저장 가능 범위·지도 SDK·계정 리드타임)와
공공데이터 실측 결과를 남긴다.

- pre-dev-check: 카카오 장소 데이터 저장·LLM 전송 금지 조건, 지도 SDK 비교
  (네이버 권장), 스토어·위치기반서비스 신고 등 계정·승인 체크리스트
- data-openapi-review: 관광공사 국문·영문·반려동물, 상가정보, 단기예보 매뉴얼 검토
- data-check: 서울·부산·대구 점검 요약과 원자료(JSON, 키 미포함)

10-29 점검의 관광공사·반려동물 개수는 위치 검색으로 센 과소 집계였다.
2026-11-02 재측정 결과를 각 요약 맨 위에 정정 메모로 달았다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 14:53:24 +09:00
parent 82087b7ac7
commit 0b385a896e
13 changed files with 4586 additions and 0 deletions
@@ -0,0 +1,290 @@
# 반려동물·영문 서비스 매뉴얼 검토 (2026-10-29)
원문(`docs/data-openapi/`):
반려동물동반여행 활용매뉴얼 v4.1(2026-02-25) · 영문 활용매뉴얼 v4.4(2026-02-10) · 다국어 서비스분류코드 v4.2 ·
참고: 국문 활용매뉴얼 v4.4 · 신분류체계정보 관광타입정보 연계 정의서 · 활용신청방법 매뉴얼 v3.3
표기: **[매뉴얼]** 원문 인용·요약 / **[추론]** 내 판단, 실제 호출로 확인 필요
---
## 핵심 요약
1. 반려동물 전용 API는 `KorPetTourService2`(`http://apis.data.go.kr/B551011/KorPetTourService2`)다. 지역·위치(반경 최대 20km)·키워드·동기화 **목록** 오퍼레이션이 있어 동반 가능 장소를 모아 받을 수 있다.
2. 국문 `KorService2/detailPetTour2` 는 contentId가 선택값이고 "미기입 시 반려동물 전부 출력"이라고 적혀 있다. 반려동물 정보 전체를 페이지 단위로 한꺼번에 받을 수 있다는 뜻이다.
3. 반려동물 필드 9개는 모두 자유 문장이다. `acmpyTypeCd` 도 코드표가 없고 "전구역/일부구역 동반가능" 같은 문자열이다. 크기·무게·종을 나타내는 코드 필드는 없다.
4. 반려동물 서비스는 "동반여행 가능한" 장소만 다룬다. 그래서 '불가'를 나타내는 값이 없다. 이 서비스에 없는 장소는 '불가'가 아니라 '미확인'이다.
5. 영문 레코드를 국문 contentid와 이어 주는 필드는 매뉴얼 어디에도 없다. 대신 법정동 코드·신분류(`lclsSystm`) 코드·좌표·우편번호가 언어와 관계없이 같고, 제목 괄호 안에 한국어 이름이 들어 있다.
6. 신분류(`lclsSystm`)는 언어 공통 코드다. 연계 정의서가 소분류마다 국문 타입(12…)과 다국어 타입(76…)을 함께 정해 둔다. 옛 cat1~3 분류는 v4.4부터 요청·응답 양쪽에서 삭제되었다.
7. 영문 서비스에는 반려동물 오퍼레이션이 없고 소개정보에도 `chkpet` 계열 필드가 없다. 영문 화면에 보여 줄 반려동물 정보는 짝지은 국문 데이터에서 가져와 번역해야 한다.
---
## 1. 반려동물 서비스 (KorPetTourService2)
### 1.1 서비스 정보
| 항목 | [매뉴얼] 내용 |
| --- | --- |
| 서비스ID | `KorPetTourService2` |
| URL (개발·운영 같음) | `http://apis.data.go.kr/B551011/KorPetTourService2` (예제는 `https://` 도 사용, 전송 레벨 "[O]HTTPS [O]HTTP") |
| 설명 | "반려동물 동반여행 가능한 관광지, 문화시설, 축제공연행사, 숙박, 음식점, 레포츠, 쇼핑의 관광정보를 제공한다." |
| 버전·시작일 | 서비스 버전 4.0, 시작일 2024-10-22 |
| 갱신 주기 | "일 1회" |
| 형식 | 기본 XML, "JSON을요청할경우“&_type=json”을추가" |
| 트래픽 | "개발계정은 일 1,000건의 트래픽을 제공합니다." / 활용신청 매뉴얼: "개발계정은 각 오퍼레이션별 일일 1,000건의 트래픽을 제공합니다." |
| 운영계정 | 활용신청 매뉴얼: "운영계정은 한국관광공사 담당자의 승인이 필요하며, 약 1~3일 정도 소요", "승인일로부터 24개월 간 활용 가능", 승인 때 "활용 어플 URL"·"개발계정의 TourAPI 호출 이력" 확인, 한국관광공사 [활용사례]에 등록되어 홍보됨. **운영계정 한도 수치는 이 문서들에 없다.** |
| 문의 | 개방데이터운영팀 / 디지털콘텐츠팀 / 070-4287-3219 / tourapi@knto.or.kr |
| 라이선스 | 이미지·콘텐츠 단위 `cpyrhtDivCd`: "Type1: 제1유형(출처표시-권장) / Type3: 제3유형(제1유형 + 변경금지)". 텍스트 전체에 대한 별도 라이선스 조항은 매뉴얼에 없다. 활용신청 때 "라이선스 표시 동의는 필수"다. |
- data.go.kr 데이터셋 번호(15135102)는 매뉴얼에 나오지 않는다. **[추론]** 같은 서비스일 가능성이 높다. 포털 페이지의 엔드포인트가 `KorPetTourService2` 인지 한 번 확인하면 된다.
- **[추론]** data.go.kr 활용신청은 서비스 단위라서 KorService2 키가 있어도 이 서비스는 따로 신청해야 할 수 있다(매뉴얼에 명시된 내용은 아니다).
**콘텐츠 타입 코드**: [매뉴얼] 코드표는 국문 코드 `12 관광지, 14 문화시설, 15 행사/공연/축제, 28 레포츠, 32 숙박, 38 쇼핑, 39 음식점` 이다. 여행코스(25)는 없다.
- [매뉴얼 불일치] `areaBasedList2`·`locationBasedList2`·`detailInfo2` 의 contentTypeId 설명에는 다국어 코드("75: 레포츠, 76: 관광지 … 85: 축제공연행사")가 적혀 있다. 하지만 코드표, 샘플(`contentTypeId=12`, 응답 `"contenttypeid": "12"`/`"38"`), `petTourSyncList2`·`detailIntro2` 응답 설명은 국문 코드를 쓴다.
**[추론]** 국문 코드가 맞고, 다국어 코드 문구는 영문 매뉴얼에서 복사하다 남은 오류로 보인다.
### 1.2 오퍼레이션 (11개)
공통 필수: `serviceKey`, `MobileOS`(IOS/AND/WIN/ETC, 코드 조회는 WEB/ETC 표기), `MobileApp`(서비스명). 공통 선택: `numOfRows`, `pageNo`, `_type`.
페이징은 `numOfRows`/`pageNo`, 응답에 `totalCount`. **numOfRows 최대값은 매뉴얼에 없다.**
| # | 오퍼레이션 | 용도 | 추가 필수 | 추가 선택 |
| --- | --- | --- | --- | --- |
| 1 | `areaBasedList2` | 지역기반 목록 | — | `arrange`, `contentTypeId`, `modifiedtime`(YYYYMMDD), `lDongRegnCd`, `lDongSignguCd`, `lclsSystm1/2/3` |
| 2 | `locationBasedList2` | 위치기반 목록 | `mapX`, `mapY`, `radius` ("Max값 20000m=20Km") | `arrange`(+ `E`/`S` 거리순), `contentTypeId`, `modifiedtime`, `lDong*`, `lclsSystm*` |
| 3 | `searchKeyword2` | 키워드 검색 | `keyword` | `arrange`, `lDong*`, `lclsSystm*` (v4.1에서 "요청항목 (관광타입) 삭제") |
| 4 | `detailCommon2` | 공통정보 | `contentId` | — |
| 5 | `detailIntro2` | 소개정보(시간·주차 등, `chkpet` 계열 포함) | `contentId`, `contentTypeId` | — |
| 6 | `detailInfo2` | 반복정보 | `contentId`, `contentTypeId` | — |
| 7 | `detailImage2` | 이미지 | `contentId` | `imageYN` (Y=콘텐츠 이미지, N=음식점 메뉴 이미지) |
| 8 | `detailPetTour2` | 반려동물 동반 정보 | `contentId` (항목구분 1=필수) | — |
| 9 | `petTourSyncList2` | 동기화 목록 | — | `showflag`(1=표출, 0=비표출), `modifiedtime`, `arrange`, `contentTypeId`, `lDong*`, `lclsSystm*` |
| 10 | `ldongCode2` | 법정동 코드 | — | `lDongRegnCd`, `lDongListYn`(N 코드조회 / Y 전체목록) |
| 11 | `lclsSystmCode2` | 신분류 코드 | — | `lclsSystm1/2/3`, `lclsSystmListYn` |
`arrange`: "A=제목순, C=수정일순, D=생성일순 / 대표이미지가 반드시 있는 정렬 (O=제목순, Q=수정일순, R=생성일순)", 위치기반은 E/S 거리순이 더 있다.
**목록 응답 필드** (1·2·3·9 공통): `contentid`, `contenttypeid`, `title`, `addr1`, `addr2`, `zipcode`, `tel`, `mapx`(경도), `mapy`(위도), `mlevel`,
`firstimage`(약 500×333), `firstimage2`(약 150×100), `cpyrhtDivCd`, `createdtime`, `modifiedtime`, `lDongRegnCd`, `lDongSignguCd`, `lclsSystm1/2/3`.
위치기반은 `dist`(m), 동기화 목록은 `showflag` 가 더 붙는다.
- [매뉴얼] 목록 응답에는 동반 조건(`acmpyTypeCd` 등)이 없다. 조건을 보려면 장소마다 `detailPetTour2` 를 호출해야 한다.
- [매뉴얼] 축제 날짜 검색(`searchFestival2`)과 숙박 목록(`searchStay2`)이 없다. 반려동물 동반 행사는 `areaBasedList2&contentTypeId=15` 로 받은 뒤 `detailIntro2` 의 `eventstartdate`/`eventenddate` 로 걸러야 한다.
- [매뉴얼 불일치] 개정이력에 "동기화 오퍼레이션 / 요청항목 (이전 콘텐츠 ID) 추가"가 있지만 `petTourSyncList2` 요청·응답표에는 `oldContentid` 가 없다. 국문·영문 동기화 목록에는 있다.
- v4.1 변경 [매뉴얼]: 지역코드·서비스분류코드 오퍼레이션 삭제, "요청/응답 : 지역코드 , 시군구코드 삭제", "서비스 대분류, 중분류, 소분류 / 삭제" → `areaCode`·`cat1~3` 대신 `lDong*`·`lclsSystm*` 를 쓴다.
### 1.3 `detailPetTour2` 응답 필드
| 필드 | [매뉴얼] 국문명 | [매뉴얼] 샘플 | 비고 |
| --- | --- | --- | --- |
| `acmpyTypeCd` | 동반유형코드(동반구분) | "전구역 동반가능"(반려 매뉴얼), "일부구역 동반가능"(국문 매뉴얼) | 이름은 '코드'지만 값은 한글 문장. **값 목록·코드표 없음** |
| `acmpyPsblCpam` | 동반가능동물 | "전 견종 출입 가능(맹견의 경우, 입마개 착용 필수)", "전 견종 동반 가능" | 크기·무게·종 정보는 이 문장 안에만 있다 (우리 표본: "5kg 이하 숙박가능한 견종") |
| `acmpyNeedMtr` | 동반시 필요사항 | "목줄 착용", "목줄 착용,반려동물 유모차 탑승,이동장(켄넬)사용" | 쉼표 구분 목록처럼 보임 |
| `etcAcmpyInfo` | 기타 동반 정보 | "- 목줄은 2m 이내로 유지\n- 맹견의 경우, 입마개 착용 필수\n- 배변봉투 지참 및 배변처리 필수" | `\n` 줄바꿈 포함 |
| `relaAcdntRiskMtr` | 관련 사고 대비사항 | "반려견보호자 서약서 서명" | |
| `relaPosesFclty` | 관련 구비 시설 | (빈 값) | |
| `relaFrnshPrdlst` | 관련 비치 품목 | (빈 값) | |
| `relaPurcPrdlst` | 관련 구매 품목 | (빈 값) | |
| `relaRntlPrdlst` | 관련 렌탈 품목 | (빈 값) | |
| `contentid` | 콘텐츠ID | 1059479 / 125534 | |
| `petTursmInfo` | 반려동물 관광정보 | (샘플 없음) | **KorService2 쪽 매뉴얼에만 있음** |
- **[매뉴얼] 크기·무게·종을 나타내는 코드 필드는 없다.** `acmpyTypeCd` 에 어떤 값이 올 수 있는지도 적혀 있지 않다.
- 소개정보(`detailIntro2`)의 반려동물 필드 [매뉴얼]: `chkpet`(관광지 12), `chkpetculture`(문화시설 14), `chkpetleports`(레포츠 28), `chkpetshopping`(쇼핑 38).
국문명은 "애완동물동반가능정보"이고 샘플 값은 모두 빈 문자열이라 **값 형식을 알 수 없다**. 숙박(32)·음식점(39)·행사(15)에는 이 필드가 없다.
- 반려 매뉴얼의 `detailIntro2` 행사(15) 필드에는 국문 매뉴얼에 없는 `festivaltype`(축제 종류)·`progresstype`(행사 진행 상황)이 있다.
### 1.4 KorService2 `detailPetTour2` 와의 관계
| | `KorService2/detailPetTour2` | `KorPetTourService2/detailPetTour2` |
| --- | --- | --- |
| 등장 | 국문 매뉴얼 v4.1(2023-01-30) "반려동물 여행 정보 오퍼레이션 추가", v4.4에도 유지 | 반려 서비스 시작 2024-10-22, 매뉴얼 v4.1(2026-02-25) |
| 유형 | "조회(상세)" | "조회(목록)" |
| contentId | 선택(0): "콘텐츠ID(미기입 시 반려동물 전부 출력)" | 필수(1) |
| 응답 | 9개 필드 + `contentid` + `petTursmInfo` | 9개 필드 + `contentid` |
- [매뉴얼] 어느 쪽도 다른 쪽을 대체하거나 폐지한다는 말이 없다. 두 매뉴얼 모두 2026년 개정판에 실려 있어 **둘 다 현행**이다. 같은 데이터인지는 적혀 있지 않다.
- **[추론]** 필드명과 값 형식이 같아 같은 원천 데이터로 보인다. 반려 매뉴얼의 contentid 예(128834 죽녹원, 264353 인사동, 1059479 여의도한강공원)도 국문 관광정보와 같은 ID 체계로 보인다.
→ 같은 contentId로 두 서비스를 한 번씩 호출해 값이 같은지 확인한다.
### 1.5 수집 방법 후보 **[추론]**
| 방법 | 호출 | 장점 | 확인할 것 |
| --- | --- | --- | --- |
| A. 동기화 목록 → 상세 | `petTourSyncList2&showflag=1` 전체 페이지 → contentid마다 `detailPetTour2` | 동반 가능 장소 전체 + 좌표·분류를 얻음. `modifiedtime` 으로 증분, `showflag=0` 으로 삭제 감지 | 상세 호출이 장소 수만큼 필요 → 개발계정 한도(오퍼레이션당 1,000/일) 안에서 여러 날로 나눠야 할 수 있음 |
| B. 국문 일괄 조회 | `KorService2/detailPetTour2` 를 contentId 없이 페이지로 | 반려 정보 전체를 적은 호출로 받음. 우리 국문 DB와 contentid로 조인 | 실제로 전체가 나오는지, `totalCount`·최대 `numOfRows` |
| C. 화면에서 바로 | `locationBasedList2` (radius ≤ 20000) | 근처 동반 가능 장소 | 우리 원칙("AI는 저장한 DB에서만 고름")상 런타임 호출보다 A/B로 미리 저장하는 쪽이 맞음 |
권장 순서: B로 전체 규모와 값 분포를 먼저 재고, A로 목록(좌표·분류)을 보강한다.
---
## 2. 영문 서비스(EngService2)와 국문 연결
### 2.1 서비스 정보 [매뉴얼]
- URL `http://apis.data.go.kr/B551011/EngService2`, 갱신 "일 1회", 개발계정 "일 1,000건".
- 오퍼레이션 12개: `areaBasedList2`, `locationBasedList2`, `searchKeyword2`, `searchFestival2`, `searchStay2`, `detailCommon2`, `detailIntro2`, `detailInfo2`, `detailImage2`, `areaBasedSyncList2`, `ldongCode2`, `lclsSystmCode2`.
- 국문과의 차이:
- **`detailPetTour2` 없음.**
- 여행코스: 코드표에 "여행코스 | 국문만 서비스".
- 교통(77): "교통(다국어만 서비스)".
- `detailInfo2`: "“숙박” 객실정보와 “여행코스” 코스정보는 국문 서비스만 제공합니다."
- 다른 언어(일·중 등) 매뉴얼은 이번 자료에 없다. 일·중 오퍼레이션 구성은 확인하지 못했다.
### 2.2 국문과 이어 주는 고리
- **[매뉴얼] 국문 contentid를 가리키는 필드는 없다.** 목록·공통정보·동기화 응답표 어디에도 없다.
- `oldContentid`(영문 `areaBasedSyncList2` 요청·응답): "이전 콘텐츠ID(DB 저장 동기화시 이전 key값으로 조회 용도)". 샘플에서 `contentid` 와 `oldContentid` 가 둘 다 `3488291` 이다.
**[추론]** 같은 영문 레코드의 이전 키다. 국문 ID가 아니다.
- 언어와 관계없이 같은 값 [매뉴얼 샘플]:
- 법정동: 영문 `ldongCode2` 가 같은 코드(11/110/140)를 주고 이름만 영문이다 ("Seoul", "Jongno-gu", "Jung-gu").
- 신분류: 영문 샘플이 국문과 같은 코드(`AC/AC04/AC040100`, `EV/EV02/EV021000`, `VE/VE07/VE070400`, `SH/SH06/SH060200`)를 쓰고, `lclsSystmCode2` 는 이름만 영문이다 ("Accommodation", "Hotels").
- `mapx`/`mapy`(WGS84), `zipcode`.
- 제목 형식 [매뉴얼 샘플 관찰 — 규칙으로 명시되어 있지는 않다]:
"HiKR Ground (하이커 그라운드)", "Wonju Sogeumsan Ulleong Bridge (원주 소금산 울렁다리)",
"Infinity Flying(인피니티 플라잉)"(괄호 앞 공백 없음), "Bupyeong (Kkangtong) Market (부평시장(깡통시장))"(괄호 중첩, 영문 부분에도 괄호).
→ 한국어 이름은 **맨 끝의 균형 잡힌 괄호 묶음**으로 뽑아야 한다.
- **[추론] 매칭 키 제안**: 같은 `lclsSystm1`(가능하면 3) + 같은 `lDongRegnCd/lDongSignguCd` + 좌표 거리 기준 + 괄호 속 한국어명 정규화 일치(+ `zipcode` 보조).
- 영문 행사 샘플은 `mapx`/`mapy` 가 빈 문자열이다(`searchFestival2` 예제). 행사는 좌표 없이 이름·법정동·기간으로 맞춰야 할 수 있다.
- 짝지은 결과는 우리 DB의 매핑 테이블로 저장하고, 어느 쪽 `modifiedtime` 이 바뀌면 다시 검증한다.
### 2.3 소개정보(`detailIntro2`) 필드 차이 [매뉴얼 응답표 비교]
영문 응답표에 **없는** 국문 필드:
| 타입 (국문/영문) | 국문에만 있는 필드 |
| --- | --- |
| 관광지 12/76 | `chkbabycarriage`, `chkcreditcard`, **`chkpet`**, `heritage2`, `heritage3` |
| 문화시설 14/78 | `chkbabycarriageculture`, `chkcreditcardculture`, **`chkpetculture`**, `discountinfo` |
| 행사 15/85 | `festivalgrade` |
| 레포츠 28/75 | `chkbabycarriageleports`, `chkcreditcardleports`, **`chkpetleports`** |
| 숙박 32/80 | `barbecue`, `beauty`, `beverage`, `bicycle`, `campfire`, `fitness`, `karaoke`, `publicbath`, `publicpc`, `sauna`, `seminar`, `sports`, `refundregulation` |
| 쇼핑 38/79 | `chkbabycarriageshopping`, `chkcreditcardshopping`, **`chkpetshopping`**, `culturecenter`, `saleitemcost` |
| 음식점 39/82 | `chkcreditcardfood`, `discountinfofood`, `kidsfacility`, `packing` |
영문에만 있는 것: 교통 77 — `chkcreditcardtraffic`, `conven`, `disablefacility`, `foreignerinfocenter`(외국인문의및안내), `infocentertraffic`, `mainroute`, `operationtimetraffic`, `parkingtraffic`, `restroomtraffic`, `shipinfo`.
**[추론]** 매뉴얼 표 기준이다. 실제 응답에 빈 키로 들어오는지는 호출로 확인해야 한다.
### 2.4 번역 여부 [매뉴얼 샘플]
- 영문으로 되어 있는 것:
- `overview`
- `usetime`: "November-April 09:00-17:00 / May-October 09:00-18:00<BR> * Last ticketing 1 hr 30 min before closing"
- `restdate`: "Mondays<br>* If Monday falls on a public holiday, it will be closed on the following business day instead"
- `parking`: "Available"
- `detailInfo2`: `infoname` "Admission Fees", `infotext` "Adults 9,000 won / Children 5,000 won"
- 대부분의 `addr1`: "40 Cheonggyecheon-ro, Jung-gu, Seoul"
- 영문이 아닌 것:
- `areaBasedList2` 예제의 `addr1` 은 국문 "경상북도 경주시 경감로 614 (천군동)"이고 `addr2` 가 영문이다.
- `detailImage2` 의 `imgname` 은 국문이다("소금산 울렁다리(2)").
- `tel` 은 국제 형식이다("+82-33-***-****").
- **[추론]** 영문 시간 문장은 국문과 형식이 달라 영업시간 파서를 언어별로 두어야 한다. 국문·영문 레코드는 수정 시각이 따로라서 같은 장소라도 값이 어긋날 수 있다.
"도착 시각 영업 중" 판정은 국문 값 하나를 기준으로 하는 편이 일관된다.
### 2.5 이미지·라이선스·한도
- [매뉴얼] `cpyrhtDivCd` Type1/Type3 정의는 국문과 같다. `detailImage2` 는 "이미지 저작권 공공누리 유형"을 함께 준다. 영문 샘플은 대부분 Type3이고, 이미지가 없으면 `cpyrhtDivCd` 도 빈 값이다.
- [매뉴얼 샘플] 영문 `detailCommon2` 의 `firstimage2` 와 `detailImage2` 의 `smallimageurl` 이 원본과 같은 `..._image2_1.jpg` 다. 영문은 썸네일이 원본 URL일 수 있다.
- **[추론]** Type3(변경금지) 이미지를 카드에서 잘라 보여 주는 것이 '변경'에 해당하는지는 공공누리 기준을 확인해야 한다. 출처 표시는 두 유형 모두 필요하다.
### 2.6 기타 차이
- [매뉴얼 불일치] 영문 `searchKeyword2` 요청표에는 `contentTypeId` 가 남아 있다. 개정이력에는 "키워드 검색 오퍼레이션 / 요청항목 (관광타입) 삭제"라고 되어 있다.
- 영문 `searchStay2` 의 `arrange` 에 "P=조회순"이 적혀 있다. 국문 v4.1에서 조회수가 제거되었으므로 남은 문구로 보인다 [추론].
- `detailIntro2`·`detailInfo2` 의 `contentTypeId` 에는 **해당 언어의 코드**를 넣는다(영문 예제: `contentTypeId=76`).
---
## 3. 다국어 분류 코드
### 3.1 콘텐츠 타입
[매뉴얼 영문 v4.4] "다국어콘텐츠 타입(ContentTypeId)코드표 — 관광정보 (다국어 공통)". 다국어 분류코드 xlsx의 일문·중문간체·중문번체·독·불·서·러 시트도 모두 같은 번호를 쓴다.
| 타입 | 국문 | 다국어(영·일·중 공통) |
| --- | --- | --- |
| 관광지 | 12 | 76 |
| 문화시설 | 14 | 78 |
| 행사/공연/축제 | 15 | 85 |
| 여행코스 | 25 | — (국문만) |
| 레포츠 | 28 | 75 |
| 숙박 | 32 | 80 |
| 쇼핑 | 38 | 79 |
| 음식점 | 39 | 82 |
| 교통 | — | 77 (다국어만) |
(같은 대응표가 연계 정의서 오른쪽 표에도 있다.)
### 3.2 신분류(`lclsSystm`) — 언어 공통
- [매뉴얼 연계 정의서] 신분류 소분류마다 "국문코드 | 다국어코드"를 함께 준다. 예:
- `AC010100` 호텔 → 32/80
- `AC05` 캠핑(일반야영장·오토캠핑장·카라반·글램핑장) → **28/75 레포츠**(숙박이 아니다)
- `VE050200` 리조트 → 32/80
- `FD050100` 카페 → 39/82
- `VE03` 도시공원 → 12/76
- `VE07` 전시시설 → 14/78
- `VE10` 레저스포츠시설 → 28/75
- `VE120100` 서점 → 14/78, `VE120200` 카지노 → 28/75
- `EV01` 축제 → 15/85
- `C01` 추천코스 → 25/–
- 대분류: AC 숙박, EV 축제/공연/행사, EX 체험관광, FD 음식, HS 역사관광, LS 레저스포츠, NA 자연관광, SH 쇼핑, VE 문화관광, C01 추천코스.
- [매뉴얼 영문] 영문 `lclsSystmCode2` 대분류 전체 조회 예제 응답은 `"totalCount": 9`(AC, EV, …)다. **[추론]** 다국어에는 C01(코스)이 빠진 9개다.
- **[추론]** 우리 DB 카테고리 키를 `lclsSystm3` 로 잡으면 언어별 타입 번호 차이가 사라진다. 일·중을 추가할 때도 그대로 쓸 수 있다.
언어별 표시 이름은 각 서비스의 `lclsSystmCode2&lclsSystmListYn=Y` 로 받아 저장한다.
같은 대분류 안에서도 타입이 갈리므로(VE → 12/14/28/32), 타입은 대분류가 아니라 **소분류 기준**으로 정해야 한다.
- [매뉴얼 이상] 반려 매뉴얼의 `lclsSystmCode2` 예제 응답 이름이 "Отель"(러시아어)로 되어 있다. 복사 오류로 보인다.
### 3.3 옛 분류(cat1~3)
- [매뉴얼] 다국어 서비스분류코드 v4.2는 옛 체계(`A01010100`, `B02010100` …)의 8개 언어 번역표다.
국문·영문 v4.4 개정이력(2026-02-10)에 "서비스분류코드 오퍼레이션 삭제", "요청/응답 : 서비스 대분류, 중분류, 소분류 / 삭제"가 있다. 반려 v4.1도 같다.
- **[추론]** 현재 API는 cat 코드로 거르거나 받을 수 없다. 이 xlsx는 예전 데이터를 옮기거나 번역어를 참고할 때만 쓴다. cat ↔ lclsSystm 대응표는 이번 자료에 없다.
---
## 4. 우리 가정과 다른 점
| 우리 가정 | 매뉴얼 | 영향 |
| --- | --- | --- |
| 전용 반려동물 서비스의 URL·오퍼레이션을 찾지 못함 | `KorPetTourService2`, 오퍼레이션 11개 (1.2) | 바로 호출해 볼 수 있다 |
| contentId마다 하나씩 조회해야 함 | 반려 서비스에 목록 4종(지역·위치·키워드·동기화)이 있다. 국문 `detailPetTour2` 는 contentId를 비우면 전체를 준다 | 표본 조사를 다시 한다. 수집은 1.5의 A/B |
| 반려동물 정보가 거의 없다(가까운 30곳 중 0/1/3) | 반려 정보는 일부 장소에만 있다. 국문 목록에서 가까운 곳을 고르면 잘 안 걸린다 | **[추론]** 표본 방식 때문에 적게 잡혔을 수 있다. 반려 서비스 목록으로 같은 지역을 다시 잰다 |
| 3상태(가능/불가/미확인) | 반려 서비스는 동반 가능 장소만 다루고 '불가' 값이 없다. `acmpyTypeCd` 는 전구역/일부구역 문장이다 | 반려 서비스에 없는 장소는 '미확인'. '불가'는 `chkpet` 문장(형식 미정)에서만 나올 수 있다. **[추론]** '일부구역'을 따로 두는 편이 정보 손실이 적다 (전구역 가능 / 일부구역 가능 / 불가 / 미확인) |
| 견종 크기 정보를 얻을 수 있다 | 구조화된 필드가 없다. `acmpyPsblCpam`·`acmpyNeedMtr`·`etcAcmpyInfo` 문장 안에만 있다 | 무게 상한(kg)·대형견 가능 여부·맹견 입마개·이동장/유모차 필수 등을 문장에서 뽑는 파서와 검수가 필요하다. 못 뽑으면 '미확인' |
| 영문↔국문은 이름+좌표로 맞춘다 | 공식 연결 키가 없다(가정과 같음) | 법정동·신분류 코드·우편번호가 언어 공통이라 매칭 키로 더 쓸 수 있다. 행사는 좌표가 비어 있을 수 있다 |
| 영문 타입 코드가 국문과 다르다 | 맞다. 공식 '다국어 공통' 코드이고 대응표가 있다(3.1) | 일·중 추가 때 그대로 재사용한다 |
| (암묵) 영문에서도 반려·편의 정보를 볼 수 있다 | 영문에는 `detailPetTour2` 와 `chkpet`·유모차·신용카드·놀이방 필드가 없다 | 영문 화면의 반려·편의 정보는 국문 짝에서 가져와 번역한다 |
---
## 5. 주의점
1. **매뉴얼끼리 맞지 않는 곳**:
- 반려 서비스 contentTypeId 설명(다국어 코드 vs 국문 코드)
- 반려 동기화 목록에 `oldContentid` 가 빠져 있음
- 영문 키워드 검색의 `contentTypeId` 가 남아 있음
- 반려 `lclsSystmCode2` 예제 이름이 러시아어
→ 실제 호출로 확인한 뒤 코드에 반영한다.
2. **contentid는 바뀔 수 있다.** 국문·영문 동기화 목록에 `oldContentid` 가 있다. 우리 DB는 이전 키 → 새 키 이관을 처리해야 한다. `showflag=0` 은 비표출 전환이므로 숨김 처리한다.
3. **오류 응답은 XML뿐이다**: "공공데이터포털에서 출력되는 오류메세지는 XML로만 출력". `_type=json` 이어도 오류는 XML이다.
4. **한도는 오퍼레이션별 1,000건/일(개발계정)**이다. 반려 상세를 장소마다 호출하는 방식은 금방 한도에 닿는다. 운영계정은 실제 앱 URL과 호출 이력이 있어야 승인된다.
5. **갱신은 일 1회**다. 야간 증분 동기화(`modifiedtime`)로 충분하다.
6. 위치기반 반경은 최대 20km다. `MobileApp` 은 필수이고 통계용이므로 앱 이름을 넣는다.
7. `homepage` 는 HTML `<a>` 태그 문자열이다. `etcAcmpyInfo` 에는 `\n`, 소개정보에는 `<br>` 이 섞여 있다. 표시하기 전에 정리한다.
8. 이미지 URL이 `http://` 인 경우가 많다. **[추론]** 앱(iOS ATS)에서는 `https://` 로 바꿔 열리는지 확인한다.
9. 외국인 방문자 기능에 쓸 만한 것 [매뉴얼]:
- 반복정보 유형: "외국어 안내서비스", "외국인 예약안내", "외국인 체험가능 프로그램", 쇼핑 "세금환급방법"
- 신분류 `SH040300` 사후면세점
- 영문 교통(77) `foreignerinfocenter`
- 이 중 반복정보는 국문·영문 양쪽 매뉴얼에 있다.
### 다음에 확인할 호출
- `KorService2/detailPetTour2` contentId 없이: `totalCount`, 최대 `numOfRows`, `acmpyTypeCd` 값 분포, `acmpyPsblCpam` 에서 무게·크기 언급 비율
- `KorPetTourService2/petTourSyncList2&showflag=1` 전체 건수, contentTypeId=12로 거를 때 국문 코드가 맞는지
- 같은 contentId로 두 `detailPetTour2` 를 비교해 값이 같은지, contentid가 KorService2와 같은지
- 성수·연남·해운대에서 `KorPetTourService2/locationBasedList2` 로 동반 가능 장소 수 다시 재기
- `chkpet` 계열 값 분포(국문)
- 영문 `detailIntro2` 에 `chkpet` 등이 빈 키로라도 오는지
@@ -0,0 +1,381 @@
# 상가정보·기상청 단기예보 API 매뉴얼 검토 (2026-10-29)
검토 대상 (원본: `docs/data-openapi/`)
- 소상공인시장진흥공단 상가(상권)정보 OpenAPI 활용가이드 (2026.7판, 파일명 260805)
- 기상청 단기예보 조회서비스 API 활용가이드 (261019)
- 기상청 단기예보 격자·위경도 표 (시트 "최종 업데이트 파일_20260930")
표기: **[매뉴얼]** = 매뉴얼 원문(따옴표는 인용) · **[추정]** = 내 판단, 확인 필요 · **[실측]** = 우리 2026-10-29 점검 결과/이번 계산.
매뉴얼에 없는 코드는 "매뉴얼에 없음"으로 적었다.
---
## 핵심 요약
1. 성수 전체 목록은 `storeListInDong`(divId=`adongCd`, key=**8자리** 행정동코드, numOfRows 최대 1000, pageNo로 반복)으로 받는다. 행정동코드는 `baroApi?resId=dong&catId=admi&signguCd=11200`로 조회한다.
2. 상가정보 데이터는 **분기마다 갱신**된다. 영업시간·전화·폐업 여부 필드는 없다. 증분 API(`storeListByDate`)는 지역 필터가 없고 신규(C)·수정(U)만 주며, 폐업 건은 오지 않는다.
3. 카페 등 업종 코드는 **매뉴얼에 표가 없다.** `I212`/`I21201`이 예시값으로만 나오고(백다방 예시), 이름은 적혀 있지 않다. `middleUpjongList`/`smallUpjongList`로 받아 고정해야 한다(2023-03 신분류, `stdrDt` 2023-02-28).
4. 단기예보는 **02~14시 발표면 글피(D+3)까지, 17~23시 발표면 그글피(D+4)까지**만 나온다. 연장된 마지막 날은 3시간 간격이고, PCP·SNO·WSD 값이 mm가 아니라 1~3 등급 코드로 온다.
5. 우리 격자 공식은 매뉴얼 C 코드와 수학적으로 같다. 격자표 3,801행 중 3,758행(98.9%)이 정확히 맞고 나머지 43행도 1칸 차이다. 성수 4개 동은 (61,126), 연남동은 (59,127), 해운대 우1동·중1동은 (99,75)로 표와 일치한다.
6. 두 매뉴얼 모두 **라이선스·이용조건·일일 트래픽을 적지 않았다.** 적힌 것은 초당 30 tps뿐이다. 이용허락범위와 일일 한도는 data.go.kr 신청 화면에서 확인해야 한다.
7. 매뉴얼의 기상청 URL은 `apihub.kma.go.kr`(authKey)다. 우리가 쓰는 `apis.data.go.kr/1360000/...`(serviceKey)는 이 매뉴얼에 나오지 않는다(실제 동작은 확인됨).
---
## 1. 상가정보 오퍼레이션·파라미터
### 공통
- **[매뉴얼]** 기본 URL: `http://apis.data.go.kr/B553077/api/open/sdsc2/{오퍼레이션}`. 예시는 모두 `https://`다.
서비스 이력에 "2025-06-09 : SSL 적용(HTTPS) 및 게이트웨이 인증 모듈 적용"이 있으므로 https를 쓴다.
- **[매뉴얼]** 인증 파라미터는 `serviceKey`다. 표에는 소문자 `servicekey`, 예시에는 `ServiceKey`/`serviceKey`가 섞여 있다.
응답 형식은 `type=xml|json`(옵션, 기본값 xml)이다.
- **[매뉴얼]** 모든 오퍼레이션에 "초당 최대 트랜잭션 [ 30 tps ]", "평균응답시간 [ 500 ms ]"가 적혀 있다.
- **[매뉴얼]** 목록형은 모두 `numOfRows` "최대 1000", `pageNo` "현재 요청 페이지번호"다. 응답 body에 `numOfRows`, `pageNo`, `totalCount`가 온다.
- **[매뉴얼]** 응답 header에 `stdrYm`(기준년월, 예 `202503`)이 온다. 적재할 때 스냅샷 버전으로 저장하는 게 좋다 **[추정]**.
- 목록형의 업종 필터(옵션, 공통): `indsLclsCd`(대, 2자리), `indsMclsCd`(중, 4자리), `indsSclsCd`(소, 6자리). **[매뉴얼]** "입력된 ○분류 업종에 해당하는 것만 조회"
### 오퍼레이션 목록 [매뉴얼]
| # | 오퍼레이션 | 용도 | 필수 파라미터 | 옵션 |
|---|---|---|---|---|
| 1 | `storeZoneOne` | 주요상권(전국 약 1,200개) 1건의 영역 폴리곤 | `key`=상권번호 | type |
| 2 | `storeZoneInRadius` | 반경 안의 상권 | `radius`(m, "반경제한:2km"), `cx`(경도), `cy`(위도) | type |
| 3 | `storeZoneInRectangle` | 사각형 안의 상권 | `minx, miny, maxx, maxy` | type |
| 4 | `storeZoneInAdmi` | 행정구역 단위 상권 | `divId`(ctprvnCd/signguCd/adongCd), `key` | type |
| 5 | `storeOne` | 상가업소 1건 | `key`=상가업소번호(bizesId) | type |
| 6 | `storeListInBuilding` | 건물 단위 업소 | `key`=건물관리번호(25자리) | 업종필터, numOfRows, pageNo, type |
| 7 | `storeListInPnu` | 지번(PNU) 단위 업소 | `key`=PNU(19자리) | 〃 |
| 8 | **`storeListInDong`** | **행정구역 단위 업소** | **`divId`, `key`** | 〃 |
| 9 | `storeListInArea` | 상권번호 안의 업소 | `key`=상권번호 | 〃 |
| 10 | `storeListInRadius` | 반경 안의 업소 | `radius`("미터단위, 최대2000 미터"), `cx`, `cy` | 〃 |
| 11 | `storeListInRectangle` | 사각형 안의 업소 | `minx, miny, maxx, maxy` | 〃 |
| 12 | `storeListInPolygon` | 다각형 안의 업소 | `key`=WKT `POLYGON((경도 위도, ...))`(최대 4300자) | 〃 |
| 13 | `storeListInUpjong` | 업종별 업소(**전국**) | `divId`(indsLclsCd/indsMclsCd/indsSclsCd), `key`=업종코드 | numOfRows, pageNo, type |
| 14 | `storeListByDate` | 수정일자 기준 업소 | `key`=일자 YYYYMMDD | 업종필터, numOfRows, pageNo, type |
| 15 | `reqStoreModify` | 업소정보 변경요청(등록) | 아래 참조 | — |
| 16 | `largeUpjongList` | 업종 대분류 코드 | — | type |
| 17 | `middleUpjongList` | 업종 중분류 코드 | — | `indsLclsCd`, type |
| 18 | `smallUpjongList` | 업종 소분류 코드 | — | `indsLclsCd`, `indsMclsCd`, type |
| 19 | `baroApi` | 행정구역 코드 조회 | `resId=dong`, `catId`(mega/cty/admi/zone) | 아래 참조 |
### 8) 행정동 단위 상가업소 조회 — `storeListInDong` [매뉴얼]
- `divId` (필수, 크기 8): "시도는 ctprvnCd, 시군구는 signguCd, 행정동은 adongCd를 사용"
- `key` (필수, 크기 8): "시도는 시도코드값, 시군구는 시군구코드값, 행정동은 행정동코드값을 사용"
- `indsLclsCd` / `indsMclsCd` / `indsSclsCd` (옵션), `numOfRows` (옵션, "최대 1000"), `pageNo` (옵션), `type` (옵션)
- 매뉴얼 예시: `storeListInDong?divId=ctprvnCd&key=11&indsLclsCd=I2&indsMclsCd=I201&indsSclsCd=I20102&numOfRows=2&pageNo=1&type=xml`
- 이 예시는 서울(11)을 요청했는데 응답은 대전 업소다. 예시 응답을 그대로 믿으면 안 된다.
### 19) 행정구역 조회 — `baroApi` [매뉴얼]
| 대상 | 요청 | 응답 주요 필드 |
|---|---|---|
| 시도 | `baroApi?resId=dong&catId=mega&type=json` | `ctprvnCd`, `ctprvnNm`, `stdrDt` |
| 시군구 | `baroApi?resId=dong&catId=cty&ctprvnCd=11&type=json` | + `signguCd`, `signguNm` |
| 행정동 | `baroApi?resId=dong&catId=admi&signguCd=11200&type=json` | + `adongCd`(예 `11740515`), `adongNm` |
| 법정동 | `baroApi?resId=dong&catId=zone&signguCd=11200&type=json` | + `ldongCd`(10자리, 예 `1174011000`), `ldongNm` |
- **[매뉴얼]** 시군구 코드표(2026년 7월 기준)는 매뉴얼에 실려 있다: 성동구 `11200`, 마포구 `11440`, 해운대구 `26350`. 매뉴얼에 행정동 코드표는 없다(API로 조회).
- **[매뉴얼]** 상가 데이터의 `adongCd`는 **8자리**다(예시 `30170540` 변동, `11740515` 강일동). 그런데 변경요청 예시에는 `3017064000`(10자리)이 나온다. 표기가 일관되지 않다.
#### 성수 행정동 코드
- **[매뉴얼(기상청 격자표)]** 행정안전부 10자리 코드: 성수1가제1동 `1120065000`, 성수1가제2동 `1120066000`, 성수2가제1동 `1120067000`, 성수2가제3동 `1120069000`. 연남동은 `1144071000`, 해운대 우제1동 `2635051000`, 중제1동 `2635053000`.
- **[추정]** 상가정보 8자리 코드는 위 10자리의 앞 8자리다. 매뉴얼 예시 `11740515`(강일동)가 행안부 `1174051500`과 같은 꼴이라서다.
→ 성수1가1동 `11200650`, 성수1가2동 `11200660`, 성수2가1동 `11200670`, 성수2가3동 `11200690`.
**반드시 `baroApi catId=admi signguCd=11200` 결과로 확인하고 쓴다.** 동 이름도 다를 수 있다(격자표는 "성수1가제1동", 상가정보는 "성수1가1동"일 가능성). 이름으로 조인하지 말고 코드로 조인한다.
- 대안: `divId=signguCd&key=11200`로 성동구 전체를 받고 `adongNm`/`adongCd`로 거른다. 페이지 수는 늘어난다.
### 13) 업종별 상가업소 조회 — `storeListInUpjong` [매뉴얼]
- `divId`: "대분류는 indsLclsCd, 중분류는 indsMclsCd, 소분류는 indsSclsCd를 사용", `key`: 해당 코드값
- **지역 파라미터가 없다** → 전국 결과다(예시 `divId=indsLclsCd&key=I2`의 totalCount `831738`).
성수 카페만 받으려면 이 오퍼레이션 대신 `storeListInDong` + `indsSclsCd`(또는 `indsMclsCd`) 필터를 쓴다 **[추정]**.
### 14) 수정일자기준 상가업소 조회 — `storeListByDate` [매뉴얼]
- `key`: "일자(YYYYMMDD 형식, 예:20151202)", 업종 필터·페이징 옵션
- 설명: "수정일자를 기준으로 신규, 수정 업소목록이 필요한 경우를 위해 설계된 오퍼레이션 (제공기준 : 영업중인 상가 업소정보)"
- 응답에 추가 필드: `chgGb` "해당 상가업소정보의 데이터 변경 구분값 (생성:C, 수정:U중1)", `chgDt` 수정일자
- **지역 필터 없음**(전국), **삭제(폐업) 구분값 없음**. "그 날짜에 바뀐 것"인지 "그 날짜 이후 바뀐 것"인지 매뉴얼에 적혀 있지 않다.
- **[추정]** 데이터 갱신이 "분기별"이므로 매일 증분 동기화는 의미가 적다. 분기마다 대상 행정동 전체를 다시 받아 `bizesId` 기준으로 비교한다(사라진 ID = 폐업 또는 ID 변경 후보). `storeListByDate`는 전국 변경분 점검용 보조 수단이다.
### 15) 상가업소정보 변경요청 — `reqStoreModify` [매뉴얼]
- 유형 "등록". "상가업소명, 전화번호, 주소 등 상가업소 정보 정정을 요청하는 서비스"
- 파라미터(표상 **모두 필수(1)**): `bizesId`, `bizesNm`, `brchNm`, `indsSclsCd`, `adongCd`, `lnoAdr`, `rdnmAdr`, `bldNm`, `dongNo`, `flrNo`, `hoNo`, `opbizDt`(개업일자), `clbizDt`(폐업일자), `etcChgReqCnts`(기타, 1000자)
- 응답: `bizesId`, `result`("정상인 경우 00"), `message`
- `bizesId`는 "해당 번호와 일치하는 업소가 있어야 변경요청이 수락 되어 진행된다"
- **[추정]** 운영자가 현장에서 폐업·이전을 확인했을 때 수동으로 쓸 수 있는 통로다. 공공기관 데이터에 쓰는 요청이므로 자동화하지 않는다.
매뉴얼은 GET 쿼리 예시만 보여주고 HTTP 메서드를 명시하지 않는다(인터페이스 표준 칸은 "REST (GET, POST, PUT, DELETE)").
### 응답 필드 (모든 상가업소 목록·단건 공통) [매뉴얼]
`bizesId, bizesNm, brchNm, indsLclsCd/Nm, indsMclsCd/Nm, indsSclsCd/Nm, ksicCd, ksicNm, ctprvnCd/Nm, signguCd/Nm, adongCd/Nm, ldongCd/Nm, lnoCd(PNU), plotSctCd/Nm, lnoMnno, lnoSlno, lnoAdr, rdnmCd, rdnm, bldMnno, bldSlno, bldMngNo, bldNm, rdnmAdr, oldZipcd, newZipcd, dongNo, flrNo, hoNo, lon, lat`
(우리 실측 필드 목록과 정확히 같다. `storeListByDate`만 `chgGb`, `chgDt`가 더 붙는다.)
| 필드 | 매뉴얼 설명 | 메모 |
|---|---|---|
| `bizesId` | "해당 상가업소에 부여된 일련번호" (크기 20) | 안정성(불변 여부)은 **매뉴얼에 없음**. 아래 주의점 참조 |
| `bizesNm` / `brchNm` | 상호명 / "지점인 경우 지점명" | 예시 상호가 `***해장국`처럼 가려진 곳과 실명인 곳이 섞여 있다 |
| `ksicCd` / `ksicNm` | 표준산업분류코드/명 (옵션) | 예 `I56111` 한식 일반 음식점업 |
| `adongCd` | 행정동 코드 (크기 20, 예 8자리) | |
| `ldongCd` | 법정동 코드 (10자리) | 성수동1가·2가는 법정동이다 |
| `lnoCd` | "지번주소에 해당하는 PNU코드" (19자리) | |
| `bldMngNo` | "도로명주소 기준 건물관리번호" (25자리) | `storeListInBuilding`의 key |
| `bldNm` | 건물명 | 예시에서 빈 값 많음 |
| `dongNo` / `flrNo` / `hoNo` | "소재한 동정보 / 층정보 / 호정보" | 형식(지하 표기 등)은 **매뉴얼에 없음**. 예시는 `1` 또는 빈 값 |
| `lon` / `lat` | WGS84. 둘 다 설명이 "위도 좌표값"으로 적혀 있다(오기) | |
| `stdrYm` (header) | 기준년월 | |
- 영업시간, 전화번호, 영업상태, 개업·폐업일 필드는 **응답에 없다**(개업·폐업일은 변경요청 입력 항목에만 있음).
---
## 2. 업종 코드 (카페 등)
### 분류 체계 [매뉴얼]
- "변경사항 : 2023년 03월 이후, 공공데이터에 서비스 되는 업소는 소진공에서 사용하는 업종분류의 체계가 변경되었으며 대분류(2자리), 중분류(4자리), 소분류(6자리)로 구성"
- 업종 코드 API 응답의 `stdrDt` = `2023-02-28`
- 대분류(서비스 대상) [매뉴얼]: `G2` 소매업, `I1` 숙박, `I2` 음식점, `L1` 부동산, `M1` 과학·기술 서비스, `N1` 시설관리·임대, `P1` 교육, `Q1` 보건의료, `R1` 예술·스포츠·여가, `S2` 수리·개인 서비스
(대분류 API 예시에는 `F1` 건설업, `O1` 공공 행정·국방, `J1` 정보통신, `C1` 제조업, `S1` 협회·단체도 나온다)
- 대분류명 표기가 일관되지 않다: 코드 API는 "음식점", 업소 응답은 "음식"이다. 우리 코드의 `'음식' in indsLclsNm`은 둘 다 잡지만, **코드(`I2`)로 거르는 게 안전하다.**
### 매뉴얼에 실제로 나오는 음식 코드 (전부)
| 코드 | 이름 | 출처 |
|---|---|---|
| `I201` | 한식 | 코드 API 예시 |
| `I20102` | 국/탕/찌개류 | 〃 |
| `I20109` | 곱창 전골/구이 | 〃 |
| `I20111` | 횟집 | 〃 |
| `I20112` | 해산물 구이/찜 | 〃 |
| `I207` | 구내식당, 뷔페 (업소 응답은 "구내식당·뷔페") | 〃 |
| `I20701` | 구내식당 | 업소 예시 |
| `I208` | 출장 음식 | 코드 API 예시 |
| `I210` | 기타 간이 | 〃 |
| `I212` | **이름 없음** — 사각형 조회의 `indsMclsCd` 샘플값 | 요청 예시 |
| `I21201` | **이름 없음** — 사각형 조회 샘플값. 변경요청 예시에서 상호 "백다방"과 함께 쓰임 | 요청 예시 |
- **카페/커피전문점, 디저트/제과, 주점의 코드는 매뉴얼에 없다.**
- **[추정]** `I212`는 비알코올 음료점(카페류), `I21201`은 카페로 보인다. 근거는 백다방 예시와, 우리 실측에서 '비알코올'이 중분류명에 있었다는 점이다. 주점·제과는 번호를 짐작하지 않는다.
- 확정 방법(호출 3번):
1. `largeUpjongList?type=json`
2. `middleUpjongList?indsLclsCd=I2&type=json`
3. `smallUpjongList?indsLclsCd=I2&type=json` (`indsMclsCd`는 옵션. 생략하면 I2 전체 소분류가 나오는지 확인 필요)
필요하면 `R1`(예술·스포츠·여가: 공방·갤러리 후보)도 받는다. 결과를 코드표로 저장소에 고정하고, `stdrDt`가 바뀌면 다시 받는다.
- 보조 필터: `ksicCd`(통계청 표준산업분류)도 함께 온다. **[추정]** KSIC 10차 기준 커피 전문점은 `56221`(데이터 표기는 `I56221`일 것)이다. 매뉴얼에 없는 값이므로 실데이터의 `ksicNm`으로 확인한다.
---
## 3. 상가정보 제한·라이선스
| 항목 | 내용 |
|---|---|
| 페이지 크기 | **[매뉴얼]** `numOfRows` 최대 1000 |
| 반경 | **[매뉴얼]** 업소 반경 "최대2000 미터", 상권 반경 "반경제한:2km" |
| 다각형 | **[매뉴얼]** `key` 항목 크기 4300자 |
| 속도 | **[매뉴얼]** 30 tps (오퍼레이션별) |
| 일일 한도 | **매뉴얼에 없음** → data.go.kr 마이페이지 개발계정 상세에서 확인 |
| 갱신 주기 | **[매뉴얼]** "데이터 갱신주기 분기별". 예시 `stdrYm` 202503 |
| 데이터 출처·수집 방법 | **매뉴얼에 없음**. "영업중인 상가 업소정보"(증분 API 제공기준)라는 문구만 있다 |
| 커버리지 한계 | 매뉴얼에 명시 없음. **[실측]** 성수 500m에 3,127곳으로 양은 많다. **[추정]** 분기 갱신이라 신규 개업·폐업이 최대 수개월 늦게 반영되고, 팝업·전시처럼 짧게 운영하는 곳은 없다 |
| 라이선스·이용조건 | **두 매뉴얼 모두 언급 없음.** 저장·상업 표시를 제한하는 문구도 없다. data.go.kr 해당 API 페이지의 "이용허락범위"를 확인해 이 문서에 적어 둔다. **[추정]** 공공데이터법은 영리 목적 이용을 보장하지만, 개별 데이터셋에 출처표시 조건이 붙을 수 있다 |
| 행정구역 변경 | **[매뉴얼]** 시도표에 `12` "전남광주통합특별시"가 있다(2026년 7월 기준). 기상청 격자표에도 반영되어 있다 → 코드는 바뀔 수 있으니 기준일과 함께 저장한다 |
| 오류 코드 | 상가정보 매뉴얼에는 오류 코드표가 **없다**(`resultCode` `00`=정상만 나옴) |
---
## 4. 기상청 단기예보 — 오퍼레이션·발표시각·범위
### 공통 [매뉴얼]
- 서비스: `VilageFcstInfoService_2.0`. **서비스 URL은 `https://apihub.kma.go.kr/api/typ02/openApi/VilageFcstInfoService_2.0`**이고, 예시 요청은 `authKey=`를 쓴다.
요청 명세 표에는 "serviceKey … 공공데이터포털에서 발급받은 인증키"라고 적혀 있다(두 체계가 섞여 있음).
우리가 쓰는 `https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst`는 이 매뉴얼에 없다. **[실측]** 이 주소는 정상 동작했다.
- 공통 파라미터: `numOfRows`(필수, "Default: 10"), `pageNo`(필수, "Default: 1"), `dataType`(옵션 XML/JSON, "Default: XML"), `base_date`, `base_time`, `nx`, `ny`(모두 필수)
- "데이터 갱신주기 | 수시 (일 8회)", 오퍼레이션마다 "초당 최대 트랙잭션 [30] tps". 일일 한도와 라이선스는 **매뉴얼에 없음**.
- "단기예보서비스는 남한에 대해서만 제공되며, 북한 및 국외는 제공되지 않습니다."
- 오류코드 [매뉴얼]: `00` 정상, `03` NODATA_ERROR, `10` 잘못된 파라미터, `11` 필수 파라미터 없음, `12` 서비스 없음/폐기, `20` 접근거부, `21` 일시 사용불가 키, `22` 요청제한횟수 초과, `30` 미등록 키, `31` 기한만료 키, `32` 미등록 IP, `33` 서명되지 않은 호출, `99` 기타 (`01/02/04/05`는 앱·DB·HTTP·연결 오류)
### 오퍼레이션 [매뉴얼]
| 오퍼레이션 | 내용 | base_time | 호출 가능 시각 | 범위 |
|---|---|---|---|---|
| `getUltraSrtNcst` 초단기실황 | "예보 구역에 대한 대표 AWS 관측값" | 매시 정각 `HH00` | "매시각 10분 이후 호출". "매시간 정시에 10분이후 생성되고 10분마다 최신 정보로 업데이트" | 현재 관측 |
| `getUltraSrtFcst` 초단기예보 | "예보시점부터 6시간 이내의 예보" | 매시 30분 `HH30` | "매시각 45분 이후 호출" | 6시간, 1시간 간격 |
| `getVilageFcst` 단기예보 | 시공간 세분화 예보 | `0200, 0500, 0800, 1100, 1400, 1700, 2000, 2300` | `02:10, 05:10, …, 23:10` | 아래 |
| `getFcstVersion` 예보버전 | "수정된 예보 버전을 파악" | — | — | `ftype`(ODAM 초단기실황 / VSRT 초단기예보 / SHRT 단기예보), `basedatetime`(YYYYMMDDHHMM) → `version`(파일 생성시각) |
### 단기예보 범위 [매뉴얼]
- "단기예보 5일 연장으로 02·05·08·11·14시 발표기준 글피 예보, 17·20·23시 발표기준 그글피 예보제공"
- "연장기간인 (02·05·08·11·14시 발표)글피, (17·20·23시 발표)그글피 예보는 3시간 간격의 자료를 제공하며, 강수량(PCP), 강설(SNO), 풍속(WSD) 요소는 정성정보(코드값)를 제공"
- 정리 **[추정: 표 해석]**:
| 발표 | 오늘·내일·모레 | 글피(D+3) | 그글피(D+4) |
|---|---|---|---|
| 02·05·08·11·14시 | 1시간 간격, 정량값 | **3시간 간격**, PCP/SNO/WSD는 코드 | 없음 |
| 17·20·23시 | 1시간 간격, 정량값 | 1시간 간격, 정량값 | **3시간 간격**, PCP/SNO/WSD는 코드 |
- 최저/최고기온(TMN/TMX) 제공일 [매뉴얼 표]:
- 02시 발표: 최저·최고 모두 오늘~글피
- 05·08·11시 발표: 최저 내일~글피, 최고 오늘~글피
- 14시 발표: 최저·최고 모두 내일~글피(**오늘 최고 없음**)
- 17·20·23시 발표: 최저·최고 모두 내일~그글피
- TMN/TMX가 몇 시 `fcstTime`에 붙는지는 **매뉴얼에 없음**. **[추정]** 최저는 0600, 최고는 1500이다. 실데이터로 확인한다.
- `numOfRows`: 매뉴얼은 기본값 10만 말한다. 예시 totalCount는 742(연장 전 예시)다.
**[추정]** 5일 연장 후 한 번에 약 850~1,000행이다(시간당 12개 요소 × 60~72시간 + 3시간 간격 하루분 + TMN/TMX). 우리가 쓴 1500은 충분하다. 그래도 `totalCount`와 받은 건수를 비교하는 검사를 둔다.
---
## 5. 기상청 카테고리 코드표 [매뉴얼]
### 단기예보 (`getVilageFcst`)
| 코드 | 이름 | 단위 |
|---|---|---|
| `POP` | 강수확률 | % |
| `PTY` | 강수형태 | 코드: 없음(0), 비(1), 비/눈(2), 눈(3), 소나기(4) |
| `PCP` | 1시간 강수량 | 범주(1 mm) — 아래 문자열 규칙 |
| `REH` | 습도 | % |
| `SNO` | 1시간 신적설 | 범주(1 cm) — 아래 문자열 규칙 |
| `SKY` | 하늘상태 | 코드: 맑음(1), 구름많음(3), 흐림(4) (전운량 0~5 / 6~8 / 9~10) |
| `TMP` | 1시간 기온 | ℃ |
| `TMN` | 일 최저기온 | ℃ |
| `TMX` | 일 최고기온 | ℃ |
| `UUU` | 풍속(동서성분) | m/s, 동(+)/서(-) |
| `VVV` | 풍속(남북성분) | m/s, 북(+)/남(-) |
| `WAV` | 파고 | M |
| `VEC` | 풍향 | deg (16방위 변환: `(풍향 + 22.5*0.5) / 22.5` 버림 → 0=N … 15=NNW, 16=N) |
| `WSD` | 풍속 | m/s |
### 초단기실황 (`getUltraSrtNcst`)
`T1H` 기온(℃), `RN1` 1시간 강수량(mm), `UUU`/`VVV`(m/s), `REH`(%), `PTY`(코드), `VEC`(deg), `WSD`(m/s). 응답 값 필드는 `obsrValue`다(예보는 `fcstValue`).
### 초단기예보 (`getUltraSrtFcst`)
`T1H`, `RN1`(범주 1 mm), `SKY`, `UUU`, `VVV`, `REH`, `PTY`, **`POP`**, `LGT` 낙뢰(kA, "에너지밀도 0.2~100kA/㎢"), `VEC`, `WSD`
- "초단기예보 강수형태(POP)요소 확대 제공: 2026.6.23. 11KST 이후" — 원문은 '강수형태'라고 썼지만 코드는 POP(강수확률)다(매뉴얼 오기).
- 초단기 PTY는 0~4에 더해 빗방울(5), 빗방울눈날림(6), 눈날림(7)이 있다.
### 특수 값 [매뉴얼]
- 강수량(RN1, PCP) 문자열 표시:
- 0.1~1.0mm 미만 → "1mm 미만"
- 1.0~30.0mm 미만 → "실수값+mm"(1.0mm~29.9mm)
- 30.0~50.0mm 미만 → "30.0~50.0mm"
- 50.0mm 이상 → "50.0mm 이상"
- "※ -, null, 0값은 '강수없음'"
- 매뉴얼 예: PCP = 6.2는 6.2mm, PCP = 30은 30.0~50.0mm
- 같은 절의 Java 예시는 "1.0~29.0mm"를 반환한다. 표와 맞지 않는 오기다.
- 신적설(SNO):
- 0.1~0.5cm 미만 → "0.5cm 미만"
- 0.5~5.0cm 미만 → "실수값+cm"
- 5.0cm 이상 → "5.0cm 이상"
- "-, null, 0값은 '적설없음'"
- 연장기간 정성 코드 ("단기예보 연장에 따른 코드값 정보(2024.11.28. 14시~)"):
- PCP: 1 약한 비(<3mm/h), 2 보통 비(3~15mm/h), 3 강한 비(≥15mm/h)
- SNO: 1 보통 눈(<1cm/h), 2 많은 눈(≥1cm/h)
- WSD: 1 약한 바람, 2 약간 강한 바람(4~9m/s), 3 강한 바람(≥9m/s)
- WSD 1의 설명이 원문에 "4m/s 이상의 약한 바람"으로 적혀 있다. 4m/s **미만**의 오기로 보인다 **[추정]**.
- 결측: "+900이상, –900 이하 값은 Missing 값으로 처리"
- 해상: "해상에는 기온군, 강수확률, 강수량/적설, 습도를 제공하지 않음(Missing값으로 마스킹처리 함)"
- **[추정]** 파서 규칙: `fcstValue`는 문자열이다. PCP/SNO는 "강수없음", "1mm 미만", "6.2mm", "30.0~50.0mm", "50.0mm 이상" 같은 문자열이 오고, 연장일에는 "1"~"3" 코드가 온다. **날짜가 연장 구간인지 보고 해석을 분기**해야 한다.
---
## 6. 격자 검증
### 공식 [매뉴얼 C 코드 vs 우리 코드]
| 상수 | 매뉴얼 | 우리 `kma_grid` |
|---|---|---|
| Re | 6371.00877 | 6371.00877 |
| grid | 5.0 km | 5.0 |
| slat1 / slat2 | 30.0 / 60.0 | 30.0 / 60.0 |
| olon / olat | 126.0 / 38.0 | 126.0 / 38.0 |
| xo / yo | `210/grid`=42, `675/grid`=135 | 43, 136 |
| 반올림 | `(int)(x1 + 1.5)` | `floor(x + 43 + 0.5)` |
- `42 + x + 1.5` = `43 + x + 0.5`이므로 같다. 남한 범위는 좌표가 양수라 `(int)` 절삭과 `floor`가 같다. θ 정규화도 같다.
- 매뉴얼 예제 `126.929810, 37.488201 → X=59, Y=125`를 우리 함수로 돌린 결과도 **(59,125)**다 [실측].
### 격자표 대조 [실측: 격자표 전 행에 우리 함수 적용]
- 좌표를 읽을 수 있는 3,801행 중 **3,758행(98.9%)이 정확히 일치**하고, 43행은 **1칸 차이**다(최대 차이 1).
나머지 36행은 텍스트 추출 때 열이 밀려 좌표를 읽지 못해 제외했다(강원 18·전북 15·이어도 2·전남광주 1).
- 1칸 차이 예: 수유1동, 문래동, 개포3동, 부산 좌천동 등. 표의 "위치업데이트" 날짜가 있는 행이 많아(43행 중 31행) 대표 좌표만 갱신되고 격자는 그대로인 경우로 보인다 **[추정]**.
→ 동 단위로 저장할 때는 **표의 nx/ny를 우선**하고, 임의 좌표(장소 좌표)는 공식으로 계산한다.
| 지역 | 격자표(동) | 표 nx, ny | 우리 계산 |
|---|---|---|---|
| 성수 | 성수1가제1동 `1120065000` | 61, 126 | 61, 126 ✓ |
| | 성수1가제2동 `1120066000` | 61, 126 | |
| | 성수2가제1동 `1120067000` | 61, 126 | |
| | 성수2가제3동 `1120069000` | 61, 126 | |
| 연남 | 연남동 `1144071000` | 59, 127 | 59, 127 ✓ |
| 해운대 | 우제1동 `2635051000` | 99, 75 | 99, 75 ✓ |
| | 중제1동 `2635053000` | 99, 75 | |
| | (참고) 중제2동 100,75 · 좌제1~4동 100,76 · 우제2동 99,76 | | |
- 주의: 성동구 전체 행은 (61,**127**)이다. 구 단위 격자를 쓰면 성수와 한 칸 어긋난다.
---
## 7. 우리 가정과 다른 점
1. **"예보가 D+4까지 닿는다"는 절반만 맞다.** 11시 발표(우리 점검 시각 12:24)는 매뉴얼상 글피(D+3, 11/1)까지이고, 글피는 3시간 간격이다.
실측의 11/2 날짜는 경계 시각(00시) 몇 건으로 보인다 **[추정, 날짜별 건수로 확인 필요]**.
일요일(D+3) 오후 판단에 PCP를 쓰면 mm가 아니라 1~3 코드가 온다. POP 15:00은 3시간 격자에 걸려 있어 받을 수 있다.
D+4 이후는 17시 이후 발표에서만 나오고, 그 너머는 중기예보가 필요하다.
2. **기상청 엔드포인트**: 매뉴얼은 API허브(`apihub.kma.go.kr`, `authKey`)만 문서화한다. 우리는 data.go.kr 게이트웨이(`apis.data.go.kr/1360000`, `serviceKey`)를 쓴다.
기능은 같아 보이지만 한도·키 체계가 다르다 **[추정]**.
3. **격자 상수 표기**(43/136 + 0.5)는 매뉴얼(42/135 + 1.5)과 모양만 다르고 결과가 같다. 문제 없음.
4. **카페 식별**: 키워드('카페/커피/비알코올') 대신 업종 코드로 거른다. 그런데 그 코드는 매뉴얼에 없어 `smallUpjongList`로 받아야 한다(I212/I21201은 이름 없는 예시값일 뿐이다).
5. **행정동 코드 자릿수**: 기상청 표·행안부는 10자리, 상가정보 `adongCd`·`key`는 8자리다.
6. **증분 동기화 가정**: `storeListByDate`는 지역 필터가 없고, 폐업(삭제)을 알려주지 않으며, 원천이 분기 갱신이다. "매일 변경분만 받기"는 성립하지 않는다.
7. **업종별 조회(`storeListInUpjong`)는 전국 단위**라 지역 수집에 쓰지 못한다. 지역+업종은 `storeListInDong`/`storeListInRadius`의 업종 필터로 한다.
8. **대분류명**: 코드 API는 "음식점", 업소 응답은 "음식"으로 표기가 다르다. 이름 문자열 비교를 코드 비교로 바꾼다.
9. **15시 POP 한 점**만 보는 것은 매뉴얼과 무관한 설계 이슈다 **[추정]**.
코스 시간대 전체의 PTY·POP·PCP를 보고, 당일 출발 직전에는 초단기예보(이제 POP 포함)·초단기실황으로 덮어쓴다.
---
## 8. 주의점
### 상가정보
- **`bizesId` 안정성은 보장되지 않는다(매뉴얼에 없음).** 매뉴얼 안에서도 형식이 섞여 있다.
- 단건조회 예시 요청은 `key=19911025`인데 응답은 `MA010120220805432049`다.
- 요청 항목 크기는 10인데 실제 ID는 20자다.
- 변경요청 예시는 `MA10868815...`/`10868815`다.
- **[추정]** 과거에 ID 체계가 바뀐 적이 있다. 우리 장소 ID는 따로 두고 `bizesId`는 외부 참조로 저장한다. 재적재할 때 ID가 사라지면 이름+주소+좌표로 다시 짝짓는다.
- 영업시간·전화·영업상태가 없다. 운영자 검수 단계에서 채워야 할 항목이다.
분기 갱신이라 "영업중"이라도 이미 폐업했을 수 있으니, 검수일(`verifiedAt`)을 저장한다.
- `flrNo`·`bldNm`·`dongNo`·`hoNo`는 옵션이고 예시에서 대부분 비어 있다. 지하 층 표기 형식은 실데이터로 확인한다.
- 상호명 일부가 `***`로 가려진 예시가 있다(`***해장국`). 실데이터에서 가림 비율을 확인한다.
- 매뉴얼 예시 응답 상당수가 요청과 맞지 않는다(서울 요청에 대전 응답 등). 스키마 참고용으로만 쓴다.
- 인증키는 URL 인코딩 키(Encoding)와 디코딩 키(Decoding) 중 하나를 골라 이중 인코딩하지 않는다. 우리 스크립트는 `urlencode`를 쓰므로 Decoding 키가 맞다 **[추정]**.
### 기상청
- base_time은 "발표 후 10분" 규칙만으로 부족할 수 있다. 지연되면 `03 NODATA_ERROR`가 오므로 직전 발표로 한 번 물러나는 재시도를 둔다 **[추정]**.
`getFcstVersion(ftype=SHRT)`로 재발표(수정) 여부를 확인할 수 있다.
- 같은 격자 안의 장소는 같은 예보를 공유한다. 격자 단위 캐시 키는 `(nx, ny, base_date, base_time)`로 하면 호출 수가 크게 준다.
- 초단기예보 표에 오기가 있다(19시 Base_time "19030", 20시 행의 "22~23시"). 규칙(매시 30분 발표, 45분 이후 호출, 6시간)만 믿는다.
- 해상 격자(해운대 해변 좌표가 바다 격자에 걸리는 경우)는 기온·강수가 결측(+900/-900)일 수 있다 **[추정]**.
해운대 우1동·중1동 (99,75)는 육상 행정동 격자이므로, 장소 좌표가 해상 격자로 계산되면 행정동 격자로 대체한다.
### 이 매뉴얼에 없어서 따로 필요한 것 **[추정]**
- **중기예보**(D+3/D+4 이후 ~10일, 오전/오후 단위 강수확률·하늘): 기상청 중기예보 조회서비스. 이번 주말을 목요일 오전에 계획하면 단기예보로 충분하지만, 다음 주말 코스는 중기예보가 필요하다.
- **기상특보**(호우·태풍·폭염·한파): 기상청 특보 조회서비스. "비 → 실내"보다 강한 신호(코스 취소·경고).
- **미세먼지**: 에어코리아(한국환경공단) 대기오염정보. 기상청 단기예보에는 없다.
- **자외선·체감온도 등 생활기상지수**: 기상청 생활기상지수 서비스(필요 시).
- 각 API의 이용허락범위·일일 한도: data.go.kr 신청 화면에서 확인해 이 문서에 추가한다.
---
## 부록: 성수 행정동 수집 요청 형태
```text
# 1) 행정동 코드 확인 (1회, 결과 고정)
GET https://apis.data.go.kr/B553077/api/open/sdsc2/baroApi
?serviceKey=…&resId=dong&catId=admi&signguCd=11200&type=json
→ adongNm 에 '성수' 포함 4건의 adongCd(8자리) 사용
# 2) 동별 전체 업소 (totalCount 까지 pageNo 증가)
GET https://apis.data.go.kr/B553077/api/open/sdsc2/storeListInDong
?serviceKey=…&divId=adongCd&key=<adongCd>&numOfRows=1000&pageNo=1&type=json
(업종만: &indsLclsCd=I2 또는 &indsMclsCd=<카페 중분류> 또는 &indsSclsCd=<카페 소분류>)
# 3) 업종 코드표 (1회, 결과 고정)
GET …/largeUpjongList?serviceKey=…&type=json
GET …/middleUpjongList?serviceKey=…&indsLclsCd=I2&type=json
GET …/smallUpjongList?serviceKey=…&indsLclsCd=I2&type=json
```
- 예상 adongCd **[추정, 1)로 확인]**: 성수1가1동 `11200650` · 성수1가2동 `11200660` · 성수2가1동 `11200670` · 성수2가3동 `11200690`
- 저장할 메타: 응답 header `stdrYm`, 수집 일시, 사용한 `adongCd`·업종 코드표 `stdrDt`
@@ -0,0 +1,400 @@
# TourAPI 국문(KorService2) 매뉴얼 검토
작성 2026-10-29
대상 문서: 국문 활용매뉴얼 v4.4(2026-02-10), 신분류체계정보↔관광타입정보 연계 정의서, 활용신청방법 매뉴얼 v3.3
원본: `docs/data-openapi/` (수정하지 않음). `L123` 은 텍스트 추출본의 줄 번호다.
표기
- **[매뉴얼]** 원문 그대로 인용하거나 요약한 것
- **[관측]** 10-29 점검 결과(`docs/research/data-check/public-data-check-20261029-1224.json`)에서 확인한 것
- **[추론]** 내 판단이라 검증이 필요한 것
---
## 핵심 요약
1. v4.4(2026-02-10)에서 `areacode`·`sigungucode`·`cat1~3` 와 지역코드·서비스분류코드 오퍼레이션이 **삭제**됐다. 이제 키는 법정동(`lDongRegnCd`/`lDongSignguCd`)과 분류체계(`lclsSystm1~3`) **뿐**이다.
2. 개발계정의 하루 1,000건은 **오퍼레이션마다 따로** 적용된다(신청 매뉴얼). 병목은 상세 조회고, 목록 조회는 거의 부담이 없다.
3. 야간 동기화에 쓸 `areaBasedSyncList2` 가 있다. `modifiedtime`(YYYY/YYYYMM/YYYYMMDD), `showflag`(1 표출 / 0 비표출), `oldContentid` 로 수정과 비공개 전환을 따라갈 수 있다. 데이터 갱신 주기는 하루 1회다.
4. `detailPetTour2` 는 contentId 를 비우면 **반려동물 정보 전체**를 돌려준다. 장소마다 따로 부를 필요가 없다.
5. 축제는 `lclsSystm2` 로 서버에서 가를 수 있다: EV01 축제 / EV02 공연 / EV03 행사. `eventEndDate` 요청값과 `progresstype`·`festivaltype` 응답값도 있다.
6. `usetimefestival` 은 이름과 달리 **이용요금** 필드다. 지금의 'usetime 부분일치' 방식으로는 축제 요금을 영업시간으로 잘못 읽는다.
7. 이미지는 장마다 `cpyrhtDivCd` 로 Type1(출처표시)/Type3(+변경금지)가 붙는다. 텍스트 라이선스, 운영계정 한도 수치, DB 저장 허용 여부는 명시되어 있지 않다. 다만 동기화 API 의 설명이 자체 DB 저장을 전제로 쓰여 있다. 운영계정은 "정상 서비스 확인 후" 승인된다.
---
## 1. 우리 가정과 다른 점
### 1-1. 지역·분류 키 — "주 키"가 아니라 "유일한 키"
- [매뉴얼] 개정이력 4.4 (L48–50):
"지역코드 오퍼레이션 삭제 / 서비스분류코드 오퍼레이션 삭제 / 요청/응답 : 지역코드 , 시군구코드 삭제 / 요청/응답 : 서비스 대분류, 중분류, 소분류 / 삭제"
- [매뉴얼] 4.3(2025-05-12)에서 법정동·분류체계 요청/응답 항목과 `ldongCode2`·`lclsSystmCode2` 오퍼레이션이 추가됐다 (L38, L46–47).
- [관측] 10-29 응답에는 `areacode, sigungucode, cat1, cat2, cat3` 키가 아직 남아 있었다(값은 기록하지 않았다). 매뉴얼의 행사 응답 예시(L461)에도 빈 문자열로 남아 있다.
- [추론] 키가 응답에 남아 있더라도 스키마·필터에는 쓰지 않는다. 매뉴얼 앞부분 호출 예시(L108, L117)의 `areaCode2` URL 은 갱신되지 않은 흔적이다.
- [매뉴얼] 필터 의존 규칙 (L234–237): `lDongSignguCd` 는 `lDongRegnCd` 가 필수이고, `lclsSystm2` 는 `lclsSystm1` 이, `lclsSystm3` 는 1·2가 필수다.
- [매뉴얼] 형식: 시도는 2자리(`11`, `26`), 시군구는 3자리(`140`, `380`) (L233–234, L1043–1049).
- 행사 오퍼레이션 예시의 `lDongRegnCd=36110`(L417)은 [추론] 매뉴얼 오기로 보인다.
- [추론] 점검 지역의 코드는 성수 11/200(성동구), 연남 11/440(마포구), 해운대 26/350 으로 보인다. 행정표준 법정동 코드로 짐작한 값이라 `ldongCode2` 로 확인해야 한다.
### 1-2. 쿼터는 오퍼레이션별 1,000건/일
- [매뉴얼-국문 L102] "개발계정은 일 1,000건의 트래픽을 제공합니다."
- [매뉴얼-신청 L26] "개발계정은 **각 오퍼레이션별** 일일 1,000건의 트래픽을 제공합니다."
- [추론] `detailIntro2` 1,000, `detailCommon2` 1,000, `detailPetTour2` 1,000 … 이 각각 따로 잡힌다. 초기 적재를 오퍼레이션별로 나눠 병렬 계획을 세울 수 있다.
### 1-3. locationBasedList2
- [매뉴얼 L284–299] 필수 항목은 `MobileOS`, `MobileApp`, `serviceKey`, `mapX`, `mapY`, `radius` 다.
- `radius`: "거리 반경(단위: m), Max값 20000m=20Km"
- 옵션: `contentTypeId`, `modifiedtime`(YYYYMMDD), `lDongRegnCd`/`lDongSignguCd`, `lclsSystm1~3`
- [매뉴얼 L290] `arrange` 값은 "A=제목순, C=수정일순, D=생성일순, E=거리순 / 대표 이미지가 반드시 있는 정렬 (O=제목순, Q=수정일순, R=생성일순, S=거리순)" 이다.
- 우리가 쓰는 `E` 가 맞다.
- [추론] 화면 목록에서 이미지 없는 항목을 빼야 하면 `S` 를 쓴다.
- [매뉴얼] 응답 `dist` 는 필수 항목이며 단위는 m 다 (L315).
- `numOfRows` 최대값은 **매뉴얼에 없다**.
- [관측] `searchFestival2` 에 `numOfRows=500` 을 주자 한 번에 455건이 왔다. 500은 동작한다.
### 1-4. searchFestival2
- [매뉴얼 L414–421] 요청 항목:
- `eventStartDate`: 필수, YYYYMMDD
- `eventEndDate`: 옵션, YYYYMMDD
- `modifiedtime`, `lDong*`, `lclsSystm*`
- **좌표·반경 필터가 없고 거리순 정렬도 없다**
- [매뉴얼 L437–454] 응답 항목:
- `eventstartdate`, `eventenddate`: 필수
- `progresstype`: "진행상태정보", 예시 "선택안함"
- `festivaltype`: "축제유형명", 예시는 빈 값
- 두 필드의 값 목록은 매뉴얼에 없다
- [관측] `eventStartDate=20260901`(토요일 60일 전)로 불렀는데 "페인터즈 20221101–20261231" 이 돌아왔다. 서버는 '시작일 ≥ 요청값'이 아니라 기간이 겹치는지(종료일 ≥ 요청값)로 거르는 것으로 보인다.
- [추론] `eventStartDate=eventEndDate=토요일` 로 부르면 그날 열리는 행사만 받을 수 있을 것 같다. 매뉴얼에 의미 정의가 없으니 한 번 호출해 검증한다.
- 상시 프로그램이 섞이는 문제는 매뉴얼에 있는 장치로 줄일 수 있다:
- `lclsSystm1=EV&lclsSystm2=EV01` 로 '축제'만 받는다
- 소개정보의 `festivalgrade`(축제등급)를 본다
- [추론] 그래도 기간 길이 필터(예: 60일 넘는 행사 제외)는 여전히 필요하다
- [매뉴얼] 예시끼리 맞지 않는다. 응답 표는 `lclsSystm2=EV03` 에 `lclsSystm3=EV010200` 이고(L451–452), JSON 예시는 `EV01` 이다(L461).
- [추론] 분류 품질을 실제 데이터로 재 봐야 한다.
### 1-5. 영업시간 감지 휴리스틱
- [매뉴얼 L651] `usetimefestival | 이용요금`. 개정이력 1.4 (L13)도 "행사의 이용시간을 이용 요금으로 수정" 이라고 적는다.
- 'usetime' 부분일치를 쓰면 축제(15)에서 요금을 시간으로 읽는다.
- [관측] 지금은 `HOURS_TYPES` 에 15가 없어 점검 결과에는 영향이 없었다.
- [매뉴얼] 타입별 시간 필드:
| 타입 | 시간 필드 |
|---|---|
| 12 관광지 | `usetime` |
| 14 문화시설 | `usetimeculture` |
| 28 레포츠 | `usetimeleports`, `openperiod`(개장기간) |
| 38 쇼핑 | `opentime`, `fairday`(장서는날) |
| 39 음식점 | `opentimefood` |
| 15 행사 | `playtime`(공연시간) |
| 32 숙박 | `checkintime`/`checkouttime` |
- [매뉴얼] 쉬는날 필드는 `restdate`, `restdateculture`, `restdateleports`, `restdateshopping`, `restdatefood` 다. 15·25·32 타입에는 없다.
- [매뉴얼] 형식은 정의되어 있지 않다. 예시 값은 "상시 개방"(L620)이고, 다른 필드 값에는 `<br>` 이 섞여 있다(L617).
- 정형 시간표 필드가 없으므로 '파서 + 미확인' 판단은 그대로 유효하다.
- [추론] 부분일치를 버리고 타입별 고정 필드맵으로 바꾼다 (3장 표).
### 1-6. detailIntro2 / detailPetTour2 호출 방식
- [매뉴얼 L593–594] `detailIntro2` 는 `contentId` 와 `contentTypeId` 가 **둘 다 필수**다. 지금 쓰는 방식이 맞다.
- [매뉴얼 L988] `detailPetTour2`:
"`contentid | 0 | 콘텐츠ID(미기입 시 반려동물 전부 출력)`"
- 장소마다 1건씩 부른 방식(10-29에 90건)을 페이지 단위 전체 수집으로 바꿀 수 있다.
- [매뉴얼] 요청 표에는 소문자 `contentid`, 예시 URL(L1013)에는 `contentId` 로 적혀 있다.
- [관측] `contentId` 로 불러 값을 받았다.
- [매뉴얼 L1008] 응답 표에 `petTursmInfo`(반려동물 관광정보)가 있다.
- [관측] 실제 응답 키에는 없었다. 있어도 되고 없어도 되는 값으로 다룬다.
- [매뉴얼] 응답에는 제목·좌표·타입이 없고 `contentid` 와 반려동물 항목만 있다.
- [추론] 우리 장소 테이블과 `contentid` 로 조인한다.
### 1-7. 야간 동기화 · 운영 쿼터
- [매뉴얼 L146] "데이터 갱신 주기 | 일 1회". 야간 1회 동기화가 주기에 맞다. 같은 날 여러 번 돌릴 이유가 없다.
- [매뉴얼-신청 L46–57] 운영계정 조건:
- "한국관광공사 담당자의 승인이 필요하며, 약 1~3일 정도 소요"
- 확인 사항: "활용 어플 URL 확인 : App/Web/기타(키오스크등)", "개발계정의 TourAPI 호출 이력"
- "**정상적인 서비스가 되고 있음이 확인 된 후** … 승인"
- "[활용사례]에 등록되어 홍보"
- "승인일로부터 24개월 … [연장신청]"
- 운영계정 한도 **수치는 어느 문서에도 없다**
- [추론] 출시 전에는 운영계정이 나오지 않을 수 있다.
- 초기 적재는 개발계정 한도(오퍼레이션별 1,000/일) 안에서 며칠에 걸쳐 끝나도록 설계한다.
- 심사용으로 스토어 링크나 웹 랜딩 URL 을 준비한다.
---
## 2. 새로 쓸 만한 기능
### 2-1. areaBasedSyncList2 — 변경분 동기화 (L906–971)
- [매뉴얼] 요청 항목:
- `showflag`: "콘텐츠표출여부(1=표출, 0=비표출)"
- `modifiedtime`: "콘텐츠변경일자 (수정년도,수정년월,수정년월일 입력)" — YYYY, YYYYMM, YYYYMMDD 모두 받는다
- `arrange`, `contentTypeId`, `lDong*`, `lclsSystm*`
- `oldContentid`: "이전 콘텐츠ID(DB저장 동기화시 이전 KEY 값으로 조회 용도)"
- [매뉴얼] 응답은 목록 공통 필드에 `showflag`(필수)가 더해진다.
- [매뉴얼 L969–971] `showflag=0` 예시에서는 `firstimage`·`cpyrhtDivCd`·`lDongRegnCd`·`lclsSystm*` 가 **모두 빈 값**이다.
- [추론] 야간 잡 설계:
1. `modifiedtime=어제(YYYYMMDD)` 로 호출한다.
2. `showflag=1` 은 upsert, `showflag=0` 은 소프트 삭제(AI 후보에서 제외)한다.
3. 놓친 날은 `YYYYMM` 월 단위로 다시 훑는다.
- [추론] 비표출 레코드는 법정동이 비어 있으므로, `lDongRegnCd` 로 거른 조회에서는 **삭제가 빠질 수 있다**. 비표출 확인은 지역 필터 없이 한다.
- [추론] 아래는 매뉴얼에 없으니 첫 실행에서 검증한다:
- `showflag` 를 생략하면 1·0이 함께 오는가
- `modifiedtime` 이 '그 날짜'인가 '그 이후'인가 (예시 문구로 보면 접두 일치에 가깝다)
- 응답에 이전 ID 필드가 있는가 (`oldContentid` 는 요청 항목에만 있다)
- [추론] 초기 적재도 반경 크롤링(`locationBasedList2`) 대신 `areaBasedList2`/`areaBasedSyncList2` 를 `lDongRegnCd`(+`lDongSignguCd`) 단위로 돌리는 편이 경계 누락이 없다.
- [매뉴얼] `modifiedtime`(YYYYMMDD) 필터는 다른 목록 오퍼레이션(지역·위치·행사·숙박)에도 있다.
### 2-2. 반려동물
- `detailPetTour2` 전체 목록 수집 (1-6 참조).
- [매뉴얼] 소개정보의 동반 필드는 아래 넷이며, 값 목록은 정의되어 있지 않다(예시는 빈 값, L735).
| 타입 | 필드 |
|---|---|
| 12 관광지 | `chkpet` |
| 14 문화시설 | `chkpetculture` |
| 28 레포츠 | `chkpetleports` |
| 38 쇼핑 | `chkpetshopping` |
- **음식점(39)·숙박(32)·행사(15)·코스(25)에는 없다.**
- [매뉴얼] KorService2 에는 반려동물 전용 contentTypeId 나 목록 필터 파라미터가 없다(타입표 L164–173, 각 요청 표).
- 서비스 개요(L91)에 별도 서비스로 "반려동물 동반여행 서비스"가 있다. 별도 매뉴얼(반려동물동반여행 v4.1)이 다룰 범위다.
- [매뉴얼] `acmpyTypeCd` 는 이름에 Cd 가 붙어 있지만 값은 "일부구역 동반가능" 같은 문장이다(L1005).
### 2-3. 축제 거르기 · 상세
- 서버 필터: `lclsSystm2=EV01`(축제), `eventEndDate` (1-4 참조).
- [매뉴얼] 소개정보(15) 필드: `festivalgrade`, `playtime`, `eventplace`, `placeinfo`, `program`, `subevent`, `agelimit`, `bookingplace`, `usetimefestival`(요금), `discountinfofestival`, `spendtimefestival`, `eventhomepage`, `sponsor1`/`sponsor1tel`/`sponsor2`/`sponsor2tel`.
- [매뉴얼 L843–849] 반복정보(15) 유형: 행사소개 · 약력 · 줄거리 · 출연 · 제작 · 행사내용 · 참가안내.
- [추론] `locationBasedList2(contentTypeId=15)` 응답에는 기간 필드가 없어 상세를 따로 불러야 한다. 지금처럼 `searchFestival2` 로 전국을 받고 거리를 로컬에서 계산하는 쪽이 쿼터에 유리하다.
### 2-4. 접근성 · 편의 · 주차 · 예약
- [매뉴얼] 접근성: KorService2 에 휠체어 같은 **무장애 전용 필드는 없다**.
- 있는 것은 `chkbabycarriage*`(유모차대여정보; 12/14/28/38)와, 반복정보 '장애인편의시설'(관광지 유형2·쇼핑 유형2, L829·L861; 자유문장)뿐이다.
- 별도 서비스 "국문 무장애 여행 정보 서비스"(L80)가 있지만 이 매뉴얼의 범위 밖이다.
- [매뉴얼] 기타 편의 필드: `chkcreditcard*`, `kidsfacility`(39 어린이놀이방), `smoking`(39), `packing`(39 포장), `seat`(39 좌석수), `restroom`(38 화장실설명).
- [매뉴얼] 주차 필드:
| 타입 | 필드 |
|---|---|
| 12 | `parking` |
| 14 | `parkingculture`, `parkingfee` |
| 28 | `parkingleports`, `parkingfeeleports` |
| 32 | `parkinglodging` |
| 38 | `parkingshopping` |
| 39 | `parkingfood` |
- 예시 값: "가능<br>요금 (최초 2시간 무료 / 이후 30분 당 400원씩 추가 요금 발생)"
- [매뉴얼] 예약 필드: `reservation`(28), `reservationlodging`+`reservationurl`(32), `reservationfood`(39), `bookingplace`(15). 반복정보 유형에도 예약안내·내국인예약안내·외국인예약안내·대관안내가 있다.
### 2-5. 코스 시간 배분 힌트
- [매뉴얼] 관련 필드: `spendtime`(14 관람소요시간), `spendtimefestival`(15), `taketime`(25 코스총소요시간), `distance`(25 코스총거리).
- [추론] AI 가 장소별 체류시간을 정할 때 힌트로 쓸 수 있다. 값은 자유문장이다.
### 2-6. detailInfo2 — 반복정보 (L737–864)
- [매뉴얼] 12/14/15/28/38 타입:
- 반복 필드는 `fldgubun`, `infoname`, `infotext`, `serialnum` 이다
- 예시: "입 장 료 / 무료", "화장실 / 있음" (L822)
- 유형표는 L824–864 에 있다
- [매뉴얼] 25 여행코스: `subcontentid`(하위콘텐츠ID), `subname`, `subdetailoverview`, `subdetailimg`, `subdetailalt`, `subnum`.
- [추론] 관광공사 추천코스가 실제 contentid 순서로 나온다. AI 코스 평가의 기준 데이터나 템플릿으로 쓸 만하다. 분류는 C01xx(가족·나홀로·힐링·도보·캠핑·맛코스)다.
- [매뉴얼] 32 숙박: `room*` 필드와 객실 사진 `roomimg1~5`, 사진별 `cpyrhtDivCd1~5`.
### 2-7. detailImage2 (L866–904)
- [매뉴얼] 요청: `contentId` 필수. `imageYN` 은 "Y=콘텐츠이미지조회 / N=”음식점”타입의 음식메뉴 이미지" 다.
- `subImageYN` 은 v4.3 에서 삭제됐다(L45).
- [매뉴얼] 응답: `originimgurl`(약 500*333), `smallimageurl`(약 160*100), `imgname`, `serialnum`, 장별 `cpyrhtDivCd`.
- [추론] 대표이미지 1장으로 부족할 때만, 상세 화면에 들어갈 때 늦게 부른다(쿼터 절약).
### 2-8. detailCommon2 (L522–576)
- [매뉴얼] 요청 항목은 `contentId` 하나다. v4.3 에서 관광타입 ID 와 '기본정보/대표이미지/지역코드/분류코드/주소/좌표/개요 조회 여부'가 모두 삭제됐다(L44).
- [매뉴얼] 응답: `overview`(개요), `homepage`(HTML `<a>` 태그가 통째로 들어 있음), `telname`, 주소·좌표·법정동·분류.
- 설명란에는 "길안내 정보, 연계 관광정보"가 적혀 있지만 응답 표에 해당 필드는 없다.
- [추론] AI 설명이나 임베딩에 쓸 `overview` 는 이 오퍼레이션에서만 나오며, 1건당 1호출이다.
### 2-9. 코드 조회
- [매뉴얼] `ldongCode2` (L1017–1060):
- `lDongRegnCd` 를 비우면 시도 목록, 넣으면 시군구 목록이 온다
- `lDongListYn=Y` 면 전체 목록(`lDongRegnCd/Nm`, `lDongSignguCd/Nm`)이 온다
- [매뉴얼] `lclsSystmCode2` (L1062–1109):
- `lclsSystmListYn=Y` 면 대·중·소 전체가 온다(`lclsSystm1Cd/Nm` … `lclsSystm3Cd/Nm`)
- 예시의 totalCount 는 243 이다
- [추론] 두 결과를 캐시 테이블로 두고 주기적으로 갱신한다.
### 2-10. 기타
- [매뉴얼 L355, L42] `searchKeyword2`: `keyword` 가 필수이고 국문은 인코딩해야 한다. `contentTypeId` 파라미터는 v4.3 에서 삭제됐으니 분류체계로 거른다.
- [매뉴얼] `arrange=O/Q/R/S` 는 대표이미지가 있는 항목만 돌려준다.
---
## 3. detailIntro2 필드표 (L604–728)
모든 값은 문자열이며 형식 정의가 없다. 공통 필드는 `contentid`, `contenttypeid` 다. 괄호 안은 매뉴얼의 국문 항목명이다.
| 타입 | 시간 | 쉬는날 | 요금 | 주차 | 반려동물 | 예약 | 기타 |
|---|---|---|---|---|---|---|---|
| 12 관광지 | `usetime`(이용시간), `useseason`(이용시기), `opendate`(개장일) | `restdate` | (반복정보: 입장료·관람료·시설이용료·주차요금) | `parking` | `chkpet` | (반복정보 예약안내) | `accomcount`(수용인원), `chkbabycarriage`(유모차대여), `chkcreditcard`, `expagerange`(체험가능연령), `expguide`(체험안내), `heritage1/2/3`(세계문화·자연·기록유산 유무, 예시 "0"), `infocenter`(문의및안내) |
| 14 문화시설 | `usetimeculture` | `restdateculture` | `usefee`(이용요금), `discountinfo` | `parkingculture`, `parkingfee` | `chkpetculture` | – | `accomcountculture`, `chkbabycarriageculture`, `chkcreditcardculture`, `infocenterculture`, `scale`(규모), `spendtime`(관람소요시간) |
| 15 행사/공연/축제 | `playtime`(공연시간), `eventstartdate`, `eventenddate` | – | **`usetimefestival`(이용요금)**, `discountinfofestival` | – | – | `bookingplace`(예매처) | `agelimit`, `eventhomepage`, `eventplace`, `festivalgrade`(축제등급), `placeinfo`, `program`, `spendtimefestival`, `sponsor1`, `sponsor1tel`, `sponsor2`, `sponsor2tel`, `subevent` |
| 25 여행코스 | `taketime`(코스총소요시간) | – | – | – | – | – | `distance`(코스총거리), `infocentertourcourse`, `schedule`(코스일정), `theme`(코스테마) |
| 28 레포츠 | `usetimeleports`, `openperiod`(개장기간) | `restdateleports` | `usefeeleports`(입장료) | `parkingleports`, `parkingfeeleports` | `chkpetleports` | `reservation` | `accomcountleports`, `chkbabycarriageleports`, `chkcreditcardleports`, `expagerangeleports`, `infocenterleports`, `scaleleports` |
| 32 숙박 | `checkintime`, `checkouttime` | – | (반복정보 객실 요금 `room*fee*`) | `parkinglodging` | –(detailPetTour2 만) | `reservationlodging`, `reservationurl` | `accomcountlodging`, `chkcooking`, `foodplace`, `infocenterlodging`, `pickup`, `roomcount`, `roomtype`, `scalelodging`, `subfacility`, `barbecue`, `beauty`, `beverage`, `bicycle`, `campfire`, `fitness`, `karaoke`, `publicbath`, `publicpc`, `sauna`, `seminar`, `sports`, `refundregulation` |
| 38 쇼핑 | `opentime`(영업시간), `opendateshopping`(개장일), `fairday`(장서는날) | `restdateshopping` | `saleitemcost`(판매품목별가격) | `parkingshopping` | `chkpetshopping` | – | `chkbabycarriageshopping`, `chkcreditcardshopping`¹, `culturecenter`, `infocentershopping`, `restroom`, `saleitem`, `scaleshopping`, `shopguide` |
| 39 음식점 | `opentimefood`(영업시간), `opendatefood`(개업일) | `restdatefood` | `discountinfofood` | `parkingfood` | –(detailPetTour2 만) | `reservationfood` | `chkcreditcardfood`, `firstmenu`(대표메뉴), `treatmenu`(취급메뉴), `infocenterfood`, `kidsfacility`, `packing`, `scalefood`, `seat`, `smoking`, `lcnsno`(인허가번호) |
¹ 매뉴얼 표에는 줄바꿈 때문에 "chkcreditcard / shopping" 으로 적혀 있다. [관측] 실제 응답 키는 `chkcreditcardshopping` 이다.
[관측] 10-29 응답에서 본 소개정보 키(12/14/38/39)는 모두 이 표 안에 있었다.
---
## 4. 분류 코드 (신분류체계 · 연계 정의서)
### 4-1. 데이트 코스용 코드 [매뉴얼]
| 목적 | 코드 | 관광타입 |
|---|---|---|
| 카페 | `FD05` 카페/찻집 → `FD050100` 카페, `FD050200` 찻집, `FD050300` 기타음료점 (+ `FD030100` 제과) | 39 |
| 술집 | `FD040100` 바/펍, `FD040200` 생맥주전문점, `FD040400` 전통주/민속주점 | 39 |
| 갤러리·전시 시설 | `VE07` 전시시설 → `VE070100` 박물관, `VE070200` 기념관, `VE070300` 전시관, `VE070400` 컨벤션센터, `VE070500` 과학관, `VE070600` 미술관/화랑 | 14 |
| 전시(기간 행사) | `EV030100` 전시회, `EV030200` 박람회 | 15 |
| 도시공원 | `VE03` 도시공원 → `VE030100` 시민공원, `VE030200` 소공원, `VE030300` 어린이공원, `VE030400` 근린공원, `VE030500` 주제공원 | 12 |
| 자연공원 | `NA04` 자연공원 → `NA040100` 국립공원, `NA040200` 도립공원, `NA040300` 군립공원, `NA040400` 지질공원, `NA040500` 생태관광지, `NA040600` 자연휴양림, `NA040700` 수목원ㆍ정원 | 12 |
| 시장 | `SH06` 시장 → `SH060100` 비상설시장, `SH060200` 상설시장 (소개정보 `fairday` 와 함께 쓴다) | 38 |
| 축제 | `EV01` 축제 → `EV010100` 문화관광축제, `EV010200` 문화예술축제, `EV010300` 지역특산물축제, `EV010400` 전통역사축제, `EV010500` 생태자연축제, `EV010600` 기타축제 | 15 |
| 공연 | `EV02` 공연 → `EV020100` 전통공연, `EV020200` 연극, `EV020300` 뮤지컬, `EV020400` 오페라, `EV020500` 무용, `EV020600` 클래식음악회, `EV020700` 대중콘서트, `EV020800` 영화, `EV020900` 기타공연, `EV021000` 넌버벌 | 15 |
| 행사 | `EV03` 행사 → `EV030100` 전시회, `EV030200` 박람회, `EV030300` 스포츠경기, `EV030400` 기타행사 | 15 |
| 동네 산책 | `VE040100` 골목길, 문화거리 · `VE040200` 마을관광지 · `VE040300` 둘레길 | 12 |
| 공방·체험 | `SH050100` 공방/공예품점(38) · `EX02` 공예체험 `EX020100`~`EX020400`(12) | 38/12 |
| 실내 데이트 | `VE060100` 공연장 · `VE060200` 영화관 · `VE120100` 서점(14) · `VE020100` 테마파크 · `VE020300` 동물원 · `VE020400` 수족관 / 아쿠라리움(12) | 14/12 |
### 4-2. 야경
- [매뉴얼] '야경' 코드는 **없다**.
- [추론] 후보 코드는 아래와 같다. 개요 텍스트 판단과 우리 쪽 큐레이션 태그를 함께 써야 한다.
- `VE010200` 타워 / 전망대
- `VE010300` 다리 / 대교
- `VE010400` 분수
- `NA020800` 해안절경
- `NA020900` 해변. 해수욕장
- `VE020500` 천문대
### 4-3. 매핑 주의 [매뉴얼]
- 대분류와 contenttypeid 는 1:1 이 아니다:
- `AC05` 캠핑(`AC050100`~`AC050400`) → **28 레포츠**
- `VE050200` 리조트 → **32 숙박**
- `VE10` 레저스포츠시설, `VE120200` 카지노 → **28**
- `VE06`·`VE07`·`VE08`·`VE09`·`VE120100`·`VE120300` → **14**
- 나머지 `VE`·`EX`·`HS`·`NA` → 12
- 다국어(영문) 관광타입 코드: 76 관광지, 78 문화시설, **85 축제/공연/행사**, 75 레포츠, 80 숙박, 79 쇼핑, 82 음식점, 여행코스 없음. 우리 요약의 영문 코드와 맞고, 85가 더해진다.
- 정의서 머리글의 "lclsSystem1", "clsSystem3" 는 오기다. API 파라미터 이름은 `lclsSystm1~3` 이다.
- 번호가 비어 있다: `VE08` 아래에는 `VE080600` 연회장만 있고 `VE11` 은 없다.
- 여행코스 분류: `C01` 추천코스 → `C0112` 가족, `C0113` 나홀로, `C0114` 힐링, `C0115` 도보, `C0116` 캠핑, `C0117` 맛코스 (소분류 8자리 `C0112001` 등).
---
## 5. 라이선스 · 쿼터
### 5-1. 이미지
- [매뉴얼] `cpyrhtDivCd`: "Type1: 제1유형(출처표시-권장) / Type3: 제3유형(제1유형 + 변경금지)"
- 목록의 `firstimage` 에 붙는다
- `detailImage2` 에서는 장마다 붙는다
- 객실 사진에는 `cpyrhtDivCd1~5` 로 붙는다
- 이 항목은 v4.1(2023-01-30)에 "응답항목(이미지 공공누리유형)"으로 추가됐다 (L34)
- [추론, 공공누리 일반 규정 기준] 해석:
- 제1유형: 출처를 표시하면 상업적 이용과 변경이 허용된다
- 제3유형: 출처표시 + 변경금지. 상업적 이용은 허용된다
- 매뉴얼은 "권장"이라고 쓰지만 공공누리 제1유형에서 출처표시는 의무로 알려져 있다. 항상 출처를 표기한다
- Type3 이미지는 보수적으로 다룬다: 크롭·필터·텍스트 합성 없이, 원비율을 유지한 축소 표시만 한다
- `cpyrhtDivCd` 가 빈 값이면(L971) 쓰지 않는다
### 5-2. 텍스트 데이터 · DB 저장 · 상업 표시
- 텍스트 라이선스: 국문 매뉴얼과 신청 매뉴얼 **어디에도 명시가 없다**.
- [매뉴얼-신청 L36] "활용목적, 상세기능, 라이선스 표시 동의는 필수 사항"
- [추론] data.go.kr 상세 페이지의 '이용허락범위'를 확인해 기록해 둔다
- DB 저장: 금지 문구가 없다.
- [매뉴얼 L930] 동기화 오퍼레이션 설명("DB저장 동기화시 이전 KEY 값으로 조회 용도")이 자체 DB 저장을 **전제**하고 있다
- [추론] 'AI 는 우리 DB 에서만 고른다'는 구조와 충돌하지 않는다
- 상업 표시: [매뉴얼 L144] "사용 제약사항 / (비고) | N/A".
- [매뉴얼-신청 L47–48] 운영계정을 승인하면 관광공사가 "[활용사례]에 등록되어 홍보" 한다
### 5-3. 쿼터 · 계정
- [매뉴얼] 개발계정: 오퍼레이션별 1,000건/일, 자동승인, 10~30분 뒤 사용 가능 (신청 L26–27).
- [매뉴얼] 운영계정: 담당자 승인 1~3일, 정상 서비스 확인 후 승인, 24개월 유효, 연장 신청 가능. **한도 수치는 없다.**
- [매뉴얼 L145] 문의처: 개방데이터운영팀 070-4287-3219 / tourapi@knto.or.kr. 운영 한도와 텍스트 라이선스는 여기로 문의한다.
- [추론] 적재량 계산:
1. `areaBasedList2` 의 `totalCount` 로 서울(11)·부산(26)의 타입별 건수를 먼저 잰다. 목록 호출 몇 건이면 된다.
2. 상세(`detailCommon2`·`detailIntro2`)는 1건당 1호출이다. 1만 건이면 개발계정으로 오퍼레이션당 10일이 걸린다.
3. 우선순위는 12/14/15/38 을 먼저, 39(음식점)는 상가정보와 겹치므로 나중이다.
---
## 6. 주의점
1. **포털 오류는 XML 로만 온다** [매뉴얼 L187]: "공공데이터포털에서 출력되는 오류 메세지는 XML로만 출력"
- `_type=json` 이어도 키 오류나 한도 초과는 `<OpenAPI_ServiceResponse><cmmMsgHeader>…<returnReasonCode>` 로 온다
- 지금 `call()` 은 "JSON 아님"으로만 기록하므로 `returnReasonCode` 를 파싱해야 한다
- 처리할 코드 [매뉴얼 L175–213]:
| 코드 | 이름 | 대응 |
|---|---|---|
| 22 | LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR | 한도 초과 → 잡을 멈추고 다음 날 이어서 |
| 30 | SERVICE_KEY_IS_NOT_REGISTERED_ERROR | 키 문제 |
| 31 | DEADLINE_HAS_EXPIRED_ERROR | 24개월 만료 |
| 20 | SERVICE_ACCESS_DENIED_ERROR | |
| 21 | TEMPORARILY_DISABLE_THE_SERVICEKEY_ERROR | |
| 12 | NO_OPENAPI_SERVICE_ERROR | 폐기된 오퍼레이션 호출 |
| 10 | INVALID_REQUEST_PARAMETER_ERROR | |
| 11 | NO_MANDATORY_REQUEST_PARAMETERS_ERROR | |
| 03 | NODATA_ERROR | |
| 05 | SERVICETIMEOUT_ERROR | 재시도 |
2. **성공 코드가 두 가지로 적혀 있다**: 예시는 `resultCode "0000"`(L242), 코드표는 `00 NORMAL_CODE`(L197)다. 둘 다 성공으로 처리한다(현재 코드가 이미 그렇게 한다).
3. **폐기된 요청 항목은 보내지 않는다** [매뉴얼 L39–50]:
- 지역코드·시군구코드
- 서비스 대·중·소분류
- 목록구분
- 공통정보의 관광타입 ID 와 각종 '조회 여부'
- 이미지의 `subImageYN`
- 키워드 검색의 관광타입
- 숙박의 베니키아·굿스테이·한옥 여부
- [추론] 옛 블로그·SDK 코드의 `areaCode`, `cat1`, `defaultYN`, `overviewYN`, `listYN` 등이 여기에 해당한다(이전 버전 이름이라 기억에 의존한 것이다). 거부되는지는 모르니 보내지 않는 쪽이 안전하다.
4. **서비스키 인코딩** [매뉴얼 L109–113]: `URLEncoder.encode(myKey, "UTF-8")`, 모든 문자셋은 UTF-8 이다.
- [추론] 포털이 주는 'Encoding' 키를 다시 인코딩하면 이중 인코딩이 되어 30 오류가 난다. env 에는 'Decoding' 키를 두고 한 번만 인코딩한다.
5. **keyword 는 국문 인코딩이 필요하다** (L355).
6. **이미지 URL 이 http 다**: 매뉴얼 예시 대부분이 `http://tong.visitkorea.or.kr/…` 이고, 같은 호스트의 https 예시도 있다(L439). 서비스는 HTTP/HTTPS 를 모두 지원한다(L132).
- [추론] 저장할 때 https 로 바꾼다. iOS ATS 와 Android cleartext 차단을 피하기 위해서다.
7. **HTML 이 섞인 값**: `homepage` 는 `<a href=…>` 태그가 통째로 들어 있고(L550, L576), `parking` 등에는 `<br>` 이 있다(L617).
- [추론] 원문 컬럼과 표시용 정제 컬럼을 따로 둔다.
8. **필수 `MobileOS`/`MobileApp`** (L226–227, L115–116): 서비스별 "활용 통계를 산출하기 위한 항목"이다.
- [추론] 운영계정 심사 때 호출 이력을 보므로 `MobileApp=WanderGo` 를 계속 같은 값으로 쓴다.
9. **시각 형식**: 요청 `modifiedtime` 은 YYYYMMDD 이고, 응답 `modifiedtime`/`createdtime` 은 14자리(YYYYMMDDhhmmss)다.
- [추론] KST 로 가정하고 UTC ISO 로 변환한다(api-design 규칙).
10. **좌표**: `mapx`=경도, `mapy`=위도, WGS84, 문자열이다 (L255–256). `mlevel` 은 "Map Level" 이라는 설명뿐이다.
11. **이미지 해상도**: 원본은 약 500*333, 썸네일은 약 150*100(목록)/160*100(이미지 조회)이다.
- [추론] 풀폭 히어로 이미지로 쓰기엔 해상도가 낮다.
12. **초당 속도 제한은 매뉴얼에 없다.** 일 한도만 있다.
13. **매뉴얼 예시 오류** — 그대로 복사하지 않는다:
- 키워드 예시 `keyword시장` 에 '=' 이 빠져 있다 (L394)
- 행사 예시가 `lDongRegnCd=36110` 이다 (L417)
- 축제 예시끼리 분류가 맞지 않는다 (L451 vs L461)
- 상단 예시가 `areaCode2` 를 쓴다 (L108)
- 행사 예시 제목은 "2025년 05월"인데 파라미터는 2026 전체다 (L459)
14. **매뉴얼에만 있고 실제 응답에는 없을 수 있는 항목**: `petTursmInfo`. 반대로 매뉴얼에서는 삭제됐지만 실제 응답에는 남아 있는 키: `areacode`·`cat1~3`.
15. **비표출 레코드**는 법정동·분류·이미지가 비어 있다 (L971). 지역 필터를 건 동기화 조회로는 삭제를 놓친다.