fix(startup-support): remove fabricated detail data

This commit is contained in:
Jeffrey (Dongkyu) Kim 2026-05-31 17:23:57 +09:00
commit cff6b29ff9
7 changed files with 165 additions and 199 deletions

View file

@ -85,7 +85,7 @@ 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 | Search Korean government startup support programs, grants, and subsidies for startups, SMEs, and entrepreneurs. Use when users ask about 창업 지원, 스타트업 지원금, 중소기업 지원, 정부 지원사업. | No login | [docs/features/startup-support.md](https://github.com/k-skill/k-skill/blob/main/docs/features/startup-support.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) |

View file

@ -43,10 +43,7 @@
#### k-skill-proxy 라우트
- `GET /v1/startup-support/list`: 지원사업 목록 조회
- `GET /v1/startup-support/detail/:program_id`: 특정 지원사업 상세 정보
- `GET /v1/startup-support/region/:region`: 특정 지역 지원사업 조회
- `GET /v1/startup-support/deadline`: 임박 마감 지원사업
공공데이터포털 K-Startup OpenAPI는 별도 `kstartup-search` 스킬과 `k-skill-proxy``/v1/kstartup/*` 라우트가 담당합니다. `startup-support` helper는 지역별 공개 API 목록을 조회하고, 상세 정보는 결과의 공식 `url` 로 확인합니다.
#### Python 스크립트
@ -63,8 +60,7 @@ keyword_programs = search_startup_support(keyword='청년')
# 마감 임박 검색
deadline_programs = search_startup_support(deadline_only=True)
# 상세 정보 조회
detail = get_startup_program_detail('test_001')
# 상세 정보는 목록 결과의 공식 url로 확인
```
## 데이터 소스
@ -72,7 +68,7 @@ detail = get_startup_program_detail('test_001')
### 1. 공공데이터포털
- **기관**: 중소벤처기업부
- **API**: 스타트업 지원사업 정보
- **인증**: API 키 필요 (DATA_GO_KR_API_KEY)
- **인증**: hosted/self-host proxy 운영 서버에서 API 키 주입
### 2. 지자체별 사이트
- **서울시**: https://seoulstartup.go.kr

View file

@ -428,9 +428,7 @@ node scripts/korean_character_count.js --text $'첫 줄\n둘째 줄🙂' --profi
startup-support 스킬은 다음과 같은 환경이 필요합니다:
#### 환경 변수
```bash
export DATA_GO_KR_API_KEY="your_api_key_here"
```
기본 사용자는 별도 API 키가 필요 없습니다. `DATA_GO_KR_API_KEY` 는 hosted/self-host `k-skill-proxy` 운영자가 서버에 설정하는 값입니다.
#### 의존성
- Python 3.7+

View file

@ -89,10 +89,9 @@ KSKILL_PROXY_BASE_URL=
### startup-support
#### API 키 관리
- **환경 변수**: `DATA_GO_KR_API_KEY`
- **용도**: 공공데이터포털 API 인증
- **보안**: 절대 코드에 하드코딩하지 않음
- **관리**: 환경 변수 또는 시크릿 매니저를 통해 관리
- 기본 사용자는 `DATA_GO_KR_API_KEY` 를 설정하지 않습니다.
- `DATA_GO_KR_API_KEY` 는 hosted/self-host `k-skill-proxy` 운영자가 서버에 설정하는 upstream 키입니다.
- 로컬에서 직접 공공데이터포털 API를 호출하는 실험 경로에서만 사용자 환경에 임시로 둘 수 있습니다.
#### 데이터 보안
- **데이터 소스**: 공공기관 공식 API만 사용

View file

@ -65,12 +65,7 @@ metadata:
### Proxy Integration
API 요청은 `k-skill-proxy``/v1/startup-support/*` 라우트로 중계되며, 다음과 같은 엔드포인트를 사용:
- `/v1/startup-support/list` - 지원사업 목록 조회
- `/v1/startup-support/detail/<program_id>` - 특정 지원사업 상세 정보
- `/v1/startup-support/region/<region>` - 특정 지역 지원사업 조회
- `/v1/startup-support/deadline` - 임박 마감 지원사업
공공데이터포털 K-Startup OpenAPI는 `kstartup-search` 스킬과 `k-skill-proxy``/v1/kstartup/*` 라우트가 담당한다. 이 스킬의 helper는 지역별 공개 API 목록을 조회하고, 상세 정보는 결과의 공식 `url` 로 확인한다.
## Output format

View file

@ -30,8 +30,8 @@ class StartupSupportAPI:
'Content-Type': 'application/json'
}
def search_programs(self, region: str = '전국', keyword: str = None,
support_type: str = None, deadline_only: bool = False) -> List[Dict]:
def search_programs(self, region: str = '전국', keyword: Optional[str] = None,
support_type: Optional[str] = None, deadline_only: bool = False) -> List[Dict]:
"""
지원사업 검색
@ -67,7 +67,7 @@ class StartupSupportAPI:
return programs
def _search_data_go_kr(self, region: str, keyword: str, support_type: str) -> List[Dict]:
def _search_data_go_kr(self, region: str, keyword: Optional[str], support_type: Optional[str]) -> List[Dict]:
"""공공데이터포털 API로 검색"""
programs = []
@ -105,7 +105,7 @@ class StartupSupportAPI:
return programs
def _search_by_region(self, region: str, keyword: str, support_type: str) -> List[Dict]:
def _search_by_region(self, region: str, keyword: Optional[str], support_type: Optional[str]) -> List[Dict]:
"""지자체별 API로 검색"""
programs = []
@ -133,9 +133,11 @@ class StartupSupportAPI:
}
}
# 해당 지역 API 호출
if region in region_apis:
api_info = region_apis[region]
target_regions = list(region_apis) if region == '전국' else [region]
for target_region in target_regions:
if target_region not in region_apis:
continue
api_info = region_apis[target_region]
try:
params = {}
@ -153,12 +155,12 @@ class StartupSupportAPI:
# 실제 API 응답 구조에 따라 데이터 추출
if 'programs' in data:
for item in data['programs']:
program = self._parse_program_from_region_api(item, region)
program = self._parse_program_from_region_api(item, target_region)
if program:
programs.append(program)
except Exception as e:
print(f"{region} API 오류: {e}")
print(f"{target_region} API 오류: {e}")
return programs
@ -275,74 +277,14 @@ class StartupSupportAPI:
def _get_data_go_kr_detail(self, program_id: str) -> Optional[Dict]:
"""공공데이터포털 상세 정보 조회"""
# 실 구현에서는 program_id를 사용해 상세 API 호출
return {
'id': program_id,
'title': '상세 정보 조회 예시',
'organization': '중소벤처기업부',
'region': '전국',
'support_type': '보조금',
'amount': '최대 1억원',
'deadline': '2024-12-31',
'target': '중소기업 창업자',
'requirements': [
'사업자등록증',
'사업계획서',
'재무제표',
'창업자 신분증'
],
'application_process': [
'온라인 신청서 작성',
'서류 제출',
'서류 심사',
'현장 면접',
'결공고'
],
'contact': {
'phone': '02-1234-5678',
'email': 'support@smbs.or.kr',
'address': '서울시 강남구 테헤란로 123'
},
'url': 'https://www.data.go.kr/program/detail',
'source': '공공데이터포털',
'last_updated': datetime.now().strftime('%Y-%m-%d')
}
return None
def _get_region_detail(self, program_id: str) -> Optional[Dict]:
"""지자체 상세 정보 조회"""
# 실 구현에서는 program_id를 사용해 상세 API 호출
return {
'id': program_id,
'title': '지자체 상세 정보 조회 예시',
'organization': '서울시 창업진흥원',
'region': '서울특별시',
'support_type': '보조금',
'amount': '최대 5천만원',
'deadline': '2024-12-31',
'target': '서울시 내 스타트업',
'requirements': [
'사업자등록증',
'사업계획서',
'재무제표'
],
'application_process': [
'온라인 신청서 작성',
'서류 제출',
'서류 심사',
'결공고'
],
'contact': {
'phone': '02-1234-5678',
'email': 'startup@seoul.go.kr',
'address': '서울시 강남구 테헤란로 123'
},
'url': 'https://seoulstartup.go.kr/program/detail',
'source': '서울시 창업진흥원',
'last_updated': datetime.now().strftime('%Y-%m-%d')
}
return None
def search_startup_support(region: str = '전국', keyword: str = None,
support_type: str = None, deadline_only: bool = False) -> List[Dict]:
def search_startup_support(region: str = '전국', keyword: Optional[str] = None,
support_type: Optional[str] = None, deadline_only: bool = False) -> List[Dict]:
"""
스타트업 지원사업 검색 함수

View file

@ -8,7 +8,7 @@ from datetime import datetime, timedelta
# 현재 디렉토리에서 모듈 임포트
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from startup_support import search_startup_support, get_startup_program_detail
from startup_support import search_startup_support, get_startup_program_detail, StartupSupportAPI
class TestStartupSupport(unittest.TestCase):
"""스타트업 지원사업 API 테스트"""
@ -64,6 +64,44 @@ class TestStartupSupport(unittest.TestCase):
self.assertEqual(result[0]['title'], '경기도 MVP 지원사업')
self.assertEqual(result[1]['title'], '서울시 청년 스타트업 창업 지원금')
@patch('startup_support.requests.get')
def test_nationwide_search_aggregates_configured_regions_without_api_key(self, mock_get):
payloads = {
'https://seoulstartup.go.kr/api/program/list': {
'programs': [{
'id': 'seoul_1',
'title': '서울 창업 지원',
'deadline': '2026-06-03',
}]
},
'https://g-startup.kr/api/support/list': {
'programs': [{
'id': 'gyeonggi_1',
'title': '경기 창업 지원',
'deadline': '2026-06-04',
}]
},
}
def fake_get(url, **_):
response = MagicMock()
response.status_code = 200
response.json.return_value = payloads.get(url, {'programs': []})
return response
mock_get.side_effect = fake_get
with patch.dict(os.environ, {}, clear=True):
result = search_startup_support(region='전국')
titles = {program['title'] for program in result}
self.assertIn('서울 창업 지원', titles)
self.assertIn('경기 창업 지원', titles)
def test_builtin_detail_lookup_does_not_return_fabricated_sample_data(self):
self.assertIsNone(get_startup_program_detail('data_gov_missing'))
self.assertIsNone(get_startup_program_detail('서울_missing'))
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
@patch('startup_support.StartupSupportAPI._search_by_region')
def test_search_programs_seoul_only(self, mock_region_search, mock_data_go_kr_search):
@ -139,8 +177,6 @@ class TestStartupSupport(unittest.TestCase):
def test_parse_program_from_data_go_kr(self):
"""공공데이터포털 데이터 파싱 테스트"""
from startup_support import StartupSupportAPI
api = StartupSupportAPI()
# 테스트 데이터