diff --git a/docs/decisions/0006-funding-discovery-automation.md b/docs/decisions/0006-funding-discovery-automation.md new file mode 100644 index 0000000..6a1149f --- /dev/null +++ b/docs/decisions/0006-funding-discovery-automation.md @@ -0,0 +1,287 @@ +# ADR 0006: 정책자금 후보는 자동으로 올리고 내리고, 지원사업은 자동으로 찾는다 + +- Status: Accepted +- Date: 2026-09-28 +- Owners: jhjh0512 (자동 규칙·수집 출처·차단 목록 책임자) +- Related issue/PR: #5, #13, #15, #33, #36 +- Supersedes: 없음 + +## Context + +자금 후보 기능은 동작하지만 보여 줄 후보가 없다. 2026-09-25 기준 카탈로그 `2026-09-23.1`의 상태는 다음과 같다. + +- **현재 후보 0건:** 상품 5개 모두 검수자가 `UNASSIGNED`라서, 어떤 계획에서도 현재 후보가 되지 않는다(AGENTS.md §3의 8번). +- **상환 계산 가능 0건:** 대출 3개 모두 금리가 확정되지 않았다. +- **곧 기한 경과:** 서울시 중소기업육성자금 3개의 재검수일이 2026-10-20이다. 나머지 둘은 이미 기한이 지났거나 접수가 끝났다. +- **병목은 사람의 시간:** 운영자가 기관 사이트를 돌며 공고를 찾고, JSON에 옮겨 적고, 기한마다 다시 확인한다. 이 방식으로는 5개도 기한 안에 유지하지 못하고 있다. + +자금 상품은 성격이 다른 두 갈래이고, 자동으로 가져올 수 있는 출처도 다르다. 아래 출처는 2026-09-28에 실제 응답과 robots.txt·약관을 확인했다. + +### 지원사업 (지원금·공간·프로그램): 공식 Open API가 있다 + +- **K-Startup 공고 정보**(창업진흥원, `apis.data.go.kr/B552735/kisedKstartupService01`) + - 기존 공공데이터포털 키로 호출된다. 모집 중 필터(`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`) + - 요청 한 번(`searchCnt`)으로 현재 공고 1,554건을 모두 받는다. 공고 URL은 모두 `www.bizinfo.go.kr`이다. + - 신청기간(`reqstBeginEndDe`)의 84%는 `YYYY-MM-DD ~ YYYY-MM-DD`다. 나머지는 "예산 소진시까지", "상시 접수", "세부사업별 상이", "모집 완료시" 같은 문장이다. + - 지역은 공고명 앞의 `[서울]` 같은 표시나 소관기관(`jrsdInsttNm`)으로 알 수 있다. + - 한계: 지원대상(`trgetNm`)이 "중소기업"·"소상공인"·"창업벤처" 같은 단순 태그라서 사업 단계를 구조적으로 알 수 없다. + - 대신 중앙부처와 지자체 공고를 함께 모은다. 지원분야 "금융" 235건에 서울시 육성자금 변경공고, 자치구 육성기금(중구·서초·관악·용산), 소진공 정책자금 변경공고가 있다. 두 갈래 모두에서 새 공고를 찾는 데 유용하다. + +### 정책자금 (대출·보증): 공식 API가 없다 + +예비 음식점 창업자에게 핵심인 정책자금은 소상공인시장진흥공단 정책자금과 서울시 중소기업육성자금이다. 두 기관 모두 공고·접수 상태·조건을 웹 페이지로만 공개한다. + +- **소진공 정책자금 사이트 `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가 모든 경로를 막는다. + +관련 공공데이터는 다음 셋이 있지만, 어느 것도 이 상품들의 공고·조건을 주지 않는다. + +- **서울시 은행별 대출금리**(OA-21098): 은행별 실행 금리 통계다. +- **중진공 정책자금 현황**: 파일로 제공되는 연간 집행 실적이다. +- **서민금융상품기본정보**: 미소금융·햇살론 같은 서민금융 상품만 담는다. + +금리는 대부분 "정책자금 기준금리 + 가산"이나 "은행 금리 − 이차보전"처럼 기준금리에 따라 바뀌는 변동금리다. + +### 지금 판정 규칙이 자동화를 막는 곳 + +1. **검수자:** 사람 검수자가 있어야 현재 후보가 된다. +2. **업종·용도:** 둘 중 하나라도 `UNKNOWN`이면 추가 확인으로 분류된다. 그런데 자동 출처에는 두 값이 없다. +3. **ACTIVE 릴리스:** ACTIVE 릴리스는 하나뿐이다(`read.ts`). 자동 상품과 사람이 기록한 상품을 한 릴리스로 합쳐야 한다. +4. **신청 종료일:** `evaluateProduct`는 신청기간을 보지 않는다. 그래서 OPEN으로 기록한 상품은 종료일이 지나도 재검수일까지 현재 후보로 남는다. 지금은 OPEN 상품이 없어 드러나지 않았다. (#34에서 고쳤다.) + +### 지켜야 할 제약 + +- 웹 요청 중에는 외부 데이터를 가져오지 않는다(AGENTS.md §2). +- 적재는 `PENDING → ACTIVE` 검증을 거치고(§3의 9번), provenance를 보존한다(§3의 10번). +- 자료 부족이나 장애를 빈 성공으로 위장하지 않는다(§3의 2번). +- 사용자에게 승인처럼 말하지 않는다(PRODUCT.md 원칙 5). + +## Decision + +기본 원칙은 세 가지다. + +- 공식 출처에서 기계적으로 확인되는 조건은 자동으로 판정하고, 그 결과에 따라 현재 후보로 올리고 내리는 일을 사람 없이 한다. +- 확인되지 않은 조건은 추측하지 않고 "신청 전 확인"으로 표시한다. +- 확인이 끊기면 자동으로 내린다. + +### 1. 두 갈래 + +- **지원사업:** 지원 유형이 `GRANT`·`SPACE`·`PROGRAM`인 상품이다. + - 자동으로 승격하지 않는다. 기업마당으로 새 공고를 찾아 추가 확인으로 올리고, 강등(6절)만 자동으로 한다. + - 현재 후보가 되려면 사람이 확인한다(#13). + - K-Startup 공고 API는 쓰지 않는다(Alternatives). +- **정책자금:** 지원 유형이 `LOAN`·`GUARANTEE`인 상품이다. 기관 웹 페이지 크롤링으로 자동화한다. + - v1 크롤링 출처는 소진공 정책자금 사이트(`ols.semas.or.kr`) 하나다. + - 서울시 육성자금은 조건이 첨부에만 있어 사람 관리로 둔다(#13). 기업마당으로 새 공고와 변경공고를 찾는 일은 자동으로 한다. +- **적재와 표시:** 두 갈래는 한 카탈로그 릴리스에 함께 적재한다. 후보 API 응답과 화면은 두 갈래를 나눠 보여 준다. 갈래는 기존 `supportType`으로 정하므로 새 enum을 만들지 않는다. + +### 2. 동기화 명령: `funding:sync` + +운영자 명령 하나가 다음을 차례로 한다. + +1. 출처별로 공고를 받는다. +2. 출처별 규칙으로 카탈로그 상품을 만든다. +3. 사람이 관리하는 `app/catalog/funding/catalog.json`의 상품과 합친다. +4. `funding:validate`와 같은 검사를 거쳐 새 릴리스로 적재한다(`PENDING → ACTIVE`). + +세부 규칙은 다음과 같다. + +- **실행 주기와 위치:** 하루 한 번 실행한다. 정기 실행 위치는 #15에서 배포 환경과 함께 정한다. 그 전까지는 운영자가 직접 실행한다. +- **상품 식별과 재사용:** 자동 상품은 출처와 공고 식별자로 식별한다. 내용이 같으면 기존 불변 상품 버전을 재사용한다. +- **같은 공고가 두 번 있을 때:** 사람이 기록한 상품이 같은 공고를 가리키면 사람 기록이 우선한다. +- **근거와 검수자 기록:** + - 근거에는 출처 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`의 출처 목록에 기록한다. + - 금지하면 그 출처는 켜지 않고 사람 관리로 둔다. +- **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초 이상 간격을 둔다. + - User-Agent에 서비스 이름과 연락처를 밝힌다. +- **차단되면 멈춘다:** + - 403·429·CAPTCHA를 만나거나 robots.txt가 바뀌면 그 출처를 실패로 기록하고 멈춘다. + - 차단을 우회하지 않는다(IP 교체, 헤더 위장, CAPTCHA 풀이 금지). +- **읽는 범위:** + - 출처마다 전용 파서를 두고, 공고 HTML 본문과 표에서 값을 규칙으로 뽑는다. + - 첨부(PDF·HWP)는 링크와 checksum만 남기고 v1에서는 해석하지 않는다. + - AI로 본문을 해석하지 않는다. +- **보관:** 뽑은 값과 URL·checksum만 저장한다. 페이지 원문과 첨부 파일은 저장소에 넣지 않는다. + +### 4. 자동 승격 조건 + +자동 상품은 다음을 모두 만족할 때만 현재 후보가 된다. 지원사업 자동 상품은 1절에 따라 승격하지 않으므로, 이 조건은 정책자금 자동 상품에만 쓰인다. + +1. **신청기간:** 시작일과 종료일이 날짜로 확인되고, 판정 기준일이 그 사이에 있으며, 출처의 접수 상태 표시와 모순이 없다. "상시", "예산 소진 시까지"처럼 날짜가 없으면 출처의 접수 상태가 "접수 중"으로 확인될 때만 인정한다. +2. **사업 단계와 지역:** 구조화 값이나 출처별 파서의 명시적 규칙으로 PASS다. 지역은 서울 또는 전국이다. +3. **제외 신호 없음:** 제외 대상·제외 업종에 음식점·외식 등 이 제품의 업종이 들어 있지 않다. 이 제외 신호는 내리는 데만 쓴다. +4. **차단 목록:** 사람이 관리하는 차단 목록(출처·식별자·사유, `catalog.json`)에 없다. +5. **최신성:** 그 출처의 마지막 성공 동기화가 판정 기준일로부터 3일 이내다. + +**정책자금 추가 조건.** + +- 금리·한도·상환 조건을 설명하는 문장이 공고 본문에서 확인되어야 한다(`FINANCIAL_CONDITION` 근거). +- 이 조건은 현재 후보가 되기 위한 것이다. 상환 계산 가능 여부는 5절이 따로 정한다. + +**업종·용도 규칙 변경.** + +- 업종·용도가 `UNKNOWN`이어도 현재 후보를 막지 않는다. 대신 "신청 전 확인"으로 표시한다. +- 연령 조건이 있는 공고도 같은 방식으로 "신청 전 확인: 연령"을 붙인다. 계획의 자금 조건에는 나이가 없기 때문이다. +- 이 규칙은 자동 상품과 사람이 기록한 상품에 똑같이 적용한다. +- `FAIL`(명시된 제외)은 지금처럼 막는다. + +### 5. 상환 계산 가능으로의 자동 승격 (정책자금) + +- **조건:** 출처별 파서가 공고 본문에서 다음을 모두 명시적인 숫자로 뽑았을 때만 상환 계산 가능으로 올린다. + - 공개 한도 + - 단일 고정 연 금리 + - 전체 상환개월과 거치개월 + - 원리금균등 또는 원금균등 상환방식 +- **검증:** 뽑은 값은 기존 스키마의 범위 검사와 "거치 < 전체 기간" 검사를 통과해야 한다. +- **올리지 않는 경우:** 범위 금리, 변동금리(기준금리 연동), 은행별로 다른 금리는 확정 숫자가 아니다. 지금처럼 `null`로 두고 "상환 계산 대상 아님 + 사유"를 표시한다. 따라서 소진공·서울시 정책자금 대부분은 자동화 뒤에도 상환 계산 대상이 아니다. + - 소진공 표의 "고정금리(2.0%P)"처럼 연 금리인지 가산폭인지 모호한 표기도 확정 숫자로 보지 않는다. + - 소진공 표에는 상환방식이 없으므로, 표만으로는 상환 계산 가능으로 올리지 않는다. +- **범위 밖:** 변동금리 상품의 상환 계산은 계산 계약의 변경이다. 필요하면 기준금리 관측값을 쓰는 방식을 별도 ADR로 다룬다. +- **사용자 적용 절차:** 상품 조건을 계획에 적용하는 일은 지금처럼 사용자가 명시적으로 실행할 때만 일어난다(§3의 3번). + +### 6. 자동 강등 + +다음은 사람 없이 적용한다. + +- **신청 종료:** 신청 종료일이 판정 기준일보다 앞서면, 관측 상태가 OPEN이어도 CLOSED로 판정한다. 사람이 기록한 상품에도 적용한다. +- **출처 변경:** 출처가 접수 마감을 표시하면 CLOSED로 바꾼다. 예산 소진으로 조기 마감한 경우도 포함한다. 공고가 사라지거나 내용이 바뀌면 다음 동기화에서 다시 판정한다. +- **제외 신호:** 제외 신호가 새로 보이면 추가 확인으로 내린다. +- **최신성 초과:** 출처의 마지막 성공이 3일을 넘으면 그 출처의 자동 상품은 현재 후보와 상환 계산 가능에서 빠진다. + +### 7. 사람의 역할과 스위치 + +- **사람이 하는 일:** + - 자동 규칙, 수집 출처와 그 허용 확인, 차단 목록을 관리한다. + - 오판 신고를 처리한다. +- **사람이 기록한 상품:** 지금처럼 `nextReviewAt` 규칙을 따른다. +- **스위치 두 개:** 켜고 끌 수 있는 스위치를 따로 둔다. 지원사업은 자동 승격하지 않으므로 스위치가 없다. + - 정책자금 자동 승격 + - 정책자금 상환 계산 자동 승격 + - 어느 것을 꺼도 강등은 계속 동작한다. +- **화면 표시:** 자동 상품에는 "자동 확인" 표시, 마지막 확인 시각, 출처, 원 공고 링크, 신청 전 확인 항목을 함께 보여 준다. 후보는 검토 시작점이지 선정·승인이 아니라는 안내를 유지한다. + +### 8. 범위 밖 + +- 공고문 본문·첨부를 AI로 해석하는 일. PRODUCT.md의 "자동 공고 해석" 제외는 "AI 기반 공고 해석"으로 좁혀서 유지한다. +- 첨부(PDF·HWP) 해석, 로그인이 필요한 페이지, 차단 우회. +- K-Startup 공고 API, 소상공인24 내부 JSON API. +- 웹 요청 중의 외부 조회. + +### 9. 구현 PR에서 함께 고칠 것 + +아래 문서와 설정은 해당 동작을 구현하는 PR에서 함께 고친다(AGENTS.md §7). 이 결정만으로는 바꾸지 않는다. + +- **AGENTS.md §3의 8번:** "미검수"를 "사람 검수 또는 ADR 0006의 자동 확인을 거치지 않은"으로 고친다. 업종·용도·연령 `UNKNOWN`의 처리를 이 ADR에 연결한다. +- **`docs/PRODUCT.md`:** 자금 후보 행을 지원사업·정책자금 두 갈래로 나누고, 제외 범위를 고친다. +- **`docs/domains/funding.md`:** 동기화·승격·강등 규칙과 수집 출처 목록(허용 확인 기록 포함)을 적는다. +- **`docs/DEVELOPMENT.md`:** 기업마당 키 `BIZINFO_API_KEY`와 `funding:sync`를 적는다. +- **공식 출처 호스트 목록:** 더할 호스트가 없다. `ols.semas.or.kr`은 기존 `semas.or.kr`로 통과하고, 소상공인24는 쓰지 않으므로 `sbiz24.kr`을 더하지 않는다. +- **HTML 파서 의존성:** 추가한다면 `app/package.json` 단일 PR 규칙을 따른다. + +## Alternatives considered + +- **사람 검수만 유지(#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은 기능을 줄이기보다 운영을 자동화하는 쪽을 제안한다. + +## Consequences + +- **장점:** + - 정책자금은 사람 없이 현재 후보가 될 수 있다. 지원사업은 새 공고가 자동으로 모인다. + - 마감·조기 마감·변경이 날마다 반영된다. + - 출처가 멈추면 그 출처의 후보가 저절로 내려간다. +- **예비 창업자에게 주는 효과는 작다:** + - 소진공 대출은 모두 사업자등록 이후 대상이라, 예비 창업자 프로필에서는 사업자등록 이후 그룹에 나온다. + - 지원사업은 사람이 확인해야 현재 후보가 된다. +- **소진공 접수 상태:** 표에는 접수 상태가 없어, 4절 1번(신청기간)은 공지 상세 HTML의 접수 안내로 채운다. + - 공지를 찾으려면 목록 JSON을 읽어야 한다. 담당자는 2026-09-28에 상세를 찾는 용도로만 읽도록 허용했다(3절). + - 목록 JSON의 형식이 바뀌면 파서가 필수 항목을 찾지 못한 경우로 보고 그 출처를 실패로 기록한다(2절). +- **자동 오판:** 첨부에만 음식점 제외가 적힌 공고가 현재 후보로 보일 수 있다. 다음 네 가지로 줄인다. + - 신청 전 확인 표시 + - 제외 신호 강등 + - 차단 목록 + - 표본 점검과 스위치(Validation) +- **크롤링 위험:** + - 기관 사이트 구조가 바뀌면 파서가 깨진다. 이때 그 출처는 실패로 남고, 3일 뒤 그 출처의 후보가 빠진다. 파서 유지보수 비용이 계속 든다. + - 이용약관이 바뀌면 그 출처를 끈다. +- **현재 후보의 의미 변화:** 업종·용도·연령을 확인하지 않은 상품도 현재 후보가 된다. 화면이 이 차이를 분명히 보여 주어야 한다. +- **상환 계산의 한계:** 변동금리 상품이 대부분이라, 상환 계산 가능 상품은 자동화 뒤에도 적을 수 있다. 소진공 표에는 상환방식이 없어 표만으로는 0건이다. +- **migration·호환성:** + - 카탈로그 상품에 출처 식별자를 추가하고, 카탈로그에 차단 목록을 추가하고, `schemaVersion`을 올린다. + - 릴리스에 출처별 동기화 결과를 저장한다(필요하면 새 migration). + - 릴리스는 하루 하나씩 늘어난다(연 약 365개). +- **운영·보안·데이터 영향:** + - 키는 `app/.env.local`과 운영 비밀값에만 둔다. + - 웹 요청은 DB만 읽는다. + - 판정 규칙이 바뀌므로 같은 상품의 후보 상태가 규칙 변경 전후로 다를 수 있다. 자금 후보는 저장하지 않는 조회 결과라, 과거 계산 결과는 바뀌지 않는다. + - 대출 가정으로 이미 적용된 상품 조건은 `loanAssumptionJson`에 출처와 함께 남는다. + +## Validation + +- **판정 단위 테스트:** + - 신청 종료일이 지나면 OPEN 상품도 CLOSED다. + - 업종·용도·연령 `UNKNOWN`은 신청 전 확인으로 표시되고, `FAIL`은 막는다. + - 자동 승격 조건을 하나씩 어기면 현재 후보가 되지 않는다. + - 지원사업 자동 상품은 조건을 모두 채워도 현재 후보가 되지 않는다. + - 3일 넘게 성공하지 못한 출처의 상품은 현재 후보에서 빠진다. +- **정책자금 파서 테스트:** + - 출처별 HTML 조각 fixture로 필수 항목 추출을 검사한다. + - 필수 항목이 없는 페이지는 실패로 처리된다. + - 범위 금리·변동금리·"은행별 상이"·"고정금리(2.0%P)" 같은 모호한 표기는 상환 계산 가능으로 올라가지 않는다. +- **동기화 테스트:** + - 출처 하나가 실패해도 그 출처의 직전 상품이 유지되고 다른 출처는 갱신된다. + - 403·429를 만나면 그 출처가 멈춘다. + - 사람 기록이 자동 상품보다 우선한다. + - 차단 목록의 공고는 승격되지 않는다. +- **첫 실행 기록:** 첫 실제 실행 뒤 출처별 받은 공고 수, 자동 상품 수, 갈래별 현재 후보 수, 상환 계산 가능 수를 이 ADR에 기록한다. +- **표본 점검:** 첫 4주 동안 매주 정책자금 자동 현재 후보 전부를 원문과 대조한다. +- **중단 조건:** + - 한 주라도 정책자금 오판이 표본의 20%를 넘으면 정책자금 자동 승격 스위치를 끈다. + - 상환 계산 가능으로 올린 상품에서 금리·기간·한도 오추출이 한 건이라도 나오면, 상환 계산 자동 승격 스위치를 즉시 끈다.