참가자격 매칭 — 기능 설계
작업일 2026-07-27~28 · 백엔드 GPTPilots_Project + 프론트 GPTPilots_Webs
상세 계획 원본은 백엔드 레포 루트 ELIGIBILITY_MATCH_PLAN.md(볼트 밖).
무엇을 만들었나
회사 프로필과 공고의 입찰 참가자격을 대조해 참여 가능 여부를 판정하는 기능. CLAUDE.md v1 스펙의 Q1 “이 공고에 우리가 적격인가?” 에 해당한다.
회사 프로필(자유서술 + 자격 체크 + 분야 + 실적)
↓
공고 요건 목록(문서당 1회 추출 후 캐시)
↓
요건별 판정: 충족 / 미충족 / 불명
↓
🟢 적격(미충족 0) · 🟡 확인필요(1~3) · 🔴 미달(4+)
판정 규칙에서 의도적으로 정한 것
- “불명”은 탈락 사유로 세지 않는다. 프로필에 언급이 없는 건 회사 잘못이 아니라 정보 부족이다. 근거 없이 불리하게 판정하지 않는다는 CLAUDE.md 신뢰성 원칙을 따름.
- 그래서 “적격”에 두 종류가 생긴다. 충족 근거가 실제로 확인된 적격과, 반박하는 게 없을 뿐인 적격(met=0). 화면에서 후자를 “적격 · 근거 부족” 으로 따로 표기한다. → 실측상 프로필에 자격을 안 적으면 이 케이스가 대다수라, 구분하지 않으면 “왜 적격인지 모르겠다”는 불신으로 직결된다.
참가자격 ≠ 적합도 (2축 분리)
화면2가 묻던 “SI/관제/데이터AI 분야 · 3년 실적 건수”는 참가자격이 아니라 적합도 신호였다. 둘은 성격이 달라 분리했다.
| 참가자격 | 적합도 | |
|---|---|---|
| 질문 | 법적으로 참여 가능한가 | 이 회사 특기와 맞는가 |
| 판정 | 충족/미충족 (딱 떨어짐) | 가까움/멂 (정도 문제) |
| 방법 | LLM이 요건별 대조 | doc_cards 임베딩 유사도 |
최종 흐름: 적합도로 후보 15건 추림 → 그 후보만 참가자격 판정 → 🟢🟡만 반환 (적합도 순 정렬). 적합도 점수는 화면에 숫자로 노출하지 않고 정렬에만 쓴다 — 프론트 CLAUDE.md의 “근거 없는 점수를 붙이지 마라” 경고를 존중.
비용·속도 설계
한 번의 “추천 받기”에 실제로 일어나는 일:
① 98건 → 15건 추리기 임베딩만. LLM 미사용, 비용 0
② 15건 참가자격 대조 LLM 호출 — 여기가 비용·시간의 전부
③ 살아남은 것 키워드 칩 문서당 1회 캐시 후 재사용
- 요건 추출은 문서당 1회 캐시 (
data/extracted/eligibility.json) — 추천을 볼 때마다 98건을 다시 추출하면 공용 $20 예산이 순식간에 녹는다. - 배치 매칭 — 15건을 1건씩 15번 왕복하지 않고 4건씩 묶어 4~5회로 줄임. 15건을 통째로 한 프롬프트에 넣지 않는 이유는 참가자격 매칭 — 추출 정확도 디버깅의 “뭉개짐” 습성이 문서 간에도 재현될 위험 때문. 4건 단위로 실측 검증했고 섞임 0건.
- 예산 분리 —
ELIGIBILITY_MAX_COMPLETION_TOKENS = 10,000. 채팅 답변용(2,500)과 같은 상수를 쓰면 일반 대화 비용까지 같이 오른다.
자격증을 안 적는 문제
자유서술은 “해온 업무”는 잘 적지만 “소프트웨어사업자 신고했다” 같은 자격·신고사항은 안 적는다 — 없어서가 아니라 쓸 생각을 안 해서. 그래서 프로필에 체크리스트 5종을 따로 뒀다(98건 요건에서 반복적으로 나온 항목만 골랐고, 지어낸 목록이 아니다).
체크 안 한 항목은 “없음”이 아니라 “말 안 함”(불명 유지) 으로 둔다.
구현 위치
백엔드
src/eligibility.py— 요건 추출(캐시) · 프로필 대조 · 배치 매칭 · 추천src/profile.py— 자유서술 → 분야·키워드 추론, 문서 키워드 칩src/api/eligibility.py·src/api/server.py— API 계약- 엔드포인트:
POST /profile/infer·POST /recommendations·POST /eligibility/{doc_id}·GET /rfps/{doc_id}/content
프론트 — 웹 UI 개편 — 사이드바 3단 구조 참고
남은 것
- 대화가 여전히 문서 1개 기준 —
/ask가doc_id하나만 받는다. “두 공고 차이는?”을 진짜로 답하려면 백엔드 확장 필요. - 비교 화면의 차이 자동 하이라이트 없음 — LLM 추가 호출이 필요해 보류.
- 치명적 단일 요건(대기업 배제 등)은 개수와 무관하게 즉시 탈락 처리해야 하는데 아직 개수 기준이라, 대기업 프로필이 🔴가 아닌 🟡로 나온 사례가 있다.