| name | ipzitalk-presale-report |
| description | 지역 또는 단지 주변의 청약홈 분양공고를 한 장으로 정리한다. 최근 6개월 신규 공고를 먼저 보여주고, 이어서 등록된 전량을 KPI·지도·입주 타임라인·공급유형/전용면적 도넛·연도별 물량·공고 목록으로 종합한다. "OO시 분양 어때", "분양 현황", "요즘 분양 뭐 있어", "이번달 신규 청약", "분양 리포트", "이 지역 분양물량", "분양 브리핑", "분양 대시보드" 등의 표현이 있으면 이 스킬을 사용한다.
|
| version | 1.1.4 |
| license | proprietary |
분양 리포트 (ipzitalk-presale-report)
지역 하나를 입력받아 최근 신규와 전체 물량을 한 화면에 낸다.
※ 청약홈에 등록된 공고를 정리한 것이며 실시간 청약 일정이 아니다. 경쟁률·접수·발표 일정은 담지 않는다.
설계 원칙 🚨
전량 1콜로 두 화면을 만든다. "최근 6개월"은 전량의 부분집합이므로
서버에 date_from을 넘길 필요가 없다. 전량을 받아 공고일로 거른다.
0건을 실패가 아니라 정보로 만든다. 최근 신규가 없으면
"최근 신규 공고 없음 · 마지막 공고 2025-08-19" 라고 쓰고, 하단 대시보드는 그대로 낸다.
(송파구 실측: 전량 5건, 최근 6개월 0건. 최신 공고가 11개월 전이다.)
입력
| 파라미터 | 필수 | 기본 | 설명 |
|---|
query(자유형) | ✅ | - | 지역명·주소·단지명·법정동코드 중 1개 |
recent_months | ✕ | 6 | 상단 "신규" 블록의 기간(공고일 기준) |
radius_km | ✕ | 3 | 주소·단지명 입력일 때만. 지역명이면 시군구 전량 |
스코프는 입력이 정한다.
- 지역명/코드 → 시군구 전량
- 주소/단지명 → 좌표 + 반경 3km
⚠️ 지역 필터는 정확 일치다. "김포" → 0건, "김포시" → 18건.
raw 지역명을 필터에 그대로 넣지 말고 지역코드 정규화로 정확명을 만든 뒤 쓴다.
필수 도구
| 도구 | 용도 | 콜 |
|---|
get_region_code / get_geocode / get_address | 입력 해소 | 0~1 |
search_announcement_info(include_units=true, limit=100) | 전량 1콜 | 1 |
get_map_embed_url | 지도 | 1 |
| | 2~3 |
워크플로우
- 입력 해소 — 지역명/코드 → 지역코드 정규화로 정확명 확정. 주소·단지명 → 좌표.
- 전량 1콜 —
search_announcement_info(sigungu=정확명, include_units=true, limit=100).
has_more이면 offset으로 이어 받는다. 안 받으면 조용히 잘린다.
- 최근 블록 분리 — 받아온 목록에서
announcement_date >= 오늘-6개월만 고른다.
🚨 서버 날짜 필터를 쓰지 않는다. 아래 §감사장치 부재 참조.
- 좌표 보완 —
unlocated는 지번 지오코딩 → 단지명 주소검색.
- 집계 — KPI · 입주 타임라인(입주월 연도별) · 공급유형 도넛 · 전용면적 도넛 · 연도별 물량(공고일).
- 지도 + 렌더 — 최근 공고는 브랜드색, 나머지는 회색. 겹친 좌표는 마커를 합친다.
🚨 units 세대수 합 ≠ total_supply
공공분양은 주택형을 전부 공개하지 않는다. 버그가 아니라 원천 데이터 특성이다.
남양주시 32건 중 11건 불일치. 합계 차이 4,001세대. 불일치 11건은 전부 LH·공공분양.
남양주진접2 A-1 공공분양 total_supply 920 vs Σ(units) 263 (−657)
왕숙 아테라 공공분양 812 vs 182 (−630)
덕소역 라온프라이빗 (민간) 348 vs 348 (일치)
- 🚨 도넛(공급유형·전용면적)은
주택형이 공개된 세대 기준임을 화면에 명시한다.
남양주: 총 공급 10,589 중 공개분 6,588세대(62%).
- 🚨 KPI
총 공급세대와 도넛 합계가 다른 것이 정상이다. 억지로 맞추지 말 것.
맞추려 들면 없는 세대를 만들어낸다.
- 두 도넛의 분모는 서로 같아야 한다(공급유형 6,588 == 전용면적 6,588). 이건 검산 가능.
⚠️ 예전 regional-presale-dashboard는 "areaMix 합계 == 총 공급세대. 어긋나면 주택형을 빠뜨린 것"
이라고 적었는데 틀렸다. 민간 위주 지역(송파 5건)에서 우연히 맞았을 뿐이다.
🚨 감사장치 부재 — filters에 date_from이 안 실린다
search_announcement_info(sigungu="송파구") → matched_count 5
search_announcement_info(sigungu="송파구", date_from="2026-01-10") → matched_count 0
두 응답의 summary.filters 는 {"sigungu":"송파구"} 로 글자 그대로 같다.
서버 필터는 정상 동작하지만 적용 여부가 응답에 드러나지 않는다.
필터를 빠뜨려도 응답이 항의하지 않아, 예전 브리핑이 조용히 틀렸다.
이 스킬은 서버 날짜 필터를 아예 쓰지 않으므로 이 함정에 걸리지 않는다.
🚨 좌표 결측과 중복 좌표
남양주 32건: exact 21 · dong_fallback 6 · unlocated 5.
dong_fallback은 여러 공고가 같은 좌표를 갖는다. 왕숙 4건이 한 점에 겹친다.
→ 마커를 합치고 (같은 지점 N건)으로 표기한다. 따로 찍으면 서로를 가린다.
unlocated는 반경 판정이 불가능하다. 지도에서 빼고 별도 회색 블록으로 분리,
"위치가 확인되지 않음"이라고 적는다. 반경 스코프면 "반경 확인 불가".
표본 규칙
| 공고 수 | 처리 |
|---|
| ≥ 20 | 도넛·타임라인 정상 |
| 5 ~ 19 | 구성 비율만. 추세 언급 금지 |
| < 5 | 표만. 도넛·타임라인 생략 |
- 등급(★)을 매기지 않는다. 지역 우열 단정 금지.
- 84㎡ 분양가는 주택형별 최고분양가 기준이다. 평균가·최저가와 다르다.
공공분양과 민간이 섞이면 편차가 크다 → 범위로 제시한다.
화면 문구는 일반인 언어로
| 쓰지 않는 말 | 대신 |
|---|
| 적재분 · REF_DB | 청약홈에 등록된 |
| 미측위 · unlocated | 위치가 확인되지 않음 |
| 특별공급 비중 | 특별공급 비율 |
| 상한제 | 분양가 상한제 적용 |
| 표본 부족 | 자료가 적어 참고용 |
- 세대수는
1,615세대처럼 단위를 붙인다. 분양가는 7.09억처럼 억 단위로.
- 안내문은 결과만. "왜 주택형 합이 다른가"의 내부 사정은 화면에 쓰지 않는다.
도넛 캡션에 "주택형이 공개된 6,588세대 기준" 한 줄이면 된다.
- 🚨 줄인다고 사실을 감추면 안 된다. "총 공급세대와 다르다"는 판단을 바꾸는 정보라 반드시 남긴다.
섹션마다 출처를 작게 단다
| 블록 | 출처 |
|---|
| 최근 신규 · KPI · 도넛 · 타임라인 · 공고 목록 | 청약홈 |
| 지도 | 네이버 지도 · 청약홈 |
실행 감사 sidecar 🚨
- 첫 조회 전에
out/ipzitalk-presale-report/audit.json을 만들고 skillBaseDirectory, shellUsed, webUsed, generatedFiles를 기록한다.
- 각 MCP 호출 직후
baseToolName, 입력 요약, resultCount, truncated, provenance, 조건부 호출 reason을 누적한다. 페이지네이션·좌표 보완·지도 호출도 실제 행 수에 포함한다.
- 감사 누락을 복구하려고 MCP를 재호출하지 않는다. 기존 반환으로 복구할 수 없으면
auditIncomplete:true로 남기고 완료 처리하지 않는다.
- 최종 응답은
audit.json에서 도구별 호출 횟수, Remote provenance, Skill base directory, shell/web 사용 여부, 생성 파일을 계산해 보고한다.
출력 = 공통 템플릿
templates/result.html의 비실행 ipzi-data JSON 블록을 채운다(스킬 폴더 내부, self-contained).
마크업·CSS·렌더 JS는 고정이다. 실행마다 바뀌는 것은 ipzi-data 블록 하나뿐.
HTML 산출물 계약 🚨
result.json·audit.json은 내부 계약용 고정 이름으로 유지하고, 사용자 전달 HTML만 대상 기반 이름을 쓴다.
- 지역 스코프는 정규화한 시군구명으로
<지역>_분양리포트, 주소·단지 반경 스코프는 resolver가 확정한 단지명 또는 정규화 주소로 <단지명>_인근_분양리포트를 만든다. 예: 송파구_분양리포트.html, 헬리오시티_인근_분양리포트.html. 대상값이 없으면 이름을 지어내지 말고 ipzitalk-presale-report.html로 폴백한다.
- 최종 HTML 경로는
out/ipzitalk-presale-report/<대상>_분양리포트.html이다.
- 셸 사용이 허용된 환경에서는 스킬 기준
../../scripts/html_artifact_contract.mjs 검증기를 --file-name "<대상>_분양리포트"와 함께 사용한다. --skill-dir에는 이 스킬의 base directory, --data에는 완성한 JSON 파일, --output-root에는 작업공간의 out 디렉터리를 전달한다.
shell-free 또는 셸 금지 환경에서는 File Read/Write로 templates/result.html을 직접 읽고 ipzi-data JSON 블록만 교체한다. 교체 전후의 fixed template region(고정 영역: 데이터 블록 앞 prefix와 뒤 suffix)이 원본과 같은지 비교한다.
audit.json.generatedFiles에는 예시 이름이 아니라 실제 최종 파일명과 경로를 기록한다.
- 검증기가 통과하기 전에는 완료로 주장하지 않는다. File Read/Write나 고정 영역 비교를 수행할 수 없거나 금지된 도구를 사용했다면 완료 처리하지 말고 제약과 실제 사용 도구를 보고한다.
섹션 순서(고정)
- 히어로 + 칩(지역·기간)
- 최근 N개월 신규 — 공고 카드. 0건이면 "최근 신규 공고 없음 · 마지막 공고 YYYY-MM-DD"
- 전량 KPI 4칸 (공고 수 · 총 공급세대 · 84㎡ 최고분양가 범위 · 특별공급 비율)
- 지도
- (2단) 입주 타임라인 + 연도별 물량 표
- (2단) 공급유형 도넛 + 전용면적 도넛 — 같은 단에 나란히
- 공고 목록 표
- 출처·안내 푸터
공고 목록 표 사양
| 열 | 내용 |
|---|
| 공고명 | 굵게 + 신규 배지. 아랫줄에 작게 읍면동 · 공공/민간 · 상한제 |
| 공고일 | YYYY-MM-DD |
| 주택형 | units[].exclusive_area_sqm의 정수부만 중복 제거해 59·74·84㎡ |
| 최고 분양가 | 그 공고의 max_price_10k 최댓값을 억 단위로 |
| 공급세대 | total_supply |
| 입주 예정 | YYYY.MM |
| 원문 | detail_url을 공고문 링크로 |
- 🚨
detail_url을 반드시 건다. 우리가 가진 건 요약이고, 경쟁률·접수일정·평면은 원문에만 있다.
링크 없이 표만 주면 사용자가 다음 행동을 할 수 없다.
- 🚨 동적 링크는 https만 허용한다. 아니면 걸지 않는다.
target="_blank" rel="noopener noreferrer".
- 표는 열이 7개라 좁은 화면에서 넘친다 →
.tbl-wrap{overflow-x:auto} + table{min-width:760px}.
- 전량이 10건을 넘으면 최신 10건만 싣고 캡션에
32건 중 10건을 적는다.
필수 단서 · 금지 표현
- 필수: 청약홈 등록 기준(실시간 아님) · 최고분양가 기준 · 도넛은 주택형 공개분 기준 ·
위치 미확인 공고 수 · 경쟁률·접수·발표일정 미포함 · 조회일.
- 금지: "이번주 청약 마감/오픈" 확정 · 최고가를 평균처럼 · 지역 우열 단정 ·
도넛 합계를 총 공급세대에 억지로 맞추기 · 내부 테이블명 노출.
엣지 · 실패 처리
| 상황 | 처리 |
|---|
| 최근 N개월 신규 0건 | 정상. "최근 신규 없음 + 마지막 공고일" 카드. 하단 대시보드는 그대로 |
| 전량 0건 | "청약홈에 등록된 공고가 없습니다". 리포트 생성 중단 |
| 지역 해소 실패 | 후보 나열 후 선택. raw 지역명 그대로 넣지 말 것 |
has_more: true | offset으로 이어 받는다 |
좌표 겹침(dong_fallback) | 마커 합치고 (같은 지점 N건) |
unlocated 존재 | 지도에서 빼고 별도 회색 블록 |
| 공고 < 5건 | 도넛·타임라인 생략. 표만 |
검증된 사항 (남양주시 · 조회 2026-07-10)
운영 MCP 실호출. search_announcement_info(sigungu="남양주시", include_units=true, limit=100) 1콜.
- 전량 32건 · 총 공급 10,589세대 ·
has_more: false
- 최근 6개월 신규 3건 · 1,615세대
- 오남역 서희스타힐스 여의재 3단지 (2026-05-13 · 117세대 · 입주 2029-01 · 민간)
- 남양주왕숙2 A-3블록 공공분양 (2026-04-30 · 686세대 · 입주 2030-05 · 상한제)
- 왕숙 아테라 공공분양 (2026-04-30 · 812세대 · 입주 2029-02 · 상한제)
- 84㎡ 최고분양가(최근 3건): 6.94억 ~ 7.32억 (평당 2,708~2,883만원)
- 입주 피크: 2028년 4,424세대. 2027년은 0세대
- 공급유형(공개분 6,588세대): 일반 3,232 · 특별 3,356 → 특별공급 51%
- 전용면적(같은 분모): 60㎡ 이하 3,414(52%) · 60~85㎡ 2,791(42%) · 85㎡ 초과 383(6%)
- 좌표: exact 21 · dong_fallback 6 · unlocated 5
- 🚨 주택형 합 6,588 ≠ 총 공급 10,589. 불일치 11건 전부 LH·공공분양.
다른 지역 대조
| 시군구 | 전량 | 최근 6개월 | 마지막 공고 |
|---|
| 남양주시 | 32 | 3 | 2026-05-13 |
| 김포시 | 18 | 2 | 2026-05-22 |
| 송파구 | 5 | 0 | 2025-08-19 |
디자인 정본
스킬 폴더 밖 문서에 의존하지 않도록 규칙을 여기 인라인으로 둔다.
이 스킬이 대체하는 것 ⚠️
regional-presale-dashboard (지역 분양현황 대시보드) — 보류
presale-briefing (분양 브리핑) — 보류
합친 이유는 "결과가 같아서"가 아니다. 브리핑이 date_from을 제대로 걸면 송파구는 0건, 대시보드는 5건이다.
합친 진짜 이유는 ① 전량 1콜로 둘 다 만든다 ② 브리핑 단독은 대부분 지역에서 0건(빈 화면)
③ 사용자는 "최근 신규"와 "전체 물량"을 같이 보고 싶어한다.
ipzitalk-announcement-search(조건 검색·목록만)와는 여전히 별개다.
분석 목적 맞춤 요약
목적을 확보하는 방법
- 사용자가 처음부터 목적을 밝혔으면 다시 묻지 않고 사용자 문장을
goal.purpose에 그대로 보존한다.
- 목적이 없고 현재 클라이언트가 4개 선택지와 직접 입력(Other/기타)을 함께 지원하는 네이티브 사용자 입력 UI를 제공하면 그 UI를 정확히 한 번 사용한다.
4인가족 실거주 검토
투자 심의 회의 자료
분양 제안서용 자료
건너뛰기
- UI가 자동 제공하는
기타(직접 입력)으로 자유 입력도 허용한다.
- 네이티브 UI가 직접 입력을 지원하고 지원 가능한 수가 2~3개이면, 그 수만큼 위 목적 프리셋을 앞에서부터 선택지로 제시한다. 질문에는
건너뛰기를 기타(직접 입력)에 입력해도 된다고 알린다.
- 네이티브 사용자 입력 UI가 없거나 직접 입력을 지원하지 않으면 다음 질문만 출력하고 그 턴을 종료해 답을 기다린다:
원하는 분석 목적을 한 문장으로 알려주세요. (예: "4인가족 실거주 검토", "투자 심의 회의 자료", "분양 제안서용 자료") 건너뛰셔도 됩니다.
- 질문 단계에서는 목적 입력 UI 또는 위 텍스트 질문 외의 도구를 사용하지 않는다.
- 사용자가 목적 또는 명시적인 건너뛰기로 답변하기 전에는 MCP·웹·파일·셸 도구를 호출하지 않는다.
- 프리셋을 고르면 해당 문구를
goal.purpose에 그대로 저장하고, 직접 입력을 고르면 사용자가 입력한 원문을 그대로 저장한다.
건너뛰기를 고르거나 사용자가 건너뛰겠다고 답한 경우에만 goal:null로 두고 목적 섹션 없이 진행한다. 목적을 지어내지 않는다.
데이터 수집 후 맞춤 요약
purpose에는 사용자 문장을 그대로 보존한다.
- 데이터 조회·수집이 완료된 후에만
conclusions(결론)·evidence(근거)·cautions(주의사항)·nextActions(다음 행동 제안)를 작성한다. 조회 전에 문구나 결론을 미리 만들지 않는다.
goal은 사용자 표현을 그대로 보존한 purpose, conclusions(35), evidence(13), cautions(02), nextActions(13)로 구성한다.
conclusions는 실제 조회·판정 결과만 목적에 맞춰 요약하고, 각 결론에 실제 evidence를 최소 1개 연결한다.
evidence에는 이번 실행에서 확보한 필드·수치·비교 결과만 쓴다. 예시·검증값·모델 지식으로 빈 값을 채우지 않는다.
- 새 데이터나 없는 수치를 창작하지 않는다.
- 조회일과 각 공고의 입주월(
YYYY.MM)을 연월 단위로 비교해 과거·당월·미래를 판정한다.
- 입주월이 조회일과 같거나 이전이면
입주 예정, 앞으로 입주, 입주 대기 물량으로 쓰지 않는다. 이미 입주 시점 경과 또는 확인된 사실만 쓴다.
- 위 금지는 부정문에도 적용한다.
앞으로 입주할 신규 물량은 확인되지 않습니다처럼 금지 표현을 부정해 쓰지 말고, 반경 내 등록 공고 중 조회일 이후 입주월이 확인된 공고는 없습니다처럼 비교 기준과 확인 범위를 직접 쓴다.
- 최근 6개월 공고가 0건이어도
성숙 시장, 공급 소진이라고 단정하거나 추론하지 않는다. 확인된 반경·기간 안에 신규 등록 공고가 없다는 사실까지만 쓴다.
cautions는 실제로 확인된 데이터 누락·표본 한계·시점 차이·방법상 제약만 쓴다. 본문 경고를 약화하거나 새 위험을 지어내지 않는다.
nextActions는 실제 발견사항·누락·사용자 목적에서 이어지는 검토 행동만 제안한다. URL이나 원시 Skill ID 대신 한글 Skill 이름과 자연어 질의 예시를 쓰며, 단지 거래 후속 예시는 최근 실거래가 추이로 표현한다.
- 템플릿은 배열 상한을 잘라내고 빈 단계는 숨긴다.
goal:null이면 goal-box 전체를 숨긴다.
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 연결 상태를 확인하도록 안내한 뒤 중단한다.