From 861cc4da96fd02de2917ebd187fac8fdf90637a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 08:10:15 +0000 Subject: [PATCH 1/7] docs(funding): propose automating funding discovery and recheck Draft ADR 0006 (Proposed) for issue #5. Discovery from the K-Startup and Bizinfo Open APIs produces catalog drafts with reviewer UNASSIGNED; demotions (application end date passed, source changed, removed or closed) apply automatically; promotion to a current candidate still needs a human-reviewed catalog release. AI interpretation of announcement text stays out of scope. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015LCE4DgT5qdHP8y8V5qtbL --- .../0006-funding-discovery-automation.md | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 docs/decisions/0006-funding-discovery-automation.md diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md new file mode 100644 index 0000000..1276618 --- /dev/null +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -0,0 +1,121 @@ +# ADR 0006: 자금 공고 탐색과 재확인을 자동화하고 승격만 사람이 승인 + +- Status: Proposed +- Date: 2026-09-25 +- Owners: 미정 (Accepted 전에 검수 책임자와 함께 지정) +- Related issue/PR: #5, #13, #15 +- Supersedes: 없음 + +## Context + +자금 후보 기능은 동작하지만 보여 줄 후보가 없다. 2026-09-25 기준 카탈로그 `2026-09-23.1`의 상태는 다음과 같다. + +- **현재 후보 0건:** 상품 5개 모두 검수자가 `UNASSIGNED`라서, 어떤 계획에서도 현재 후보가 되지 않는다(AGENTS.md §3의 8번). +- **상환 계산 가능 0건:** 대출 3개 모두 금리가 확정되지 않아(`interestRateConfirmed=false`) 계획의 대출 가정으로 적용할 수 없다. +- **곧 기한 경과:** 서울시 중소기업육성자금 3개의 재검수일이 2026-10-20이다. 강북창업지원센터는 이미 기한이 지났고, 예비창업패키지는 접수가 끝났다. +- **병목은 사람의 시간:** 운영자가 기관 사이트를 돌며 공고를 찾고, 필드를 JSON에 옮겨 적고, 기한마다 다시 확인한다. 검수 경고(검수자 미지정·기한 경과·1차 원문 아님)는 이미 `funding:validate`에 있다. 없는 것은 이 일을 계속할 사람이다. + +공고를 구조화된 형태로 주는 공식 Open API가 두 개 있다. 아래 항목은 공개된 이용 사례에서 확인한 것이며, 실제 응답은 구현 전에 다시 확인한다. + +- **K-Startup 공고 정보**(창업진흥원, `apis.data.go.kr/B552735/kisedKstartupService01`, `getAnnouncementInformation01`) + - 주는 값: 공고명, 접수 시작·종료일, 지원 지역, 신청 대상, 창업 업력, 모집 진행 여부, 상세 페이지 URL. 하루 한 번 갱신된다. + - 키: 공공데이터포털 인증키(지금 `SEMAS_SERVICE_KEY`)를 쓰며, 이 API에 대한 활용신청이 따로 필요하다. + - 한계: 지역 조건을 서버가 적용하지 않고 다른 지역 공고가 섞여 오는 사례가 보고됐다. +- **기업마당 지원사업정보**(중소벤처기업부, `www.bizinfo.go.kr/uss/rss/bizinfoApi.do`) + - 주는 값: 공고 ID, 공고명, 소관·수행기관, 신청기간(`YYYYMMDD ~ YYYYMMDD` 문자열), 지원대상(자유 서술), 사업개요, 공고 URL, 분야·지역 해시태그. 중앙부처와 지자체 공고를 함께 모은다. + - 키: 기업마당에서 발급하는 별도 인증키가 필요하다. + +두 API 모두 금리·한도·상환기간·거치·업종 제한·자금 용도를 구조화된 값으로 주지 않는다. 이 조건은 공고문 본문과 첨부(PDF·HWP)에만 있다. + +현재 판정에는 빈틈이 하나 있다. `evaluateProduct`는 신청기간을 보지 않는다. 그래서 검수를 마치고 OPEN으로 기록한 상품은 신청 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. + +지켜야 할 제약은 다음과 같다. + +- 웹 요청 중에는 외부 데이터를 가져오지 않는다(AGENTS.md §2). +- 미검수·종료·근거 부족 상품은 현재 후보로 표시하지 않는다(§3의 8번). +- 적재는 `PENDING → ACTIVE` 검증을 거치고(§3의 9번), provenance를 보존한다(§3의 10번). +- `docs/PRODUCT.md`는 "자동 공고 해석"을 제외 범위로 둔다. + +## Decision + +기본 원칙: **상품을 "덜 현재적인" 상태로 바꾸는 강등은 자동으로 적용한다. 현재 후보나 상환 계산 가능 상태로 올리는 승격은 사람이 승인해야만 일어난다.** + +### 1. 자동 탐색: `funding:discover` + +- 운영자 명령이 K-Startup과 기업마당에서 공고를 받는다. 그중 제품 범위에 들 가능성이 있는 공고만 검수 대기 목록으로 만든다. 기준은 지역이 서울 또는 전국이고, 대상이 예비 창업자·소상공인·초기 창업기업인 공고다. +- 공고마다 구조화 필드로 채울 수 있는 값만 채운 카탈로그 상품 초안을 만든다. 형식은 기존 `fundingProductSchema`를 그대로 따른다. + - 채우는 값: 이름, 기관, 공식 URL, 신청기간, 접수 상태. K-Startup이 구조화해 주는 경우에는 사업 단계와 지역도 채운다. + - 근거: `retrievalMethod: OFFICIAL_API`로 기록하고, 출처 API·공고 ID·조회일·응답 checksum을 남긴다. + - 채우지 않는 값: 금리·한도·상환·업종·용도는 `null` 또는 `UNKNOWN`으로 둔다. + - `reviewer`는 `UNASSIGNED`로 둔다. 그래서 초안은 기존 규칙대로 현재 후보가 되지 않는다. +- 자유 서술 필드(지원대상, 사업개요)는 키워드로 분류해 검수자에게 "힌트"로만 보여 준다. 조건 판정(PASS·FAIL)의 근거로는 쓰지 않는다. +- 이미 카탈로그에 있는 상품은 출처 공고 ID로 맞춰 보고, 바뀐 필드만 보여 준다. +- 초안은 파일로 출력한다. 검수자가 초안을 `app/catalog/funding/catalog.json`에 옮기는 PR이 검수 기록이 된다. 새 DB 테이블이나 관리자 화면은 만들지 않는다. + +### 2. 자동 강등: 사람 없이 적용 + +1. **판정 시점 날짜 규칙(순수 함수):** + - 신청 종료일이 판정 기준일보다 앞서면, 관측 상태가 OPEN이어도 CLOSED로 판정한다. + - 신청 시작일이 판정 기준일보다 뒤면 현재 후보가 아니다. +2. **출처 재확인: `funding:recheck`** + - 운영자 명령이 ACTIVE 카탈로그의 상품을 출처 공고 ID로 다시 조회한다. + - 결과는 `SAME`·`CHANGED`·`REMOVED`·`CLOSED`·`FAILED` 중 하나로, append-only 재확인 기록에 남긴다. 기록에는 상품 버전, 확인 시각, 출처, 응답 checksum을 함께 둔다. + - 판정은 상품별 가장 최근 기록을 읽는다. `CLOSED`면 CLOSED, `CHANGED`·`REMOVED`면 추가 확인으로 강등한다. +3. **실패는 강등 근거가 아니다:** + - API 장애·인증 오류·빈 응답은 `FAILED`로 기록하고 판정에 쓰지 않는다. 빈 응답을 "공고 없음"(`REMOVED`)으로 바꾸지 않는다(§3의 2번). + - 재확인 기록이 없거나 전부 `FAILED`면 강등하지 않는다. 이때는 기존 검수 기한 규칙(`nextReviewAt`)이 안전망이다. + +### 3. 승격: 사람 승인 + +- 현재 후보가 되려면 지금처럼 검수자가 지정된 카탈로그 버전이 `funding:load`로 ACTIVE가 되어야 한다. 판정 규칙(`eligibility.ts`)의 승격 조건은 바꾸지 않는다. +- 검수자가 하는 일은 두 가지다. + - 초안에 자동으로 채운 값을 공식 원문과 대조한다. + - 자동으로 채울 수 없는 조건(업종·용도·금리·한도·상환)을 원문에서 확인해 기록한다. +- `OFFICIAL_API` 근거는 지금처럼 1차 근거로 인정한다. 단, 구조화 필드에서 온 값에만 해당하며, 자유 서술 필드의 키워드 분류는 근거가 아니다. +- **검수 주기:** + - 자동 재확인: 주 1회 이상. + - 사람 재검수: 재확인이 `CHANGED`·`REMOVED`를 알릴 때마다, 그리고 적어도 90일마다(`nextReviewAt`은 `reviewedAt`에서 90일 이내). +- **검수 책임자:** 이 ADR을 Accepted로 바꾸기 전에 이름을 적는다. 비어 있으면 Accepted로 바꾸지 않는다. + +### 4. 범위 밖 + +- 공고문 본문·첨부를 AI로 해석해 조건을 확정하는 일. `docs/PRODUCT.md`의 "자동 공고 해석" 제외는 "공고문 본문의 자동 해석"으로 좁혀서 유지한다. 초안 작성 보조로 쓰는 방안은 필요해지면 별도 ADR로 다룬다. +- 기관 웹사이트 스크래핑. Open API가 있는 두 출처부터 시작한다. +- 사람 승인 없는 승격, 웹 요청 중의 외부 조회, 공고 원문 파일의 저장소 보관. +- 정기 실행 위치(배포 플랫폼 cron, GitHub Actions 예약 실행 등)와 결과 전달 방식. #15에서 배포 환경과 함께 정한다. 그 전까지는 운영자가 두 명령을 직접 실행한다. + +## Alternatives considered + +- **사람 검수만 유지(#5의 선택지 1):** 규칙 변경은 없다. 하지만 병목이 사람의 시간이라 상품 수를 늘릴 수 없고, 5개도 기한 안에 유지하지 못하고 있다. +- **검수 관리 화면 구현(#5의 선택지 3):** 관리자 역할이라는 새 권한 모델이 필요하다. 지금 인증에는 역할이 없다. 카탈로그 PR이 이미 변경 내용·검수자·시점을 남기므로, 화면 없이도 검수 기록이 된다. +- **공식 API 필드만으로 사람 없이 승격:** 지원대상은 자유 서술이고, 지역 조건을 서버가 적용하지 않는 사례도 있어 잘못 판정할 위험이 있다. 게다가 업종·용도는 API에 없어서 어차피 `UNKNOWN`으로 남으므로, 현재 후보가 되지 않는다. +- **AI로 공고문을 해석해 금리·기간을 채움:** 한 번 잘못 읽으면 곧바로 틀린 상환액이 된다(§3의 1·8번). 근거 없는 정밀도를 만들지 않는다는 원칙(PRODUCT.md 원칙 7)과도 맞지 않는다. +- **출시에서 숨기거나 베타로 표시(#5의 선택지 4):** 이 ADR은 기능을 줄이기보다 운영 비용을 낮추는 쪽을 제안한다. 첫 검수가 끝나기 전에 출시하게 되면 그때 화면 처리를 #15 출시 점검에서 정한다. + +## Consequences + +- **장점:** + - 검수자의 일이 "찾고 옮겨 적기"에서 "원문과 대조하고 승인하기"로 줄어든다. + - 마감·변경·공고 삭제가 사람 없이 판정에 반영되어, 종료된 상품을 현재 후보로 보여 줄 위험이 줄어든다. +- **비용·위험:** + - 외부 API 두 개에 의존한다. API가 멈추면 탐색이 멈추고, 재확인은 `FAILED`로 남아 기존 기한 규칙으로 돌아간다. + - 인증키가 하나(기업마당) 늘어난다. + - 사람 검수가 없으면 현재 후보는 여전히 0건이다. 자동화가 검수 책임자를 대신하지 않는다. +- **migration·호환성:** + - 카탈로그 상품에 출처 공고 식별자(출처와 공고 ID) 필드를 추가하고 `schemaVersion`을 올린다. 식별자가 없는 기존 상품은 재확인 대상에서 빠진다. + - 재확인 기록 테이블을 새 migration으로 추가한다. 기존 릴리스·상품 버전 테이블은 바꾸지 않는다. +- **운영·보안·데이터 영향:** + - 키는 `app/.env.local`과 운영 비밀값에만 둔다. + - 웹 요청은 DB만 읽는다. + - 재확인 기록은 판정 근거이므로 지우지 않는다. + +## Validation + +- 신청 종료일이 판정 기준일보다 앞서면 OPEN 상품도 CLOSED로 판정하는 단위 테스트. +- `funding:discover` 초안이 `funding:validate`를 통과하고, `reviewer`가 `UNASSIGNED`이며, 현재 후보가 0건인지 확인하는 테스트. +- 재확인 기록 테스트: + - 최근 기록이 `CHANGED`·`REMOVED`인 상품은 현재 후보가 되지 않는다. + - `FAILED`만 있거나 기록이 없으면 판정이 바뀌지 않는다. + - API 오류와 빈 응답은 `REMOVED`로 기록되지 않는다. +- 첫 실제 실행 뒤 탐색 공고 수, 검수 초안 수, 검수 후 현재 후보 수를 이 ADR에 기록한다. +- **중단 조건:** 두 번 연속 검수 주기 동안 탐색 초안 중 검수를 통과한 상품이 0건이면 출처와 필터를 다시 검토한다. 그래도 없으면 #5의 선택지 4(출시에서 숨김)로 돌아간다. From d7a570a1cc6c0ac4f389e896176e7bbb06a32255 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 09:08:03 +0000 Subject: [PATCH 2/7] docs(funding): automate both promotion and demotion in ADR 0006 Rework the draft per the decision to automate both directions: funding:sync builds the catalog from the K-Startup and Bizinfo APIs and promotes non-loan announcements whose dates, stage and region come from structured fields. Industry and purpose UNKNOWN become pre-application checks instead of blocking. Loans and guarantees stay human-reviewed. Source failures keep the current release, and auto products drop out when syncs stop for more than three days. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015LCE4DgT5qdHP8y8V5qtbL --- .../0006-funding-discovery-automation.md | 216 +++++++++++------- 1 file changed, 138 insertions(+), 78 deletions(-) diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md index 1276618..8364f6d 100644 --- a/docs/decisions/0006-funding-discovery-automation.md +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -1,8 +1,8 @@ -# ADR 0006: 자금 공고 탐색과 재확인을 자동화하고 승격만 사람이 승인 +# ADR 0006: 자금 후보의 승격·강등을 공식 API로 자동화 - Status: Proposed - Date: 2026-09-25 -- Owners: 미정 (Accepted 전에 검수 책임자와 함께 지정) +- Owners: 미정 (자동 규칙과 차단 목록 책임자. Accepted 전에 지정) - Related issue/PR: #5, #13, #15 - Supersedes: 없음 @@ -11,111 +11,171 @@ 자금 후보 기능은 동작하지만 보여 줄 후보가 없다. 2026-09-25 기준 카탈로그 `2026-09-23.1`의 상태는 다음과 같다. - **현재 후보 0건:** 상품 5개 모두 검수자가 `UNASSIGNED`라서, 어떤 계획에서도 현재 후보가 되지 않는다(AGENTS.md §3의 8번). -- **상환 계산 가능 0건:** 대출 3개 모두 금리가 확정되지 않아(`interestRateConfirmed=false`) 계획의 대출 가정으로 적용할 수 없다. +- **상환 계산 가능 0건:** 대출 3개 모두 금리가 확정되지 않아 계획의 대출 가정으로 적용할 수 없다. - **곧 기한 경과:** 서울시 중소기업육성자금 3개의 재검수일이 2026-10-20이다. 강북창업지원센터는 이미 기한이 지났고, 예비창업패키지는 접수가 끝났다. -- **병목은 사람의 시간:** 운영자가 기관 사이트를 돌며 공고를 찾고, 필드를 JSON에 옮겨 적고, 기한마다 다시 확인한다. 검수 경고(검수자 미지정·기한 경과·1차 원문 아님)는 이미 `funding:validate`에 있다. 없는 것은 이 일을 계속할 사람이다. +- **병목은 사람의 시간:** 운영자가 기관 사이트를 돌며 공고를 찾고, JSON에 옮겨 적고, 기한마다 다시 확인한다. 이 방식으로는 5개도 기한 안에 유지하지 못하고 있다. -공고를 구조화된 형태로 주는 공식 Open API가 두 개 있다. 아래 항목은 공개된 이용 사례에서 확인한 것이며, 실제 응답은 구현 전에 다시 확인한다. +공고를 구조화된 형태로 주는 공식 Open API가 두 개 있고, 2026-09-25에 두 API 모두 활용신청했다. 아래 항목은 공개된 이용 사례에서 확인한 것이며, 실제 응답은 구현 전에 다시 확인한다. - **K-Startup 공고 정보**(창업진흥원, `apis.data.go.kr/B552735/kisedKstartupService01`, `getAnnouncementInformation01`) - - 주는 값: 공고명, 접수 시작·종료일, 지원 지역, 신청 대상, 창업 업력, 모집 진행 여부, 상세 페이지 URL. 하루 한 번 갱신된다. - - 키: 공공데이터포털 인증키(지금 `SEMAS_SERVICE_KEY`)를 쓰며, 이 API에 대한 활용신청이 따로 필요하다. - - 한계: 지역 조건을 서버가 적용하지 않고 다른 지역 공고가 섞여 오는 사례가 보고됐다. + - 주는 값: 공고명, 접수 시작·종료일, 지원 지역, 신청 대상, 창업 업력, 지원 분야, 모집 진행 여부, 상세 페이지 URL. 하루 한 번 갱신된다. + - 키: 공공데이터포털 인증키(지금 `SEMAS_SERVICE_KEY`)를 쓴다. + - 한계: 지역 조건을 서버가 적용하지 않고 다른 지역 공고가 섞여 오는 사례가 보고됐다. 요청 조건 대신 응답 항목의 지역 값으로 거른다. - **기업마당 지원사업정보**(중소벤처기업부, `www.bizinfo.go.kr/uss/rss/bizinfoApi.do`) - 주는 값: 공고 ID, 공고명, 소관·수행기관, 신청기간(`YYYYMMDD ~ YYYYMMDD` 문자열), 지원대상(자유 서술), 사업개요, 공고 URL, 분야·지역 해시태그. 중앙부처와 지자체 공고를 함께 모은다. - 키: 기업마당에서 발급하는 별도 인증키가 필요하다. -두 API 모두 금리·한도·상환기간·거치·업종 제한·자금 용도를 구조화된 값으로 주지 않는다. 이 조건은 공고문 본문과 첨부(PDF·HWP)에만 있다. +두 API 모두 업종 제한·자금 용도·금리·한도·상환기간·거치를 구조화된 값으로 주지 않는다. 이 조건은 공고문 본문과 첨부(PDF·HWP)에만 있다. -현재 판정에는 빈틈이 하나 있다. `evaluateProduct`는 신청기간을 보지 않는다. 그래서 검수를 마치고 OPEN으로 기록한 상품은 신청 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. +지금 판정 규칙(`eligibility.ts`)으로는 자동화된 상품이 현재 후보가 될 수 없다. 막는 곳은 네 군데다. + +1. **검수자:** 사람 검수자가 있어야 현재 후보가 된다. +2. **업종·용도:** 둘 중 하나라도 `UNKNOWN`이면 추가 확인으로 분류된다. 그런데 API에는 두 값이 없다. +3. **대출·보증:** 금융 조건의 1차 근거가 있어야 현재 후보가 된다. +4. **ACTIVE 릴리스:** ACTIVE 릴리스는 하나뿐이다(`read.ts`). 따라서 자동으로 모은 상품과 사람이 기록한 상품을 한 릴리스로 합쳐야 한다. + +현재 판정에는 빈틈도 하나 있다. `evaluateProduct`는 신청기간을 보지 않는다. 그래서 OPEN으로 기록한 상품은 신청 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. 지켜야 할 제약은 다음과 같다. - 웹 요청 중에는 외부 데이터를 가져오지 않는다(AGENTS.md §2). -- 미검수·종료·근거 부족 상품은 현재 후보로 표시하지 않는다(§3의 8번). - 적재는 `PENDING → ACTIVE` 검증을 거치고(§3의 9번), provenance를 보존한다(§3의 10번). -- `docs/PRODUCT.md`는 "자동 공고 해석"을 제외 범위로 둔다. +- 자료 부족이나 장애를 빈 성공으로 위장하지 않는다(§3의 2번). +- 사용자에게 승인처럼 말하지 않는다(PRODUCT.md 원칙 5). ## Decision -기본 원칙: **상품을 "덜 현재적인" 상태로 바꾸는 강등은 자동으로 적용한다. 현재 후보나 상환 계산 가능 상태로 올리는 승격은 사람이 승인해야만 일어난다.** - -### 1. 자동 탐색: `funding:discover` - -- 운영자 명령이 K-Startup과 기업마당에서 공고를 받는다. 그중 제품 범위에 들 가능성이 있는 공고만 검수 대기 목록으로 만든다. 기준은 지역이 서울 또는 전국이고, 대상이 예비 창업자·소상공인·초기 창업기업인 공고다. -- 공고마다 구조화 필드로 채울 수 있는 값만 채운 카탈로그 상품 초안을 만든다. 형식은 기존 `fundingProductSchema`를 그대로 따른다. - - 채우는 값: 이름, 기관, 공식 URL, 신청기간, 접수 상태. K-Startup이 구조화해 주는 경우에는 사업 단계와 지역도 채운다. - - 근거: `retrievalMethod: OFFICIAL_API`로 기록하고, 출처 API·공고 ID·조회일·응답 checksum을 남긴다. - - 채우지 않는 값: 금리·한도·상환·업종·용도는 `null` 또는 `UNKNOWN`으로 둔다. - - `reviewer`는 `UNASSIGNED`로 둔다. 그래서 초안은 기존 규칙대로 현재 후보가 되지 않는다. -- 자유 서술 필드(지원대상, 사업개요)는 키워드로 분류해 검수자에게 "힌트"로만 보여 준다. 조건 판정(PASS·FAIL)의 근거로는 쓰지 않는다. -- 이미 카탈로그에 있는 상품은 출처 공고 ID로 맞춰 보고, 바뀐 필드만 보여 준다. -- 초안은 파일로 출력한다. 검수자가 초안을 `app/catalog/funding/catalog.json`에 옮기는 PR이 검수 기록이 된다. 새 DB 테이블이나 관리자 화면은 만들지 않는다. - -### 2. 자동 강등: 사람 없이 적용 - -1. **판정 시점 날짜 규칙(순수 함수):** - - 신청 종료일이 판정 기준일보다 앞서면, 관측 상태가 OPEN이어도 CLOSED로 판정한다. - - 신청 시작일이 판정 기준일보다 뒤면 현재 후보가 아니다. -2. **출처 재확인: `funding:recheck`** - - 운영자 명령이 ACTIVE 카탈로그의 상품을 출처 공고 ID로 다시 조회한다. - - 결과는 `SAME`·`CHANGED`·`REMOVED`·`CLOSED`·`FAILED` 중 하나로, append-only 재확인 기록에 남긴다. 기록에는 상품 버전, 확인 시각, 출처, 응답 checksum을 함께 둔다. - - 판정은 상품별 가장 최근 기록을 읽는다. `CLOSED`면 CLOSED, `CHANGED`·`REMOVED`면 추가 확인으로 강등한다. -3. **실패는 강등 근거가 아니다:** - - API 장애·인증 오류·빈 응답은 `FAILED`로 기록하고 판정에 쓰지 않는다. 빈 응답을 "공고 없음"(`REMOVED`)으로 바꾸지 않는다(§3의 2번). - - 재확인 기록이 없거나 전부 `FAILED`면 강등하지 않는다. 이때는 기존 검수 기한 규칙(`nextReviewAt`)이 안전망이다. - -### 3. 승격: 사람 승인 - -- 현재 후보가 되려면 지금처럼 검수자가 지정된 카탈로그 버전이 `funding:load`로 ACTIVE가 되어야 한다. 판정 규칙(`eligibility.ts`)의 승격 조건은 바꾸지 않는다. -- 검수자가 하는 일은 두 가지다. - - 초안에 자동으로 채운 값을 공식 원문과 대조한다. - - 자동으로 채울 수 없는 조건(업종·용도·금리·한도·상환)을 원문에서 확인해 기록한다. -- `OFFICIAL_API` 근거는 지금처럼 1차 근거로 인정한다. 단, 구조화 필드에서 온 값에만 해당하며, 자유 서술 필드의 키워드 분류는 근거가 아니다. -- **검수 주기:** - - 자동 재확인: 주 1회 이상. - - 사람 재검수: 재확인이 `CHANGED`·`REMOVED`를 알릴 때마다, 그리고 적어도 90일마다(`nextReviewAt`은 `reviewedAt`에서 90일 이내). -- **검수 책임자:** 이 ADR을 Accepted로 바꾸기 전에 이름을 적는다. 비어 있으면 Accepted로 바꾸지 않는다. - -### 4. 범위 밖 - -- 공고문 본문·첨부를 AI로 해석해 조건을 확정하는 일. `docs/PRODUCT.md`의 "자동 공고 해석" 제외는 "공고문 본문의 자동 해석"으로 좁혀서 유지한다. 초안 작성 보조로 쓰는 방안은 필요해지면 별도 ADR로 다룬다. -- 기관 웹사이트 스크래핑. Open API가 있는 두 출처부터 시작한다. -- 사람 승인 없는 승격, 웹 요청 중의 외부 조회, 공고 원문 파일의 저장소 보관. -- 정기 실행 위치(배포 플랫폼 cron, GitHub Actions 예약 실행 등)와 결과 전달 방식. #15에서 배포 환경과 함께 정한다. 그 전까지는 운영자가 두 명령을 직접 실행한다. +기본 원칙은 세 가지다. + +- 공식 API의 구조화 값으로 확인되는 조건은 자동으로 판정하고, 그 결과에 따라 현재 후보로 올리고 내리는 일을 사람 없이 한다. +- 구조화 값이 없는 조건은 추측하지 않고 "신청 전 확인"으로 표시한다. +- 확인이 끊기면 자동으로 내린다. + +### 1. 동기화 명령: `funding:sync` + +운영자 명령 하나가 다음을 차례로 한다. + +1. K-Startup과 기업마당에서 공고를 받는다. +2. 자동 규칙으로 카탈로그 상품을 만든다. +3. 사람이 관리하는 `app/catalog/funding/catalog.json`의 상품과 합친다. +4. `funding:validate`와 같은 검사를 거쳐 새 릴리스로 적재한다(`PENDING → ACTIVE`). + +세부 규칙은 다음과 같다. + +- **실행 주기:** 하루 한 번 실행한다(K-Startup의 갱신 주기). +- **실행 위치:** 정기 실행 위치(배포 플랫폼 cron, GitHub Actions 예약 실행 등)는 #15에서 배포 환경과 함께 정한다. 그 전까지는 운영자가 직접 실행한다. +- **상품 식별과 재사용:** 자동 상품은 출처와 공고 ID로 식별한다. 내용이 같으면 기존 불변 상품 버전을 재사용하므로 상품 버전이 날마다 늘지 않는다. +- **같은 공고가 두 번 있을 때:** 사람이 기록한 상품이 같은 출처 공고를 가리키면 사람 기록이 우선한다. +- **근거와 검수자 기록:** + - 자동 상품의 근거는 `retrievalMethod: OFFICIAL_API`이고, 출처 API·공고 ID·조회 시각·응답 checksum을 남긴다. + - `reviewer`에는 사람 이름 대신 자동 규칙 버전(예: `AUTO:funding-auto-v1`)을 적는다. 규칙을 바꾸면 버전을 올린다. + +### 2. 자동 승격 조건 + +자동 상품은 다음을 모두 만족할 때만 현재 후보가 된다. + +1. **지원 유형:** 구조화된 지원 분야에서 지원금·공간·프로그램(`GRANT`·`SPACE`·`PROGRAM`)으로 정해진다. 대출·보증은 자동 승격하지 않는다(4절). +2. **신청기간:** 시작일과 종료일이 모두 날짜로 확인되고, 판정 기준일이 그 사이에 있으며, 출처의 모집 진행 표시와 모순이 없다. "상시", "예산 소진 시까지" 같은 문구는 날짜가 아니므로 승격하지 않는다. +3. **사업 단계와 지역:** 구조화 값으로 PASS다. 지역은 서울 또는 전국만 해당한다. 자치구 한정 공고는 API로 구분할 수 없으므로, 서울 공고는 지금처럼 "서울 대상, 자치구 추가 확인"으로 표시한다. +4. **제외 신호 없음:** 지원대상·사업개요 자유 서술에 제외 신호가 없다. 예를 들어 음식점·외식·유흥 제외, 제조·기술 등 특정 업종 한정 같은 키워드다. 키워드는 내리는 데만 쓰고, 올리는 근거로는 쓰지 않는다. +5. **차단 목록:** 사람이 관리하는 차단 목록에 없다. +6. **최신성:** 마지막으로 성공한 동기화가 판정 기준일로부터 3일 이내다. + +### 3. 업종·용도 규칙 변경 + +- 업종·용도가 `UNKNOWN`이어도 현재 후보를 막지 않는다. 대신 "신청 전 확인: 업종 제한과 자금 용도는 공고문에서 확인하세요"를 후보에 붙인다. +- 이 규칙은 자동 상품과 사람이 기록한 상품에 똑같이 적용한다. +- `FAIL`(명시된 제외)은 지금처럼 막는다. 사업 단계·지역·신청기간이 `UNKNOWN`이면 지금처럼 막는다. + +### 4. 대출·보증과 상환 계산 + +- 대출·보증은 금리·한도가 판단의 핵심인데, 구조화 출처에 없다. 그래서 자동 승격하지 않고 추가 확인으로 둔다. +- 사람이 금융 조건 근거를 기록하면 기존 규칙대로 현재 후보가 되고, 조건이 확정되면 상환 계산도 할 수 있다. +- "상환 계산 가능"으로의 자동 승격은 이 ADR의 범위가 아니다. + +### 5. 자동 강등 + +다음은 사람 없이 적용한다. + +- **신청 종료:** 신청 종료일이 판정 기준일보다 앞서면, 관측 상태가 OPEN이어도 CLOSED로 판정한다. 사람이 기록한 상품에도 적용한다. +- **출처 변경:** 출처가 모집 종료를 표시하면 CLOSED로 바꾼다. 공고가 사라지거나 내용이 바뀌면 다음 동기화에서 다시 판정한다. +- **제외 신호:** 제외 신호가 새로 보이면 추가 확인으로 내린다. +- **최신성 초과:** 동기화가 3일 넘게 성공하지 않으면 자동 상품은 현재 후보에서 빠진다. 파이프라인이 멈추면 후보가 저절로 내려가는 안전장치다. + +### 6. 실패 처리 + +- **새 릴리스를 만들지 않는 경우:** 출처 하나라도 장애·인증 오류이거나, 빈 응답이거나, 받은 공고 수가 직전 성공 대비 절반 미만으로 줄면 새 릴리스를 만들지 않는다. 이때 기존 ACTIVE를 유지한다(§3의 9번). +- **"공고 없음"으로 해석하지 않는다:** 실패를 "공고 없음"으로 해석해 상품을 지우지 않는다. +- **실패가 이어질 때:** 최신성 규칙(5절)에 따라 자동 상품은 3일 뒤 현재 후보에서 빠진다. + +### 7. 사람의 역할과 표시 + +- **사람이 하는 일:** + - 자동 규칙과 차단 목록을 관리한다. 차단 목록에는 출처·공고 ID·사유를 적고 `catalog.json`에 둔다. + - 대출·보증의 금융 조건을 기록한다. + - 오판 신고를 처리한다. +- **사람이 기록한 상품:** 지금처럼 `nextReviewAt` 규칙을 따른다. +- **자동 승격 끄기:** 스위치를 하나 둔다. 끄면 자동 상품은 추가 확인으로만 표시되고, 강등은 계속 동작한다. +- **화면 표시:** 자동 상품에는 "자동 확인" 표시, 마지막 확인 시각, 출처 API, 원 공고 링크, 신청 전 확인 항목을 함께 보여 준다. 후보는 검토 시작점이지 선정·승인이 아니라는 안내를 유지한다. + +### 8. 범위 밖 + +- 공고문 본문·첨부를 AI로 해석해 조건을 확정하는 일. `docs/PRODUCT.md`의 "자동 공고 해석" 제외는 "공고문 본문의 자동 해석"으로 좁혀서 유지한다. +- 기관 웹사이트 스크래핑. +- 웹 요청 중의 외부 조회, 공고 원문 파일의 저장소 보관. + +### 9. Accepted 뒤 함께 고칠 문서 + +- AGENTS.md §3의 8번: "미검수"를 "사람 검수 또는 ADR 0006의 자동 확인을 거치지 않은"으로 고친다. 업종·용도 `UNKNOWN`의 처리를 이 ADR에 연결한다. +- `docs/PRODUCT.md`: 자금 후보 행과 제외 범위를 고친다. +- `docs/domains/funding.md`: 동기화·승격·강등 규칙을 적는다. +- `docs/DEVELOPMENT.md`: 기업마당 키 `BIZINFO_API_KEY`와 `funding:sync`를 적는다. ## Alternatives considered -- **사람 검수만 유지(#5의 선택지 1):** 규칙 변경은 없다. 하지만 병목이 사람의 시간이라 상품 수를 늘릴 수 없고, 5개도 기한 안에 유지하지 못하고 있다. -- **검수 관리 화면 구현(#5의 선택지 3):** 관리자 역할이라는 새 권한 모델이 필요하다. 지금 인증에는 역할이 없다. 카탈로그 PR이 이미 변경 내용·검수자·시점을 남기므로, 화면 없이도 검수 기록이 된다. -- **공식 API 필드만으로 사람 없이 승격:** 지원대상은 자유 서술이고, 지역 조건을 서버가 적용하지 않는 사례도 있어 잘못 판정할 위험이 있다. 게다가 업종·용도는 API에 없어서 어차피 `UNKNOWN`으로 남으므로, 현재 후보가 되지 않는다. -- **AI로 공고문을 해석해 금리·기간을 채움:** 한 번 잘못 읽으면 곧바로 틀린 상환액이 된다(§3의 1·8번). 근거 없는 정밀도를 만들지 않는다는 원칙(PRODUCT.md 원칙 7)과도 맞지 않는다. -- **출시에서 숨기거나 베타로 표시(#5의 선택지 4):** 이 ADR은 기능을 줄이기보다 운영 비용을 낮추는 쪽을 제안한다. 첫 검수가 끝나기 전에 출시하게 되면 그때 화면 처리를 #15 출시 점검에서 정한다. +- **사람 검수만 유지(#5의 선택지 1):** 규칙은 바뀌지 않는다. 하지만 병목이 사람의 시간이라 상품 수를 늘릴 수 없다. +- **자동 탐색 + 사람 승인으로 승격(이 ADR의 첫 초안):** 사람의 일은 줄지만, 병목이 그대로 남는다. 검수 책임자가 없으면 현재 후보가 계속 0건이다. +- **검수 관리 화면 구현(#5의 선택지 3):** 관리자 역할이라는 새 권한 모델이 필요하다. 사람 검수를 전제로 하므로 병목이 남는다. +- **업종·용도 `UNKNOWN`은 계속 막기:** 두 API에 업종·용도가 없으므로 자동 승격이 되어도 현재 후보는 0건이다. 결과적으로 선택지 1과 같다. +- **AI로 공고문을 해석해 업종·용도·금리를 채움:** 한 번 잘못 읽으면 틀린 자격 판정이나 틀린 상환액이 된다(§3의 1·8번, PRODUCT.md 원칙 7). 필요해지면 별도 ADR로 다룬다. +- **출시에서 숨기거나 베타로 표시(#5의 선택지 4):** 이 ADR은 기능을 줄이기보다 운영을 자동화하는 쪽을 제안한다. ## Consequences - **장점:** - - 검수자의 일이 "찾고 옮겨 적기"에서 "원문과 대조하고 승인하기"로 줄어든다. - - 마감·변경·공고 삭제가 사람 없이 판정에 반영되어, 종료된 상품을 현재 후보로 보여 줄 위험이 줄어든다. + - 사람 없이 현재 후보가 생긴다. + - 마감·변경·삭제가 날마다 반영된다. + - 파이프라인이 멈추면 후보가 저절로 내려간다. - **비용·위험:** - - 외부 API 두 개에 의존한다. API가 멈추면 탐색이 멈추고, 재확인은 `FAILED`로 남아 기존 기한 규칙으로 돌아간다. - - 인증키가 하나(기업마당) 늘어난다. - - 사람 검수가 없으면 현재 후보는 여전히 0건이다. 자동화가 검수 책임자를 대신하지 않는다. + - **자동 오판:** 첨부 공고문에만 음식점 제외가 적혀 있으면, 그 공고가 현재 후보로 보일 수 있다. 다음 네 가지로 줄인다. + - 업종 "신청 전 확인" 표시 + - 제외 신호 강등 + - 차단 목록 + - 표본 점검과 자동 승격 스위치(Validation) + - **현재 후보의 의미 변화:** 업종·용도를 확인하지 않은 상품도 현재 후보가 된다. 화면이 이 차이를 분명히 보여 주어야 한다. + - **외부 API 의존:** 외부 API 두 개에 의존하고, 인증키가 하나(기업마당) 늘어난다. - **migration·호환성:** - - 카탈로그 상품에 출처 공고 식별자(출처와 공고 ID) 필드를 추가하고 `schemaVersion`을 올린다. 식별자가 없는 기존 상품은 재확인 대상에서 빠진다. - - 재확인 기록 테이블을 새 migration으로 추가한다. 기존 릴리스·상품 버전 테이블은 바꾸지 않는다. + - 카탈로그 상품에 출처 공고 식별자를 추가하고, 카탈로그에 차단 목록을 추가하고, `schemaVersion`을 올린다. + - 릴리스·상품 버전 테이블은 그대로 쓴다. 최신성은 릴리스 기준일로 판단하므로 새 테이블이 필요 없다. + - 릴리스는 하루 하나씩 늘어난다(연 약 365개). - **운영·보안·데이터 영향:** - 키는 `app/.env.local`과 운영 비밀값에만 둔다. - 웹 요청은 DB만 읽는다. - - 재확인 기록은 판정 근거이므로 지우지 않는다. + - 판정 규칙이 바뀌므로, 같은 상품이라도 규칙 변경 전후의 후보 상태가 다를 수 있다. + - 자금 후보는 저장하지 않는 조회 결과라, 과거 계산 결과는 바뀌지 않는다. ## Validation -- 신청 종료일이 판정 기준일보다 앞서면 OPEN 상품도 CLOSED로 판정하는 단위 테스트. -- `funding:discover` 초안이 `funding:validate`를 통과하고, `reviewer`가 `UNASSIGNED`이며, 현재 후보가 0건인지 확인하는 테스트. -- 재확인 기록 테스트: - - 최근 기록이 `CHANGED`·`REMOVED`인 상품은 현재 후보가 되지 않는다. - - `FAILED`만 있거나 기록이 없으면 판정이 바뀌지 않는다. - - API 오류와 빈 응답은 `REMOVED`로 기록되지 않는다. -- 첫 실제 실행 뒤 탐색 공고 수, 검수 초안 수, 검수 후 현재 후보 수를 이 ADR에 기록한다. -- **중단 조건:** 두 번 연속 검수 주기 동안 탐색 초안 중 검수를 통과한 상품이 0건이면 출처와 필터를 다시 검토한다. 그래도 없으면 #5의 선택지 4(출시에서 숨김)로 돌아간다. +- **단위 테스트:** + - 신청 종료일이 지나면 OPEN 상품도 CLOSED다. + - 업종·용도 `UNKNOWN`은 현재 후보를 막지 않고 신청 전 확인으로 표시된다. `FAIL`은 막는다. + - 자동 승격 조건 여섯 개를 각각 하나씩 어기면 현재 후보가 되지 않는다. + - 대출·보증 자동 상품은 현재 후보가 되지 않는다. + - 동기화가 3일을 넘기면 자동 상품은 현재 후보에서 빠진다. +- **동기화 테스트:** + - 출처 장애·빈 응답·급감 때 새 릴리스가 만들어지지 않고 기존 ACTIVE가 유지된다. + - 같은 공고의 사람 기록이 자동 상품보다 우선한다. + - 차단 목록의 공고는 현재 후보가 되지 않는다. +- **첫 실행 기록:** 첫 실제 실행 뒤 받은 공고 수, 자동 상품 수, 현재 후보 수를 이 ADR에 기록한다. +- **표본 점검:** 첫 4주 동안 매주 자동 현재 후보를 최대 10건씩 사람이 원문과 대조해 오판(실제로는 서울 예비 음식점·카페 창업자가 신청할 수 없는 공고)을 기록한다. +- **중단 조건:** 어느 한 주라도 오판이 표본의 20%를 넘으면 자동 승격 스위치를 끈다. 규칙을 고쳐 다시 점검할 때까지 강등만 유지한다. From 57f6d8829cc79018f897bb93f1a59ec7d4b5aee4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 09:40:51 +0000 Subject: [PATCH 3/7] docs(funding): split ADR 0006 into support programs and policy loans Support programs (grant, space, program) are automated from the K-Startup API. Policy loans and guarantees have no official API, so they are automated by crawling the SEMAS and Seoul Shinbo pages under explicit rules: robots.txt and terms checked per source, public pages only, once a day, no evasion when blocked. Repayment calculation is auto-promoted only when the page states a single fixed rate, term, grace and method; variable-rate loans stay non-calculable. Each track has its own promotion switch, and a failing source keeps its last products until they go stale after three days. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015LCE4DgT5qdHP8y8V5qtbL --- .../0006-funding-discovery-automation.md | 271 +++++++++++------- 1 file changed, 168 insertions(+), 103 deletions(-) diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md index 8364f6d..a7fdcdc 100644 --- a/docs/decisions/0006-funding-discovery-automation.md +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -1,8 +1,8 @@ -# ADR 0006: 자금 후보의 승격·강등을 공식 API로 자동화 +# ADR 0006: 지원사업과 정책자금 후보를 자동으로 올리고 내린다 - Status: Proposed - Date: 2026-09-25 -- Owners: 미정 (자동 규칙과 차단 목록 책임자. Accepted 전에 지정) +- Owners: 미정 (자동 규칙·수집 출처·차단 목록 책임자. Accepted 전에 지정) - Related issue/PR: #5, #13, #15 - Supersedes: 없음 @@ -11,32 +11,47 @@ 자금 후보 기능은 동작하지만 보여 줄 후보가 없다. 2026-09-25 기준 카탈로그 `2026-09-23.1`의 상태는 다음과 같다. - **현재 후보 0건:** 상품 5개 모두 검수자가 `UNASSIGNED`라서, 어떤 계획에서도 현재 후보가 되지 않는다(AGENTS.md §3의 8번). -- **상환 계산 가능 0건:** 대출 3개 모두 금리가 확정되지 않아 계획의 대출 가정으로 적용할 수 없다. -- **곧 기한 경과:** 서울시 중소기업육성자금 3개의 재검수일이 2026-10-20이다. 강북창업지원센터는 이미 기한이 지났고, 예비창업패키지는 접수가 끝났다. +- **상환 계산 가능 0건:** 대출 3개 모두 금리가 확정되지 않았다. +- **곧 기한 경과:** 서울시 중소기업육성자금 3개의 재검수일이 2026-10-20이다. 나머지 둘은 이미 기한이 지났거나 접수가 끝났다. - **병목은 사람의 시간:** 운영자가 기관 사이트를 돌며 공고를 찾고, JSON에 옮겨 적고, 기한마다 다시 확인한다. 이 방식으로는 5개도 기한 안에 유지하지 못하고 있다. -공고를 구조화된 형태로 주는 공식 Open API가 두 개 있고, 2026-09-25에 두 API 모두 활용신청했다. 아래 항목은 공개된 이용 사례에서 확인한 것이며, 실제 응답은 구현 전에 다시 확인한다. +자금 상품은 성격이 다른 두 갈래이고, 자동으로 가져올 수 있는 출처도 다르다. -- **K-Startup 공고 정보**(창업진흥원, `apis.data.go.kr/B552735/kisedKstartupService01`, `getAnnouncementInformation01`) - - 주는 값: 공고명, 접수 시작·종료일, 지원 지역, 신청 대상, 창업 업력, 지원 분야, 모집 진행 여부, 상세 페이지 URL. 하루 한 번 갱신된다. - - 키: 공공데이터포털 인증키(지금 `SEMAS_SERVICE_KEY`)를 쓴다. - - 한계: 지역 조건을 서버가 적용하지 않고 다른 지역 공고가 섞여 오는 사례가 보고됐다. 요청 조건 대신 응답 항목의 지역 값으로 거른다. +### 지원사업 (지원금·공간·프로그램): 공식 Open API가 있다 + +2026-09-25에 두 API 모두 활용신청했다. 아래 항목은 공개된 이용 사례에서 확인한 것이며, 실제 응답은 구현 전에 다시 확인한다. + +- **K-Startup 공고 정보**(창업진흥원, `apis.data.go.kr/B552735/kisedKstartupService01`) + - 주는 값: 공고명, 접수 시작·종료일(`pbanc_rcpt_bgng_dt`·`pbanc_rcpt_end_dt`), 모집 진행 여부(`rcrt_prgs_yn`), 지원 지역(`supt_regin`), 창업 업력(`biz_enyy`: 예비창업자, 1년 미만 …), 대상 연령(`biz_trgt_age`), 지원 분야(`supt_biz_clsfc`), 신청 대상·제외 대상 서술(`aply_trgt_ctnt`·`aply_excl_trgt_ctnt`), 상세 URL. + - 갱신: 하루 한 번 갱신된다. + - 한계: 지역 필터를 서버가 적용하지 않는 사례가 보고됐다. - **기업마당 지원사업정보**(중소벤처기업부, `www.bizinfo.go.kr/uss/rss/bizinfoApi.do`) - - 주는 값: 공고 ID, 공고명, 소관·수행기관, 신청기간(`YYYYMMDD ~ YYYYMMDD` 문자열), 지원대상(자유 서술), 사업개요, 공고 URL, 분야·지역 해시태그. 중앙부처와 지자체 공고를 함께 모은다. - - 키: 기업마당에서 발급하는 별도 인증키가 필요하다. + - 주는 값: 공고 ID·공고명·기관, 신청기간 문자열, 지원분야 대분류, 해시태그, 공고 URL. + - 한계: 지원대상(`trgetNm`)이 "중소기업" 같은 단순 태그라서 사업 단계를 구조적으로 알 수 없다. 대신 중앙부처와 지자체 공고를 함께 모으므로 새 공고를 찾는 데 유용하다. -두 API 모두 업종 제한·자금 용도·금리·한도·상환기간·거치를 구조화된 값으로 주지 않는다. 이 조건은 공고문 본문과 첨부(PDF·HWP)에만 있다. +### 정책자금 (대출·보증): 공식 API가 없다 -지금 판정 규칙(`eligibility.ts`)으로는 자동화된 상품이 현재 후보가 될 수 없다. 막는 곳은 네 군데다. +예비 음식점 창업자에게 핵심인 정책자금은 다음과 같다. 두 기관 모두 공고·접수 상태·조건을 웹 페이지로만 공개한다. -1. **검수자:** 사람 검수자가 있어야 현재 후보가 된다. -2. **업종·용도:** 둘 중 하나라도 `UNKNOWN`이면 추가 확인으로 분류된다. 그런데 API에는 두 값이 없다. -3. **대출·보증:** 금융 조건의 1차 근거가 있어야 현재 후보가 된다. -4. **ACTIVE 릴리스:** ACTIVE 릴리스는 하나뿐이다(`read.ts`). 따라서 자동으로 모은 상품과 사람이 기록한 상품을 한 릴리스로 합쳐야 한다. +- 소상공인시장진흥공단 정책자금: 소상공인24 `www.sbiz24.kr`, 신청 사이트 `ols.semas.or.kr` +- 서울시 중소기업육성자금: 서울신용보증재단 `www.seoulshinbo.co.kr`, 서울시 `news.seoul.go.kr` + +관련 공공데이터는 다음 셋이 있지만, 어느 것도 이 상품들의 공고·조건을 주지 않는다. + +- **서울시 은행별 대출금리**(OA-21098): 은행별 실행 금리 통계다. +- **중진공 정책자금 현황**: 파일로 제공되는 연간 집행 실적이다. +- **서민금융상품기본정보**: 미소금융·햇살론 같은 서민금융 상품만 담는다. -현재 판정에는 빈틈도 하나 있다. `evaluateProduct`는 신청기간을 보지 않는다. 그래서 OPEN으로 기록한 상품은 신청 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. +금리는 대부분 "정책자금 기준금리 + 가산"이나 "은행 금리 − 이차보전"처럼 기준금리에 따라 바뀌는 변동금리다. -지켜야 할 제약은 다음과 같다. +### 지금 판정 규칙이 자동화를 막는 곳 + +1. **검수자:** 사람 검수자가 있어야 현재 후보가 된다. +2. **업종·용도:** 둘 중 하나라도 `UNKNOWN`이면 추가 확인으로 분류된다. 그런데 두 API에는 두 값이 없다. +3. **ACTIVE 릴리스:** ACTIVE 릴리스는 하나뿐이다(`read.ts`). 자동 상품과 사람이 기록한 상품을 한 릴리스로 합쳐야 한다. +4. **신청 종료일:** `evaluateProduct`는 신청기간을 보지 않는다. 그래서 OPEN으로 기록한 상품은 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. + +### 지켜야 할 제약 - 웹 요청 중에는 외부 데이터를 가져오지 않는다(AGENTS.md §2). - 적재는 `PENDING → ACTIVE` 검증을 거치고(§3의 9번), provenance를 보존한다(§3의 10번). @@ -47,135 +62,185 @@ 기본 원칙은 세 가지다. -- 공식 API의 구조화 값으로 확인되는 조건은 자동으로 판정하고, 그 결과에 따라 현재 후보로 올리고 내리는 일을 사람 없이 한다. -- 구조화 값이 없는 조건은 추측하지 않고 "신청 전 확인"으로 표시한다. +- 공식 출처에서 기계적으로 확인되는 조건은 자동으로 판정하고, 그 결과에 따라 현재 후보로 올리고 내리는 일을 사람 없이 한다. +- 확인되지 않은 조건은 추측하지 않고 "신청 전 확인"으로 표시한다. - 확인이 끊기면 자동으로 내린다. -### 1. 동기화 명령: `funding:sync` +### 1. 두 갈래 + +- **지원사업:** 지원 유형이 `GRANT`·`SPACE`·`PROGRAM`인 상품이다. 공식 Open API(K-Startup)로 자동화한다. +- **정책자금:** 지원 유형이 `LOAN`·`GUARANTEE`인 상품이다. 기관 웹 페이지 크롤링으로 자동화한다. +- **적재와 표시:** 두 갈래는 한 카탈로그 릴리스에 함께 적재한다. 후보 API 응답과 화면은 두 갈래를 나눠 보여 준다. 갈래는 기존 `supportType`으로 정하므로 새 enum을 만들지 않는다. + +### 2. 동기화 명령: `funding:sync` 운영자 명령 하나가 다음을 차례로 한다. -1. K-Startup과 기업마당에서 공고를 받는다. -2. 자동 규칙으로 카탈로그 상품을 만든다. +1. 출처별로 공고를 받는다. +2. 출처별 규칙으로 카탈로그 상품을 만든다. 3. 사람이 관리하는 `app/catalog/funding/catalog.json`의 상품과 합친다. 4. `funding:validate`와 같은 검사를 거쳐 새 릴리스로 적재한다(`PENDING → ACTIVE`). 세부 규칙은 다음과 같다. -- **실행 주기:** 하루 한 번 실행한다(K-Startup의 갱신 주기). -- **실행 위치:** 정기 실행 위치(배포 플랫폼 cron, GitHub Actions 예약 실행 등)는 #15에서 배포 환경과 함께 정한다. 그 전까지는 운영자가 직접 실행한다. -- **상품 식별과 재사용:** 자동 상품은 출처와 공고 ID로 식별한다. 내용이 같으면 기존 불변 상품 버전을 재사용하므로 상품 버전이 날마다 늘지 않는다. -- **같은 공고가 두 번 있을 때:** 사람이 기록한 상품이 같은 출처 공고를 가리키면 사람 기록이 우선한다. +- **실행 주기와 위치:** 하루 한 번 실행한다. 정기 실행 위치는 #15에서 배포 환경과 함께 정한다. 그 전까지는 운영자가 직접 실행한다. +- **상품 식별과 재사용:** 자동 상품은 출처와 공고 식별자로 식별한다. 내용이 같으면 기존 불변 상품 버전을 재사용한다. +- **같은 공고가 두 번 있을 때:** 사람이 기록한 상품이 같은 공고를 가리키면 사람 기록이 우선한다. - **근거와 검수자 기록:** - - 자동 상품의 근거는 `retrievalMethod: OFFICIAL_API`이고, 출처 API·공고 ID·조회 시각·응답 checksum을 남긴다. - - `reviewer`에는 사람 이름 대신 자동 규칙 버전(예: `AUTO:funding-auto-v1`)을 적는다. 규칙을 바꾸면 버전을 올린다. - -### 2. 자동 승격 조건 - -자동 상품은 다음을 모두 만족할 때만 현재 후보가 된다. - -1. **지원 유형:** 구조화된 지원 분야에서 지원금·공간·프로그램(`GRANT`·`SPACE`·`PROGRAM`)으로 정해진다. 대출·보증은 자동 승격하지 않는다(4절). -2. **신청기간:** 시작일과 종료일이 모두 날짜로 확인되고, 판정 기준일이 그 사이에 있으며, 출처의 모집 진행 표시와 모순이 없다. "상시", "예산 소진 시까지" 같은 문구는 날짜가 아니므로 승격하지 않는다. -3. **사업 단계와 지역:** 구조화 값으로 PASS다. 지역은 서울 또는 전국만 해당한다. 자치구 한정 공고는 API로 구분할 수 없으므로, 서울 공고는 지금처럼 "서울 대상, 자치구 추가 확인"으로 표시한다. -4. **제외 신호 없음:** 지원대상·사업개요 자유 서술에 제외 신호가 없다. 예를 들어 음식점·외식·유흥 제외, 제조·기술 등 특정 업종 한정 같은 키워드다. 키워드는 내리는 데만 쓰고, 올리는 근거로는 쓰지 않는다. -5. **차단 목록:** 사람이 관리하는 차단 목록에 없다. -6. **최신성:** 마지막으로 성공한 동기화가 판정 기준일로부터 3일 이내다. - -### 3. 업종·용도 규칙 변경 - -- 업종·용도가 `UNKNOWN`이어도 현재 후보를 막지 않는다. 대신 "신청 전 확인: 업종 제한과 자금 용도는 공고문에서 확인하세요"를 후보에 붙인다. + - 근거에는 출처 URL·조회 시각·응답(페이지) checksum을 남긴다. `retrievalMethod`는 API면 `OFFICIAL_API`, 크롤링이면 `OFFICIAL_WEB_PAGE`다. + - `reviewer`에는 자동 규칙 버전(예: `AUTO:funding-auto-v1`)을 적는다. 규칙을 바꾸면 버전을 올린다. +- **출처별 동기화 결과:** 릴리스에 출처마다 결과·마지막 성공 시각·checksum을 JSON으로 저장한다. 열 추가가 필요하면 새 migration으로 한다. +- **실패한 출처만 따로 처리한다:** + - 한 출처가 실패하면, 그 출처의 상품은 직전 ACTIVE 릴리스의 것을 그대로 가져온다. 마지막 성공 시각도 그대로 둔다. 다른 출처는 정상 갱신한다. + - 다음 경우는 실패로 본다: 장애·인증 오류, 빈 응답, 받은 공고 수가 직전 성공 대비 절반 미만으로 줄어든 경우, 크롤링 파서가 필수 항목을 찾지 못한 경우. + - 실패를 "공고 없음"으로 해석해 상품을 지우지 않는다. +- **기업마당의 역할:** 두 갈래 모두에서 새 공고를 찾는 데 쓴다. 기업마당만으로 확인된 상품은 사업 단계를 알 수 없으므로 추가 확인에 머문다. 사람이나 다른 출처가 확인하면 승격될 수 있다. + +### 3. 정책자금 크롤링 규칙 + +- **출처를 켜기 전:** + - robots.txt가 해당 경로를 허용하는지, 이용약관이 자동 수집을 금지하지 않는지 확인한다. + - 확인 날짜와 결과를 `docs/domains/funding.md`의 출처 목록에 기록한다. + - 금지하면 그 출처는 켜지 않고 사람 관리로 둔다. +- **수집 방식:** + - 로그인 없이 공개된 페이지만 읽는다. + - 출처당 하루 한 번, 요청 사이 1초 이상 간격을 둔다. + - User-Agent에 서비스 이름과 연락처를 밝힌다. +- **차단되면 멈춘다:** + - 403·429·CAPTCHA를 만나거나 robots.txt가 바뀌면 그 출처를 실패로 기록하고 멈춘다. + - 차단을 우회하지 않는다(IP 교체, 헤더 위장, CAPTCHA 풀이 금지). +- **읽는 범위:** + - 출처마다 전용 파서를 두고, 공고 HTML 본문과 표에서 값을 규칙으로 뽑는다. + - 첨부(PDF·HWP)는 링크와 checksum만 남기고 v1에서는 해석하지 않는다. + - AI로 본문을 해석하지 않는다. +- **보관:** 뽑은 값과 URL·checksum만 저장한다. 페이지 원문과 첨부 파일은 저장소에 넣지 않는다. + +### 4. 자동 승격 조건 + +**두 갈래 공통.** 자동 상품은 다음을 모두 만족할 때만 현재 후보가 된다. + +1. **신청기간:** 시작일과 종료일이 날짜로 확인되고, 판정 기준일이 그 사이에 있으며, 출처의 접수 상태 표시와 모순이 없다. "상시", "예산 소진 시까지"처럼 날짜가 없으면 출처의 접수 상태가 "접수 중"으로 확인될 때만 인정한다. +2. **사업 단계와 지역:** 구조화 값이나 출처별 파서의 명시적 규칙으로 PASS다. 지역은 서울 또는 전국이다. +3. **제외 신호 없음:** 제외 대상·제외 업종에 음식점·외식 등 이 제품의 업종이 들어 있지 않다. 이 제외 신호는 내리는 데만 쓴다. +4. **차단 목록:** 사람이 관리하는 차단 목록(출처·식별자·사유, `catalog.json`)에 없다. +5. **최신성:** 그 출처의 마지막 성공 동기화가 판정 기준일로부터 3일 이내다. + +**정책자금 추가 조건.** + +- 금리·한도·상환 조건을 설명하는 문장이 공고 본문에서 확인되어야 한다(`FINANCIAL_CONDITION` 근거). +- 이 조건은 현재 후보가 되기 위한 것이다. 상환 계산 가능 여부는 5절이 따로 정한다. + +**업종·용도 규칙 변경.** + +- 업종·용도가 `UNKNOWN`이어도 현재 후보를 막지 않는다. 대신 "신청 전 확인"으로 표시한다. +- 연령 조건이 있는 공고도 같은 방식으로 "신청 전 확인: 연령"을 붙인다. 계획의 자금 조건에는 나이가 없기 때문이다. - 이 규칙은 자동 상품과 사람이 기록한 상품에 똑같이 적용한다. -- `FAIL`(명시된 제외)은 지금처럼 막는다. 사업 단계·지역·신청기간이 `UNKNOWN`이면 지금처럼 막는다. +- `FAIL`(명시된 제외)은 지금처럼 막는다. -### 4. 대출·보증과 상환 계산 +### 5. 상환 계산 가능으로의 자동 승격 (정책자금) -- 대출·보증은 금리·한도가 판단의 핵심인데, 구조화 출처에 없다. 그래서 자동 승격하지 않고 추가 확인으로 둔다. -- 사람이 금융 조건 근거를 기록하면 기존 규칙대로 현재 후보가 되고, 조건이 확정되면 상환 계산도 할 수 있다. -- "상환 계산 가능"으로의 자동 승격은 이 ADR의 범위가 아니다. +- **조건:** 출처별 파서가 공고 본문에서 다음을 모두 명시적인 숫자로 뽑았을 때만 상환 계산 가능으로 올린다. + - 공개 한도 + - 단일 고정 연 금리 + - 전체 상환개월과 거치개월 + - 원리금균등 또는 원금균등 상환방식 +- **검증:** 뽑은 값은 기존 스키마의 범위 검사와 "거치 < 전체 기간" 검사를 통과해야 한다. +- **올리지 않는 경우:** 범위 금리, 변동금리(기준금리 연동), 은행별로 다른 금리는 확정 숫자가 아니다. 지금처럼 `null`로 두고 "상환 계산 대상 아님 + 사유"를 표시한다. 따라서 소진공·서울시 정책자금 대부분은 자동화 뒤에도 상환 계산 대상이 아니다. +- **범위 밖:** 변동금리 상품의 상환 계산은 계산 계약의 변경이다. 필요하면 기준금리 관측값을 쓰는 방식을 별도 ADR로 다룬다. +- **사용자 적용 절차:** 상품 조건을 계획에 적용하는 일은 지금처럼 사용자가 명시적으로 실행할 때만 일어난다(§3의 3번). -### 5. 자동 강등 +### 6. 자동 강등 다음은 사람 없이 적용한다. - **신청 종료:** 신청 종료일이 판정 기준일보다 앞서면, 관측 상태가 OPEN이어도 CLOSED로 판정한다. 사람이 기록한 상품에도 적용한다. -- **출처 변경:** 출처가 모집 종료를 표시하면 CLOSED로 바꾼다. 공고가 사라지거나 내용이 바뀌면 다음 동기화에서 다시 판정한다. +- **출처 변경:** 출처가 접수 마감을 표시하면 CLOSED로 바꾼다. 예산 소진으로 조기 마감한 경우도 포함한다. 공고가 사라지거나 내용이 바뀌면 다음 동기화에서 다시 판정한다. - **제외 신호:** 제외 신호가 새로 보이면 추가 확인으로 내린다. -- **최신성 초과:** 동기화가 3일 넘게 성공하지 않으면 자동 상품은 현재 후보에서 빠진다. 파이프라인이 멈추면 후보가 저절로 내려가는 안전장치다. - -### 6. 실패 처리 - -- **새 릴리스를 만들지 않는 경우:** 출처 하나라도 장애·인증 오류이거나, 빈 응답이거나, 받은 공고 수가 직전 성공 대비 절반 미만으로 줄면 새 릴리스를 만들지 않는다. 이때 기존 ACTIVE를 유지한다(§3의 9번). -- **"공고 없음"으로 해석하지 않는다:** 실패를 "공고 없음"으로 해석해 상품을 지우지 않는다. -- **실패가 이어질 때:** 최신성 규칙(5절)에 따라 자동 상품은 3일 뒤 현재 후보에서 빠진다. +- **최신성 초과:** 출처의 마지막 성공이 3일을 넘으면 그 출처의 자동 상품은 현재 후보와 상환 계산 가능에서 빠진다. -### 7. 사람의 역할과 표시 +### 7. 사람의 역할과 스위치 - **사람이 하는 일:** - - 자동 규칙과 차단 목록을 관리한다. 차단 목록에는 출처·공고 ID·사유를 적고 `catalog.json`에 둔다. - - 대출·보증의 금융 조건을 기록한다. + - 자동 규칙, 수집 출처와 그 허용 확인, 차단 목록을 관리한다. - 오판 신고를 처리한다. - **사람이 기록한 상품:** 지금처럼 `nextReviewAt` 규칙을 따른다. -- **자동 승격 끄기:** 스위치를 하나 둔다. 끄면 자동 상품은 추가 확인으로만 표시되고, 강등은 계속 동작한다. -- **화면 표시:** 자동 상품에는 "자동 확인" 표시, 마지막 확인 시각, 출처 API, 원 공고 링크, 신청 전 확인 항목을 함께 보여 준다. 후보는 검토 시작점이지 선정·승인이 아니라는 안내를 유지한다. +- **스위치 세 개:** 켜고 끌 수 있는 스위치를 따로 둔다. + - 지원사업 자동 승격 + - 정책자금 자동 승격 + - 정책자금 상환 계산 자동 승격 + - 어느 것을 꺼도 강등은 계속 동작한다. +- **화면 표시:** 자동 상품에는 "자동 확인" 표시, 마지막 확인 시각, 출처, 원 공고 링크, 신청 전 확인 항목을 함께 보여 준다. 후보는 검토 시작점이지 선정·승인이 아니라는 안내를 유지한다. ### 8. 범위 밖 -- 공고문 본문·첨부를 AI로 해석해 조건을 확정하는 일. `docs/PRODUCT.md`의 "자동 공고 해석" 제외는 "공고문 본문의 자동 해석"으로 좁혀서 유지한다. -- 기관 웹사이트 스크래핑. -- 웹 요청 중의 외부 조회, 공고 원문 파일의 저장소 보관. +- 공고문 본문·첨부를 AI로 해석하는 일. PRODUCT.md의 "자동 공고 해석" 제외는 "AI 기반 공고 해석"으로 좁혀서 유지한다. +- 첨부(PDF·HWP) 해석, 로그인이 필요한 페이지, 차단 우회. +- 웹 요청 중의 외부 조회. -### 9. Accepted 뒤 함께 고칠 문서 +### 9. Accepted 뒤 함께 고칠 것 -- AGENTS.md §3의 8번: "미검수"를 "사람 검수 또는 ADR 0006의 자동 확인을 거치지 않은"으로 고친다. 업종·용도 `UNKNOWN`의 처리를 이 ADR에 연결한다. -- `docs/PRODUCT.md`: 자금 후보 행과 제외 범위를 고친다. -- `docs/domains/funding.md`: 동기화·승격·강등 규칙을 적는다. -- `docs/DEVELOPMENT.md`: 기업마당 키 `BIZINFO_API_KEY`와 `funding:sync`를 적는다. +- **AGENTS.md §3의 8번:** "미검수"를 "사람 검수 또는 ADR 0006의 자동 확인을 거치지 않은"으로 고친다. 업종·용도·연령 `UNKNOWN`의 처리를 이 ADR에 연결한다. +- **`docs/PRODUCT.md`:** 자금 후보 행을 지원사업·정책자금 두 갈래로 나누고, 제외 범위를 고친다. +- **`docs/domains/funding.md`:** 동기화·승격·강등 규칙과 수집 출처 목록(허용 확인 기록 포함)을 적는다. +- **`docs/DEVELOPMENT.md`:** 기업마당 키 `BIZINFO_API_KEY`와 `funding:sync`를 적는다. +- **공식 출처 호스트 목록:** `OFFICIAL_URL_HOST_SUFFIXES`에 `sbiz24.kr`을 더한다. +- **HTML 파서 의존성:** 추가한다면 `app/package.json` 단일 PR 규칙을 따른다. ## Alternatives considered -- **사람 검수만 유지(#5의 선택지 1):** 규칙은 바뀌지 않는다. 하지만 병목이 사람의 시간이라 상품 수를 늘릴 수 없다. -- **자동 탐색 + 사람 승인으로 승격(이 ADR의 첫 초안):** 사람의 일은 줄지만, 병목이 그대로 남는다. 검수 책임자가 없으면 현재 후보가 계속 0건이다. -- **검수 관리 화면 구현(#5의 선택지 3):** 관리자 역할이라는 새 권한 모델이 필요하다. 사람 검수를 전제로 하므로 병목이 남는다. -- **업종·용도 `UNKNOWN`은 계속 막기:** 두 API에 업종·용도가 없으므로 자동 승격이 되어도 현재 후보는 0건이다. 결과적으로 선택지 1과 같다. -- **AI로 공고문을 해석해 업종·용도·금리를 채움:** 한 번 잘못 읽으면 틀린 자격 판정이나 틀린 상환액이 된다(§3의 1·8번, PRODUCT.md 원칙 7). 필요해지면 별도 ADR로 다룬다. +- **사람 검수만 유지(#5의 선택지 1):** 병목이 사람의 시간이라 상품 수를 늘릴 수 없다. +- **자동 탐색 + 사람 승인(이 ADR의 첫 초안):** 병목이 그대로 남는다. +- **지원사업만 자동화하고 정책자금은 사람 관리(두 번째 초안):** 제품의 핵심인 정책자금이 계속 비어 있거나 낡는다. +- **정책자금 대신 서민금융 API 사용:** 대출 조건을 구조화해 주지만, 서민금융 상품이라 소진공·서울시 정책자금이 아니다. +- **AI로 공고문을 해석해 조건을 채움:** 잘못 읽으면 틀린 자격 판정이나 틀린 상환액이 된다(§3의 1·8번, PRODUCT.md 원칙 7). 필요해지면 별도 ADR로 다룬다. - **출시에서 숨기거나 베타로 표시(#5의 선택지 4):** 이 ADR은 기능을 줄이기보다 운영을 자동화하는 쪽을 제안한다. ## Consequences - **장점:** - - 사람 없이 현재 후보가 생긴다. - - 마감·변경·삭제가 날마다 반영된다. - - 파이프라인이 멈추면 후보가 저절로 내려간다. -- **비용·위험:** - - **자동 오판:** 첨부 공고문에만 음식점 제외가 적혀 있으면, 그 공고가 현재 후보로 보일 수 있다. 다음 네 가지로 줄인다. - - 업종 "신청 전 확인" 표시 - - 제외 신호 강등 - - 차단 목록 - - 표본 점검과 자동 승격 스위치(Validation) - - **현재 후보의 의미 변화:** 업종·용도를 확인하지 않은 상품도 현재 후보가 된다. 화면이 이 차이를 분명히 보여 주어야 한다. - - **외부 API 의존:** 외부 API 두 개에 의존하고, 인증키가 하나(기업마당) 늘어난다. + - 사람 없이 두 갈래 모두 현재 후보가 생긴다. + - 마감·조기 마감·변경이 날마다 반영된다. + - 출처가 멈추면 그 출처의 후보가 저절로 내려간다. +- **자동 오판:** 첨부에만 음식점 제외가 적힌 공고가 현재 후보로 보일 수 있다. 다음 네 가지로 줄인다. + - 신청 전 확인 표시 + - 제외 신호 강등 + - 차단 목록 + - 표본 점검과 스위치(Validation) +- **크롤링 위험:** + - 기관 사이트 구조가 바뀌면 파서가 깨진다. 이때 그 출처는 실패로 남고, 3일 뒤 그 출처의 후보가 빠진다. 파서 유지보수 비용이 계속 든다. + - 이용약관이 바뀌면 그 출처를 끈다. +- **현재 후보의 의미 변화:** 업종·용도·연령을 확인하지 않은 상품도 현재 후보가 된다. 화면이 이 차이를 분명히 보여 주어야 한다. +- **상환 계산의 한계:** 변동금리 상품이 대부분이라, 상환 계산 가능 상품은 자동화 뒤에도 적을 수 있다. - **migration·호환성:** - - 카탈로그 상품에 출처 공고 식별자를 추가하고, 카탈로그에 차단 목록을 추가하고, `schemaVersion`을 올린다. - - 릴리스·상품 버전 테이블은 그대로 쓴다. 최신성은 릴리스 기준일로 판단하므로 새 테이블이 필요 없다. + - 카탈로그 상품에 출처 식별자를 추가하고, 카탈로그에 차단 목록을 추가하고, `schemaVersion`을 올린다. + - 릴리스에 출처별 동기화 결과를 저장한다(필요하면 새 migration). - 릴리스는 하루 하나씩 늘어난다(연 약 365개). - **운영·보안·데이터 영향:** - 키는 `app/.env.local`과 운영 비밀값에만 둔다. - 웹 요청은 DB만 읽는다. - - 판정 규칙이 바뀌므로, 같은 상품이라도 규칙 변경 전후의 후보 상태가 다를 수 있다. - - 자금 후보는 저장하지 않는 조회 결과라, 과거 계산 결과는 바뀌지 않는다. + - 판정 규칙이 바뀌므로 같은 상품의 후보 상태가 규칙 변경 전후로 다를 수 있다. 자금 후보는 저장하지 않는 조회 결과라, 과거 계산 결과는 바뀌지 않는다. + - 대출 가정으로 이미 적용된 상품 조건은 `loanAssumptionJson`에 출처와 함께 남는다. ## Validation -- **단위 테스트:** +- **판정 단위 테스트:** - 신청 종료일이 지나면 OPEN 상품도 CLOSED다. - - 업종·용도 `UNKNOWN`은 현재 후보를 막지 않고 신청 전 확인으로 표시된다. `FAIL`은 막는다. - - 자동 승격 조건 여섯 개를 각각 하나씩 어기면 현재 후보가 되지 않는다. - - 대출·보증 자동 상품은 현재 후보가 되지 않는다. - - 동기화가 3일을 넘기면 자동 상품은 현재 후보에서 빠진다. + - 업종·용도·연령 `UNKNOWN`은 신청 전 확인으로 표시되고, `FAIL`은 막는다. + - 자동 승격 조건을 하나씩 어기면 현재 후보가 되지 않는다. + - 3일 넘게 성공하지 못한 출처의 상품은 현재 후보에서 빠진다. +- **정책자금 파서 테스트:** + - 출처별 HTML 조각 fixture로 필수 항목 추출을 검사한다. + - 필수 항목이 없는 페이지는 실패로 처리된다. + - 범위 금리·변동금리·"은행별 상이"는 상환 계산 가능으로 올라가지 않는다. - **동기화 테스트:** - - 출처 장애·빈 응답·급감 때 새 릴리스가 만들어지지 않고 기존 ACTIVE가 유지된다. - - 같은 공고의 사람 기록이 자동 상품보다 우선한다. - - 차단 목록의 공고는 현재 후보가 되지 않는다. -- **첫 실행 기록:** 첫 실제 실행 뒤 받은 공고 수, 자동 상품 수, 현재 후보 수를 이 ADR에 기록한다. -- **표본 점검:** 첫 4주 동안 매주 자동 현재 후보를 최대 10건씩 사람이 원문과 대조해 오판(실제로는 서울 예비 음식점·카페 창업자가 신청할 수 없는 공고)을 기록한다. -- **중단 조건:** 어느 한 주라도 오판이 표본의 20%를 넘으면 자동 승격 스위치를 끈다. 규칙을 고쳐 다시 점검할 때까지 강등만 유지한다. + - 출처 하나가 실패해도 그 출처의 직전 상품이 유지되고 다른 출처는 갱신된다. + - 403·429를 만나면 그 출처가 멈춘다. + - 사람 기록이 자동 상품보다 우선한다. + - 차단 목록의 공고는 승격되지 않는다. +- **첫 실행 기록:** 첫 실제 실행 뒤 출처별 받은 공고 수, 자동 상품 수, 갈래별 현재 후보 수, 상환 계산 가능 수를 이 ADR에 기록한다. +- **표본 점검:** 첫 4주 동안 매주 원문과 대조한다. + - 지원사업: 자동 현재 후보를 최대 10건. + - 정책자금: 자동 현재 후보 전부. +- **중단 조건:** + - 한 주라도 갈래별 오판이 표본의 20%를 넘으면 그 갈래의 자동 승격 스위치를 끈다. + - 상환 계산 가능으로 올린 상품에서 금리·기간·한도 오추출이 한 건이라도 나오면, 상환 계산 자동 승격 스위치를 즉시 끈다. From 1f586021b71be46d850e717e411091e29f7e5421 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 09:43:47 +0000 Subject: [PATCH 4/7] docs(funding): set jhjh0512 as ADR 0006 owner Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015LCE4DgT5qdHP8y8V5qtbL --- docs/decisions/0006-funding-discovery-automation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md index a7fdcdc..74e9e47 100644 --- a/docs/decisions/0006-funding-discovery-automation.md +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -2,7 +2,7 @@ - Status: Proposed - Date: 2026-09-25 -- Owners: 미정 (자동 규칙·수집 출처·차단 목록 책임자. Accepted 전에 지정) +- Owners: jhjh0512 (자동 규칙·수집 출처·차단 목록 책임자) - Related issue/PR: #5, #13, #15 - Supersedes: 없음 From cad48b1a92364d961f8ee19f257e7d6a654d0ace Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 05:26:24 +0000 Subject: [PATCH 5/7] docs(funding): accept ADR 0006 Mark ADR 0006 Accepted with jhjh0512 as owner, and state that the follow-up doc changes in section 9 land with the implementation PRs (AGENTS.md section 7), not with this decision. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015LCE4DgT5qdHP8y8V5qtbL --- docs/decisions/0006-funding-discovery-automation.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md index 74e9e47..3325a51 100644 --- a/docs/decisions/0006-funding-discovery-automation.md +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -1,9 +1,9 @@ # ADR 0006: 지원사업과 정책자금 후보를 자동으로 올리고 내린다 -- Status: Proposed -- Date: 2026-09-25 +- Status: Accepted +- Date: 2026-09-28 - Owners: jhjh0512 (자동 규칙·수집 출처·차단 목록 책임자) -- Related issue/PR: #5, #13, #15 +- Related issue/PR: #5, #13, #15, #33 - Supersedes: 없음 ## Context @@ -177,7 +177,10 @@ - 첨부(PDF·HWP) 해석, 로그인이 필요한 페이지, 차단 우회. - 웹 요청 중의 외부 조회. -### 9. Accepted 뒤 함께 고칠 것 +### 9. 구현 PR에서 함께 고칠 것 + +아래 문서와 설정은 해당 동작을 구현하는 PR에서 함께 고친다(AGENTS.md §7). 이 결정만으로는 바꾸지 않는다. + - **AGENTS.md §3의 8번:** "미검수"를 "사람 검수 또는 ADR 0006의 자동 확인을 거치지 않은"으로 고친다. 업종·용도·연령 `UNKNOWN`의 처리를 이 ADR에 연결한다. - **`docs/PRODUCT.md`:** 자금 후보 행을 지원사업·정책자금 두 갈래로 나누고, 제외 범위를 고친다. From 361da2659f493c9e01c2b07846541c77f5682a25 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 05:26:41 +0000 Subject: [PATCH 6/7] docs(funding): drop a stray blank line in ADR 0006 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015LCE4DgT5qdHP8y8V5qtbL --- docs/decisions/0006-funding-discovery-automation.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md index 3325a51..5a7b8f8 100644 --- a/docs/decisions/0006-funding-discovery-automation.md +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -181,7 +181,6 @@ 아래 문서와 설정은 해당 동작을 구현하는 PR에서 함께 고친다(AGENTS.md §7). 이 결정만으로는 바꾸지 않는다. - - **AGENTS.md §3의 8번:** "미검수"를 "사람 검수 또는 ADR 0006의 자동 확인을 거치지 않은"으로 고친다. 업종·용도·연령 `UNKNOWN`의 처리를 이 ADR에 연결한다. - **`docs/PRODUCT.md`:** 자금 후보 행을 지원사업·정책자금 두 갈래로 나누고, 제외 범위를 고친다. - **`docs/domains/funding.md`:** 동기화·승격·강등 규칙과 수집 출처 목록(허용 확인 기록 포함)을 적는다. From e71981a216f42a189918c244142f322d0ab7b360 Mon Sep 17 00:00:00 2001 From: jhjh0512 Date: Mon, 28 Sep 2026 19:35:15 +0900 Subject: [PATCH 7/7] docs(funding): record source checks and drop K-Startup in ADR 0006 Record the 2026-09-28 checks of real API responses, robots.txt and terms. K-Startup would surface 116 mostly unrelated current candidates for a Seoul pre-founder, so support programs are discovered through Bizinfo and promoted only by a person. Policy loans are crawled from the SEMAS policy fund site, including its notice list JSON to locate notice pages. Sbiz24's internal API is not used, and Seoul development funds stay human-managed because their terms exist only in HWP attachments. Co-Authored-By: Claude Opus 5.5 --- .../0006-funding-discovery-automation.md | 97 +++++++++++++------ 1 file changed, 68 insertions(+), 29 deletions(-) diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md index 5a7b8f8..6a1149f 100644 --- a/docs/decisions/0006-funding-discovery-automation.md +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -1,9 +1,9 @@ -# ADR 0006: 지원사업과 정책자금 후보를 자동으로 올리고 내린다 +# ADR 0006: 정책자금 후보는 자동으로 올리고 내리고, 지원사업은 자동으로 찾는다 - Status: Accepted - Date: 2026-09-28 - Owners: jhjh0512 (자동 규칙·수집 출처·차단 목록 책임자) -- Related issue/PR: #5, #13, #15, #33 +- Related issue/PR: #5, #13, #15, #33, #36 - Supersedes: 없음 ## Context @@ -15,26 +15,44 @@ - **곧 기한 경과:** 서울시 중소기업육성자금 3개의 재검수일이 2026-10-20이다. 나머지 둘은 이미 기한이 지났거나 접수가 끝났다. - **병목은 사람의 시간:** 운영자가 기관 사이트를 돌며 공고를 찾고, JSON에 옮겨 적고, 기한마다 다시 확인한다. 이 방식으로는 5개도 기한 안에 유지하지 못하고 있다. -자금 상품은 성격이 다른 두 갈래이고, 자동으로 가져올 수 있는 출처도 다르다. +자금 상품은 성격이 다른 두 갈래이고, 자동으로 가져올 수 있는 출처도 다르다. 아래 출처는 2026-09-28에 실제 응답과 robots.txt·약관을 확인했다. ### 지원사업 (지원금·공간·프로그램): 공식 Open API가 있다 -2026-09-25에 두 API 모두 활용신청했다. 아래 항목은 공개된 이용 사례에서 확인한 것이며, 실제 응답은 구현 전에 다시 확인한다. - - **K-Startup 공고 정보**(창업진흥원, `apis.data.go.kr/B552735/kisedKstartupService01`) - - 주는 값: 공고명, 접수 시작·종료일(`pbanc_rcpt_bgng_dt`·`pbanc_rcpt_end_dt`), 모집 진행 여부(`rcrt_prgs_yn`), 지원 지역(`supt_regin`), 창업 업력(`biz_enyy`: 예비창업자, 1년 미만 …), 대상 연령(`biz_trgt_age`), 지원 분야(`supt_biz_clsfc`), 신청 대상·제외 대상 서술(`aply_trgt_ctnt`·`aply_excl_trgt_ctnt`), 상세 URL. - - 갱신: 하루 한 번 갱신된다. - - 한계: 지역 필터를 서버가 적용하지 않는 사례가 보고됐다. + - 기존 공공데이터포털 키로 호출된다. 모집 중 필터(`cond[rcrt_prgs_yn::EQ]=Y`)를 서버가 적용해, 전체 30,168건 중 220건만 받으면 된다. + - 구조화된 값: 창업 업력(`biz_enyy`: "예비창업자", "1년미만" …), 지역(`supt_regin`: 서울·전국 등), 대상 연령(`biz_trgt_age`: 구간 3개), 접수 시작·종료일(`yyyyMMdd`), 지원 분야(`supt_biz_clsfc`). 상세 URL은 모두 `www.k-startup.go.kr`이다. + - 제외 대상(`aply_excl_trgt_ctnt`)은 자유 서술이다. 창업지원법의 표준 제외 업종(유흥주점업 등)이 자주 나오고, "첨부 참조"만 적힌 공고도 많다. + - 이 ADR의 승격 규칙을 그대로 적용하면, 서울 예비 창업자에게 현재 후보가 116건 나온다. 대부분 음식점 창업과 무관하다(기술 창업 보육, 해커톤, 투자 행사 등). 외식·소상공인 관련 신호가 있는 공고는 약 10건이다. + - "전국"으로 표시된 후보 96건 중 20건은 공고명·기관·신청 대상에 다른 지역이 적혀 있다(예: "경기도 거주 … 도내 창업"). 창업자가 아니라 운영기관을 모집하는 공고도 섞여 있다. - **기업마당 지원사업정보**(중소벤처기업부, `www.bizinfo.go.kr/uss/rss/bizinfoApi.do`) - - 주는 값: 공고 ID·공고명·기관, 신청기간 문자열, 지원분야 대분류, 해시태그, 공고 URL. - - 한계: 지원대상(`trgetNm`)이 "중소기업" 같은 단순 태그라서 사업 단계를 구조적으로 알 수 없다. 대신 중앙부처와 지자체 공고를 함께 모으므로 새 공고를 찾는 데 유용하다. + - 요청 한 번(`searchCnt`)으로 현재 공고 1,554건을 모두 받는다. 공고 URL은 모두 `www.bizinfo.go.kr`이다. + - 신청기간(`reqstBeginEndDe`)의 84%는 `YYYY-MM-DD ~ YYYY-MM-DD`다. 나머지는 "예산 소진시까지", "상시 접수", "세부사업별 상이", "모집 완료시" 같은 문장이다. + - 지역은 공고명 앞의 `[서울]` 같은 표시나 소관기관(`jrsdInsttNm`)으로 알 수 있다. + - 한계: 지원대상(`trgetNm`)이 "중소기업"·"소상공인"·"창업벤처" 같은 단순 태그라서 사업 단계를 구조적으로 알 수 없다. + - 대신 중앙부처와 지자체 공고를 함께 모은다. 지원분야 "금융" 235건에 서울시 육성자금 변경공고, 자치구 육성기금(중구·서초·관악·용산), 소진공 정책자금 변경공고가 있다. 두 갈래 모두에서 새 공고를 찾는 데 유용하다. ### 정책자금 (대출·보증): 공식 API가 없다 -예비 음식점 창업자에게 핵심인 정책자금은 다음과 같다. 두 기관 모두 공고·접수 상태·조건을 웹 페이지로만 공개한다. - -- 소상공인시장진흥공단 정책자금: 소상공인24 `www.sbiz24.kr`, 신청 사이트 `ols.semas.or.kr` -- 서울시 중소기업육성자금: 서울신용보증재단 `www.seoulshinbo.co.kr`, 서울시 `news.seoul.go.kr` +예비 음식점 창업자에게 핵심인 정책자금은 소상공인시장진흥공단 정책자금과 서울시 중소기업육성자금이다. 두 기관 모두 공고·접수 상태·조건을 웹 페이지로만 공개한다. + +- **소진공 정책자금 사이트 `ols.semas.or.kr`** + - "정책자금 한눈에 보기"(`/ols/man/SMAN018M/page.do`)는 서버가 그린 HTML 표다. 직접대출 6종과 대리대출 7종의 자금명·신청요건·대출기간(거치 포함)·한도·금리, 분기별 정책자금 기준금리(2026년 3분기 3.85%)가 있다. + - 금리는 대부분 "기준금리+0.6%P"처럼 분기마다 바뀌는 기준금리에 가산하는 방식이다. 고정금리 3종은 "고정금리(2.0%P)"처럼 표기되어 연 금리인지 모호하다. 상환방식 열은 없다. + - 공통 자격은 「소상공인 보호 및 지원에 관한 법률」상 소상공인(상시근로자 5인 미만 등)이다. 즉 사업자등록 이후 대상이다. + - 접수 상태는 표에 없다("접수 순서대로 처리, 예산 소진 시 마감"). 자금별 분기 접수 안내는 공지사항에 있다. + - 공지 상세(`/ols/man/SMAN052M/page.do`, 폼 POST)는 서버가 그린 HTML이다. 본문에 "□ 접수기간 : 2026. 10. 6.(화) 10:00 ~ 예산소진 시까지" 같은 문장이 있다. + - 공지 목록은 페이지 스크립트가 JSON 요청(`/ols/man/SMAN051M/search.do`)으로 불러온다. 로그인 없이 읽힌다(2026-09-28 기준 78건). + - robots.txt가 없다(404). 약관에 자동 수집을 금지하는 조항은 없다. + - 약관 13조 3항은 "허락 없이 다른 홈페이지에 사용 또는 인용"을 금지한다. 반면 같은 사이트의 통합 저작권정책은 공단이 저작재산권 전부를 가진 콘텐츠를 출처 표시 조건으로 자유이용하게 한다(저작권법 24조의2). 담당자는 2026-09-28에 출처 표시 조건으로 수집을 허용하기로 했다. +- **소상공인24 `www.sbiz24.kr`** + - 화면을 스크립트로만 그린다. 공고와 전국 정책자금 상품(서울시 육성자금 포함)을 내부 JSON API(`/api/sbr/selectLoanProduct/anonymousUser` 등)로 불러오며, 호출 전에 익명 토큰을 받는다. + - 공개 문서가 없는 내부 API이고, 약관 13조 3항도 같다. +- **서울시 중소기업육성자금** + - `news.seoul.go.kr/economy/rearing-funds`는 robots.txt가 허용하는 HTML이다. 자금 이름 목록, 서울시 직접융자는 고정금리·시중은행 협력자금은 변동금리라는 구분, 공통 자격("서울특별시 소재 사업자등록을 필한 중소기업 및 소상공인")이 있다. + - 자금별 금리·한도·상환 조건은 HWP 첨부(공고문, 별표 1~3)에만 있다. `www.seoul.go.kr`의 공고 게시물도 본문 없이 같은 첨부만 있다. + - 서울신용보증재단 `www.seoulshinbo.co.kr` 첫 화면은 포털 선택 화면이다. 소상공인 종합지원 포털 `www.seoulsbdc.or.kr`은 robots.txt가 모든 경로를 막는다. +- 소진공 본사 `www.semas.or.kr`도 robots.txt가 모든 경로를 막는다. 관련 공공데이터는 다음 셋이 있지만, 어느 것도 이 상품들의 공고·조건을 주지 않는다. @@ -47,9 +65,9 @@ ### 지금 판정 규칙이 자동화를 막는 곳 1. **검수자:** 사람 검수자가 있어야 현재 후보가 된다. -2. **업종·용도:** 둘 중 하나라도 `UNKNOWN`이면 추가 확인으로 분류된다. 그런데 두 API에는 두 값이 없다. +2. **업종·용도:** 둘 중 하나라도 `UNKNOWN`이면 추가 확인으로 분류된다. 그런데 자동 출처에는 두 값이 없다. 3. **ACTIVE 릴리스:** ACTIVE 릴리스는 하나뿐이다(`read.ts`). 자동 상품과 사람이 기록한 상품을 한 릴리스로 합쳐야 한다. -4. **신청 종료일:** `evaluateProduct`는 신청기간을 보지 않는다. 그래서 OPEN으로 기록한 상품은 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. +4. **신청 종료일:** `evaluateProduct`는 신청기간을 보지 않는다. 그래서 OPEN으로 기록한 상품은 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. (#34에서 고쳤다.) ### 지켜야 할 제약 @@ -68,8 +86,13 @@ ### 1. 두 갈래 -- **지원사업:** 지원 유형이 `GRANT`·`SPACE`·`PROGRAM`인 상품이다. 공식 Open API(K-Startup)로 자동화한다. +- **지원사업:** 지원 유형이 `GRANT`·`SPACE`·`PROGRAM`인 상품이다. + - 자동으로 승격하지 않는다. 기업마당으로 새 공고를 찾아 추가 확인으로 올리고, 강등(6절)만 자동으로 한다. + - 현재 후보가 되려면 사람이 확인한다(#13). + - K-Startup 공고 API는 쓰지 않는다(Alternatives). - **정책자금:** 지원 유형이 `LOAN`·`GUARANTEE`인 상품이다. 기관 웹 페이지 크롤링으로 자동화한다. + - v1 크롤링 출처는 소진공 정책자금 사이트(`ols.semas.or.kr`) 하나다. + - 서울시 육성자금은 조건이 첨부에만 있어 사람 관리로 둔다(#13). 기업마당으로 새 공고와 변경공고를 찾는 일은 자동으로 한다. - **적재와 표시:** 두 갈래는 한 카탈로그 릴리스에 함께 적재한다. 후보 API 응답과 화면은 두 갈래를 나눠 보여 준다. 갈래는 기존 `supportType`으로 정하므로 새 enum을 만들지 않는다. ### 2. 동기화 명령: `funding:sync` @@ -94,7 +117,7 @@ - 한 출처가 실패하면, 그 출처의 상품은 직전 ACTIVE 릴리스의 것을 그대로 가져온다. 마지막 성공 시각도 그대로 둔다. 다른 출처는 정상 갱신한다. - 다음 경우는 실패로 본다: 장애·인증 오류, 빈 응답, 받은 공고 수가 직전 성공 대비 절반 미만으로 줄어든 경우, 크롤링 파서가 필수 항목을 찾지 못한 경우. - 실패를 "공고 없음"으로 해석해 상품을 지우지 않는다. -- **기업마당의 역할:** 두 갈래 모두에서 새 공고를 찾는 데 쓴다. 기업마당만으로 확인된 상품은 사업 단계를 알 수 없으므로 추가 확인에 머문다. 사람이나 다른 출처가 확인하면 승격될 수 있다. +- **기업마당의 역할:** 두 갈래 모두에서 새 공고를 찾는 데 쓴다. 기업마당만으로 확인된 상품은 사업 단계를 알 수 없으므로 추가 확인에 머문다. 사람이 확인하면 승격될 수 있다. ### 3. 정책자금 크롤링 규칙 @@ -102,6 +125,10 @@ - robots.txt가 해당 경로를 허용하는지, 이용약관이 자동 수집을 금지하지 않는지 확인한다. - 확인 날짜와 결과를 `docs/domains/funding.md`의 출처 목록에 기록한다. - 금지하면 그 출처는 켜지 않고 사람 관리로 둔다. +- **2026-09-28 확인 결과(Context):** + - 켠다: 소진공 정책자금 사이트 `ols.semas.or.kr`. 수집한 값을 보여 줄 때 출처를 표시한다. + - 소진공 공지 목록 JSON(`/ols/man/SMAN051M/search.do`)은 공지 상세 페이지를 찾는 데만 읽는다. 로그인·토큰이 필요 없는 요청이다. 조건 값은 상세 HTML에서만 뽑는다. + - 쓰지 않는다: 소상공인24 내부 JSON API, robots.txt가 막는 `www.seoulsbdc.or.kr`·`www.semas.or.kr`. - **수집 방식:** - 로그인 없이 공개된 페이지만 읽는다. - 출처당 하루 한 번, 요청 사이 1초 이상 간격을 둔다. @@ -117,7 +144,7 @@ ### 4. 자동 승격 조건 -**두 갈래 공통.** 자동 상품은 다음을 모두 만족할 때만 현재 후보가 된다. +자동 상품은 다음을 모두 만족할 때만 현재 후보가 된다. 지원사업 자동 상품은 1절에 따라 승격하지 않으므로, 이 조건은 정책자금 자동 상품에만 쓰인다. 1. **신청기간:** 시작일과 종료일이 날짜로 확인되고, 판정 기준일이 그 사이에 있으며, 출처의 접수 상태 표시와 모순이 없다. "상시", "예산 소진 시까지"처럼 날짜가 없으면 출처의 접수 상태가 "접수 중"으로 확인될 때만 인정한다. 2. **사업 단계와 지역:** 구조화 값이나 출처별 파서의 명시적 규칙으로 PASS다. 지역은 서울 또는 전국이다. @@ -146,6 +173,8 @@ - 원리금균등 또는 원금균등 상환방식 - **검증:** 뽑은 값은 기존 스키마의 범위 검사와 "거치 < 전체 기간" 검사를 통과해야 한다. - **올리지 않는 경우:** 범위 금리, 변동금리(기준금리 연동), 은행별로 다른 금리는 확정 숫자가 아니다. 지금처럼 `null`로 두고 "상환 계산 대상 아님 + 사유"를 표시한다. 따라서 소진공·서울시 정책자금 대부분은 자동화 뒤에도 상환 계산 대상이 아니다. + - 소진공 표의 "고정금리(2.0%P)"처럼 연 금리인지 가산폭인지 모호한 표기도 확정 숫자로 보지 않는다. + - 소진공 표에는 상환방식이 없으므로, 표만으로는 상환 계산 가능으로 올리지 않는다. - **범위 밖:** 변동금리 상품의 상환 계산은 계산 계약의 변경이다. 필요하면 기준금리 관측값을 쓰는 방식을 별도 ADR로 다룬다. - **사용자 적용 절차:** 상품 조건을 계획에 적용하는 일은 지금처럼 사용자가 명시적으로 실행할 때만 일어난다(§3의 3번). @@ -164,8 +193,7 @@ - 자동 규칙, 수집 출처와 그 허용 확인, 차단 목록을 관리한다. - 오판 신고를 처리한다. - **사람이 기록한 상품:** 지금처럼 `nextReviewAt` 규칙을 따른다. -- **스위치 세 개:** 켜고 끌 수 있는 스위치를 따로 둔다. - - 지원사업 자동 승격 +- **스위치 두 개:** 켜고 끌 수 있는 스위치를 따로 둔다. 지원사업은 자동 승격하지 않으므로 스위치가 없다. - 정책자금 자동 승격 - 정책자금 상환 계산 자동 승격 - 어느 것을 꺼도 강등은 계속 동작한다. @@ -175,6 +203,7 @@ - 공고문 본문·첨부를 AI로 해석하는 일. PRODUCT.md의 "자동 공고 해석" 제외는 "AI 기반 공고 해석"으로 좁혀서 유지한다. - 첨부(PDF·HWP) 해석, 로그인이 필요한 페이지, 차단 우회. +- K-Startup 공고 API, 소상공인24 내부 JSON API. - 웹 요청 중의 외부 조회. ### 9. 구현 PR에서 함께 고칠 것 @@ -185,7 +214,7 @@ - **`docs/PRODUCT.md`:** 자금 후보 행을 지원사업·정책자금 두 갈래로 나누고, 제외 범위를 고친다. - **`docs/domains/funding.md`:** 동기화·승격·강등 규칙과 수집 출처 목록(허용 확인 기록 포함)을 적는다. - **`docs/DEVELOPMENT.md`:** 기업마당 키 `BIZINFO_API_KEY`와 `funding:sync`를 적는다. -- **공식 출처 호스트 목록:** `OFFICIAL_URL_HOST_SUFFIXES`에 `sbiz24.kr`을 더한다. +- **공식 출처 호스트 목록:** 더할 호스트가 없다. `ols.semas.or.kr`은 기존 `semas.or.kr`로 통과하고, 소상공인24는 쓰지 않으므로 `sbiz24.kr`을 더하지 않는다. - **HTML 파서 의존성:** 추가한다면 `app/package.json` 단일 PR 규칙을 따른다. ## Alternatives considered @@ -193,6 +222,11 @@ - **사람 검수만 유지(#5의 선택지 1):** 병목이 사람의 시간이라 상품 수를 늘릴 수 없다. - **자동 탐색 + 사람 승인(이 ADR의 첫 초안):** 병목이 그대로 남는다. - **지원사업만 자동화하고 정책자금은 사람 관리(두 번째 초안):** 제품의 핵심인 정책자금이 계속 비어 있거나 낡는다. +- **K-Startup 공고 API로 지원사업 자동 승격(2026-09-28까지의 결정):** + - 실제 응답에 승격 규칙을 적용하면 서울 예비 창업자의 현재 후보가 116건이고, 대부분 음식점 창업과 무관하다. + - "전국" 표시도 믿기 어렵다(후보 96건 중 20건에 다른 지역 신호). + - 외식·소상공인 관련 신호로 거르면 약 10건이 남지만, 업종 `UNKNOWN`을 막지 않는다는 4절과 충돌하고 키워드 오탐도 있다. +- **소상공인24 내부 JSON API:** 서울시 육성자금까지 구조화된 조건을 주지만, 공개 문서가 없는 내부 API이고 익명 토큰을 받아 호출해야 한다. 3절의 "공고 HTML 본문과 표"에서 값을 뽑는 방식과 다르고, 약관 13조 3항도 걸린다. - **정책자금 대신 서민금융 API 사용:** 대출 조건을 구조화해 주지만, 서민금융 상품이라 소진공·서울시 정책자금이 아니다. - **AI로 공고문을 해석해 조건을 채움:** 잘못 읽으면 틀린 자격 판정이나 틀린 상환액이 된다(§3의 1·8번, PRODUCT.md 원칙 7). 필요해지면 별도 ADR로 다룬다. - **출시에서 숨기거나 베타로 표시(#5의 선택지 4):** 이 ADR은 기능을 줄이기보다 운영을 자동화하는 쪽을 제안한다. @@ -200,9 +234,15 @@ ## Consequences - **장점:** - - 사람 없이 두 갈래 모두 현재 후보가 생긴다. + - 정책자금은 사람 없이 현재 후보가 될 수 있다. 지원사업은 새 공고가 자동으로 모인다. - 마감·조기 마감·변경이 날마다 반영된다. - 출처가 멈추면 그 출처의 후보가 저절로 내려간다. +- **예비 창업자에게 주는 효과는 작다:** + - 소진공 대출은 모두 사업자등록 이후 대상이라, 예비 창업자 프로필에서는 사업자등록 이후 그룹에 나온다. + - 지원사업은 사람이 확인해야 현재 후보가 된다. +- **소진공 접수 상태:** 표에는 접수 상태가 없어, 4절 1번(신청기간)은 공지 상세 HTML의 접수 안내로 채운다. + - 공지를 찾으려면 목록 JSON을 읽어야 한다. 담당자는 2026-09-28에 상세를 찾는 용도로만 읽도록 허용했다(3절). + - 목록 JSON의 형식이 바뀌면 파서가 필수 항목을 찾지 못한 경우로 보고 그 출처를 실패로 기록한다(2절). - **자동 오판:** 첨부에만 음식점 제외가 적힌 공고가 현재 후보로 보일 수 있다. 다음 네 가지로 줄인다. - 신청 전 확인 표시 - 제외 신호 강등 @@ -212,7 +252,7 @@ - 기관 사이트 구조가 바뀌면 파서가 깨진다. 이때 그 출처는 실패로 남고, 3일 뒤 그 출처의 후보가 빠진다. 파서 유지보수 비용이 계속 든다. - 이용약관이 바뀌면 그 출처를 끈다. - **현재 후보의 의미 변화:** 업종·용도·연령을 확인하지 않은 상품도 현재 후보가 된다. 화면이 이 차이를 분명히 보여 주어야 한다. -- **상환 계산의 한계:** 변동금리 상품이 대부분이라, 상환 계산 가능 상품은 자동화 뒤에도 적을 수 있다. +- **상환 계산의 한계:** 변동금리 상품이 대부분이라, 상환 계산 가능 상품은 자동화 뒤에도 적을 수 있다. 소진공 표에는 상환방식이 없어 표만으로는 0건이다. - **migration·호환성:** - 카탈로그 상품에 출처 식별자를 추가하고, 카탈로그에 차단 목록을 추가하고, `schemaVersion`을 올린다. - 릴리스에 출처별 동기화 결과를 저장한다(필요하면 새 migration). @@ -229,20 +269,19 @@ - 신청 종료일이 지나면 OPEN 상품도 CLOSED다. - 업종·용도·연령 `UNKNOWN`은 신청 전 확인으로 표시되고, `FAIL`은 막는다. - 자동 승격 조건을 하나씩 어기면 현재 후보가 되지 않는다. + - 지원사업 자동 상품은 조건을 모두 채워도 현재 후보가 되지 않는다. - 3일 넘게 성공하지 못한 출처의 상품은 현재 후보에서 빠진다. - **정책자금 파서 테스트:** - 출처별 HTML 조각 fixture로 필수 항목 추출을 검사한다. - 필수 항목이 없는 페이지는 실패로 처리된다. - - 범위 금리·변동금리·"은행별 상이"는 상환 계산 가능으로 올라가지 않는다. + - 범위 금리·변동금리·"은행별 상이"·"고정금리(2.0%P)" 같은 모호한 표기는 상환 계산 가능으로 올라가지 않는다. - **동기화 테스트:** - 출처 하나가 실패해도 그 출처의 직전 상품이 유지되고 다른 출처는 갱신된다. - 403·429를 만나면 그 출처가 멈춘다. - 사람 기록이 자동 상품보다 우선한다. - 차단 목록의 공고는 승격되지 않는다. - **첫 실행 기록:** 첫 실제 실행 뒤 출처별 받은 공고 수, 자동 상품 수, 갈래별 현재 후보 수, 상환 계산 가능 수를 이 ADR에 기록한다. -- **표본 점검:** 첫 4주 동안 매주 원문과 대조한다. - - 지원사업: 자동 현재 후보를 최대 10건. - - 정책자금: 자동 현재 후보 전부. +- **표본 점검:** 첫 4주 동안 매주 정책자금 자동 현재 후보 전부를 원문과 대조한다. - **중단 조건:** - - 한 주라도 갈래별 오판이 표본의 20%를 넘으면 그 갈래의 자동 승격 스위치를 끈다. + - 한 주라도 정책자금 오판이 표본의 20%를 넘으면 정책자금 자동 승격 스위치를 끈다. - 상환 계산 가능으로 올린 상품에서 금리·기간·한도 오추출이 한 건이라도 나오면, 상환 계산 자동 승격 스위치를 즉시 끈다.