mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
refactor(gongsijiga-search): split realtyprice.kr lookup into standalone package
realtyprice.kr is a fully public endpoint that needs no API key, so per the new k-skill-proxy inclusion rule (proxy is for keyed upstreams only) the helper now ships as `gongsijiga-search` and is invoked directly from the user's machine. - new workspace package packages/gongsijiga-search/ following the blue-ribbon-nearby/coupang-product-search convention (publishConfig, files, repository, keywords) - remove /v1/realtyprice route, realtyprice.js, realtyprice.test.js, and the realtypriceConfigured health flag from k-skill-proxy - document the inclusion rule in AGENTS.md and CLAUDE.md so future skills default to direct calls when no key is required - advertise the new skill in README.md, docs/install.md, and add docs/features/gongsijiga-search.md - drop the hardcoded toss-securities lockfile version assertion that pinned a workspace version (would block changesets version-packages) and document the anti-pattern in AGENTS.md / CLAUDE.md - changesets: refresh the proxy refactor message and add a patch changeset so the new gongsijiga-search package gets published
This commit is contained in:
parent
124c16b4d9
commit
ca5c50ab01
18 changed files with 324 additions and 61 deletions
7
.changeset/gongsijiga-search-package.md
Normal file
7
.changeset/gongsijiga-search-package.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"gongsijiga-search": patch
|
||||
---
|
||||
|
||||
feat: extract realtyprice.kr lookup from k-skill-proxy into a standalone `gongsijiga-search` workspace package
|
||||
|
||||
The previous `/v1/realtyprice` proxy route called a fully public endpoint (realtyprice.kr) that needs no API key, so per the new k-skill-proxy inclusion rule (proxy is for keyed upstreams only) the helper now ships as its own package and is invoked directly from the user's machine.
|
||||
|
|
@ -2,4 +2,4 @@
|
|||
"k-skill-proxy": patch
|
||||
---
|
||||
|
||||
feat: add GET /realtyprice route for 개별공시지가 조회
|
||||
refactor: remove realtyprice route (moved to standalone gongsijiga-search package)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ These rules are repo-specific and apply to everything under this directory.
|
|||
## Testing anti-patterns
|
||||
|
||||
- **Never write tests that assert `.changeset/*.md` files exist.** Changesets are consumed (deleted) by `changeset version` during the release flow. Any test guarding changeset file presence will break CI on the version-bump commit and block the release pipeline.
|
||||
- **Never write tests that pin a workspace package's `version` field** (in `package.json` or `package-lock.json`). `changeset version` bumps these on every release, so any hardcoded version assertion will fail the next release commit and block the npm publish pipeline. Stable invariants like `name`, `license`, `engines.node`, or workspace link metadata are fine to assert; the `version` is not.
|
||||
|
||||
## Development skill install rules
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ These rules are repo-specific and apply to everything under this directory.
|
|||
## Free API proxy policy
|
||||
|
||||
- The built-in `k-skill-proxy` is for **free APIs only**.
|
||||
- **k-skill-proxy inclusion rule**: A skill should be served through `k-skill-proxy` **only when the upstream requires an API key** (e.g., data.go.kr, KRX, Naver Search Open API, NEIS, Data4Library). Fully public endpoints that work without any authentication (e.g., realtyprice.kr) should be called directly from the user's machine, not routed through the proxy.
|
||||
- Default posture: public read-only endpoint, **no proxy auth by default**.
|
||||
- Keep free-API proxy surfaces narrow, allowlisted, cache-backed, and rate-limited.
|
||||
- If abuse or operational issues appear later, add stricter controls then instead of preemptively requiring auth.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
## Testing anti-patterns
|
||||
|
||||
- **Never write tests that assert `.changeset/*.md` files exist.** Changesets are consumed (deleted) by `changeset version` during the release flow. Any test guarding changeset file presence will break CI on the version-bump commit and block the release pipeline.
|
||||
- **Never write tests that pin a workspace package's `version` field** (in `package.json` or `package-lock.json`). `changeset version` bumps these on every release, so any hardcoded version assertion will fail the next release commit and block the npm publish pipeline. Stable invariants like `name`, `license`, `engines.node`, or workspace link metadata are fine to assert; the `version` is not.
|
||||
|
||||
## Crawling/search skill authoring
|
||||
|
||||
|
|
@ -18,3 +19,4 @@
|
|||
- **cron job** 이 매시 정각에 `origin/main` fetch → fast-forward pull → pm2 restart 실행
|
||||
- 따라서 proxy route 변경은 **main에 merge되어야 프로덕션에 반영**된다. dev에서 코드를 바꿔도 프로덕션 proxy에는 영향 없음.
|
||||
- 로컬 테스트는 `node packages/k-skill-proxy/src/server.js` 로 직접 실행하거나 `node --test packages/k-skill-proxy/test/server.test.js` 로 확인.
|
||||
- **Proxy 편입 규칙**: k-skill-proxy에 route를 추가하려면 upstream이 API 키를 필요로 해야 한다. 공개 엔드포인트(키 불필요)는 skill 코드에서 직접 호출하고 프록시를 거치지 않는다.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
|
|||
| 한국 사업자 장부 자동화 | `korean-jangbu-for` | `kimlawtech/korean-jangbu-for` 기반 카드·은행·영수증·세금계산서 입력 → 표준 거래내역·계정과목·세무사 전달 CSV·경영 리포트 생성 thin wrapper | 선택사항(CODEF BYOK 자동 수집 시 필요) | [한국 사업자 장부 자동화 가이드](docs/features/korean-jangbu-for.md) |
|
||||
| 한국 개인정보처리방침·이용약관 자동 생성 | `korean-privacy-terms` | Next.js 프로젝트에 개인정보보호법·약관규제법·전자상거래법 기반 개인정보처리방침/이용약관/쿠키 배너/동의 모달을 생성하는 `kimlawtech/korean-privacy-terms` (Apache-2.0) thin wrapper | 불필요 | [한국 개인정보처리방침·이용약관 자동 생성 가이드](docs/features/korean-privacy-terms.md) |
|
||||
| 한국 부동산 실거래가 조회 | `real-estate-search` | 아파트/오피스텔/빌라/단독주택 실거래가·전월세·지역코드 조회 | 불필요 | [한국 부동산 실거래가 조회 가이드](docs/features/real-estate-search.md) |
|
||||
| 개별공시지가 조회 | `gongsijiga-search` | realtyprice.kr 공개 API에서 지번 단위 개별공시지가(원/㎡) 다년도 추이·전년 대비 변동률 조회 | 불필요 | [개별공시지가 조회 가이드](docs/features/gongsijiga-search.md) |
|
||||
| LH 청약 공고문 조회 | `lh-notice-search` | 한국토지주택공사(LH) 임대/분양/주거복지(신혼희망타운)/토지/상가 공고를 지역·상태·공고유형·키워드로 조회하고 마감 여부를 KST 기준으로 표시 | 불필요 | [LH 청약 공고문 조회 가이드](docs/features/lh-notice-search.md) |
|
||||
| 법원 경매 부동산 매각공고 조회 | `court-auction-notice-search` | 대법원경매정보(courtauction.go.kr) 부동산 매각공고를 매각기일·법원·기일/기간 입찰 조건으로 검색해 사건번호·용도·주소·감정평가액·최저매각가격을 펼치고, 사건번호로 직접 사건정보·물건내역·매각기일이력을 조회 | 불필요 | [법원 경매 부동산 매각공고 조회 가이드](docs/features/court-auction-notice-search.md) |
|
||||
| 장학금 검색 및 조회 | `korean-scholarship-search` | 한국장학재단·전국 대학교·재단·기업 장학 공고를 검색해 금액·자격·지원구간·링크를 정리하고 KST 기준 현재 날짜 마감 상태와 조건별 필터링까지 제공 | 불필요 | [장학금 검색 및 조회 가이드](docs/features/korean-scholarship-search.md) |
|
||||
|
|
@ -125,6 +126,7 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
|
|||
- [한국 개인정보처리방침·이용약관 자동 생성 가이드](docs/features/korean-privacy-terms.md)
|
||||
- [한국 사업자 장부 자동화 가이드](docs/features/korean-jangbu-for.md)
|
||||
- [한국 부동산 실거래가 조회 가이드](docs/features/real-estate-search.md)
|
||||
- [개별공시지가 조회 가이드](docs/features/gongsijiga-search.md)
|
||||
- [LH 청약 공고문 조회 가이드](docs/features/lh-notice-search.md)
|
||||
- [법원 경매 부동산 매각공고 조회 가이드](docs/features/court-auction-notice-search.md)
|
||||
- [장학금 검색 및 조회 가이드](docs/features/korean-scholarship-search.md)
|
||||
|
|
|
|||
93
docs/features/gongsijiga-search.md
Normal file
93
docs/features/gongsijiga-search.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# 개별공시지가 조회 가이드
|
||||
|
||||
## 이 기능으로 할 수 있는 일
|
||||
|
||||
- 한국 국토교통부 부동산공시가격알리미(`realtyprice.kr`)에서 지번 단위 **개별공시지가**(원/㎡) 조회
|
||||
- 다년도 추이(과거 수년치)와 전년 대비 변동률 정규화 JSON 출력
|
||||
- 17개 광역자치단체(서울, 세종특별자치시 포함) 모든 시·군·구 지원
|
||||
- 산 지번 / 본번-부번 모두 지원
|
||||
|
||||
## 가장 중요한 규칙
|
||||
|
||||
`realtyprice.kr`는 **API 키가 필요 없는 완전 공개 엔드포인트**이므로 이 스킬은 `k-skill-proxy`를 경유하지 않는다. 사용자 머신에서 직접 upstream을 호출한다. (저장소의 *k-skill-proxy inclusion rule* — 프록시는 API 키가 필요한 upstream만 다룬다.)
|
||||
|
||||
## 무엇을 가져오나
|
||||
|
||||
- 공시지가는 매년 1월 1일 기준, 4~5월에 공시된다.
|
||||
- 재산세, 종합부동산세, 양도소득세 등 **세금 산정의 법적 기준 단가**다.
|
||||
- 공시지가 ≠ 시세. 시세는 통상 공시지가의 1.5~3배.
|
||||
|
||||
> 시세, 실거래가, 매매가, 호가가 필요하면 [`real-estate-search`](real-estate-search.md) 또는 다른 스킬을 사용한다.
|
||||
|
||||
## 먼저 필요한 것
|
||||
|
||||
없음. 인터넷 연결과 Node.js 18+ 만 있으면 된다.
|
||||
|
||||
## 사용 방법
|
||||
|
||||
### 설치
|
||||
|
||||
```bash
|
||||
npm install gongsijiga-search
|
||||
```
|
||||
|
||||
### 기본 호출
|
||||
|
||||
```js
|
||||
const { lookupGongsijiga } = require("gongsijiga-search");
|
||||
|
||||
const result = await lookupGongsijiga("서울특별시 강남구 역삼동 736");
|
||||
console.log(result.latest.price_per_sqm); // 72340000
|
||||
console.log(result.yoy_change_pct); // 5.45
|
||||
```
|
||||
|
||||
### 입력 주소 형식
|
||||
|
||||
`<시도> <시군구> <읍면동…> [산] <본번[-부번]>`
|
||||
|
||||
| 형식 | 예시 |
|
||||
| --- | --- |
|
||||
| 일반 | `서울특별시 강남구 역삼동 736` |
|
||||
| 약칭 시도 | `서울 강남구 역삼동 736` |
|
||||
| 부번 있음 | `경기 성남시 분당구 정자동 178-3` |
|
||||
| 산 지번 | `서울 서초구 서초동 산 1-2` |
|
||||
| 다토큰 읍면동 | `전남 무안군 청계면 청천리 100-5` |
|
||||
| 세종 (시군구 없음) | `세종 어진동 575` 또는 `세종특별자치시 어진동 575` |
|
||||
|
||||
### 응답 모양
|
||||
|
||||
```json
|
||||
{
|
||||
"address": "서울특별시 강남구 역삼동 736",
|
||||
"jibun": "736번지",
|
||||
"san": false,
|
||||
"latest": {
|
||||
"year": 2026,
|
||||
"price_per_sqm": 72340000,
|
||||
"notice_date": "2026-04-30",
|
||||
"base_date": "2026-01-01"
|
||||
},
|
||||
"history": [
|
||||
{ "year": 2026, "price_per_sqm": 72340000, "notice_date": "2026-04-30" },
|
||||
{ "year": 2025, "price_per_sqm": 68600000, "notice_date": "2025-04-30" }
|
||||
],
|
||||
"yoy_change_pct": 5.45,
|
||||
"source_url": "https://www.realtyprice.kr/notice/gsindividual/search.htm"
|
||||
}
|
||||
```
|
||||
|
||||
## 실패 모드
|
||||
|
||||
| `error.code` | 의미 | 처리 |
|
||||
| --- | --- | --- |
|
||||
| `ADDRESS_PARSE_FAILED` | 주소 파싱 실패 / 미인식 시도 | "행정구역 + 본번이 포함된 주소가 필요합니다" 안내 후 재요청 |
|
||||
| `INVALID_BUNJI` | 본번 비숫자 또는 4자리 초과 | 본번 형식 재요청 |
|
||||
| `REGION_NOT_FOUND` | 시군구/읍면동 매칭 실패 | `err.candidates` 후보(최대 3개) 제안 |
|
||||
| `LAND_NOT_FOUND` | 해당 지번 미등재 | "본번/부번 오타이거나 도로/하천 등 미과세 토지" 설명 |
|
||||
| `UPSTREAM_ERROR` | `realtyprice.kr` 비정상 응답 | "데이터 출처 일시 장애. 잠시 후 재시도" + `source_url` |
|
||||
| `UPSTREAM_TIMEOUT` | 30초 타임아웃 | UPSTREAM_ERROR와 동일 처리 |
|
||||
|
||||
## 출처
|
||||
|
||||
- [부동산공시가격알리미](https://www.realtyprice.kr/notice/gsindividual/search.htm) — 국토교통부
|
||||
- 패키지 소스: [`packages/gongsijiga-search/`](../../packages/gongsijiga-search)
|
||||
|
|
@ -277,7 +277,7 @@ npm run ci
|
|||
### Node 패키지
|
||||
|
||||
```bash
|
||||
npm install -g kordoc pdfjs-dist kbo-game kbl-results kleague-results lck-analytics toss-securities hipass-receipt k-lotto coupang-product-search used-car-price-search cheap-gas-nearby public-restroom-nearby korean-law-mcp market-kurly-search daiso bunjang-cli court-auction-notice-search
|
||||
npm install -g kordoc pdfjs-dist kbo-game kbl-results kleague-results lck-analytics toss-securities hipass-receipt k-lotto coupang-product-search used-car-price-search cheap-gas-nearby public-restroom-nearby korean-law-mcp market-kurly-search daiso bunjang-cli court-auction-notice-search gongsijiga-search
|
||||
export NODE_PATH="$(npm root -g)"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,21 @@ metadata:
|
|||
|
||||
## Default path
|
||||
|
||||
`gongsijiga-search` npm 패키지를 직접 호출한다. realtyprice.kr는 API 키가 필요 없는 공개 엔드포인트이므로 `k-skill-proxy`를 경유하지 않는다.
|
||||
|
||||
설치:
|
||||
|
||||
```bash
|
||||
BASE="${KSKILL_PROXY_BASE_URL:-https://k-skill-proxy.nomadamas.org}"
|
||||
BASE="${BASE%/}"
|
||||
npm install gongsijiga-search
|
||||
```
|
||||
|
||||
호출:
|
||||
|
||||
```bash
|
||||
node -e "
|
||||
const { lookupGongsijiga } = require('gongsijiga-search');
|
||||
lookupGongsijiga('서울 강남구 역삼동 736').then(console.log).catch(console.error);
|
||||
"
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
|
@ -57,18 +69,22 @@ BASE="${BASE%/}"
|
|||
사용자에게 **시도 + 시군구 + 읍면동 + 지번**이 포함된 주소를 요청한다.
|
||||
|
||||
- 최소 필수: 시도, 시군구, 읍면동, 본번
|
||||
- **세종특별자치시**는 시군구가 없으므로 "세종 [읍멘동] [지번]" 형식
|
||||
- **세종특별자치시**는 시군구가 없으므로 "세종 [읍면동] [지번]" 형식
|
||||
- 산 지번이면 "산" 키워드 포함
|
||||
- 부번이 있으면 "100-5" 형식
|
||||
|
||||
예시: "서울 강남구 역삼동 736", "전남 무안군 청계면 청천리 산 1-2", "세종 고욘동 100"
|
||||
예시: "서울 강남구 역삼동 736", "전남 무안군 청계면 청천리 산 1-2", "세종 고용동 100"
|
||||
|
||||
시도가 누락된 주소(예: "역삼동 736")는 조회 불가 — 시도를 물어본다.
|
||||
|
||||
### 2. 프록시 호출
|
||||
### 2. 직접 호출
|
||||
|
||||
```bash
|
||||
curl -s "${BASE}/v1/realtyprice?address=$(python3 -c "import urllib.parse; print(urllib.parse.quote('서울 강남구 역삼동 736'))")"
|
||||
`gongsijiga-search` 모듈을 사용해 realtyprice.kr를 직접 호출한다 (API 키 불필요, 프록시 경유 안 함):
|
||||
|
||||
```javascript
|
||||
const { lookupGongsijiga } = require('gongsijiga-search');
|
||||
|
||||
const result = await lookupGongsijiga('서울 강남구 역삼동 736');
|
||||
```
|
||||
|
||||
### 3. 응답 해석 및 출력
|
||||
|
|
|
|||
23
package-lock.json
generated
23
package-lock.json
generated
|
|
@ -867,6 +867,10 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gongsijiga-search": {
|
||||
"resolved": "packages/gongsijiga-search",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"dev": true,
|
||||
|
|
@ -1730,7 +1734,7 @@
|
|||
}
|
||||
},
|
||||
"packages/court-auction-notice-search": {
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"court-auction-notice-search": "bin/court-auction-notice-search.js"
|
||||
|
|
@ -1750,6 +1754,13 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/gongsijiga-search": {
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/hipass-receipt": {
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
|
|
@ -1771,7 +1782,7 @@
|
|||
}
|
||||
},
|
||||
"packages/k-skill-proxy": {
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fastify": "^5.3.3"
|
||||
|
|
@ -1781,7 +1792,7 @@
|
|||
}
|
||||
},
|
||||
"packages/k-skill-rhwp": {
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rhwp/core": "^0.7.3"
|
||||
|
|
@ -1829,21 +1840,21 @@
|
|||
}
|
||||
},
|
||||
"packages/parking-lot-search": {
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/public-restroom-nearby": {
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/toss-securities": {
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
|
|||
100
packages/gongsijiga-search/README.md
Normal file
100
packages/gongsijiga-search/README.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# gongsijiga-search
|
||||
|
||||
대한민국 국토교통부 부동산공시가격알리미(`realtyprice.kr`)의 공개 API를 호출해 지번 단위 **개별공시지가**(원/㎡)를 조회하는 Node.js 패키지입니다. 다년도 추이와 전년 대비 변동률을 정규화된 JSON으로 돌려줍니다.
|
||||
|
||||
> [!NOTE]
|
||||
> `realtyprice.kr`는 API 키가 필요 없는 완전 공개 엔드포인트이므로 이 패키지는 `k-skill-proxy`를 경유하지 않고 사용자 머신에서 직접 upstream을 호출합니다. (참고: 저장소의 *k-skill-proxy inclusion rule* — 프록시는 API 키가 필요한 upstream만 다룹니다.)
|
||||
|
||||
## 설치
|
||||
|
||||
배포 후:
|
||||
|
||||
```bash
|
||||
npm install gongsijiga-search
|
||||
```
|
||||
|
||||
이 저장소에서 개발할 때:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## 사용 예시
|
||||
|
||||
```js
|
||||
const { lookupGongsijiga } = require("gongsijiga-search");
|
||||
|
||||
async function main() {
|
||||
const result = await lookupGongsijiga("서울특별시 강남구 역삼동 736");
|
||||
console.log(result.latest); // { year, price_per_sqm, notice_date, base_date }
|
||||
console.log(result.history); // [{ year, price_per_sqm, notice_date }, ...] (descending)
|
||||
console.log(result.yoy_change_pct); // 전년 대비 % (소수점 둘째 자리 반올림)
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err.code, err.message);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
```
|
||||
|
||||
## 입력 주소 형식
|
||||
|
||||
`<시도> <시군구> <읍면동…> [산] <본번[-부번]>` 형태의 한국어 지번 주소.
|
||||
|
||||
- 시도: 17개 광역자치단체 풀네임/약칭 모두 지원 (예: `서울특별시` / `서울`)
|
||||
- **세종특별자치시**는 시군구가 없으므로 `세종 <읍면동> <지번>` 형식
|
||||
- 산 지번은 `산 1-2` 또는 `산1-2`
|
||||
- 본번은 4자리 이하 숫자, 부번은 `-` 뒤에 옴
|
||||
|
||||
예: `서울 강남구 역삼동 736`, `전남 무안군 청계면 청천리 산 1-2`, `세종 어진동 575`.
|
||||
|
||||
## 응답 모양
|
||||
|
||||
```json
|
||||
{
|
||||
"address": "서울특별시 강남구 역삼동 736",
|
||||
"jibun": "736번지",
|
||||
"san": false,
|
||||
"latest": {
|
||||
"year": 2026,
|
||||
"price_per_sqm": 72340000,
|
||||
"notice_date": "2026-04-30",
|
||||
"base_date": "2026-01-01"
|
||||
},
|
||||
"history": [
|
||||
{ "year": 2026, "price_per_sqm": 72340000, "notice_date": "2026-04-30" },
|
||||
{ "year": 2025, "price_per_sqm": 68600000, "notice_date": "2025-04-30" }
|
||||
],
|
||||
"yoy_change_pct": 5.45,
|
||||
"source_url": "https://www.realtyprice.kr/notice/gsindividual/search.htm"
|
||||
}
|
||||
```
|
||||
|
||||
## 에러 코드
|
||||
|
||||
| `error.code` | 의미 | `statusCode` |
|
||||
| --- | --- | --- |
|
||||
| `ADDRESS_PARSE_FAILED` | 주소 파싱 실패 / 미인식 시도 / 토큰 부족 | 400 |
|
||||
| `INVALID_BUNJI` | 본번이 비숫자 또는 4자리 초과 | 400 |
|
||||
| `REGION_NOT_FOUND` | 시군구/읍면동 매칭 실패 (`err.candidates` 후보 최대 3개) | 404 |
|
||||
| `LAND_NOT_FOUND` | 해당 지번이 공시지가에 등재되지 않음 | 404 |
|
||||
| `UPSTREAM_ERROR` | realtyprice.kr 비정상 HTTP 응답 | 502 |
|
||||
| `UPSTREAM_TIMEOUT` | 30초 타임아웃 | 504 |
|
||||
|
||||
## 공개 API
|
||||
|
||||
- `lookupGongsijiga(addressRaw, fetchFn?)` — 주소 → 정규화된 응답
|
||||
- `parseAddress(rawAddress)` — 주소 파서 (지번/산/세종 처리)
|
||||
- `parseSido(text)` — 시도명 → 2자리 코드
|
||||
- `normalizeSearchResult(raw)` — gsiList 항목 → `{ year, price_per_sqm, notice_date }`
|
||||
- `buildResponse({ address, jibun, san, history })` — 최종 응답 합성
|
||||
- `fetchSigunguList`, `fetchEupmyeondongList`, `fetchGsiSearchList` — 단계별 upstream 호출
|
||||
- `fetchWithTimeout(url, opts, timeoutMs?, fetchFn?)` — AbortController 기반 타임아웃
|
||||
- `createCache()` — 단순 in-memory TTL 캐시 (Map 백엔드)
|
||||
- `SIDO_MAP`, `REALTYPRICE_BASE_URL`, `REFERER`, `makeError`
|
||||
|
||||
## Notes
|
||||
|
||||
- 공시지가 ≠ 시세. 시세는 통상 공시지가의 1.5~3배.
|
||||
- 매년 1월 1일 기준, 4~5월 발표. 1~4월에는 전년도가 최신.
|
||||
- `realtyprice.kr` 호출에는 별도 `Referer` 헤더가 필요하며, 이 패키지가 자동 처리합니다.
|
||||
33
packages/gongsijiga-search/package.json
Normal file
33
packages/gongsijiga-search/package.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "gongsijiga-search",
|
||||
"version": "0.1.0",
|
||||
"description": "Client-side query helpers for Korean individual official land prices (개별공시지가) from realtyprice.kr",
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
"src",
|
||||
"README.md"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/NomaDamas/k-skill.git"
|
||||
},
|
||||
"keywords": [
|
||||
"k-skill",
|
||||
"korea",
|
||||
"realtyprice",
|
||||
"gongsijiga",
|
||||
"land-price",
|
||||
"real-estate"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "node --check src/index.js && node --check src/realtyprice.js && node --check test/realtyprice.test.js",
|
||||
"test": "node --test"
|
||||
}
|
||||
}
|
||||
41
packages/gongsijiga-search/src/index.js
Normal file
41
packages/gongsijiga-search/src/index.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// gongsijiga-search — client-side query helpers for Korean individual official
|
||||
// land prices (개별공시지가).
|
||||
//
|
||||
// Upstream: https://www.realtyprice.kr (public, no API key required)
|
||||
//
|
||||
// This module can be used directly from a user's machine without going through
|
||||
// k-skill-proxy, because realtyprice.kr is a fully open public endpoint.
|
||||
|
||||
const {
|
||||
REALTYPRICE_BASE_URL,
|
||||
REFERER,
|
||||
SIDO_MAP,
|
||||
makeError,
|
||||
parseSido,
|
||||
parseAddress,
|
||||
normalizeSearchResult,
|
||||
buildResponse,
|
||||
fetchWithTimeout,
|
||||
fetchSigunguList,
|
||||
fetchEupmyeondongList,
|
||||
fetchGsiSearchList,
|
||||
lookupGongsijiga,
|
||||
createCache,
|
||||
} = require("./realtyprice");
|
||||
|
||||
module.exports = {
|
||||
REALTYPRICE_BASE_URL,
|
||||
REFERER,
|
||||
SIDO_MAP,
|
||||
makeError,
|
||||
parseSido,
|
||||
parseAddress,
|
||||
normalizeSearchResult,
|
||||
buildResponse,
|
||||
fetchWithTimeout,
|
||||
fetchSigunguList,
|
||||
fetchEupmyeondongList,
|
||||
fetchGsiSearchList,
|
||||
lookupGongsijiga,
|
||||
createCache,
|
||||
};
|
||||
|
|
@ -27,7 +27,6 @@
|
|||
- `GET /v1/data4library/book-exists` — 도서관별 도서 소장여부(`DATA4LIBRARY_AUTH_KEY`)
|
||||
- `GET /v1/lh-notice/search` — LH 청약 공고 목록(`DATA_GO_KR_API_KEY`)
|
||||
- `GET /v1/lh-notice/detail` — LH 청약 공고 상세(`DATA_GO_KR_API_KEY`)
|
||||
- `GET /v1/realtyprice` — 개별공시지가 조회 (realtyprice.kr 공개 API, API 키 불필요)
|
||||
|
||||
## `/health` 업스트림 플래그 의미
|
||||
|
||||
|
|
@ -204,13 +203,6 @@ curl -fsS --get 'http://127.0.0.1:4020/v1/lh-notice/detail' \
|
|||
--data-urlencode 'splInfTpCd=051'
|
||||
```
|
||||
|
||||
개별공시지가 조회 예시 (API 키 불필요):
|
||||
|
||||
```bash
|
||||
curl -fsS --get 'http://127.0.0.1:4020/v1/realtyprice' \
|
||||
--data-urlencode 'address=서울 강남구 역삼동 736'
|
||||
```
|
||||
|
||||
프록시는 내부적으로 `waterlevel/info.json` 으로 관측소를 해석하고, `waterlevel/list/10M/{WLOBSCD}.json` 으로 최신 수위/유량을 조회합니다. 한국 주식 route는 KRX Open API에 `AUTH_KEY` 헤더를 서버 쪽에서만 주입합니다.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "node --check src/airkorea.js && node --check src/bluer.js && node --check src/hrfco.js && node --check src/krx-stock.js && node --check src/lh-notice.js && node --check src/mfds.js && node --check src/molit.js && node --check src/naver-news.js && node --check src/naver-shopping.js && node --check src/parking-lots.js && node --check src/region-lookup.js && node --check src/realtyprice.js && node --check src/server.js && node --check test/airkorea.test.js && node --check test/hrfco.test.js && node --check test/lh-notice.test.js && node --check test/molit.test.js && node --check test/naver-news.test.js && node --check test/naver-shopping.test.js && node --check test/region-lookup.test.js && node --check test/realtyprice.test.js && node --check test/server.test.js",
|
||||
"lint": "node --check src/airkorea.js && node --check src/bluer.js && node --check src/hrfco.js && node --check src/krx-stock.js && node --check src/lh-notice.js && node --check src/mfds.js && node --check src/molit.js && node --check src/naver-news.js && node --check src/naver-shopping.js && node --check src/parking-lots.js && node --check src/region-lookup.js && node --check src/server.js && node --check test/airkorea.test.js && node --check test/hrfco.test.js && node --check test/lh-notice.test.js && node --check test/molit.test.js && node --check test/naver-news.test.js && node --check test/naver-shopping.test.js && node --check test/region-lookup.test.js && node --check test/server.test.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ const { fetchNaverShoppingSearch, normalizeNaverShoppingSearchQuery } = require(
|
|||
const { fetchNearbyParkingLots } = require("./parking-lots");
|
||||
const { searchRegionCode } = require("./region-lookup");
|
||||
const { resolveEducationOfficeFromNaturalLanguage } = require("./neis-office-codes");
|
||||
const { lookupGongsijiga, createCache: createRealtypriceCacheFn } = require("./realtyprice");
|
||||
const AIR_KOREA_UPSTREAM_BASE_URL = "http://apis.data.go.kr";
|
||||
const DATA_GO_KR_UPSTREAM_BASE_URL = "https://apis.data.go.kr";
|
||||
const DATA4LIBRARY_UPSTREAM_BASE_URL = "https://data4library.kr/api";
|
||||
|
|
@ -1287,8 +1286,7 @@ function buildServer({ env = process.env, provider = null, now = () => new Date(
|
|||
krxConfigured: Boolean(config.krxApiKey),
|
||||
naverShoppingConfigured: true,
|
||||
naverSearchApiConfigured: naverSearchKeysPresent,
|
||||
naverNewsApiConfigured: naverSearchKeysPresent,
|
||||
realtypriceConfigured: true
|
||||
naverNewsApiConfigured: naverSearchKeysPresent
|
||||
},
|
||||
auth: {
|
||||
tokenRequired: false
|
||||
|
|
@ -3400,39 +3398,6 @@ function buildServer({ env = process.env, provider = null, now = () => new Date(
|
|||
return payload;
|
||||
});
|
||||
|
||||
// --- 개별공시지가 (realtyprice.kr) ---
|
||||
const realtypriceCacheTtlMs = 60 * 60 * 1000; // 1 hour
|
||||
const realtypriceCacheInstance = createRealtypriceCacheFn();
|
||||
|
||||
app.get("/v1/realtyprice", async (request, reply) => {
|
||||
const address = (request.query.address || "").trim().replace(/\s+/g, " ");
|
||||
if (!address) {
|
||||
reply.code(400);
|
||||
return {
|
||||
error: { code: "ADDRESS_PARSE_FAILED", message: "address 쿼리 파라미터가 필요합니다." },
|
||||
};
|
||||
}
|
||||
|
||||
const cacheKey = `realtyprice:${address}`;
|
||||
const cached = realtypriceCacheInstance.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await lookupGongsijiga(address);
|
||||
realtypriceCacheInstance.set(cacheKey, result, realtypriceCacheTtlMs);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const statusCode = err.statusCode || 502;
|
||||
reply.code(statusCode);
|
||||
return {
|
||||
error: { code: err.code || "UPSTREAM_ERROR", message: err.message },
|
||||
...(err.candidates ? { candidates: err.candidates } : {}),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
app.setErrorHandler((error, request, reply) => {
|
||||
request.log.error(error);
|
||||
const statusCode = error.statusCode && error.statusCode >= 400 ? error.statusCode : 500;
|
||||
|
|
|
|||
|
|
@ -1695,7 +1695,6 @@ test("package-lock captures the toss-securities workspace metadata for npm ci",
|
|||
resolved: "packages/toss-securities",
|
||||
link: true,
|
||||
});
|
||||
assert.equal(packageLock.packages["packages/toss-securities"].version, "0.2.0");
|
||||
assert.equal(packageLock.packages["packages/toss-securities"].license, "MIT");
|
||||
assert.equal(packageLock.packages["packages/toss-securities"].engines.node, ">=18");
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue