창업지원 공고 × 익명 빌더 커뮤니티
Commit
요약
28일에 0에서 제품 하나를 출시했다. 정부 창업지원 공고를 매일 모아 정규화하는 파이프라인 위에 익명 빌더 커뮤니티를 얹었다. 데이터가 확실하지 않은 자리에서는 채우지 않고 비워 두는 쪽을 골랐다.
- 28일
- 11,757
- 22.1%
개요
Commit은 공공데이터를 제품 정보로 바꾸는 일을 제품의 절반으로 잡았다. 공고 API가 주는 것은 기관 표기가 1,722종으로 흩어진 원문이다. 크롤러가 매일 그것을 모아 7단계로 정규화하고, 제품은 정리된 결과만 보여준다.
그 위에 공고를 기관 계정이 올린 글처럼 다뤘다. 공고 목록과 커뮤니티 피드가 같은 골격을 쓴다. 기관 페이지는 사람의 프로필과 같은 컴포넌트로 그린다. 공고를 발견하고, 익명으로 아이디어를 검증받고, 실행을 기록하는 일이 한 타임라인에서 이어진다. 제품은 모바일 우선 PWA다. 홈 화면에 설치하면 네이티브 앱처럼 열린다.
라이브: commit.ai.kr
문제
공고를 찾는 일부터 이미 어렵다.
- 출처가 흩어져 있다 – 운영 DB 기준 기관 표기만 1,722종이다.
- 원문이 지저분하다 – 기관명 중복, 인코딩이 깨진 "?", 제목 앞의 "(재공고)(연장)" 같은 괄호.
- 금액이 없다 – 공고 API에는 지원금액이 없다. 금액은 별도의 사업정보 API에만 있다.
- 정렬이 맞지 않다 – 수집한 목록은 등록순으로 온다. 공고에서 반드시 지켜야 하는 기한은 마감일뿐이다.
- 찾은 다음이 없다 – 실명 커뮤니티에는 설익은 아이디어를 올리기 어렵다고 봤다.
핵심 결정
모르는 금액은 AI로 추정하지 않고 비워 둔다
두 API를 이어 붙이면 금액을 추정할 수는 있다. 그런데 틀린 금액 하나가 맞는 금액 여러 개보다 신뢰를 더 깎는다고 봤다. 지원금액은 사용자가 지원 여부를 결정하는 숫자다.
규칙이 확실히 매칭한 공고에만 금액을 표시한다. 11,757건 중 2,595건(22.1%)이다. 나머지는 빈칸으로 두고 원문 링크를 보낸다. 커버리지 지표는 낮아졌고, 화면에 틀린 금액은 올라가지 않는다.
공고를 '기관 계정이 올린 글'로 다룬다
공고 탐색과 커뮤니티를 따로 만들 이유가 없어진다. 사람과 기관이 같은 규칙으로 글을 쓰고, 팔로우와 저장도 같은 규칙을 따른다.
공고 목록과 피드가 같은 컴포넌트 골격을 쓴다. 기관 페이지는 프로필 헤드를 재사용한다. 팔로우·북마크·신고도 같은 방식으로 동작한다.
커뮤니티 중심에서 공고 중심으로 방향을 바꾼다
검색으로 들어오는 사람이 찾는 것은 공고였다. 커뮤니티는 사람이 있어야 성립한다. 공고는 크롤러만 있으면 성립한다. 실시간 채팅도 같은 이유로 버렸다. 동시 접속자가 없으면 빈 방이다.
멘토, 수요 검증 게이트, 빌드 스코어, 광고 삽입을 걷어냈다. 채팅은 영구 보존되는 토픽으로 바꿨다. 홈은 로그인 없이 공개하고 글쓰기 같은 행동에서만 로그인을 요구한다. 검색 색인 문제도 함께 풀렸다.
익명성을 약속이 아니라 스키마로 보장한다
운영자가 마음먹어도 실명이 드러나지 않아야 익명 커뮤니티를 믿는다. 실제로 users 테이블에 '누구나 읽기' 정책을 두면 공개 키만으로 전 회원의 이메일과 이름을 긁을 수 있었다.
모든 테이블에 행 단위 권한(RLS)을 처음부터 걸었다. 글은 작성자 식별 정보가 빠진 공개 뷰로만 읽는다. 개인정보 컬럼은 컬럼 단위 권한으로 막았다. 서로 다른 사용자 3명이 신고하면 글이 자동으로 숨고, 그다음은 사람이 삭제하거나 되살린다.
과정
리서치
결론은 "아무도 안 한다"가 아니라 통합이 비어 있다였다. K-Startup, 기업마당, 링커리어·위비티, 아웃스탠딩·플래텀, 디스콰이엇, 인디해커스·Product Hunt, 노션을 한 표에 놓고 비교했다. 공고를 모으는 곳, 창업 뉴스를 쓰는 곳, 만드는 과정을 공유하는 곳은 각각 있었다. 셋을 잇는 곳만 없었다.
데이터를 직접 열어 보고 찾은 것도 있다. 창업 기간·연령 필드는 "가능한 범위 전체"를 나열하는 경우가 많았다. 그대로 태그를 붙이면 모든 공고에 모든 태그가 붙었다.
디자인
기준은 "토스의 정보 위계 + Threads의 타임라인"이다. 파랑은 행동, 초록은 살아 있음을 뜻한다. 임의의 값은 쓰지 않는다.
- 토큰 – 타입 7단(11 / 12 / 13 / 15 / 17 / 20 / 24), 버튼 36 / 44 / 48, 라운드 8 / 12 / 16 / full, 색 토큰 15종 × 라이트·다크 2테마, Pretendard 한 가지 서체에 자간 −0.02em. 모두 설계값이다.
- 행동 색과 링크 색을 나눴다 – 강조 파랑을 바꾸자 본문 속 파란 링크가 읽히지 않았다. 버튼 색과 글 속 링크 색을 분리했다.
- 반응형 세 벌 – 768px 미만 / 768px 이상 / 1280px 이상. 가운데 콘텐츠 640px, 오른쪽 레일 360px. 모바일 탭 타깃은 36px에서 44px로 키웠다.
개발
수집과 정규화 파이프라인
Python 크롤러가 매일 공고를 모아 7단계로 정리한다. 제목은 정규식 5단계로 다듬는다. 중복된 기관명을 떼고, 깨진 인코딩을 지우고, 앞머리 괄호 묶음을 풀고, 공백을 정리하고, 같은 공고의 회차를 묶는다. 같은 규칙을 과거 데이터에도 소급 적용했다.
알림은 고유 키로 중복 발송을 막는다. 실행마다 하트비트를 남긴다. 36시간 넘게 수집이 없으면 경보가 울린다. GitHub Actions로 돌고, 추가 비용은 없다. 다중 페이지 백필만 개인 맥의 launchd에서 돈다. 맥이 꺼져 있으면 그 작업은 멈춘다.
데이터베이스와 검색
Supabase(PostgreSQL) 위에 테이블 24개, 뷰·RPC 12개. 검색은 3단계로 쌓았다: 오타에 강한 트라이그램 검색 → pgvector 의미 검색(내장 임베딩 모델 gte-small) → 자동완성과 맞춤 가중치. 그 위에 임베딩 유사도로 비슷한 공고, 제목 시리즈로 과거 같은 공고, 기관별 임베딩 중심값으로 비슷한 기관을 붙였다. 공고 상세에 카드 3개를 두니 산만해서 하나로 합쳤다. RPC가 실패하면 클라이언트 쪽 결과로 대신한다.
한계도 그대로 적는다. LLM 호출은 없다. 공고 요약과 자격 진단은 규칙 기반이다. 외부 유료 API를 썼다면 비용 때문에 검색 기능 자체를 넣지 못했을 것이다.
증명 컴포넌트
commit_logs 테이블에는 CHECK 제약을 걸었다. figma_url · github_commit_hash · image_url · link_url 중 최소 하나가 없으면 행이 저장되지 않는다. is_verified는 GENERATED 컬럼이다. Figma나 GitHub 링크가 있으면 자동으로 true가 된다. 말이 아니라 실행으로 증명하게 만드는 규칙을 DB 제약으로 박아뒀다.
로고를 세 겹으로 처리한 방법
기관 로고도 금액과 같은 규칙을 따른다. 파비콘 서비스가 빈 아이콘을 '성공'으로 돌려줬기 때문에 자동 수집을 믿지 않았다. 손으로 확인한 로고 116곳, 도메인 매칭 210곳, 핫링크가 막혀 파일로 저장한 17곳이다. 이름 앞부분 매칭으로 산하 조직까지 덮는다. 그래도 표기 1,722종 전체에는 한참 못 미친다.
규모와 시행착오
28일 동안 938개 파일이 바뀌었다(+27,927 / −6,233줄). 코드는 118개 파일 · 10,536줄, 컴포넌트 47개다. 모두 산출물 수치다. 시행착오 13건은 따로 기록했다. 피드에 4일치만 보이던 limit 문제, UTC와 한국 시간의 날짜 어긋남, 광고 스크립트가 미리 불러오기로만 들어가 소유권 확인이 실패한 문제, 흰 로고 SVG가 라이트 테마에서 사라져 결국 currentColor 기반 인라인 컴포넌트로 바꾼 일 등이다.
결과
- 11,757
- 250
- 22.1%
제품은 완성됐다. 제품이 옳은지는 아직 모른다. 안 된 것도 된 것만큼 적는다.
- 가입 14명 (실측) – 공식 봇 계정 5개, 개인 9명. 개인 9명은 대부분 본인과 지인이다.
- 사람 글 33개 대 봇 뉴스 407개 (실측) – 타임라인에 쌓인 글의 대부분은 사람이 아니라 봇 계정의 뉴스다. 빈 피드를 스타트업 뉴스로 채우기로 한 결정의 결과다.
- 매출 0원 (실측). 월 인프라 비용도 0원이다.
- CSP는 아직 Report-Only다 – 위반을 막지 않고, 수집 엔드포인트도 없어 관찰조차 되지 않는다.
사람이 개입하는 지점
- 자동 – Python 크롤러가 매일 공고를 모아 7단계로 정규화하고, 실행마다 하트비트를 남긴다. 36시간 넘게 수집이 없으면 알린다. 글쓰기·신고 같은 행동에는 종류별 레이트리밋을 걸었다.
- 사람 – 금액 매칭은 커버리지보다 정확도를 택했다. 규칙이 확신하지 못하는 공고는 숫자를 비우고 원문을 보게 했다(22.1%). 자동으로 숨은 글은 운영자가 삭제하거나 되살린다. 운영자 도구는 페이지 진입이 아니라 서버 액션마다 권한을 다시 확인한다.
- 아예 안 맡긴 것 – 런타임에 LLM을 부르지 않는다. 금액 추정도, 공고 요약도 규칙 기반이다. 틀릴 자리를 만들지 않는 편이 틀린 답을 고치는 것보다 싸다.
회고
맞았던 것
- 모르는 숫자를 비워 둔 결정. 금액 커버리지는 22.1%에서 멈췄다. 대신 화면에 있는 금액은 원문과 같다. 공공데이터를 제품 정보로 바꿀 때 가장 비싼 실수는 빈칸이 아니라 틀린 값이라고 봤다.
- 외부 의존이 죽어도 화면은 살아야 한다. 검색 RPC, 로고, 금액 매칭이 모두 실패했을 때의 모습을 먼저 설계했다.
- 디자인 시스템을 뒤에서 만든 것. 화면을 다 만든 뒤에 뽑아서 추측이 아니라 관찰이 됐다.
- 수집하지 않은 데이터는 유출되지 않는다. 익명이라는 제품 결정이 규제 비용까지 같이 낮췄다. 탈퇴 버튼은 출시 전 체크리스트에 넣었다.
틀렸던 것
- 행 단위 권한이 컬럼까지 막아 준다고 봤다. RLS는 '어떤 행'만 통제한다. '어떤 컬럼'은 GRANT의 일이다. 그 사이로 전 회원의 이메일이 열릴 수 있었다.
- 커뮤니티를 먼저 만들면 사람이 온다고 봤다. 검색으로 들어온 사람이 찾은 것은 공고였다. 커뮤니티 기능을 걷어내고 공고 중심으로 바꿨다.
- 자동 수집이 성공을 돌려주면 결과물도 맞다고 봤다. 파비콘 서비스는 빈 아이콘을 '성공'으로 응답했다. 그다음부터 로고는 손으로 확인했다.
- UX가 어긋나는 것을 화면 문제로 봤다. 대부분은 정보 구조 문제였다. 화면을 고치는 대신 소유권을 한 곳으로 모아야 풀렸다.
다음에 확인할 것
- 통합 가설. 발견과 기록을 한 타임라인에 두면 이탈이 줄어드는지, 사용자 10명에서 100명 사이에서 처음 확인할 수 있다.
- 검색의 효용. 3단 검색이 단순 텍스트 검색보다 나은지 A/B로 재야 한다. 지금은 표본이 없다.
- 금액 커버리지를 올리는 방법. 22.1%를 올리려면 K-Startup 하나뿐인 출처부터 늘려야 한다.
- 어뷰즈 대응. 레이트리밋, 자동 숨김, 차단은 만들었지만 실제 트래픽에서 시험된 적이 없다.
- 남은 빚 – 테스트 코드 0개, 마이그레이션만으로 재현되지 않는 원격 DB, Report-Only에 멈춘 CSP.