| name | ipzitalk-transit-complex-ranking |
| description | 주소·단지명·지역명 주변 반경의 K-apt 단지를 수집하고, 카카오 POI 최근접 지하철역까지의 직선거리 기준으로 지하철 접근성 단지 랭킹을 만든다. "역세권 단지", "지하철 가까운 단지 랭킹", "도보권 아파트", "반경 내 지하철 좋은 단지"를 물을 때 사용한다. POI 기반 단일 역세권 배지와 구분한다.
|
| version | 1.0.3 |
| license | proprietary |
지하철 도보권 단지 랭킹
tier: L1
반경 내 K-apt 매칭 단지들을 최근접 지하철역까지의 직선거리 기준으로 정렬해 랭킹을 만든다.
K-apt 신고 도보시간은 참고 열로 함께 보여주되, 정렬 근거로 쓰지 않는다.
정렬키 — 왜 K-apt 도보시간이 아닌가 (🚨 반드시 유지)
K-apt 신고 도보시간(subway_walk_min)은 관리사무소가 신고한 자기보고값이고 검증 절차가 없다.
구간 문자열("15~20분이내")이라 정렬도 거칠다. 검증 불가능한 값을 랭킹 1차 정렬키로 쓰면 안 된다.
실측 원천값이 부정확한 사례가 확인됐다: 실제 최근접역이 직선 279m인 단지가 K-apt 신고값에는 15~20분이내로 들어가 있었다.
우회안: 정렬키를 카카오 POI 최근접역 직선거리로 교체한다. 직선거리는 좌표만 있으면 재현 가능하고 모든 단지에 같은 잣대가 적용되며,
신고값 결측 단지도 랭킹에 되살아난다. K-apt 신고 도보시간은 화면에서 참고 열로만 병기하고, 화면 문구는 내부 필드명 대신 **"K-apt 제공 도보시간 기준"**으로만 표기한다.
| 표본 단지 | K-apt 신고값 정렬 | POI 직선거리 정렬 |
|---|
| 기준 단지 | #7 (꼴찌) · 15~20분이내 | #5 · 306m |
| 신고값 결측 단지 | 랭킹 제외 | #4 · 301m |
| 랭킹 단지 수 | 7 | 8 |
두 번째 실증 — 위례 (조회 2026-07-09)
방배는 한 단지만 어긋났지만, 위례에서는 대량으로 뒤집혔다.
| 단지 | POI 직선거리 | K-apt 신고 도보시간 |
|---|
| 송파레이크힐 | #7 (839m) | 15~20분이내 (최하) |
| 위례포레샤인18 | #6 (800m) | 15~20분이내 (최하) |
| 래미안위례 | #11 (945m, 표시 중 최원거리) | 10~15분이내 (위 둘보다 좋게 신고) |
| 위례 송파푸르지오(기준) | 최원거리 1,313m | 10~15분이내 |
K-apt로 줄 세우면 가장 가까운 단지가 바닥으로, 가장 먼 단지가 위로 간다. 자기보고값이라 낙관 편향이 있다.
→ 정렬키를 POI 직선거리로 두는 결정은 예외가 아니라 원칙이다.
🚨 좌표 출처를 하나로 고정한다
같은 단지라도 좌표 출처에 따라 최근접역 거리가 달라진다. 실측 2026-07-09 위례 송파푸르지오:
도로명 주소 지오코딩 (37.4809657, 127.140733) → 장지역 1,313m
카카오 단지 POI 중심 좌표 → 장지역 1,211m (102m 차)
같은 대상지의 산출물끼리 숫자가 어긋나면 사용자는 어느 쪽도 믿지 못한다.
두 숫자는 다 맞다 (haversine 재계산 2026-07-10). 두 좌표가 서로 120m 떨어져 있어서 생긴 차이다.
어느 한쪽이 틀린 게 아니므로 정본을 하나로 강제하면 오히려 랭킹이 틀어진다.
| 산출물 성격 | 쓰는 좌표 | 이유 |
|---|
| 단일 대상지 (배지·보고서) | resolve-site 표준번들(지오코딩) | 그 집 한 곳을 잰다 |
| 다단지 비교 (랭킹) | 카카오 단지 POI — 기준 단지 포함 전 행 | 여러 단지를 한 잣대로 줄 세운다 |
- 한 산출물 안에서는 한 출처만 쓴다. 기준 단지만 지오코딩 좌표를 쓰고 나머지를 POI로 재면 안 된다.
- 🚨 어느 출처를 썼는지 화면에 명시한다. (예:
직선 1,211m — 단지 중심 좌표 기준)
- 🚨 숫자가 다른 것 자체는 버그가 아니다. 사용자가 그 이유를 알 수 없는 것이 버그다. 출처 한 줄이 반드시 있어야 한다.
- 배지형(
ipzitalk-subway-proximity)·조립형(ipzitalk-location-report)은 표준번들 좌표를 쓴다. 랭킹만 예외를 둘 경우 그 사실을 밝힌다.
🚨 표본 수와 순위는 정확히 쓴다
수집이 다단계라 숫자가 어긋나기 쉽다. 실제로 산출물에 80개 표본 과 39개 중 약 25위 가 함께 찍힌 사고가 있었다.
- 단계별 수를 한 문장에 모두 밝힌다. 예:
카카오 후보 273개(수집 상한 도달) → 근접 80개 조회 → K-apt 매칭 39개 → 상위 11개 표시
- 화면의 모든 표본 문구가 같은 숫자를 써야 한다.
sampleNote 와 근거 표가 다른 수를 쓰면 안 된다.
- 🚨
약 N위 금지. 직선거리는 이미 계산돼 있어 순위는 정수로 확정된다. N위 / 전체 M개 로 쓴다.
동순위·결측으로 확정이 불가능하면 "약"으로 뭉개지 말고 그 이유를 한 줄로 적는다.
입력
| 파라미터 | 필수 | 기본값 | 설명 |
|---|
target | ✅ | - | 기준 주소/단지명/지역명/법정동코드 |
radius_m | ✕ | 2000 | 후보 수집 반경 |
limit | ✕ | 20 | 후보 단지 수. 크레딧 절약 위해 작게 시작 |
show_kapt_walk_min | ✕ | true | K-apt 신고 도보시간을 참고 열로 병기할지 |
필수 도구
| 도구 | 용도 |
|---|
resolve-site 규약 | 기준점 좌표·코드 확보 |
search_by_nearby_keyword(preset="apartment", grid=true) | 반경 내 아파트 후보 수집 |
enrich_complex_info | 후보 → K-apt 매칭 (단지 좌표 확보) |
search_by_nearby_category(categories=['subway']) | 역 좌표 1콜 수집 → 정렬키 산출 |
get_complex_info(detail=true) | (선택) K-apt 참고 도보시간. 병기할 때만 |
get_map_embed_url | 후보 지도 생성 |
크레딧: 역 좌표를 1콜로 모으고 거리 계산은 로컬이라 단지 수 N과 무관하다.
구(舊): keyword + enrich + get_complex_info(detail) × N = 2 + N 크레딧
현재: keyword + enrich + category(subway) = 3 크레딧
워크플로우
- 기준점 해소 —
target → 좌표. 지역명/법정동코드만이면 area_center 경고.
- 후보 수집 —
search_by_nearby_keyword(center, radius_m, preset="apartment", grid=true, deduplicate_by="name").
- K-apt 매칭 —
enrich_complex_info(complexes, radius_m=300~500). matched만 랭킹 후보. 단지 좌표를 여기서 얻는다.
- 역 좌표 수집 (1콜) —
search_by_nearby_category(center, radius_m = radius_m + 1500, categories=['subway']).
- 여유 1.5km: 반경 경계 단지의 최근접역이 반경 밖일 수 있다.
- 카카오 45건 캡 확인.
truncated:true면 "목록 불완전" 표기.
- 정렬키 산출 (로컬) — 단지별로 모든 역에 haversine 거리 계산 → 최근접역과 그 직선거리.
- 랭킹 — 최근접역 직선거리 오름차순. 동점은 세대수/기준점 거리 보조 표시.
- 입력이 단지명/단지 주소이고 해당 단지가 랭킹 후보에 포함되면 순위표 행을
검색 기준으로 강조한다.
- 좌표 결측으로 거리 계산이 불가하면 순위표와 별도로
검색 기준 단지: 랭킹 제외 사유 카드를 표시한다.
- K-apt 참고 열 + 불일치 플래그 (
show_kapt_walk_min:true일 때)
- 같은 행에 K-apt 도보시간을 병기한다.
- 불일치 판정(잠정): POI 직선거리 ≤ 500m 인데 K-apt 신고값이
10~15분이내 이상.
- 불일치 행에는
데이터 불일치 라벨을 붙인다. 순위는 그대로 매긴다(정렬키가 검증 가능한 값이므로).
K-apt 값이 왜 다른지는 판정하지 않는다.
- 지도/출처 — 정렬 근거는
카카오 POI 직선거리, 참고 열은 K-apt 제공 도보시간으로 출처를 분리 표기.
데모에서 확인한 파라미터
- 입력:
방배롯데캐슬아르떼, 반경 2km
- 후보 수집: 원천 후보 309개 중 30개 샘플 반환 → 최종 표본 8개
- 역 수집:
search_by_nearby_category(center, radius_m=3500, categories=['subway']) → 23건 · truncated:false
- 정렬 결과(직선거리): 이수자이 142m · 방배디오빌 194m · 방배1차현대 269m · 스타팰리스이수 301m ·
방배롯데캐슬아르떼 306m · 방배2차현대홈타운 403m · 방배브라운가 482m · 방배임광1,2차 656m
- 불일치 1건: 기준 단지 — POI 이수역 306m vs K-apt
15~20분이내
- 레이아웃 기준:
templates/result.html
- 🚨 데모 HTML의 랭킹 순서는 구버전이다. 데모는 K-apt
subway_walk_min 으로 정렬돼 있어
기준 단지가 꼴찌(#7)로 내려가 있다. 현재 정렬키는 POI 최근접역 직선거리이고, 재정렬하면 #5다
(결측으로 빠져 있던 스타팰리스 이수 복귀 → 7개에서 8개). 데모는 레이아웃 참고용으로만 본다.
출력 포맷
- 랭킹 바: 순위, 단지명, 최근접역명 + 직선거리(정렬키), 사용승인연도, K-apt 도보시간(참고 열).
- 검색 기준 단지가 랭킹에 포함되면 해당 행은 강조 배경/테두리와
검색 기준 라벨을 붙인다.
- 불일치 행에는
데이터 불일치 라벨과 두 출처의 값을 함께 표시한다. 순위는 그대로 매긴다.
- 🚨 반경 눈금 마커를 넣지 않는다. 예전엔
fitBounds가 반경 원을 무시해 원이 잘리는 걸 막으려고
정북·남·동·서에 회색 더미 마커 4개를 심었다. 사용자에게는 정체를 알 수 없는 점으로 보여 혼란만 준다.
원이 잘리더라도 마커는 실제 장소만 찍는다. (근본 해결은 render.ts가 원을 bounds에 포함하도록 고치는 것 — 발견사항 6번)
- 화면 표기는 사용자용 용어만: 출처는
공동주택관리정보시스템(K-apt) · 카카오맵 · 네이버 지도.
🚨 방법론을 화면에 늘어놓지 않는다
사용자는 순위를 보러 온다. 수집 파이프라인·정렬 근거는 궁금해하지 않는다.
| 내용 | 어디로 |
|---|
| 수집 단계(273 → 80 → 13 → 12), 정렬키 정의, K-apt 대비 설명 | SKILL.md (화면 X) |
정렬 근거 · 검색 기준 단지 근거 카드 | 만들지 않는다 (info: {} 로 두면 숨는다) |
| 부분 표본이라는 사실 | sampleNote 한 줄 |
| 기준 단지 순위 | 그 단지 행의 meta |
| 직선거리 한계 · K-apt 참고열 · 표본 범위 | hedges 3줄 이내 |
✕ sampleNote(268자): "수집 단계: 카카오 아파트 후보 273개(수집 상한 도달·목록 불완전) → 이 페이지 80개
반환 → 표본 후보 13개 조회 → K-apt 매칭 12개 단지 → … 반환 후보 80개 전체를 같은 방식으로
정렬하면 검색 기준 단지는 66위입니다."
✓ sampleNote(44자): "반경 3km 아파트 273곳 중 12곳만 조회한 부분 표본입니다. 전수 랭킹이 아닙니다."
-
🚨 경고 자체는 지우지 않는다. "전수 아님"과 "전체 기준 66위"는 남긴다. 지우는 건 어떻게 세었는지다.
-
기준 단지가 표본 내 순위와 전체 순위가 다르면 둘 다 적는다. 표본 순위만 보이면 실제보다 좋아 보인다.
-
표본 범위 · 제외 단지는 독립 카드(#scope-card)로 낸다. 지도 아래, 푸터 위.
- 🚨 푸터의 유의사항·출처와
<br> 로 이어붙이지 말 것. 예전엔 그렇게 했고,
"어느 단지가 왜 빠졌는가"가 잡문에 묻혀 보이지 않았다.
- 제외 사유는 두 갈래다 — K-apt 매칭 실패, 그리고 카카오 장소명의 미준공 표기(
(20NN년MM월예정)).
출처를 공동주택관리정보시스템(K-apt) · 카카오맵 으로 병기한다.
- 표본에서 빠진 단지는 사용자의 판단을 바꾸는 정보다. 표본 수를 인용할 때 반드시 함께 보인다.
excluded 가 비고 sampleNote 도 없으면 카드째 숨는다.
엣지 · 실패 처리
| 상황 | 처리 |
|---|
| 단지 후보 과다 | limit 작게, grid 사용, truncated 표기 |
역 수집이 45건 cap 도달(truncated:true) | "역 목록 불완전" 표기. 경계 단지의 최근접역이 누락될 수 있음 |
| K-apt 매칭 실패 | 랭킹 제외, 후보/미매칭 수 요약 |
| 단지 좌표 결측 | 거리 계산 불가 → 랭킹 제외, 사유 표기 |
| K-apt 도보시간 결측 | 랭킹 유지(정렬키가 아니므로). 참고 열만 자료없음 |
| 단지명 후보 다수 | 자동확정 금지 |
필수 단서
- 🚨 정렬키는 직선거리다. 실제 도보 경로·출입구 위치·고저차를 반영하지 않는다.
- 🚨 직선거리를 도보 "분"으로 환산하지 않는다(값 추정 금지). 거리(m)로만 제시한다.
- 역 POI 좌표는 역 중심이며 출입구가 아니다. 실제 도보 시작점과 다를 수 있다.
- K-apt 도보시간은 신고 입력값이며 실제 경로 검증이 아님.
- POI 대체는 직선거리 기준이며 K-apt 랭킹과 혼합하지 않는다.
- 데모·샘플 랭킹은 전수 랭킹이 아님.
- 출처 표기에는 공식 명칭
공동주택관리정보시스템(K-apt)과 링크 https://www.k-apt.go.kr/web/main/index.do를 함께 표시한다.
섹션마다 출처를 작게 단다
데이터 블록 하단에 .src 한 줄. 도구·API 이름은 쓰지 않는다. 사용자가 아는 기관명만.
| 블록 | 출처 표기 |
|---|
| 단지 개요·세대수·준공·주차·연차 | 공동주택관리정보시스템(K-apt) |
| 매매·전세·평당가·거래량 | 국토교통부 실거래가 |
| 학교·교통·생활·상권 등 장소 | 카카오맵 |
| 분양공고·분양가·주택형·입주월 | 청약홈 |
| 지도 (장소 마커) | 네이버 지도 · 카카오맵 |
| 지도 (분양공고 마커) | 네이버 지도 · 청약홈 |
- 🚨 출처 문자열은
ipzi-data JSON으로 받지 않고 템플릿 마크업에 직접 박는다.
어느 블록이 어디서 왔는지는 실행마다 달라지지 않는다. 데이터로 받으면 채우는 걸 잊거나 틀리게 쓸 여지만 생긴다.
- 🚨 한 블록에 두 출처가 섞이면 병기한다. 예:
세대수·주차 — 공동주택관리정보시스템(K-apt) · 위치 — 카카오맵.
하나로 뭉뚱그리면 어느 숫자가 어디서 왔는지 사용자가 알 수 없다.
- 🚨 쓰지 않은 기관을 출처로 적지 않는다. 우리가 부르는 곳은 위 다섯 곳뿐이다.
- 히어로·유의사항·푸터에는 달지 않는다. 데이터 블록에만.
디자인 정본
스킬 폴더 밖 문서에 의존하지 않도록 규칙을 여기 인라인으로 둔다.
- CDN·외부 폰트·이모지 금지. 아이콘은
<symbol> 인라인 + <use> 참조로 self-contained.
- 라이트/다크 양쪽.
prefers-color-scheme + :root[data-theme] 모두 대응.
- 토큰만 사용:
--g/--y/--o/--r/--x/--brand/--up/--down/--zebra (+ -s 배경 변형).
- 예외 상태:
null은 "정보없음"(0 아님) · 표본 부족은 판정 유보 · 45건 캡 도달은 "목록 불완전" 표기.
★·☆는 활자 기호이며 이모지가 아니다. 등급 표기에 사용 가능.
- 지도 카드 CSS:
.map{overflow-x:auto; overflow-y:hidden} + .map iframe{display:block; min-width:720px}.
overflow:hidden만 주면 iframe이 카드 폭에 짓눌려 지도가 최소 줌(한반도)으로 떨어진다.
- 🚨 지도 링크는 발급 후 7일 만료. 산출물 지도 캡션에 유효기간·재발급 필요를 반드시 적는다.
만료 시 iframe이 빈 화면이 되는데, 원인 표기가 없으면 리포트가 고장난 것처럼 보인다.
표본 규칙
- 반경 후보가 많으면 전수가 아니라 표본이다. 표본 수와 원천 후보 수를 함께 표기한다.
- 카카오 검색은 쿼리당 45건 캡이 있다. 역 수집이 캡에 도달하면 경계 단지의 최근접역 누락 가능성을 표기한다.
🚨 개통 예정 역이 장소명에 섞인다 (실측 2026-07-09)
카카오는 개통 예정 역을 장소명 문자열로 표기하고, 카테고리도 지하철로 준다.
"위례호수공원역 (2026년12월예정)" 215m category_name: 교통,수송 > 지하철,전철
다행히 search_by_nearby_category(categories=['subway']) 는 이것을 반환하지 않는다. 실측:
반경 500m · categories:['subway'] → 0건 (215m 예정역이 있는데도)
반경 1000m · query="공원"(키워드) → 예정역이 나옴
- 🚨 지하철은 반드시 카테고리 검색을 쓴다. 키워드 검색으로 바꾸면 미개통 역이 최근접이 되어 등급이 통째로 뒤집힌다.
- 키워드 검색을 쓰는 축(철도
query="기차역", 터미널 query="버스터미널")은 장소명에
예정·개통·(20NN년 이 포함되면 판정에서 제외하고 목록에만 꼬리표를 단다. 연도를 지어내지 말 것.
- 학교도 같은 함정이다(
"산빛초등학교 (2027년 3월 예정)"). 운영 중인 시설로만 판정한다.
🚨 enrich_complex_info 는 이름이 같은 단지도 떨어뜨린다 (실측 2026-07-10)
"K-apt 미매칭 = 그 단지가 없다"가 아니다. 좌표가 어긋나면 이름이 글자까지 같아도 제외된다.
위례2차아이파크아파트 실측 — K-apt 에 분명히 있다(A10027553 · 495세대 · 세대당 주차 1.76):
카카오 좌표 37.479738, 127.143743
K-apt 좌표 └─ 261m 떨어져 있음 ← 기본 radius_m=250 을 11m 넘긴다
radius_m | 이름 완전일치 후보 | 이름 무관 후보(힐스테이트) | 결과 |
|---|
| 250 (기본) | 후보에 없음 | 104m | NEARBY_BUT_NAME_MISMATCH |
| 400 | 0.543 | 0.558 ← 더 높다 | NEARBY_BUT_NAME_MISMATCH |
| 500 | 0.635 (임계 0.65에 0.015 부족) | 0.594 | LOW_CONFIDENCE_MATCH |
점수가 radius_m 에 따라 움직이고, 이름 완전일치가 거리에 밀린다. 반경을 늘려도 해결되지 않는다.
- 🚨
not_found 를 "K-apt 미매칭"으로 뭉뚱그려 적지 않는다. 도구가 주는 reason 을 그대로 구분해 쓴다.
NO_COMPLEX_WITHIN_RADIUS(반경 안에 후보 없음) · NEARBY_BUT_NAME_MISMATCH(후보는 있으나 이름 불일치)
· LOW_CONFIDENCE_MATCH(임계 미달). 사유가 다르면 사용자가 할 일도 다르다.
- 🚨
candidates[] 에 입력과 이름이 사실상 같은 후보가 있으면 화면에 보인다.
조용히 버리면 "반경 내 전수"라고 읽힌다. 표본 수를 인용할 때 누락 가능성을 함께 적는다.
- 🚨 반대 방향 오류도 있다. 거리가 0이면 이름이 아무리 달라도
matched(score 0.75)가 난다.
→ 후보 좌표는 반드시 그 단지의 카카오 좌표를 넣는다. 기준 단지 좌표를 돌려쓰면 엉뚱한 단지로 바뀐다.
복구 절차 (코드 수정 없이 지금 가능)
not_found 여도 candidates[] 는 kapt_code 를 함께 준다. 반경만 넓히면 이름 완전일치 후보가 그 안에 나타난다.
1. enrich_complex_info(complexes, radius_m=500) ← 기본 250 으로는 후보에조차 안 뜬다
2. status != "matched" 인 항목의 candidates[] 를 훑는다
3. 정규화한 이름이 입력과 같으면(공백·'아파트' 꼬리 제거 후 일치) 그 kapt_code 를 채택
4. get_complex_info(kapt_code, detail=true) 로 값을 직접 가져와 랭킹에 넣는다
5. 화면에 "좌표 불일치로 자동 매칭 실패 → 이름 일치로 복구" 를 명시한다
실측: 위례2차아이파크아파트 는 radius_m=500 에서 candidates[0] 으로 나오고(A10027553, 261m, score 0.635),
get_complex_info 는 parking_per_unit 1.76 을 정상 반환한다.
- 🚨 이름이 "사실상 같다"의 기준을 느슨하게 잡지 말 것. 공백·
아파트 꼬리 제거 후 완전일치만 복구한다.
위례아이파크 와 위례2차아이파크 는 다른 단지다. 애매하면 복구하지 말고 제외 사유를 적는다.
- 근본 해결은 MCP 몫이다(점수에서 반경 정규화 제거 · 이름 완전일치 가산 · 사유 코드 정정) — 발견사항 30번.
고쳐지면 이 복구 절차는 불필요해진다. 그때까지는 스킬이 직접 메운다.
MCP 도구 네임스페이스와 출처
ipzitalk MCP 도구의 네임스페이스는 실행 환경(Codex, Claude Code, Hermes, claude.ai 커넥터 등)에 따라 다르다.
이 문서에 적힌 도구 이름(search_announcement_info, get_geocode, get_map_embed_url 등)은 접두사 없는 기본 도구명(base tool name) 이다.
- 먼저 연결된 도구 목록에서 같은 기본 도구명을 찾는다.
- 그중
ipzitalk-remote 플러그인의 ipzitalk 서버 provenance가 확인되는 도구만 우선 사용한다. Codex에서는 실제 도구 호출 이벤트의 server: ipzitalk과 기본 도구명을 기준으로 확인한다.
presale-mcp 또는 다른 로컬 MCP provenance의 동명 도구는 Remote Skill의 대체 수단으로 사용하지 않는다.
- provenance를 확인할 수 없거나 같은 기본 도구명이 여러 서버에 있어 모호하면 임의 선택하지 말고 중단하여 필요한 Remote 도구명을 안내한다.
클라이언트가 연결 도구 목록에 plugin/server provenance를 구조적으로 제공하지 않을 때만 다음 명시적 fallback을 사용한다.
mcp__plugin_ipzitalk-remote_ipzitalk__<도구명>
mcp__ipzitalk_mcp__<도구명>
mcp__ipzitalk__<도구명>
mcp__claude_ai_ipzitalk__<도구명>
fallback으로도 Remote 출처를 유일하게 확인할 수 없으면 값을 추정하지 말고, 사용자에게 ipzitalk Remote MCP 연결 상태를 확인하도록 안내한 뒤 중단한다.