mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
chore: remove startup-support skill
This commit is contained in:
parent
cc64d66d56
commit
46f44ed724
13 changed files with 2 additions and 998 deletions
|
|
@ -93,7 +93,6 @@
|
|||
"./seoul-subway-arrival",
|
||||
"./sh-notice-search",
|
||||
"./srt-booking",
|
||||
"./startup-support",
|
||||
"./subway-lost-property",
|
||||
"./ticket-availability",
|
||||
"./toss-securities",
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
|
|||
| ~~근처 블루리본 맛집~~ ⚠️ 지원 중단 | ~~`blue-ribbon-nearby`~~ | ~~현재 위치 기준 근처 블루리본 선정 맛집 조회~~ | ~~불필요~~ | ~~[근처 블루리본 맛집 가이드](docs/features/blue-ribbon-nearby.md)~~ |
|
||||
| 근처 술집 조회 | `kakao-bar-nearby` | 현재 위치 기준 영업 상태·메뉴·좌석·전화번호가 포함된 근처 술집 조회 | 불필요 | [근처 술집 조회 가이드](docs/features/kakao-bar-nearby.md) |
|
||||
| 우편번호 검색 | `zipcode-search` | 주소 키워드로 우편번호 + 공식 영문주소 조회 | 불필요 | [우편번호 검색 가이드](docs/features/zipcode-search.md) |
|
||||
| 창업 지원사업 조회 | `startup-support` | 정부·지자체 스타트업 지원사업 목록 조회 (상세 조회는 원본 공고 링크로 확인) | 불필요 | [창업 지원사업 조회 가이드](docs/features/startup-support.md) |
|
||||
| 다이소 상품 조회 | `daiso-product-search` | 다이소 매장별 상품 픽업 가능 여부 확인 (정확한 매장별 재고 수량은 다이소몰 보안 정책으로 2026-05-05 부터 차단됨) | 불필요 | [다이소 상품 조회 가이드](docs/features/daiso-product-search.md) |
|
||||
| 강남언니 병원 조회 | `gangnamunni-clinic-search` | 강남언니 공개 검색 페이지에서 성형외과·피부과 병원 후보, 평점, 리뷰 수, 지원 언어, 공개 링크 조회 | 불필요 | [강남언니 병원 조회 가이드](docs/features/gangnamunni-clinic-search.md) |
|
||||
| 마켓컬리 상품 조회 | `market-kurly-search` | 마켓컬리 상품 검색, 현재 가격, 할인 여부, 품절 여부 조회 | 불필요 | [마켓컬리 상품 조회 가이드](docs/features/market-kurly-search.md) |
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
# startup-support
|
||||
|
||||
`startup-support` helps agents answer Korean startup support-program questions by searching K-Startup announcement data through `k-skill-proxy`.
|
||||
|
||||
Use it for questions such as:
|
||||
|
||||
- "서울 청년 창업 지원사업 찾아줘"
|
||||
- "모집 중인 사업화 지원 공고 알려줘"
|
||||
- "이번 달 확인할 K-Startup 공고를 요약해줘"
|
||||
|
||||
Do not use it for application submission, legal eligibility decisions, payment automation, or grant amount calculation. The final source of truth is always the official announcement URL returned in each result.
|
||||
|
||||
## Data Flow
|
||||
|
||||
The helper calls:
|
||||
|
||||
```text
|
||||
GET /v1/kstartup/announcements
|
||||
```
|
||||
|
||||
It maps user-friendly terms onto the K-Startup query fields:
|
||||
|
||||
- `region` -> `supt_regin`
|
||||
- `keyword` -> `biz_pbanc_nm`
|
||||
- `support_type` -> `supt_biz_clsfc`
|
||||
- `deadline_only` -> `rcrt_prgs_yn=Y`
|
||||
|
||||
The hosted proxy injects the data.go.kr API key. A user running the helper does not need `DATA_GO_KR_API_KEY`.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
python3 startup-support/scripts/startup_support.py \
|
||||
--region 서울특별시 \
|
||||
--keyword 청년 \
|
||||
--deadline-only \
|
||||
--per-page 5 \
|
||||
--text
|
||||
```
|
||||
|
||||
Use dry-run when reviewing request construction without network access:
|
||||
|
||||
```bash
|
||||
python3 startup-support/scripts/startup_support.py \
|
||||
--region 서울특별시 \
|
||||
--keyword 청년 \
|
||||
--deadline-only \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
The helper normalizes K-Startup announcement rows into:
|
||||
|
||||
```json
|
||||
{
|
||||
"programs": [
|
||||
{
|
||||
"id": "A1",
|
||||
"title": "서울 청년 창업 지원",
|
||||
"organization": "창업진흥원",
|
||||
"region": "서울",
|
||||
"support_type": "사업화",
|
||||
"amount": "공식 공고 확인",
|
||||
"deadline": "2026-06-30",
|
||||
"target": "예비창업자",
|
||||
"contact": "",
|
||||
"url": "https://www.k-startup.go.kr/...",
|
||||
"source": "K-Startup",
|
||||
"last_updated": "2026-06-01"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
python3 -m py_compile startup-support/scripts/startup_support.py startup-support/scripts/test_startup_support.py
|
||||
PYTHONPATH=startup-support/scripts python3 -m unittest discover -s startup-support/scripts -p 'test_startup_support.py'
|
||||
python3 startup-support/scripts/startup_support.py --region 서울특별시 --keyword 청년 --deadline-only --dry-run
|
||||
```
|
||||
|
||||
The root `npm run ci` also compiles and runs this helper's tests.
|
||||
|
||||
## Failure Modes
|
||||
|
||||
- `400 bad_request`: invalid query parameter rejected by `k-skill-proxy`.
|
||||
- `503 upstream_not_configured`: proxy lacks a configured `DATA_GO_KR_API_KEY`.
|
||||
- `502 upstream_error` or `upstream_invalid_response`: data.go.kr returned an error, non-JSON body, or invalid payload.
|
||||
- Empty `programs`: no matching announcements in the selected page. Broaden filters or check additional pages.
|
||||
|
|
@ -420,27 +420,3 @@ node scripts/korean_character_count.js --text $'첫 줄\n둘째 줄🙂' --profi
|
|||
|
||||
- [공통 설정 가이드](setup.md)
|
||||
- [보안/시크릿 정책](security-and-secrets.md)
|
||||
|
||||
|
||||
|
||||
### startup-support
|
||||
|
||||
startup-support 스킬은 다음과 같은 환경이 필요합니다:
|
||||
|
||||
#### 환경 변수
|
||||
기본 사용자는 별도 API 키가 필요 없습니다. `DATA_GO_KR_API_KEY` 는 hosted/self-host `k-skill-proxy` 운영자가 서버에 설정하는 값입니다.
|
||||
|
||||
#### 의존성
|
||||
- Python 3.7+
|
||||
- requests 라이브러리
|
||||
- datetime 라이브러리
|
||||
|
||||
#### 설치
|
||||
```bash
|
||||
# 스킬 설치
|
||||
cd startup-support
|
||||
pip install requests
|
||||
|
||||
# 테스트 실행
|
||||
python3 scripts/test_startup_support.py
|
||||
```
|
||||
|
|
|
|||
|
|
@ -83,17 +83,3 @@ KSKILL_PROXY_BASE_URL=
|
|||
`LAW_OC` 는 `korean-law-mcp` 가 법제처 Open API 를 호출할 때 쓰는 표준 변수명이다. 이 값은 로컬 CLI/로컬 MCP server 경로에서만 사용자 쪽에 필요하고, upstream remote MCP endpoint 예시는 사용자 `LAW_OC` 없이 `url`만 등록한다. `DATA_GO_KR_API_KEY` 는 프록시 운영자 문맥에서만 서버에 넣는다. 부동산 실거래가 조회는 기본 hosted proxy(`k-skill-proxy.nomadamas.org`)를 경유하므로 사용자 쪽 키가 불필요하다. 생활쓰레기 배출정보 조회는 `k-skill-proxy`의 `/v1/household-waste/info` 라우트를 거쳐 `serviceKey`(`DATA_GO_KR_API_KEY`)를 proxy 서버에서 주입하므로 사용자 쪽 키가 불필요하다. 의약품 안전 체크도 `k-skill-proxy`의 `/v1/mfds/drug-safety/lookup` 라우트를 거쳐 `DATA_GO_KR_API_KEY` 를 proxy 서버에서만 주입하므로 사용자 쪽 키가 불필요하다. 식품 안전 체크는 `k-skill-proxy`의 `/v1/mfds/food-safety/search` 라우트를 거쳐 `DATA_GO_KR_API_KEY` 및 선택적 `FOODSAFETYKOREA_API_KEY` 를 proxy 서버에서만 주입하므로 사용자 쪽 키가 불필요하다. 한국 주식 정보 조회도 기본 hosted proxy를 경유하므로 사용자 쪽 `KRX_API_KEY` 가 불필요하다. `KRX_API_KEY` 는 self-host proxy 운영자 문맥에서만 서버에 넣는다. KOSIS 일반 조회도 기본 hosted proxy를 경유하므로 사용자 쪽 KOSIS 키가 불필요하다. `KOSIS_API_KEY` 또는 `KSKILL_KOSIS_API_KEY` 는 self-host proxy 운영자, direct 호출, 또는 bigdata 호출 문맥에서만 쓴다. Kakao Local geocoding도 기본 hosted proxy를 경유하므로 사용자 쪽 `KAKAO_REST_API_KEY` 가 불필요하다. `KAKAO_REST_API_KEY` 는 self-host proxy 운영자 문맥에서만 서버에 넣는다. 근처 가장 싼 주유소 찾기는 기본 hosted proxy를 경유하므로 사용자 쪽 `OPINET_API_KEY` 가 불필요하다. `OPINET_API_KEY` 는 프록시 운영자 문맥에서만 서버에 넣는다. 창업진흥원 K-Startup 조회도 `k-skill-proxy`의 `/v1/kstartup/*` 라우트를 거쳐 `ServiceKey`(`DATA_GO_KR_API_KEY`)를 proxy 서버에서만 주입하므로 사용자 쪽 키가 불필요하다. `KSKILL_KSTARTUP_API_KEY` 는 `--direct` 호출 문맥에서만 사용자 쪽에 둔다. `KIPRIS_PLUS_API_KEY` 는 한국 특허 정보 검색 helper가 KIPRIS Plus Open API에 보낼 `ServiceKey` 값을 담는 표준 변수명이다. 공공데이터포털에서 복사한 percent-encoded key도 helper가 한 번 정규화한 뒤 요청한다. public 공유용 Cloudflare Tunnel/Auth0/operator secret은 사용자 기본 secrets 파일에 넣지 않는다. 프록시 운영자 문맥에서는 upstream 환경변수 `SEOUL_OPEN_API_KEY`, `KMA_OPEN_API_KEY`, `AIR_KOREA_OPEN_API_KEY`, `HRFCO_OPEN_API_KEY`, `OPINET_API_KEY`, `DATA_GO_KR_API_KEY`, `FOODSAFETYKOREA_API_KEY`, `KRX_API_KEY`, `KOSIS_API_KEY`, `KAKAO_REST_API_KEY` 를 사용할 수 있다. 다만 일반 사용자/client 쪽 기본 secrets 파일에는 넣지 않는다. `KSKILL_PROXY_BASE_URL` 은 별도 self-host proxy를 쓸 때만 넣는다. 서울 지하철, 서울 실시간 혼잡도, 서울 따릉이, 한국 날씨, 미세먼지, 한강 수위, 주유소 가격, 한국 주식 정보 조회, KOSIS 일반 조회, Kakao Local geocoding, 의약품 안전 체크, 식품 안전 체크는 이 값이 없거나 비어 있으면 기본 hosted proxy(`k-skill-proxy.nomadamas.org`)를 사용한다.
|
||||
|
||||
이 레포의 credential-bearing skill은 전부 이 정책을 전제로 작성한다. 자세한 공통 설치 절차는 [공통 설정 가이드](setup.md)를 본다.
|
||||
|
||||
|
||||
|
||||
### startup-support
|
||||
|
||||
#### API 키 관리
|
||||
- 기본 사용자는 `DATA_GO_KR_API_KEY` 를 설정하지 않습니다.
|
||||
- `DATA_GO_KR_API_KEY` 는 hosted/self-host `k-skill-proxy` 운영자가 서버에 설정하는 upstream 키입니다.
|
||||
- 로컬에서 직접 공공데이터포털 API를 호출하는 실험 경로에서만 사용자 환경에 임시로 둘 수 있습니다.
|
||||
|
||||
#### 데이터 보안
|
||||
- **데이터 소스**: 공공기관 공식 API만 사용
|
||||
- **개인정보**: 개인정보는 처리하지 않음
|
||||
- **접근 제어**: API 키를 통한 접근 제어
|
||||
|
|
|
|||
|
|
@ -210,19 +210,6 @@
|
|||
- 도서관 정보나루 도서 소장 도서관 endpoint: https://data4library.kr/api/libSrchByBook
|
||||
- 도서관 정보나루 도서관별 도서 소장여부 endpoint: https://data4library.kr/api/bookExist
|
||||
|
||||
## startup-support
|
||||
|
||||
### 공공데이터포털 (data.go.kr)
|
||||
- **기관**: 창업진흥원 (K-Startup)
|
||||
- **서비스명**: K-Startup 조회서비스
|
||||
- **데이터셋 페이지**: https://www.data.go.kr/data/15125364/openapi.do
|
||||
- **Open API base URL**: https://apis.data.go.kr/B552735/kisedKstartupService01
|
||||
- **인증**: API 키 필수 (`DATA_GO_KR_API_KEY`, proxy 서버 측 주입)
|
||||
- **프록시 매핑**: `k-skill-proxy`의 `/v1/kstartup/business-info`, `/v1/kstartup/announcements`, `/v1/kstartup/contents`, `/v1/kstartup/statistics`가 각각 `getBusinessInformation01`, `getAnnouncementInformation01`, `getContentInformation01`, `getStatisticalInformation01`으로 중계 (`returnType=json` 고정)
|
||||
|
||||
### 공식 포털 및 상세 공고 진입점
|
||||
- **K-Startup 공식 포털**: https://www.k-startup.go.kr
|
||||
- API 응답의 `detl_pg_url` 필드가 사용자 상세 공고 진입점으로 사용됨
|
||||
|
||||
### 지자체/유관기관 참고 사이트 (보조 소스)
|
||||
- **서울시 창업플러스**: https://seoulstartup.go.kr
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@
|
|||
"build": "npm run build --workspaces --if-present",
|
||||
"build:manus-bundle": "node scripts/build-manus-bundle.js",
|
||||
"generate:plugin-manifest": "node scripts/generate-plugin-manifest.js",
|
||||
"lint": "node --check scripts/skill-docs.test.js scripts/korean_character_count.js scripts/test_korean_character_count.js scripts/korean_middle_korean.js scripts/test_korean_middle_korean.js scripts/build-manus-bundle.js scripts/test_build_manus_bundle.js scripts/workflow-actions.test.js scripts/generate-plugin-manifest.js scripts/test_generate_plugin_manifest.js && python3 -m py_compile scripts/k_skill_cleaner.py scripts/test_k_skill_cleaner.py corporate-registration-consulting/scripts/fill_official_hwp.py k-skill-cleaner/scripts/k_skill_cleaner.py scripts/fine_dust.py scripts/test_fine_dust.py scripts/ktx_booking.py scripts/test_ktx_booking.py scripts/sillok_search.py scripts/test_sillok_search.py scripts/korean_spell_check.py scripts/test_korean_spell_check.py scripts/patent_search.py scripts/test_patent_search.py scripts/mfds_drug_safety.py scripts/test_mfds_drug_safety.py scripts/nts_business_registration.py scripts/test_nts_business_registration.py scripts/mfds_food_safety.py scripts/test_mfds_food_safety.py scripts/zipcode_search.py scripts/test_zipcode_search.py scripts/subway_lost_property.py scripts/test_subway_lost_property.py scripts/geeknews_search.py scripts/test_geeknews_search.py nts-business-registration/scripts/nts_business_registration.py scripts/test_naver_blog_search.py scripts/test_korean_slang_writing.py scripts/kakaotalk_mac.py scripts/test_kakaotalk_mac.py scripts/test_coupang_partners_mcp_wrapper.py scripts/test_ohou_today_deal.py scripts/ticket_availability.py scripts/test_ticket_availability.py scripts/test_danawa_price_search.py ticket-availability/scripts/ticket_availability.py coupang-product-search/scripts/coupang_partners_mcp.py ohou-today-deal/scripts/ohou_today_deal.py kakaotalk-mac/scripts/kakaotalk_mac.py naver-blog-research/scripts/_naver_http.py naver-blog-research/scripts/naver_search.py naver-blog-research/scripts/naver_read.py naver-blog-research/scripts/naver_download_images.py korean-slang-writing/scripts/_slang_http.py korean-slang-writing/scripts/slang_search.py korean-slang-writing/scripts/slang_lookup.py korean-scholarship-search/scripts/scholarship_filter.py korean-scholarship-search/scripts/test_scholarship_filter.py korean-scholarship-search/scripts/university_search_plan.py seoul-bike/scripts/seoul_bike.py scripts/test_seoul_bike.py danawa-price-search/scripts/danawa_search.py kosis-stats/scripts/run_kosis_stats.py kosis-stats/tests/test_run_kosis_stats.py kstartup-search/scripts/run_kstartup.py kstartup-search/tests/test_run_kstartup.py intercity-bus-booking/scripts/intercity_bus_search.py daangn-used-goods-search/scripts/daangn_used_goods.py daangn-realty-search/scripts/daangn_realty.py daangn-jobs-search/scripts/daangn_jobs.py daangn-cars-search/scripts/daangn_cars.py foresttrip-vacancy/scripts/run_foresttrip_vacancy.py foresttrip-vacancy/tests/test_run_foresttrip_vacancy.py startup-support/scripts/startup_support.py startup-support/scripts/test_startup_support.py && npm run lint --workspaces --if-present && ./scripts/validate-skills.sh && node scripts/generate-plugin-manifest.js --check",
|
||||
"lint": "node --check scripts/skill-docs.test.js scripts/korean_character_count.js scripts/test_korean_character_count.js scripts/korean_middle_korean.js scripts/test_korean_middle_korean.js scripts/build-manus-bundle.js scripts/test_build_manus_bundle.js scripts/workflow-actions.test.js scripts/generate-plugin-manifest.js scripts/test_generate_plugin_manifest.js && python3 -m py_compile scripts/k_skill_cleaner.py scripts/test_k_skill_cleaner.py corporate-registration-consulting/scripts/fill_official_hwp.py k-skill-cleaner/scripts/k_skill_cleaner.py scripts/fine_dust.py scripts/test_fine_dust.py scripts/ktx_booking.py scripts/test_ktx_booking.py scripts/sillok_search.py scripts/test_sillok_search.py scripts/korean_spell_check.py scripts/test_korean_spell_check.py scripts/patent_search.py scripts/test_patent_search.py scripts/mfds_drug_safety.py scripts/test_mfds_drug_safety.py scripts/nts_business_registration.py scripts/test_nts_business_registration.py scripts/mfds_food_safety.py scripts/test_mfds_food_safety.py scripts/zipcode_search.py scripts/test_zipcode_search.py scripts/subway_lost_property.py scripts/test_subway_lost_property.py scripts/geeknews_search.py scripts/test_geeknews_search.py nts-business-registration/scripts/nts_business_registration.py scripts/test_naver_blog_search.py scripts/test_korean_slang_writing.py scripts/kakaotalk_mac.py scripts/test_kakaotalk_mac.py scripts/test_coupang_partners_mcp_wrapper.py scripts/test_ohou_today_deal.py scripts/ticket_availability.py scripts/test_ticket_availability.py scripts/test_danawa_price_search.py ticket-availability/scripts/ticket_availability.py coupang-product-search/scripts/coupang_partners_mcp.py ohou-today-deal/scripts/ohou_today_deal.py kakaotalk-mac/scripts/kakaotalk_mac.py naver-blog-research/scripts/_naver_http.py naver-blog-research/scripts/naver_search.py naver-blog-research/scripts/naver_read.py naver-blog-research/scripts/naver_download_images.py korean-slang-writing/scripts/_slang_http.py korean-slang-writing/scripts/slang_search.py korean-slang-writing/scripts/slang_lookup.py korean-scholarship-search/scripts/scholarship_filter.py korean-scholarship-search/scripts/test_scholarship_filter.py korean-scholarship-search/scripts/university_search_plan.py seoul-bike/scripts/seoul_bike.py scripts/test_seoul_bike.py danawa-price-search/scripts/danawa_search.py kosis-stats/scripts/run_kosis_stats.py kosis-stats/tests/test_run_kosis_stats.py kstartup-search/scripts/run_kstartup.py kstartup-search/tests/test_run_kstartup.py intercity-bus-booking/scripts/intercity_bus_search.py daangn-used-goods-search/scripts/daangn_used_goods.py daangn-realty-search/scripts/daangn_realty.py daangn-jobs-search/scripts/daangn_jobs.py daangn-cars-search/scripts/daangn_cars.py foresttrip-vacancy/scripts/run_foresttrip_vacancy.py foresttrip-vacancy/tests/test_run_foresttrip_vacancy.py && npm run lint --workspaces --if-present && ./scripts/validate-skills.sh && node scripts/generate-plugin-manifest.js --check",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepare:python-test-env": "python3 -m venv .cache/python-test-venv && ./.cache/python-test-venv/bin/python -m pip install --quiet beautifulsoup4",
|
||||
"test": "npm run prepare:python-test-env && node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js scripts/test_korean_middle_korean.js scripts/test_build_manus_bundle.js scripts/workflow-actions.test.js scripts/test_generate_plugin_manifest.js && PYTHONPATH=.:scripts ./.cache/python-test-venv/bin/python -m unittest scripts.test_k_skill_cleaner scripts.test_fine_dust scripts.test_ktx_booking scripts.test_sillok_search scripts.test_korean_spell_check scripts.test_patent_search scripts.test_mfds_drug_safety scripts.test_nts_business_registration scripts.test_mfds_food_safety scripts.test_zipcode_search scripts.test_subway_lost_property scripts.test_geeknews_search scripts.test_naver_blog_search scripts.test_korean_slang_writing scripts.test_kakaotalk_mac scripts.test_coupang_partners_mcp_wrapper scripts.test_ohou_today_deal scripts.test_ticket_availability scripts.test_seoul_bike scripts.test_danawa_price_search && PYTHONPATH=.:scripts:korean-scholarship-search/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s korean-scholarship-search/scripts -p 'test_scholarship_filter.py' && PYTHONPATH=.:scripts:kosis-stats/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s kosis-stats/tests -p 'test_run_kosis_stats.py' && PYTHONPATH=.:scripts:kstartup-search/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s kstartup-search/tests -p 'test_run_kstartup.py' && PYTHONPATH=.:foresttrip-vacancy/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s foresttrip-vacancy/tests -p 'test_run_foresttrip_vacancy.py' && PYTHONPATH=startup-support/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s startup-support/scripts -p 'test_startup_support.py' && npm run test --workspaces --if-present && ./scripts/validate-skills.sh",
|
||||
"test": "npm run prepare:python-test-env && node --test scripts/skill-docs.test.js scripts/test_korean_character_count.js scripts/test_korean_middle_korean.js scripts/test_build_manus_bundle.js scripts/workflow-actions.test.js scripts/test_generate_plugin_manifest.js && PYTHONPATH=.:scripts ./.cache/python-test-venv/bin/python -m unittest scripts.test_k_skill_cleaner scripts.test_fine_dust scripts.test_ktx_booking scripts.test_sillok_search scripts.test_korean_spell_check scripts.test_patent_search scripts.test_mfds_drug_safety scripts.test_nts_business_registration scripts.test_mfds_food_safety scripts.test_zipcode_search scripts.test_subway_lost_property scripts.test_geeknews_search scripts.test_naver_blog_search scripts.test_korean_slang_writing scripts.test_kakaotalk_mac scripts.test_coupang_partners_mcp_wrapper scripts.test_ohou_today_deal scripts.test_ticket_availability scripts.test_seoul_bike scripts.test_danawa_price_search && PYTHONPATH=.:scripts:korean-scholarship-search/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s korean-scholarship-search/scripts -p 'test_scholarship_filter.py' && PYTHONPATH=.:scripts:kosis-stats/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s kosis-stats/tests -p 'test_run_kosis_stats.py' && PYTHONPATH=.:scripts:kstartup-search/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s kstartup-search/tests -p 'test_run_kstartup.py' && PYTHONPATH=.:foresttrip-vacancy/scripts ./.cache/python-test-venv/bin/python -m unittest discover -s foresttrip-vacancy/tests -p 'test_run_foresttrip_vacancy.py' && npm run test --workspaces --if-present && ./scripts/validate-skills.sh",
|
||||
"pack:dry-run": "npm pack --workspace k-lotto --dry-run && npm pack --workspace daiso-product-search --dry-run && npm pack --workspace market-kurly-search --dry-run && npm pack --workspace blue-ribbon-nearby --dry-run && npm pack --workspace kakao-bar-nearby --dry-run && npm pack --workspace cheap-gas-nearby --dry-run && npm pack --workspace public-restroom-nearby --dry-run && npm pack --workspace parking-lot-search --dry-run && npm pack --workspace court-auction-notice-search --dry-run && npm pack --workspace donation-place-search --dry-run && npm pack --workspace gongsijiga-search --dry-run && npm pack --workspace kbl-results --dry-run && npm pack --workspace kleague-results --dry-run && npm pack --workspace lck-analytics --dry-run && npm pack --workspace toss-securities --dry-run && npm pack --workspace hipass-receipt --dry-run && npm pack --workspace used-car-price-search --dry-run && npm pack --workspace k-skill-rhwp --dry-run && npm pack --workspace korean-marathon-schedule --dry-run && npm pack --workspace gangnamunni-clinic-search --dry-run && npm pack --workspace daishin-report-search --dry-run && npm pack --workspace sh-notice-search --dry-run && npm pack --workspace emergency-room-beds --dry-run && npm pack --workspace local-election-candidate-search --dry-run",
|
||||
"ci": "npm run lint && npm run typecheck && npm run test && npm run pack:dry-run",
|
||||
"version-packages": "changeset version",
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
---
|
||||
name: startup-support
|
||||
description: Search Korean K-Startup government startup support announcements through k-skill-proxy. Use when users ask about 창업 지원, 스타트업 지원금, 중소기업 지원, 정부 지원사업, or 모집 중인 창업 공고.
|
||||
license: MIT
|
||||
metadata:
|
||||
category: business-support
|
||||
locale: ko-KR
|
||||
phase: v1
|
||||
---
|
||||
|
||||
# 스타트업 지원사업 조회
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
`startup-support` searches K-Startup announcement data through the hosted or self-hosted `k-skill-proxy` `/v1/kstartup/announcements` route and summarizes matching startup support programs.
|
||||
|
||||
Use it for:
|
||||
|
||||
- "서울 청년 창업 지원사업 알려줘"
|
||||
- "모집 중인 사업화 지원 공고 찾아줘"
|
||||
- "정부 창업 지원사업 마감일 확인해줘"
|
||||
- "예비창업자 대상 K-Startup 공고 요약해줘"
|
||||
|
||||
Do not use it for:
|
||||
|
||||
- Application submission or account/payment automation
|
||||
- Final legal eligibility decisions
|
||||
- Inventing requirements, award amounts, contacts, or application steps not present in upstream data
|
||||
- Local-government site crawling outside K-Startup
|
||||
|
||||
## Data Source And Credentials
|
||||
|
||||
- Source: 공공데이터포털 창업진흥원 K-Startup 조회서비스 (`15125364`)
|
||||
- Proxy route: `/v1/kstartup/announcements`
|
||||
- User credential requirement: none for normal hosted-proxy use
|
||||
- Proxy operator credential: `DATA_GO_KR_API_KEY`
|
||||
|
||||
Set `KSKILL_PROXY_BASE_URL` or pass `--proxy-base-url` only when using a self-host proxy. For direct data.go.kr calls with a user key, use `kstartup-search` and its `--direct` mode.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Translate the user request into K-Startup announcement filters.
|
||||
2. Run a small bounded search first.
|
||||
3. Check each returned row's official `url` before making eligibility-sensitive claims.
|
||||
4. Cite the official URL when summarizing.
|
||||
|
||||
Common filter mapping:
|
||||
|
||||
- region -> `supt_regin`
|
||||
- keyword -> `biz_pbanc_nm`
|
||||
- support type -> `supt_biz_clsfc`
|
||||
- deadline-only / recruiting-only -> `rcrt_prgs_yn=Y`
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
python3 scripts/startup_support.py \
|
||||
--region 서울특별시 \
|
||||
--keyword 청년 \
|
||||
--deadline-only \
|
||||
--per-page 5 \
|
||||
--text
|
||||
```
|
||||
|
||||
Dry-run request construction without network or credentials:
|
||||
|
||||
```bash
|
||||
python3 scripts/startup_support.py \
|
||||
--region 서울특별시 \
|
||||
--keyword 청년 \
|
||||
--deadline-only \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
The helper returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"programs": [
|
||||
{
|
||||
"id": "A1",
|
||||
"title": "서울 청년 창업 지원",
|
||||
"organization": "창업진흥원",
|
||||
"region": "서울",
|
||||
"support_type": "사업화",
|
||||
"amount": "공식 공고 확인",
|
||||
"deadline": "2026-06-30",
|
||||
"target": "예비창업자",
|
||||
"contact": "",
|
||||
"url": "https://www.k-startup.go.kr/...",
|
||||
"source": "K-Startup",
|
||||
"last_updated": "2026-06-01"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Failure Modes
|
||||
|
||||
- Empty result page: broaden filters or inspect additional pages.
|
||||
- `400 bad_request`: invalid query rejected by proxy validation.
|
||||
- `503 upstream_not_configured`: proxy lacks `DATA_GO_KR_API_KEY`.
|
||||
- `502 upstream_error` or invalid response: data.go.kr returned an upstream error or non-JSON body.
|
||||
|
||||
## Maintainer Checks
|
||||
|
||||
```bash
|
||||
python3 -m py_compile startup-support/scripts/startup_support.py startup-support/scripts/test_startup_support.py
|
||||
PYTHONPATH=startup-support/scripts python3 -m unittest discover -s startup-support/scripts -p 'test_startup_support.py'
|
||||
python3 startup-support/scripts/startup_support.py --region 서울특별시 --keyword 청년 --deadline-only --dry-run
|
||||
```
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
# startup-support API surface
|
||||
|
||||
`startup-support` is a read-only helper over the existing K-Startup proxy route. It does not add independent proxy endpoints.
|
||||
|
||||
## Proxy route used
|
||||
|
||||
```text
|
||||
GET /v1/kstartup/announcements
|
||||
```
|
||||
|
||||
Common query mapping:
|
||||
|
||||
- `region` -> `supt_regin`
|
||||
- `keyword` -> `biz_pbanc_nm`
|
||||
- `support_type` -> `supt_biz_clsfc`
|
||||
- `deadline_only=true` -> `rcrt_prgs_yn=Y`
|
||||
- `page` -> `page`
|
||||
- `per_page` -> `perPage`
|
||||
|
||||
Authentication is handled by hosted or self-hosted `k-skill-proxy`. Users do not pass a data.go.kr service key to this helper.
|
||||
|
||||
## Python helper
|
||||
|
||||
```python
|
||||
programs = search_startup_support(
|
||||
region="서울특별시",
|
||||
keyword="청년",
|
||||
support_type="사업화",
|
||||
deadline_only=True,
|
||||
)
|
||||
```
|
||||
|
||||
The CLI exposes the same search:
|
||||
|
||||
```bash
|
||||
python3 startup-support/scripts/startup_support.py \
|
||||
--region 서울특별시 \
|
||||
--keyword 청년 \
|
||||
--deadline-only \
|
||||
--per-page 5 \
|
||||
--text
|
||||
```
|
||||
|
||||
For request inspection without network or credentials:
|
||||
|
||||
```bash
|
||||
python3 startup-support/scripts/startup_support.py \
|
||||
--region 서울특별시 \
|
||||
--keyword 청년 \
|
||||
--deadline-only \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Detailed eligibility and application steps must be confirmed from each result's official `url`.
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# startup-support data sources
|
||||
|
||||
## Primary Source
|
||||
|
||||
- K-Startup announcement data through `k-skill-proxy` `/v1/kstartup/announcements`
|
||||
- Upstream dataset: 공공데이터포털 창업진흥원 K-Startup 조회서비스 (`15125364`)
|
||||
- Authentication: proxy server injects `DATA_GO_KR_API_KEY`
|
||||
- Update cadence: official K-Startup/data.go.kr feed cadence, not realtime monitoring
|
||||
|
||||
## Helper Scope
|
||||
|
||||
The helper searches announcement rows and returns the official detail URL for each result. It does not crawl local-government sites directly and does not synthesize eligibility, required documents, contacts, or award amounts when upstream data omits them.
|
||||
|
||||
## Fallback Order
|
||||
|
||||
1. Hosted proxy from `KSKILL_PROXY_BASE_URL` or `https://k-skill-proxy.nomadamas.org`
|
||||
2. User-provided self-host proxy via `--proxy-base-url`
|
||||
3. Dry-run URL inspection when network, proxy configuration, or upstream credentials are unavailable
|
||||
|
||||
For direct data.go.kr calls with a user-held key, use the narrower `kstartup-search` helper's `--direct` mode.
|
||||
|
||||
## Failure Modes
|
||||
|
||||
- Empty result page: no matching K-Startup announcements for the selected filters/page.
|
||||
- HTTP 400: invalid query accepted by the helper but rejected by proxy validation.
|
||||
- HTTP 503: proxy has no configured `DATA_GO_KR_API_KEY`.
|
||||
- HTTP 502: upstream data.go.kr error or invalid response.
|
||||
- Missing detail fields: answer with the returned official URL instead of inventing requirements, amounts, or contacts.
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
# 스타트업 지원사업 분류
|
||||
|
||||
## 1. 지원 유형별 분류
|
||||
|
||||
### 보조금
|
||||
**특징**: 상환 불필요한 정부 지원 금액
|
||||
**대상**: 초기 스타트업, R&D 중소기업
|
||||
**기관**: 중소벤처기업부, 지자체
|
||||
|
||||
**주요 프로그램**:
|
||||
- 서울시 청년 스타트업 창업 지원금
|
||||
- 경기도 MVP 개발 지원사업
|
||||
- 부산시 스타트업 보육 지원
|
||||
- 중소기업 기술개발 보조금
|
||||
|
||||
### 융자
|
||||
**특징**: 저리 융자 (상환 필요)
|
||||
**대상**: 성장 단계 스타트업
|
||||
**기관**: 중소기업진흥공단, 금융공단
|
||||
|
||||
**주요 프로그램**:
|
||||
- 스타트업 성장지원 융자
|
||||
- 중소기업 기술보증 융자
|
||||
- 청년창업 보증지원
|
||||
|
||||
### 투자
|
||||
**특징**: 자금 투자 (지분 참여)
|
||||
**대상**: 성장 잠재력 있는 스타트업
|
||||
**기관**: 벤처캐피탈, 정부투자기관
|
||||
|
||||
**주요 프로그램**:
|
||||
- KDB산업은행 벤처투자
|
||||
- 중소벤처기업공단 투자
|
||||
- 지역별 스타트업 투자펀드
|
||||
|
||||
### 멘토링/교육
|
||||
**특징**: 전문가 멘토링, 교육 프로그램
|
||||
**대상**: 모든 단계 스타트업
|
||||
**기관**: 창업진흥원, 민간 기관
|
||||
|
||||
**주요 프로그램**:
|
||||
- 스타트업 비즈니스 멘토링
|
||||
- 창업아이디어 개발 교육
|
||||
- 기술 스타트업 양성 프로그램
|
||||
|
||||
### 인프라 지원
|
||||
**특징**: 물리적 공간, 장비 지원
|
||||
**대상**: 공간 필요 스타트업
|
||||
**기관**: 지자체, 공공기관
|
||||
|
||||
**주요 프로그램**:
|
||||
- 창업 보육 센터 입주
|
||||
- 기술장비 공유
|
||||
- 테스트베드 지원
|
||||
|
||||
## 2. 지역별 분류
|
||||
|
||||
### 서울특별시
|
||||
**주요 기관**: 서울시 창업진흥원
|
||||
**특징**: 대도시 기반 다양한 지원
|
||||
|
||||
**프로그램 유형**:
|
||||
- 청년 창업 지원
|
||||
- 기술 스타트업 지원
|
||||
- 해외 진출 지원
|
||||
- 문화콘텐츠 스타트업 지원
|
||||
|
||||
### 경기도
|
||||
**주요 기관**: 경기도 창업진흥원
|
||||
**특징**: 수도권 중심 기술 집약적
|
||||
|
||||
**프로그램 유형**:
|
||||
- MVP 개발 지원
|
||||
- 기술 이전 지원
|
||||
- 중소기업 혁신 지원
|
||||
- 바이오 스타트업 지원
|
||||
|
||||
### 부산광역시
|
||||
**주요 기관**: 부산시 스타트업 허브
|
||||
**특징**: 해양, 조선 산업 특화
|
||||
|
||||
**프로그램 유형**:
|
||||
- 해양 스타트업 지원
|
||||
- 스마트시티 지원
|
||||
- 관광 스타트업 지원
|
||||
- 제조업 스타트업 지원
|
||||
|
||||
### 광주광역시
|
||||
**주요 기관**: 광주창업파크
|
||||
**특징**: 전통 공업 도시 전환
|
||||
|
||||
**프로그램 유형**:
|
||||
- 전자 스타트업 지원
|
||||
- 지역 특화 산업 지원
|
||||
- 청년 창업 아카데미
|
||||
- 기술 이전 지원
|
||||
|
||||
### 대구광역시
|
||||
**주요 기관**: 대구창업진흥원
|
||||
**특징**: 제조업 중심 스마트화
|
||||
|
||||
**프로그램 유형**:
|
||||
- 스마트제조 지원
|
||||
- IT 스타트업 지원
|
||||
- 의료 스타트업 지원
|
||||
- 중소기업 디지털 전환 지원
|
||||
|
||||
## 3. 산업 분야별 분류
|
||||
|
||||
### IT/소프트웨어
|
||||
**특징**: 디지털 전반 지원
|
||||
**주요 프로그램**:
|
||||
- 소프트웨어 개발 지원
|
||||
- AI/빅데이터 지원
|
||||
- 클라우드 서비스 지원
|
||||
|
||||
### 바이오/의료
|
||||
**특징**: 높은 진입 장벽, 장기 개발
|
||||
**주요 프로그램**:
|
||||
- 의료기기 개발 지원
|
||||
- 바이오 기술 개발 지원
|
||||
- 헬스케어 스타트업 지원
|
||||
|
||||
### 제조업
|
||||
**특징**: 자본 집약적, 기술 집약적
|
||||
**주요 프로그램**:
|
||||
- 스마트제조 지원
|
||||
- 공정 혁신 지원
|
||||
- 재료 개발 지원
|
||||
|
||||
### 에너지/환경
|
||||
**특징**: 친환경 기술 중심
|
||||
**주요 프로그램**:
|
||||
- 신에너지 기술 지원
|
||||
- 환경 기술 개발 지원
|
||||
- 탄소중립 기술 지원
|
||||
|
||||
### 문화/콘텐츠
|
||||
**특징**: 창의성 중심
|
||||
**주요 프로그램**:
|
||||
- 게임 개발 지원
|
||||
- 콘텐츠 제작 지원
|
||||
- 크리에이티브 산업 지원
|
||||
|
||||
### 금융/핀테크
|
||||
**특징**: 규제 산업
|
||||
**주요 프로그램**:
|
||||
- 핀테크 서비스 지원
|
||||
- 블록체인 기술 지원
|
||||
- 디지털 금융 지원
|
||||
|
||||
## 4. 창업 단계별 분류
|
||||
|
||||
### 아이디어 단계
|
||||
**특징**: 초기 구체화 단계
|
||||
**지원 내용**:
|
||||
- 아이디어 개발 교육
|
||||
- 시장조사 지원
|
||||
- 비즈니스 플랜 작성 지원
|
||||
|
||||
### 초기 단계 (Pre-seed)
|
||||
**특징**: 제품 개발 시작
|
||||
**지원 내용**:
|
||||
- MVP 개발 지원
|
||||
- 초기 자금 조달
|
||||
- 창업 보육 센터 입주
|
||||
|
||||
### 성장 단계 (Seed)
|
||||
**특징**: 제품 출시, 고객 확보
|
||||
**지원 내용**:
|
||||
- 시장 진출 지원
|
||||
- 투자 유치 지원
|
||||
- 기술 개발 지원
|
||||
|
||||
### 확장 단계 (Growth)
|
||||
**특징**: 시장 점유율 확대
|
||||
**지원 내용**:
|
||||
- 글로벌 진출 지원
|
||||
- 대규모 투자 유치
|
||||
- 인프라 확장 지원
|
||||
|
||||
### 성숙 단계 (Mature)
|
||||
**특징**: 안정화 단계
|
||||
**지원 내용**:
|
||||
- 기술 고도화 지원
|
||||
- M&A 지원
|
||||
- 국제화 지원
|
||||
|
||||
## 5. 대상별 분류
|
||||
|
||||
### 청년 창업가
|
||||
**연령**: 만 19~34세
|
||||
**특징**: 초기 창업, 경험 부족
|
||||
**주요 지원**:
|
||||
- 청년 창업 보조금
|
||||
- 창업 교육 프로그램
|
||||
- 멘토링 지원
|
||||
|
||||
### 여성 창업가
|
||||
**특징**: 일-가정 병행 지원
|
||||
**주요 지원**:
|
||||
- 여성 창업 보조금
|
||||
- 육아 지원 서비스
|
||||
- 네트워킹 지원
|
||||
|
||||
### 장애인 창업가
|
||||
**특징**: 접근성 지원 필요
|
||||
**주요 지원**:
|
||||
- 장애인 창업 지원금
|
||||
- 장애인 고용 지원
|
||||
- 접근성 컨설팅
|
||||
|
||||
### 외국인 창업가
|
||||
**특징**: 규제, 문화 장벽
|
||||
**주요 지원**:
|
||||
- 외국인 창업 지원금
|
||||
- 법률/세무 컨설팅
|
||||
- 한글/한국어 교육
|
||||
|
||||
### 기술 창업가
|
||||
**특징**: R&D 중심
|
||||
**주요 지원**:
|
||||
- 기술 개발 지원
|
||||
- 특허 출원 지원
|
||||
- 연구 인프라 지원
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections.abc import Sequence
|
||||
|
||||
DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
|
||||
REGION_ALIASES = {
|
||||
"서울특별시": "서울",
|
||||
"부산광역시": "부산",
|
||||
"대구광역시": "대구",
|
||||
"인천광역시": "인천",
|
||||
"광주광역시": "광주",
|
||||
"대전광역시": "대전",
|
||||
"울산광역시": "울산",
|
||||
"세종특별자치시": "세종",
|
||||
"경기도": "경기",
|
||||
"강원특별자치도": "강원",
|
||||
"강원도": "강원",
|
||||
"충청북도": "충북",
|
||||
"충청남도": "충남",
|
||||
"전북특별자치도": "전북",
|
||||
"전라북도": "전북",
|
||||
"전라남도": "전남",
|
||||
"경상북도": "경북",
|
||||
"경상남도": "경남",
|
||||
"제주특별자치도": "제주",
|
||||
}
|
||||
|
||||
|
||||
class HelperError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _compact(value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
text = value.strip()
|
||||
return text or None
|
||||
|
||||
|
||||
def _yyyymmdd_to_iso(value: str | None) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
digits = "".join(char for char in value if char.isdigit())
|
||||
if len(digits) != 8:
|
||||
return value
|
||||
return f"{digits[0:4]}-{digits[4:6]}-{digits[6:8]}"
|
||||
|
||||
|
||||
def build_query(args: argparse.Namespace) -> dict[str, str | int]:
|
||||
if args.page < 1:
|
||||
raise HelperError("--page must be >= 1")
|
||||
if args.per_page < 1 or args.per_page > 100:
|
||||
raise HelperError("--per-page must be in [1, 100]")
|
||||
|
||||
query: dict[str, str | int] = {
|
||||
"page": args.page,
|
||||
"perPage": args.per_page,
|
||||
"returnType": "json",
|
||||
}
|
||||
region = _compact(args.region)
|
||||
keyword = _compact(args.keyword)
|
||||
support_type = _compact(args.support_type)
|
||||
if region and region != "전국":
|
||||
query["supt_regin"] = region
|
||||
if keyword:
|
||||
query["biz_pbanc_nm"] = keyword
|
||||
if support_type:
|
||||
query["supt_biz_clsfc"] = support_type
|
||||
if args.deadline_only:
|
||||
query["rcrt_prgs_yn"] = "Y"
|
||||
return query
|
||||
|
||||
|
||||
def build_url(query: dict[str, str | int], proxy_base_url: str = DEFAULT_PROXY_BASE_URL) -> str:
|
||||
base = proxy_base_url.rstrip("/")
|
||||
encoded = urllib.parse.urlencode([(key, str(value)) for key, value in query.items()])
|
||||
return f"{base}/v1/kstartup/announcements?{encoded}"
|
||||
|
||||
|
||||
def http_get(url: str, *, timeout: int) -> tuple[int, str, str]:
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"accept": "application/json",
|
||||
"user-agent": "k-skill/startup-support",
|
||||
},
|
||||
method="GET",
|
||||
)
|
||||
context = ssl.create_default_context()
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=timeout, context=context) as response:
|
||||
body = response.read().decode("utf-8", errors="replace")
|
||||
return response.status, response.headers.get("content-type", ""), body
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = exc.read().decode("utf-8", errors="replace") if exc.fp else ""
|
||||
content_type = exc.headers.get("content-type", "") if exc.headers else ""
|
||||
return exc.code, content_type, body
|
||||
except urllib.error.URLError as exc:
|
||||
raise HelperError(f"network error: {exc.reason}") from exc
|
||||
|
||||
|
||||
def _rows_from_payload(payload: dict[str, object]) -> list[dict[str, object]]:
|
||||
rows = payload.get("data", [])
|
||||
if not isinstance(rows, list):
|
||||
raise HelperError("proxy response data must be a list")
|
||||
normalized: list[dict[str, object]] = []
|
||||
for row in rows:
|
||||
if isinstance(row, dict):
|
||||
normalized.append(row)
|
||||
return normalized
|
||||
|
||||
|
||||
def _text(row: dict[str, object], *keys: str) -> str:
|
||||
for key in keys:
|
||||
value = row.get(key)
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
return ""
|
||||
|
||||
|
||||
def _canonical_region(value: str | None) -> str:
|
||||
text = _compact(value)
|
||||
if not text:
|
||||
return ""
|
||||
compacted = text.replace(" ", "")
|
||||
return REGION_ALIASES.get(compacted, compacted)
|
||||
|
||||
|
||||
def _region_parts(value: str) -> list[str]:
|
||||
return [
|
||||
_canonical_region(part)
|
||||
for part in value.replace("/", ",").replace("|", ",").split(",")
|
||||
if _canonical_region(part)
|
||||
]
|
||||
|
||||
|
||||
def _matches_region(row: dict[str, object], requested_region: str | None) -> bool:
|
||||
requested = _canonical_region(requested_region)
|
||||
if not requested or requested == "전국":
|
||||
return True
|
||||
row_region = _text(row, "supt_regin", "region")
|
||||
if not row_region:
|
||||
return False
|
||||
return requested in _region_parts(row_region)
|
||||
|
||||
|
||||
def _program_from_row(row: dict[str, object]) -> dict[str, str]:
|
||||
program_id = _text(row, "pbanc_sn", "id")
|
||||
title = _text(row, "biz_pbanc_nm", "title")
|
||||
end_date = _text(row, "pbanc_rcpt_end_dt", "deadline")
|
||||
return {
|
||||
"id": program_id,
|
||||
"title": title,
|
||||
"organization": _text(row, "sprv_inst", "organization"),
|
||||
"region": _text(row, "supt_regin", "region", "전국"),
|
||||
"support_type": _text(row, "supt_biz_clsfc", "support_type", "기타"),
|
||||
"amount": _text(row, "supt_cn", "amount", "공식 공고 확인"),
|
||||
"deadline": _yyyymmdd_to_iso(end_date),
|
||||
"target": _text(row, "aply_trgt", "target"),
|
||||
"contact": _text(row, "biz_gdnc_url", "contact"),
|
||||
"url": _text(row, "detl_pg_url", "url"),
|
||||
"source": "K-Startup",
|
||||
"last_updated": _yyyymmdd_to_iso(_text(row, "pbanc_rcpt_bgng_dt", "last_updated")),
|
||||
}
|
||||
|
||||
|
||||
def search_startup_support(
|
||||
region: str = "전국",
|
||||
keyword: str | None = None,
|
||||
support_type: str | None = None,
|
||||
deadline_only: bool = False,
|
||||
*,
|
||||
page: int = 1,
|
||||
per_page: int = 10,
|
||||
proxy_base_url: str | None = None,
|
||||
timeout: int = 30,
|
||||
) -> list[dict[str, str]]:
|
||||
args = argparse.Namespace(
|
||||
region=region,
|
||||
keyword=keyword,
|
||||
support_type=support_type,
|
||||
deadline_only=deadline_only,
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
)
|
||||
base_url = proxy_base_url or os.environ.get("KSKILL_PROXY_BASE_URL", DEFAULT_PROXY_BASE_URL)
|
||||
url = build_url(build_query(args), proxy_base_url=base_url)
|
||||
status, _, body = http_get(url, timeout=timeout)
|
||||
if status < 200 or status >= 300:
|
||||
raise HelperError(f"proxy returned HTTP {status}: {body[:300]}")
|
||||
try:
|
||||
payload = json.loads(body)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise HelperError("proxy response was not valid JSON") from exc
|
||||
if not isinstance(payload, dict):
|
||||
raise HelperError("proxy response must be a JSON object")
|
||||
rows = [row for row in _rows_from_payload(payload) if _matches_region(row, region)]
|
||||
return [_program_from_row(row) for row in rows]
|
||||
|
||||
|
||||
def get_startup_program_detail(program_id: str) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class StartupSupportAPI:
|
||||
def search_programs(
|
||||
self,
|
||||
region: str = "전국",
|
||||
keyword: str | None = None,
|
||||
support_type: str | None = None,
|
||||
deadline_only: bool = False,
|
||||
) -> list[dict[str, str]]:
|
||||
return search_startup_support(region, keyword, support_type, deadline_only)
|
||||
|
||||
def get_program_detail(self, program_id: str) -> None:
|
||||
return get_startup_program_detail(program_id)
|
||||
|
||||
|
||||
def _print_text(programs: Sequence[dict[str, str]]) -> None:
|
||||
if not programs:
|
||||
print("일치하는 K-Startup 지원사업 공고가 없습니다.")
|
||||
return
|
||||
for index, program in enumerate(programs, start=1):
|
||||
deadline = program["deadline"] or "마감일 공고 확인"
|
||||
url = program["url"] or "상세 URL 없음"
|
||||
print(f"{index}. {program['title']} | {program['region']} | {deadline}")
|
||||
print(f" {url}")
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="K-Startup 창업 지원사업 공고 조회")
|
||||
parser.add_argument("--region", default="전국")
|
||||
parser.add_argument("--keyword")
|
||||
parser.add_argument("--support-type")
|
||||
parser.add_argument("--deadline-only", action="store_true")
|
||||
parser.add_argument("--page", type=int, default=1)
|
||||
parser.add_argument("--per-page", type=int, default=10)
|
||||
parser.add_argument("--text", action="store_true")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--timeout", type=int, default=30)
|
||||
parser.add_argument("--proxy-base-url", default=os.environ.get("KSKILL_PROXY_BASE_URL", DEFAULT_PROXY_BASE_URL))
|
||||
return parser
|
||||
|
||||
|
||||
def run(argv: Sequence[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
query = build_query(args)
|
||||
url = build_url(query, proxy_base_url=args.proxy_base_url)
|
||||
if args.dry_run:
|
||||
print(json.dumps({"operation": "announcements", "query": query, "url": url}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
programs = search_startup_support(
|
||||
region=args.region,
|
||||
keyword=args.keyword,
|
||||
support_type=args.support_type,
|
||||
deadline_only=args.deadline_only,
|
||||
page=args.page,
|
||||
per_page=args.per_page,
|
||||
proxy_base_url=args.proxy_base_url,
|
||||
timeout=args.timeout,
|
||||
)
|
||||
except HelperError as exc:
|
||||
parser.error(str(exc))
|
||||
if args.text:
|
||||
_print_text(programs)
|
||||
else:
|
||||
print(json.dumps({"programs": programs}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(run())
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
|
||||
import startup_support # noqa: E402
|
||||
|
||||
|
||||
def make_args(**overrides: object) -> argparse.Namespace:
|
||||
defaults = {
|
||||
"region": "서울특별시",
|
||||
"keyword": "청년",
|
||||
"support_type": None,
|
||||
"deadline_only": False,
|
||||
"page": 1,
|
||||
"per_page": 5,
|
||||
"text": False,
|
||||
"dry_run": False,
|
||||
"timeout": 30,
|
||||
"proxy_base_url": "https://proxy.example",
|
||||
}
|
||||
defaults.update(overrides)
|
||||
return argparse.Namespace(**defaults)
|
||||
|
||||
|
||||
class StartupSupportHelperTests(unittest.TestCase):
|
||||
def test_build_query_maps_startup_terms_to_kstartup_announcements(self) -> None:
|
||||
args = make_args(deadline_only=True, support_type="사업화")
|
||||
|
||||
query = startup_support.build_query(args)
|
||||
|
||||
self.assertEqual(query["supt_regin"], "서울특별시")
|
||||
self.assertEqual(query["biz_pbanc_nm"], "청년")
|
||||
self.assertEqual(query["supt_biz_clsfc"], "사업화")
|
||||
self.assertEqual(query["rcrt_prgs_yn"], "Y")
|
||||
self.assertEqual(query["page"], 1)
|
||||
self.assertEqual(query["perPage"], 5)
|
||||
self.assertEqual(query["returnType"], "json")
|
||||
|
||||
def test_build_query_rejects_bad_page_size(self) -> None:
|
||||
with self.assertRaises(startup_support.HelperError):
|
||||
startup_support.build_query(make_args(per_page=0))
|
||||
with self.assertRaises(startup_support.HelperError):
|
||||
startup_support.build_query(make_args(per_page=101))
|
||||
|
||||
def test_dry_run_uses_hosted_proxy_without_requests_dependency_or_api_key(self) -> None:
|
||||
out = StringIO()
|
||||
|
||||
with mock.patch.object(sys, "stdout", out):
|
||||
rc = startup_support.run([
|
||||
"--region", "서울특별시",
|
||||
"--keyword", "청년",
|
||||
"--deadline-only",
|
||||
"--per-page", "5",
|
||||
"--dry-run",
|
||||
"--proxy-base-url", "https://proxy.example",
|
||||
])
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
payload = json.loads(out.getvalue())
|
||||
self.assertEqual(payload["operation"], "announcements")
|
||||
self.assertTrue(payload["url"].startswith("https://proxy.example/v1/kstartup/announcements?"))
|
||||
self.assertIn("rcrt_prgs_yn=Y", payload["url"])
|
||||
self.assertNotIn("ServiceKey", payload["url"])
|
||||
|
||||
def test_dry_run_uses_env_proxy_base_url_when_cli_option_is_absent(self) -> None:
|
||||
out = StringIO()
|
||||
|
||||
with mock.patch.dict(os.environ, {"KSKILL_PROXY_BASE_URL": "https://env-proxy.example"}):
|
||||
with mock.patch.object(sys, "stdout", out):
|
||||
rc = startup_support.run(["--dry-run"])
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
payload = json.loads(out.getvalue())
|
||||
self.assertTrue(payload["url"].startswith("https://env-proxy.example/v1/kstartup/announcements?"))
|
||||
|
||||
def test_search_helper_uses_env_proxy_base_url_when_argument_is_absent(self) -> None:
|
||||
payload = {"data": []}
|
||||
calls = []
|
||||
|
||||
def fake_http_get(url: str, *, timeout: int) -> tuple[int, str, str]:
|
||||
calls.append((url, timeout))
|
||||
return 200, "application/json", json.dumps(payload)
|
||||
|
||||
with mock.patch.dict(os.environ, {"KSKILL_PROXY_BASE_URL": "https://env-proxy.example"}):
|
||||
with mock.patch.object(startup_support, "http_get", side_effect=fake_http_get):
|
||||
result = startup_support.search_startup_support()
|
||||
|
||||
self.assertEqual(result, [])
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertTrue(calls[0][0].startswith("https://env-proxy.example/v1/kstartup/announcements?"))
|
||||
|
||||
def test_search_startup_support_parses_proxy_payload_and_filters_deadline(self) -> None:
|
||||
payload = {
|
||||
"data": [
|
||||
{
|
||||
"pbanc_sn": "A1",
|
||||
"biz_pbanc_nm": "서울 청년 창업 지원",
|
||||
"sprv_inst": "창업진흥원",
|
||||
"supt_regin": "서울",
|
||||
"supt_biz_clsfc": "사업화",
|
||||
"pbanc_rcpt_bgng_dt": "20260601",
|
||||
"pbanc_rcpt_end_dt": "20260630",
|
||||
"aply_trgt": "예비창업자",
|
||||
"detl_pg_url": "https://www.k-startup.go.kr/detail/A1",
|
||||
},
|
||||
{
|
||||
"pbanc_sn": "B1",
|
||||
"biz_pbanc_nm": "부산 청년 창업 지원",
|
||||
"supt_regin": "부산",
|
||||
"pbanc_rcpt_end_dt": "20260630",
|
||||
"detl_pg_url": "https://www.k-startup.go.kr/detail/B1",
|
||||
},
|
||||
{
|
||||
"pbanc_sn": "C1",
|
||||
"biz_pbanc_nm": "전국 창업 지원",
|
||||
"supt_regin": "전국",
|
||||
"pbanc_rcpt_end_dt": "20260630",
|
||||
"detl_pg_url": "https://www.k-startup.go.kr/detail/C1",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with mock.patch.object(startup_support, "http_get", return_value=(200, "application/json", json.dumps(payload))):
|
||||
result = startup_support.search_startup_support(
|
||||
region="서울특별시",
|
||||
keyword="청년",
|
||||
deadline_only=True,
|
||||
proxy_base_url="https://proxy.example",
|
||||
)
|
||||
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(result[0]["id"], "A1")
|
||||
self.assertEqual(result[0]["title"], "서울 청년 창업 지원")
|
||||
self.assertEqual(result[0]["deadline"], "2026-06-30")
|
||||
self.assertEqual(result[0]["url"], "https://www.k-startup.go.kr/detail/A1")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
Loading…
Add table
Add a link
Reference in a new issue