참가자격 매칭 — 기능 설계

작업일 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개 기준/askdoc_id 하나만 받는다. “두 공고 차이는?”을 진짜로 답하려면 백엔드 확장 필요.
  • 비교 화면의 차이 자동 하이라이트 없음 — LLM 추가 호출이 필요해 보류.
  • 치명적 단일 요건(대기업 배제 등)은 개수와 무관하게 즉시 탈락 처리해야 하는데 아직 개수 기준이라, 대기업 프로필이 🔴가 아닌 🟡로 나온 사례가 있다.