instruction.md
# 정부지원 전수조사
## What this skill does
`k-skill-proxy`의 `/v1/government-support/survey`를 호출해 다음 공개 공고를 하나의 스키마로 조사한다.
- K-Startup Open API
- 기업마당
- 정보통신산업진흥원(NIPA)
- 한국콘텐츠진흥원(KOCCA)
- 중소기업기술개발사업 종합관리시스템(SMTECH)
이 스킬은 현재 모집 공고의 제목·분야·주관기관·접수기간·공식 URL을 수집하고, 사용자의 프로젝트 조건과 대조해 `즉시 지원 가능`, `요건 충족 시 가능`, `사업 변형 시 가능`, `부적합`으로 보수적으로 분류한다.
## Required workflow
1. 회사·팀·프로젝트의 업력, 소재지, 업종, 기술, 대표자 연령, 기업 형태, 매출·투자 단계, 원하는 지원 유형을 확인한다.
2. 먼저 5개 소스를 모두 조회한다. 사용자가 범위를 좁힌 경우에만 `--sources`를 제한한다.
3. 응답의 `complete`와 소스별 `ok`를 확인한다. 하나라도 실패하면 “전수조사 완료”라고 표현하지 말고 누락 소스를 명시한다.
4. 제목만으로 자격을 확정하지 않는다. 후보의 공식 상세 URL과 첨부 공고문에서 신청대상·제외조건·지역·업력·중복수혜 제한을 확인한다.
5. 결과마다 공식 출처 URL, 접수 마감일, 판정 근거, 추가 확인사항을 표시한다.
6. 실제 신청은 공식 사이트에서 진행한다. 최종 제출 직전에는 사용자 승인을 받는다.
## Command
```bash
npx -y @nomadamas/k-skill@0 exec government-support-survey scripts/run_survey.py -- \
--sources kstartup bizinfo nipa kocca smtech \
--keyword "AI 바우처" \
--max-pages 3
```
전체 공고를 넓게 조사할 때는 `--keyword`를 생략한다. 응답이 너무 클 때만 공고 유형별 키워드로 여러 번 나눠 조회한다.
## Output contract
- `complete`: 요청한 모든 소스가 정상 조사됐는지
- `sources`: 소스별 성공 여부, 조회 페이지, 수집 건수, 오류
- `items`: `source`, `id`, `title`, `field`, `org`, `apply_start`, `apply_end`, `reg_date`, `url`
- `attribution`: upstream 코드와 공식 데이터 출처, 재배포 범위
## Failure modes
- `complete=false`: 일부 소스 실패. 수집된 결과는 부분 결과이며 누락 소스를 수동 확인한다.
- `401/403` 또는 차단 신호: 우회하지 말고 공식 브라우저 화면으로 전환한다.
- `0 items parsed; site layout may have changed`: 파서 개편 가능성이므로 해당 소스를 성공 처리하지 않는다.
- K-Startup 키 미설정: 나머지 공개 포털 결과는 유지하되 K-Startup 누락을 명시한다.
- CAPTCHA·로그인·본인인증: 자동 우회하지 않는다.
## Legal and redistribution boundary
이 스킬의 조사 로직은 `djfksjd/ir-search`의 MIT 라이선스 구현을 참고해 재작성했으며 저작권·라이선스 고지는 `npx -y @nomadamas/k-skill@0 read government-support-survey references/NOTICE.md`에 보존한다. 정부 포털 공고 원문과 첨부는 포털별 이용조건이 다르므로 k-skill은 이를 미러링하거나 재배포하지 않고 구조화 메타데이터와 공식 링크만 제공한다.
references/NOTICE.md
# 출처, 라이선스, 재배포 범위
## 참고한 오픈소스
- 프로젝트: [djfksjd/ir-search](https://github.com/djfksjd/ir-search)
- 라이선스: [MIT License](https://github.com/djfksjd/ir-search/blob/main/LICENSE)
- 원 저작권 표시: `Copyright (c) 2026 ir-search contributors`
MIT 라이선스는 사용·수정·병합·배포·재라이선스를 허용하며, 소프트웨어의 복제물 또는 중요한 부분에 저작권 고지와 허가 고지를 포함해야 한다. 이 문서는 해당 attribution을 보존한다.
이번 통합은 upstream 파일을 그대로 복제하지 않고, 공개 포털 접근 경로·정규화 스키마·fail-closed 원칙을 참고해 k-skill-proxy의 Node.js 구현과 k-skill CLI 계약으로 재작성했다.
## 공식 데이터 출처
| 소스 | 공식 URL | k-skill 처리 |
|---|---|---|
| K-Startup | https://www.data.go.kr/data/15125364/openapi.do | 공공데이터포털 Open API 응답의 공고 메타데이터와 링크 |
| 기업마당 | https://www.bizinfo.go.kr/ | 공개 모집 목록의 메타데이터와 공식 상세 링크 |
| NIPA | https://www.nipa.kr/home/2-2 | 공개 사업공고 목록의 메타데이터와 공식 상세 링크 |
| KOCCA | https://www.kocca.kr/kocca/pims/list.do | 공개 지원공고 목록의 메타데이터와 공식 상세 링크 |
| SMTECH | https://www.smtech.go.kr/front/ifg/no/notice02_list.do | 공개 R&D 공고 목록의 메타데이터와 공식 상세 링크 |
## 재배포 판단
- **오픈소스 코드**: MIT 조건에 따라 attribution을 유지하면 재사용·수정·재배포 가능.
- **K-Startup Open API 데이터**: 데이터셋 상세 페이지의 이용조건을 따라 사용하며 출처를 표시한다.
- **각 기관의 공고 원문·첨부파일**: 개별 저작권·공공누리·제3자 권리가 다를 수 있으므로 일괄 재배포 가능하다고 간주하지 않는다.
- **k-skill-proxy 응답**: 제목, 기관, 날짜, 식별자, 공식 링크 같은 사실 메타데이터만 구조화한다. 공고 본문·첨부파일을 저장하거나 미러링하지 않는다.
공고 내용을 인용하거나 첨부파일을 재배포해야 하는 별도 기능은 각 자료에 표시된 공공누리 유형과 이용약관을 건별 확인한 뒤 구현해야 한다.
scripts/run_survey.py
#!/usr/bin/env python3
"""Government support survey helper. Proxy-first, stdlib only."""
from __future__ import annotations
import argparse
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
VALID_SOURCES = ("kstartup", "bizinfo", "nipa", "kocca", "smtech")
def build_url(args: argparse.Namespace) -> str:
base = (args.proxy_base_url or os.environ.get("KSKILL_PROXY_BASE_URL")
or DEFAULT_PROXY_BASE_URL).rstrip("/")
params = {
"sources": ",".join(args.sources),
"maxPages": str(args.max_pages),
"perPage": str(args.per_page),
}
if args.keyword:
params["keyword"] = args.keyword
return f"{base}/v1/government-support/survey?{urllib.parse.urlencode(params)}"
def parser() -> argparse.ArgumentParser:
result = argparse.ArgumentParser(
description="K-Startup·기업마당·NIPA·KOCCA·SMTECH 정부지원 공고 전수조사"
)
result.add_argument("--sources", nargs="+", choices=VALID_SOURCES, default=list(VALID_SOURCES))
result.add_argument("--keyword", default="")
result.add_argument("--max-pages", type=int, default=1)
result.add_argument("--per-page", type=int, default=100)
result.add_argument("--proxy-base-url")
result.add_argument("--timeout", type=float, default=30)
result.add_argument("--compact", action="store_true")
return result
def main(argv: list[str] | None = None) -> int:
args = parser().parse_args(argv)
if not 1 <= args.max_pages <= 10:
parser().error("--max-pages must be between 1 and 10")
if not 1 <= args.per_page <= 100:
parser().error("--per-page must be between 1 and 100")
request = urllib.request.Request(
build_url(args),
headers={"Accept": "application/json", "User-Agent": "k-skill-government-support-survey/1.0"},
)
try:
with urllib.request.urlopen(request, timeout=args.timeout) as response:
payload = json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
print(body or f"HTTP {error.code}", file=sys.stderr)
return 2
except (urllib.error.URLError, TimeoutError) as error:
print(f"k-skill-proxy request failed: {error}", file=sys.stderr)
return 2
json.dump(payload, sys.stdout, ensure_ascii=False, indent=None if args.compact else 2)
sys.stdout.write("\n")
return 0 if payload.get("complete") else 2
if __name__ == "__main__":
raise SystemExit(main())
skill.json
{
"name": "government-support-survey",
"description": "K-Startup·기업마당·NIPA·KOCCA·SMTECH의 공개 정부지원 공고를 k-skill-proxy로 전수조사하고 프로젝트 적합성을 보수적으로 분류한다. 사용자가 정부지원사업, 창업지원, 사업화·R&D·바우처·입주공간 공고를 한 번에 찾아달라고 할 때 사용한다.",
"profiles": [
"proxy",
"browser",
"action:submission"
],
"frontmatter": "name: government-support-survey\ndescription: K-Startup·기업마당·NIPA·KOCCA·SMTECH의 공개 정부지원 공고를 k-skill-proxy로 전수조사하고 프로젝트 적합성을 보수적으로 분류한다. 사용자가 정부지원사업, 창업지원, 사업화·R&D·바우처·입주공간 공고를 한 번에 찾아달라고 할 때 사용한다."
}
SKILL.md
---
name: government-support-survey
description: K-Startup·기업마당·NIPA·KOCCA·SMTECH의 공개 정부지원 공고를 k-skill-proxy로 전수조사하고 프로젝트 적합성을 보수적으로 분류한다. 사용자가 정부지원사업, 창업지원, 사업화·R&D·바우처·입주공간 공고를 한 번에 찾아달라고 할 때 사용한다.
---
# government-support-survey
<!-- 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 government-support-survey
```
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 government-support-survey
```
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/government-support-survey/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.
tests/test_run_survey.py
import argparse
import os
import sys
import unittest
SCRIPT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "scripts")
sys.path.insert(0, SCRIPT_DIR)
import run_survey # noqa: E402
class SurveyUrlTests(unittest.TestCase):
def test_build_url_uses_proxy_and_encodes_query(self):
args = argparse.Namespace(
sources=["kstartup", "bizinfo"],
keyword="AI 바우처",
max_pages=2,
per_page=50,
proxy_base_url="https://proxy.example/",
)
self.assertEqual(
run_survey.build_url(args),
"https://proxy.example/v1/government-support/survey?"
"sources=kstartup%2Cbizinfo&maxPages=2&perPage=50&keyword=AI+%EB%B0%94%EC%9A%B0%EC%B2%98",
)
if __name__ == "__main__":
unittest.main()