instruction.md
# 국민연금 가입 사업장 내역 조회
## What this skill does
공공데이터포털의 **국민연금공단_국민연금 가입 사업장 내역 서비스**(data.go.kr 3046071, V2)를 `k-skill-proxy` 경유로 호출해 다음을 조회한다.
- 가입 사업장 후보: 사업장명 + 사업자번호 앞 6자리로 매칭된 사업장 목록 (자료생성년월별 중복은 사업장당 최신 월로 정리)
- 단일 사업장이 특정되면 상세: 가입자수(`jnngpCnt`), 당월 고지금액(`crrmmNtcAmt`), 신규취득/상실 인원
- 월별 가입 현황 시계열
사업자등록번호는 **앞 6자리만 공개**(뒷자리 마스킹)되므로 사업장명이 필수이며, 후보가 여럿이면 특정하지 않고 목록 그대로 돌려준다.
## Design principles
- 점수·등급·"위험" 같은 해석 라벨을 만들지 않는다. upstream이 돌려준 사실만 담는다.
- 후보가 여럿이면 동일성을 단정하지 않는다.
## When to use
- "○○ 회사 직원 규모가 얼마나 돼? 국민연금 가입자수로 보자"
- "이 사업장 당월 국민연금 고지금액이 얼마야?"
- "최근 인원이 늘었는지 줄었는지 월별로 보자"
## Prerequisites
- 인터넷 연결, `python3`
- `scripts/national_pension_workplace.py` helper
- hosted/self-host `k-skill-proxy`의 `/v1/national-pension/workplace` route 접근 가능
## Credential requirements
- 사용자 측 필수 시크릿 없음.
- `KSKILL_PROXY_BASE_URL` — self-host 프록시를 쓸 때만 설정. 비우면 hosted `https://k-skill-proxy.nomadamas.org` 사용.
- `DATA_GO_KR_API_KEY` 는 프록시 운영 서버 환경에만 둔다. 공공데이터포털에서 `국민연금공단_국민연금 가입 사업장 내역` 활용신청이 되어 있어야 한다.
## Inputs
- `--name`: 사업장명(상호) — 필수
- `--b-no`: 사업자등록번호(하이픈 허용). 앞 6자리만 prefix 필터로 쓰인다.
## Privacy boundary
- 국민연금 데이터는 사업자번호 앞 6자리만 공개되므로, 6자리 일치 + 상호 유사 후보를 나열할 뿐 사업장 동일성을 단정하지 않는다.
- 공개 범위는 법인·근로자 일정 규모 이상 사업장 위주이며, 소규모/개인 사업장은 미공개일 수 있다.
## 법적 안전 운영 기준
1. **공단이 공식 공개한 사업장 정보만 조회한다.** 가입자수·고지금액은 국민연금공단이 공공데이터포털로 공개한 사업장 단위 정보이며, 개별 근로자의 개인정보는 조회·추정하지 않는다.
2. **조회 목적을 명시한다.** 거래처 실사, 입사 검토, 시장 조사 같은 정당한 확인 목적의 개별 조회로 한정하고, 특정 사업장·업종의 대량·정기 수집에는 목적과 법적 근거를 먼저 확인한다. 근거가 불명확하면 진행하지 않는다.
3. **결과 저장은 최소화한다.** 조회 결과는 요청 시점 참고용으로 한정하고 장기 저장·재배포하지 않는다.
## CLI examples
```bash
npx -y @nomadamas/k-skill@0 exec national-pension-workplace scripts/national_pension_workplace.py -- \
--name "삼성전자(주)" --b-no 124-81-00998
```
## Failure modes
- `400 bad_request`: 사업장명을 주지 않음.
- `503 upstream_not_configured`: 프록시 서버에 `DATA_GO_KR_API_KEY` 없음.
- `502 upstream_forbidden`: 프록시 키가 3046071에 활용신청되지 않음.
- 후보 다수: `selected_candidate`가 `null` — 사용자가 후보 목록에서 특정한다.
## Official surfaces
- 공공데이터포털: <https://www.data.go.kr/data/3046071/openapi.do>
- upstream: `https://apis.data.go.kr/B552015/NpsBplcInfoInqireServiceV2` (요청 파라미터 camelCase)
- 프록시 route: `GET /v1/national-pension/workplace`
scripts/national_pension_workplace.py
"""National Pension Service workplace-coverage lookup via k-skill-proxy.
The proxy holds DATA_GO_KR_API_KEY server-side; this helper only builds the
query and reads the structured response. No user secret is required.
"""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
PROXY_BASE_URL_ENV_VAR = "KSKILL_PROXY_BASE_URL"
DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
PROXY_DOWN_MSG = "설정된 k-skill-proxy 서버가 응답하지 않습니다. 잠시 후 재시도하거나 운영자에게 문의하세요."
PROXY_KEY_NOT_CONFIGURED_MSG = "k-skill-proxy에 필요한 API 키가 설정되어 있지 않습니다. 운영자에게 문의하세요."
ROUTE = "/v1/national-pension/workplace"
class ApiError(RuntimeError):
def __init__(self, message: str, *, status_code: int | None = None):
super().__init__(message)
self.status_code = status_code
def _text_or_none(value: Any) -> str | None:
if value is None:
return None
text = str(value).strip()
return text or None
def resolve_proxy_base_url(explicit: str | None = None, env: dict[str, str] | None = None) -> str:
env = os.environ if env is None else env
candidate = _text_or_none(explicit or env.get(PROXY_BASE_URL_ENV_VAR))
if candidate and candidate.casefold() in {"off", "false", "0", "disable", "disabled", "none"}:
raise ValueError("KSKILL_PROXY_BASE_URL 가 비활성화되어 있습니다.")
if candidate and candidate != "replace-me":
return candidate.rstrip("/")
return DEFAULT_PROXY_BASE_URL
def read_json_response(request: urllib.request.Request) -> dict[str, Any]:
try:
with urllib.request.urlopen(request, timeout=30) as response:
try:
payload = json.loads(response.read().decode("utf-8"))
except json.JSONDecodeError as error:
raise ApiError("national-pension proxy returned invalid JSON.") from error
if not isinstance(payload, dict):
raise ApiError("national-pension proxy returned a non-object JSON payload.")
return payload
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
try:
payload = json.loads(body)
except json.JSONDecodeError:
payload = None
if error.code == 503 and isinstance(payload, dict) and payload.get("error") == "upstream_not_configured":
raise ApiError(PROXY_KEY_NOT_CONFIGURED_MSG, status_code=error.code) from error
if isinstance(payload, dict) and payload.get("message"):
raise ApiError(str(payload["message"]), status_code=error.code) from error
raise ApiError(f"national-pension proxy request failed with HTTP {error.code}", status_code=error.code) from error
except urllib.error.URLError as error:
raise ApiError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
def query_workplace(name: str, b_no: str | None = None, *, base_url: str | None = None,
read_json: Any = read_json_response) -> dict[str, Any]:
name = _text_or_none(name)
if not name:
raise ValueError("사업장명(상호)을 입력하세요. 국민연금 API는 사업자번호 앞 6자리만 공개해 상호가 필수입니다.")
params = {"name": name}
if _text_or_none(b_no):
digits = re.sub(r"\D", "", str(b_no))
if not re.fullmatch(r"\d{10}", digits):
raise ValueError("사업자등록번호는 숫자 10자리여야 합니다 (하이픈 허용).")
params["b_no"] = digits
url = f"{resolve_proxy_base_url(base_url)}{ROUTE}?{urllib.parse.urlencode(params)}"
request = urllib.request.Request(url, headers={
"Accept": "application/json",
"User-Agent": "k-skill-national-pension-workplace/1.0",
}, method="GET")
return read_json(request)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="국민연금 가입 사업장 내역 조회 (k-skill-proxy 경유)")
parser.add_argument("--name", required=True, help="사업장명(상호) — 필수")
parser.add_argument("--b-no", help="사업자등록번호(앞 6자리만 prefix 필터로 사용)")
parser.add_argument("--proxy-base-url")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
result = query_workplace(args.name, args.b_no, base_url=args.proxy_base_url)
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
except (ValueError, ApiError) as error:
print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())
skill.json
{
"name": "national-pension-workplace",
"description": "국민연금공단 국민연금 가입 사업장 내역을 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 사업장명으로 가입자수·당월 고지금액·월별 취득/상실 추이를 확인해 그 회사의 직원 규모와 변화를 본다.",
"profiles": [
"proxy",
"lookup"
],
"frontmatter": "name: national-pension-workplace\ndescription: 국민연금공단 국민연금 가입 사업장 내역을 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 사업장명으로 가입자수·당월 고지금액·월별 취득/상실 추이를 확인해 그 회사의 직원 규모와 변화를 본다.\nlicense: MIT\nmetadata:\n category: business\n locale: ko-KR\n phase: v1"
}
SKILL.md
---
name: national-pension-workplace
description: 국민연금공단 국민연금 가입 사업장 내역을 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 사업장명으로 가입자수·당월 고지금액·월별 취득/상실 추이를 확인해 그 회사의 직원 규모와 변화를 본다.
license: MIT
metadata:
category: business
locale: ko-KR
phase: v1
---
# national-pension-workplace
<!-- k-skill:cli-stub — generated by scripts/generate-skill-stubs.js; edit skill.json / instruction.md instead -->
## Get the full instructions (required first step)
Run this and follow its output as the primary instructions for this skill:
```bash
npx -y @nomadamas/k-skill@0 instruct national-pension-workplace
```
The CLI detects the current runtime (Dolshoi vault/CloakBrowser vs generic) and prints only the applicable instructions, always up to date. Helper files bundled with the CLI are listed by:
```bash
npx -y @nomadamas/k-skill@0 files national-pension-workplace
```
Keep the CLI and every coding-agent skill install current (including Vercel Agent Skills) with:
```bash
npx -y @nomadamas/k-skill@0 update
```
If `npx` is unavailable, install Node.js 18+ or follow https://github.com/NomaDamas/k-skill#readme, or read the source instructions at https://github.com/NomaDamas/k-skill/blob/main/national-pension-workplace/instruction.md.
## Hard rules even without the CLI
- Never execute payment, message/email delivery, final submission, cancellation, or public posting without the user's explicit approval immediately beforehand.
- Never ask for, print, or store plaintext credentials in chat, files, or shell arguments.
- Never bypass legal, physical-presence, CAPTCHA, identity-proofing, or electronic-signature boundaries.