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:
Jeffrey (Dongkyu) Kim 2026-05-05 00:20:26 +09:00
commit ca5c50ab01
18 changed files with 324 additions and 61 deletions

View 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.

View file

@ -2,4 +2,4 @@
"k-skill-proxy": patch
---
feat: add GET /realtyprice route for 개별공시지가 조회
refactor: remove realtyprice route (moved to standalone gongsijiga-search package)

View file

@ -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.

View file

@ -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 코드에서 직접 호출하고 프록시를 거치지 않는다.

View file

@ -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)

View 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)

View file

@ -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)"
```

View file

@ -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
View file

@ -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"

View 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` 헤더가 필요하며, 이 패키지가 자동 처리합니다.

View 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"
}
}

View 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,
};

View file

@ -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` 헤더를 서버 쪽에서만 주입합니다.

View file

@ -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": {

View file

@ -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;

View file

@ -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");
});