입찰 분석 도구를 여섯 개 만들어 운영하고 있습니다. 각각이 무엇을 근거로 답을 내는지, 어디에 GPT를 쓰고 어디에는 일부러 쓰지 않았는지, 그리고 앞으로 무엇을 고치려 하는지 정리했습니다.
만든 사람이 직접 쓰는 글이라 잘된 부분보다 아직 미흡한 부분을 더 자세히 적었습니다.
━━━━━━━━━━━━━━━━━━━━
1. 여섯 개가 무엇인지
화면에서는 두 묶음으로 나눠 배치했습니다. 이 구분이 이 글의 핵심이기도 합니다.
[확인된 사실] — 실제 낙찰 이력을 집계한 값
· 경쟁사 이력 — 이 발주기관에서 어떤 업체가 몇 건을 가져갔는지
· 유사 사례 — 비슷한 공고들이 실제로 몇 %에 낙찰됐는지
· 발주처 낙찰 이력 — 이 기관의 낙찰가가 어느 구간에 몰리는지
[추정·참고용] — 과거 데이터로 만든 예측
· 낙찰 전략 — 공고문을 읽고 평가 기준과 준비 사항을 정리
· 점수 시뮬 — 배점표 기준 예상 점수
· 투찰가 시뮬 — 권장 투찰 구간
같은 크기, 같은 톤으로 나열하면 사용자가 추정치를 실측으로 오해합니다. 그래서 위아래로 나누고 추정 쪽에는 배지를 달았습니다. 이건 디자인 문제가 아니라 신뢰의 문제라고 봤습니다.
━━━━━━━━━━━━━━━━━━━━
2. GPT를 어디에 쓰고 어디에 안 쓰는가
여섯 개 중 다섯 개가 GPT를 부릅니다. 하나는 부르지 않습니다.
GPT를 쓰지 않는 것: 발주처 낙찰 이력
이 기능은 데이터베이스에서 해당 기관의 낙찰 결과를 꺼내 평균과 분포를 코드로 계산합니다. 그게 전부입니다. GPT가 들어갈 자리가 없습니다.
왜 안 넣었냐면, 넣을 이유가 없었기 때문입니다. "평균 낙찰률 98.7%"는 계산하면 나오는 값입니다. 여기에 GPT를 태우면 비용이 늘고, 응답이 느려지고, 무엇보다 숫자가 틀릴 가능성이 생깁니다. 얻는 게 없고 잃는 게 있습니다.
GPT를 쓰는 다섯 개도, 숫자는 GPT에게 맡기지 않습니다.
이게 저희가 세운 원칙입니다. 통계는 전부 코드로 먼저 계산해서 GPT에게 "재료"로 넘겨줍니다. GPT는 그 재료를 읽고 문장으로 설명할 뿐, 새로운 숫자를 만들어내면 안 됩니다.
프롬프트에도 이렇게 못 박아 뒀습니다.
"제공된 '코드 계산 통계'와 '유사 사례'에서 발견되는 수치만 사용하세요. 그 외 새로운 수치 환각 절대 금지."
응답 형식도 JSON으로 강제하고, 온도는 0~0.3으로 낮췄습니다. 점수 시뮬레이션은 아예 0입니다. 창의성이 필요한 작업이 아니라 규칙을 따르는 작업이기 때문입니다.
이 원칙을 최근에 한 번 더 확인할 일이 있었습니다. 투찰가 시뮬레이션 화면에서 GPT가 실제로 무엇을 만들고 있는지 코드를 되짚어봤더니, 화면에 뜨는 숫자는 거의 전부 GPT를 거치지 않고 있었습니다.
· 평균·중앙값·표본 수 — 데이터베이스 집계
· 분포 그래프 — 저장된 낙찰률 값 그대로
· 예상 투찰 금액 — 예정가격 × 투찰율, 코드 곱셈
· 권장 구간 — GPT 값을 받되 코드가 다시 계산해 덮어씀
권장 구간은 GPT가 제안하더라도, 표본이 얕으면 구간 폭을 강제로 넓히고 중앙값 쪽으로 당기는 보정이 뒤에 붙습니다. GPT가 구간을 주지 않으면 중앙값으로 대신합니다. 금액은 아예 GPT 값을 쓰지 않고 직접 곱합니다.
결국 이 기능에서 GPT가 만드는 건 설명 문장과 인사이트뿐이었습니다. 원칙대로 만들어놨다는 걸 확인한 셈인데, 동시에 다음 항목의 문제로 이어졌습니다.
━━━━━━━━━━━━━━━━━━━━
3. 그래서 GPT는 무엇을 잘하나
이걸 여러 번 시행착오하면서 얻은 결론이 있습니다. GPT가 확실히 잘하는 지점과, 넣으면 오히려 해로운 지점이 꽤 명확하게 갈립니다.
잘하는 것 — 비정형 문서에서 구조를 뽑아내기
공공입찰 공고문은 기관마다 양식이 제각각입니다. 배점표가 표로 들어간 곳도 있고, 문단 안에 문장으로 풀어쓴 곳도 있고, 첨부 한글 파일 안에만 있는 경우도 있습니다. 이걸 정규식이나 규칙으로 파싱하려면 기관 수만큼 예외 처리를 해야 합니다.
GPT는 이걸 잘합니다. "이 문서에서 평가 항목과 배점을 뽑아줘"라고 하면 양식이 달라도 찾아냅니다. 저희가 점수 시뮬레이션에서 GPT를 쓰는 이유가 이것입니다. 점수 계산은 코드가 하고, GPT는 배점표를 찾아내는 일만 합니다.
잘하는 것 — 흩어진 정보를 읽는 순서로 정리하기
낙찰 전략 카드가 그렇습니다. 공고문에는 제출 서류, 자격 요건, 과업 범위, 평가 기준이 여기저기 흩어져 있습니다. "무엇부터 확인해야 하는가"로 다시 배열하는 건 사람이 하면 시간이 걸리고, GPT는 몇 초면 합니다. 새로운 정보를 만드는 게 아니라 있는 것을 재배열하는 작업이라 환각 위험도 낮습니다.
넣으면 안 되는 것 — 계산
앞서 말한 그대로입니다. 평균, 비율, 순위처럼 답이 정해진 것을 GPT에게 시키면 대체로 맞지만 가끔 틀립니다. 그 "가끔"이 문제입니다. 틀린 숫자도 자신 있게 말하기 때문에 사용자가 알아챌 방법이 없습니다.
넣으면 안 되는 것 — 근거 없는 상태에서 답을 만들기
이게 가장 위험한 지점이라고 봅니다. 비교할 과거 사례가 없을 때 GPT에게 "그래도 추정해봐"라고 하면 그럴듯한 답을 만들어냅니다. 근거가 없다는 사실 자체가 사라집니다.
여기를 최근에 고쳤는데, 고치기 전 방식이 부끄러워서 그대로 적습니다.
원래는 표본이 얕아도 일단 하트를 받고 GPT를 부른 다음, 결과를 돌려주면서 "표본이 부족합니다"라고 적고 하트 절반을 환불했습니다. 정직하게 밝히고 일부는 돌려주니 괜찮다고 생각했습니다.
실행 기록을 세어보고 생각이 바뀌었습니다. 139건 중 85건, 61%에서 이 환불이 발동하고 있었습니다. 예외 처리가 아니라 그게 기본 동작이었던 겁니다. 사용자 입장에서는 하트를 냈다가 절반만 돌려받는 일이 절반 넘게 일어나고 있었고, 저희는 그때마다 근거가 얕은 줄 알면서 GPT 비용을 썼습니다.
순서를 뒤집었습니다. 지금은 GPT를 부르기 전에 표본을 먼저 셉니다. 실제 낙찰가가 공개된 사례가 3건 미만이면 GPT를 호출하지 않고, 하트를 전액 돌려준 뒤, 데이터베이스에서 계산한 값만 무료로 보여줍니다.
평균·중앙값·분포 그래프·표본 목록·권장 구간·예상 금액은 그대로 나옵니다. 앞 항목에서 확인했듯 이 숫자들에는 애초에 GPT가 필요 없기 때문입니다. 빠지는 건 GPT가 쓰던 해설뿐이고, 화면 위쪽에 왜 해설이 없는지와 하트가 나가지 않았다는 사실을 적어둡니다.
처음에는 아예 실행을 막을까 생각했습니다. 그런데 표본 3건 미만이 실행의 61%라, 막으면 대부분의 사용자가 빈손으로 돌아갑니다. 있는 데이터를 있는 만큼 보여주면서 얕다는 사실을 같이 밝히는 쪽이 낫다고 판단했습니다.
"최소 10건은 돼야 하지 않나"도 검토했는데, 재보니 실측 10건 이상은 전체의 7.9%뿐이었습니다. 15건 이상은 한 건도 없었습니다. 통계적으로는 10건이 맞지만 조달 데이터에서는 실현 불가능한 기준이라, 바람직한 기준과 실현 가능한 기준을 구분해야 했습니다.
━━━━━━━━━━━━━━━━━━━━
4. 환각을 완전히 막았다고는 못 합니다
위의 장치들을 다 해놔도 남는 위험이 있습니다. 정직하게 적어둡니다.
첫째, 숫자는 막았지만 문장은 못 막습니다.
"경쟁이 치열한 편입니다" 같은 서술은 검증할 방법이 없습니다. 코드 계산 통계를 넘겨줬으니 그걸 근거로 썼을 가능성이 높지만, GPT가 넘겨받은 데이터와 다른 뉘앙스로 요약할 여지는 남아 있습니다. 지금은 이걸 기계적으로 검증하지 못합니다.
둘째, 공고문 해석 자체가 틀릴 수 있습니다.
배점표를 뽑아내는 건 GPT가 잘하지만, 공고문이 애매하게 쓰여 있으면 잘못 읽을 수 있습니다. 그러면 점수 시뮬레이션 전체가 틀린 전제 위에 서게 됩니다. 지금은 추출한 배점표를 화면에 같이 보여줘서 사용자가 대조할 수 있게 하는 정도입니다.
셋째, 저희가 준 데이터가 틀렸으면 그대로 나갑니다.
이건 GPT 문제가 아니라 저희 문제인데, 결과는 같습니다. 실제로 겪었습니다. 낙찰 통계를 낼 때 데이터를 2,000건까지 가져오도록 해뒀는데 API가 1,000건에서 조용히 자르고 있었습니다. 오류도 경고도 없었고, 절반이 사라진 채로 통계가 나가고 있었습니다.
GPT는 그 잘린 데이터를 성실하게 요약했습니다. 환각은 없었지만 결과는 틀렸습니다.
━━━━━━━━━━━━━━━━━━━━
5. 지금 개선하고 있는 것
가장 최근에 손댄 것부터 적습니다.
표본을 눌러서 근거를 직접 보게 하기
투찰가 시뮬레이션에 "표본 수 10건"이라고만 적혀 있었습니다. 그 10건이 무엇인지 확인할 방법이 없으니, 사실상 숫자를 믿어달라는 요구였습니다.
눌러서 열 수 있게 했습니다. 통계에 실제로 들어간 공고들이 낙찰률 낮은 순으로 나오고, 각 건마다 공고명·발주기관·추정가격·낙찰가·경쟁 업체 수·낙찰사가 붙습니다. 권장 구간에 드는 건은 초록색으로 표시해서, 그래프의 초록 막대가 어느 공고였는지 대응됩니다. 낙찰사 이름을 누르면 그 업체 정보로 이어집니다.
실제 낙찰가가 공개되지 않아 낙찰하한율로 대신한 건은 그 사실을 따로 표시합니다. 같은 표본이라도 근거의 무게가 다르기 때문입니다.
만들면서 하나 걷어냈습니다. 목록에 날짜를 넣었는데 전부 같은 날짜로 찍혔습니다. 그게 공고일이 아니라 저희가 데이터를 수집한 시각이었기 때문입니다. 사용자에게는 의미가 없고 오해만 부르는 값이라 뺐습니다.
물품 공고의 유사 사례 찾기
비슷한 공고를 찾을 때 제목 단어가 겹치는지를 봤는데, 물품은 이 방식이 잘 듣지 않습니다. "칠서정수장 분말활성탄 구입"과 제목이 겹치는 과거 사례를 못 찾으면 곧장 "물품 전체 평균"으로 물러섰고, 그 평균에는 학교 우유급식과 의료장비가 한 통에 섞여 있었습니다.
조달청 공고에 붙는 세부품명번호(10자리 품목 코드)를 쓰도록 바꿨습니다. 몇 자리까지 볼지는 짐작하지 않고 전부 돌려서 비교했는데, 직관과 반대로 세밀할수록 나빴습니다. 8자리는 품목이 정확해지는 대신 같은 코드를 가진 과거 사례가 급감해서 17%의 공고에만 적용됐습니다. 결국 4자리가 가장 나았고, 물품 적중률이 39.5%에서 45.5%로 올랐습니다.
발주처별 편차를 드러내기
발주처를 평균 낙찰률로 세워봤더니 상위 열 곳이 전부 97~101% 사이였습니다. 이걸로는 기관을 구분할 수 없습니다.
대신 낙찰가가 몰리는 폭을 재봤더니 2.2%p부터 25.3%p까지 열 배 넘게 벌어졌습니다. 어느 발주처가 예측 가능하고 어느 쪽이 그렇지 않은지는 평균이 아니라 이 폭이 말해줍니다. 화면에서도 평균 대신 이 폭을 앞세우도록 바꿨습니다.
응답 속도
기업정보 모달이 처음 열 때 2.9초 걸렸습니다. 뜯어보니 데이터베이스는 277밀리초로 문제가 아니었고, 공공데이터포털 호출들이 서로를 기다리며 줄 서 있었습니다. 동시에 던지도록 바꿔 1.4초가 됐고, 조회 결과를 저장해두는 것까지 붙여 같은 업체를 다시 열면 99밀리초입니다.
━━━━━━━━━━━━━━━━━━━━
6. 지난번에 하겠다고 적었던 것 중 끝난 것
이 글을 처음 쓸 때 "앞으로 하려는 것"에 적어둔 항목 두 개가 끝났습니다. 결과를 적습니다.
한계를 수치로 재기 — 측정했습니다
예정가격은 복수예비가격 15개 중 4개를 추첨해 정해집니다. 추첨 결과는 누구도 예측할 수 없으니 낙찰률에는 어떤 모델도 맞힐 수 없는 무작위 성분이 섞여 있는데, 그 폭이 얼마인지 모르겠다고 적었었습니다.
쟀습니다. 발주처·낙찰방식·경쟁 강도가 모두 같은 그룹만 792개를 모아 그 안에서 낙찰률이 얼마나 흔들리는지 봤습니다. 표본 30,838건 기준입니다.
· 같은 조건인데도 낙찰률의 표준편차 중앙값 2.47%p
· 같은 조건 그룹 안에서 낙찰률이 벌어지는 폭의 중앙값 3.45%p
조건을 아무리 똑같이 맞춰도 2.5%p 정도는 원래 흔들린다는 뜻입니다. 이건 데이터를 더 모아도 줄지 않는 하한선입니다.
이 숫자가 생기니 저희 적중률을 처음으로 해석할 수 있게 됐습니다. 지금 평균 오차가 8.64%p인데, 이론적 하한이 2.47%p이니 3.5배 남아 있습니다. "아직 개선 여지가 3.5배"라고 말할 수 있게 된 것이고, 동시에 "0%p를 목표로 하는 건 불가능하다"는 것도 분명해졌습니다.
투찰 결과 기록하기 — 만들었습니다
저희가 구간을 제시하고 끝나서, 사용자가 실제로 얼마에 넣었고 결과가 어땠는지 남길 곳이 없다고 적었었습니다. 마감된 공고에서 낙찰·패찰·결과 미확인을 기록할 수 있게 만들었습니다.
기록할 때 그 시점의 권장 구간을 함께 저장합니다. 나중에 알고리즘을 고쳐도 "그때 그 추천이 맞았는지"를 다시 계산할 수 있어야 하기 때문입니다.
다른 사람의 투찰가는 절대 보이지 않습니다. 본인 기록만 본인에게 보입니다. 투찰가는 영업 정보라 이 부분은 데이터베이스 권한 수준에서 막아뒀습니다.
아직 기록은 0건입니다. 마감된 공고에서만 뜨는 화면이라 그렇기도 하고, 만든 지 얼마 안 됐습니다. 이 숫자는 사용자가 실제로 써야만 늘어납니다.
━━━━━━━━━━━━━━━━━━━━
7. 앞으로 하려는 것
아직 안 한 것들입니다. 순서는 저희가 생각하는 우선순위입니다.
문장의 근거를 추적할 수 있게 만들기
지금은 GPT가 쓴 문장이 어느 데이터에서 나왔는지 표시하지 않습니다. "경쟁이 치열합니다"라는 문장 옆에 "평균 경쟁 128곳 기준"처럼 근거를 붙이면, 사용자가 그 문장을 믿을지 말지 스스로 판단할 수 있습니다. 검증할 수 없는 주장은 정보가 아니라고 생각합니다.
표본 목록을 열 수 있게 만든 것이 이 방향의 첫 걸음입니다. 다만 그건 숫자의 근거고, 문장의 근거는 아직 남아 있습니다.
표본 수에 따라 신뢰도를 함께 표시하기
지금은 "표본 10건"이라는 사실만 보여주고, 그게 많은지 적은지는 사용자가 판단해야 합니다. 앞서 잰 노이즈 플로어가 있으니 "표본 10건이면 평균 오차 ±0.8%p 정도"처럼 계산해서 붙일 수 있습니다. 지어낸 등급이 아니라 측정값에서 나오는 숫자입니다.
같은 공고를 여러 번 돌렸을 때 답이 달라지는지 재보기
온도를 낮춰뒀지만 완전히 같은 답이 나오지는 않습니다. 같은 공고를 열 번 돌려 결과가 얼마나 흔들리는지 측정해본 적이 없습니다. 많이 흔들린다면 그건 그 기능의 신뢰도 문제이고, 사용자에게 알려야 할 사실입니다.
업종별로 다른 접근을 쓰기
공사는 적중률이 89.1%인데 물품은 45.5%입니다. 같은 방식을 모든 업종에 똑같이 쓰고 있기 때문입니다. 공사는 적격심사 규칙이 정형화돼 있어 규칙 기반으로 더 정확하게 갈 수 있고, 물품은 품목 코드를 더 활용할 여지가 있습니다.
━━━━━━━━━━━━━━━━━━━━
8. 넣을까 고민 중인 기능
확정된 것은 아니고, 생각 중인 것들입니다. 의견 주시면 반영하겠습니다.
경쟁 업체 알림
특정 업체가 어느 공고에 참여했는지 추적하는 기능입니다. 데이터는 이미 있습니다. 다만 이건 남의 영업 활동을 감시하는 성격이 있어서, 넣을지 말지부터 고민하고 있습니다.
발주 시기 예측
같은 기관이 매년 비슷한 시기에 비슷한 공고를 내는 경우가 많습니다. 과거 패턴에서 "이 기관은 3월에 이런 공고를 낸다"를 뽑아내면 미리 준비할 수 있습니다. 다만 이건 명확히 추정이고, 빗나가면 사용자가 헛수고를 하게 되므로 표현을 신중히 해야 합니다.
공고문 원문 대조
GPT가 뽑아낸 배점표 옆에 공고문 원문의 해당 부분을 같이 보여주는 것입니다. 잘못 읽었는지 사용자가 바로 확인할 수 있습니다. 앞의 "근거 추적"과 같은 방향입니다.
발주기관 리스크 확인
낙찰받아도 발주처 사정으로 사업이 중단되면 잔금을 못 받는 일이 있습니다. 그런 신호는 공고문에 안 적히고 뉴스에만 나옵니다. 지금은 발주기관 관련 기사를 제목 기준으로 걸러 목록만 보여주고 있는데, 걸러내는 정확도가 아직 낮습니다. 어느 기관은 일곱 건이 걸렸는데 실제로 관련 있는 건 한 건이었습니다. 판단은 원문에서 하시라고 안내하고 있지만, 이 정도 정확도로 계속 둘지 고민 중입니다.
━━━━━━━━━━━━━━━━━━━━
9. 정리하면
저희가 지금까지 지키려 한 원칙은 세 가지입니다.
· 계산은 코드가 한다. GPT는 설명만 한다.
· 근거가 없으면 답을 만들지 않고, 없다고 말한다.
· 실측과 추정을 화면에서 구분한다.
세 번째가 특히 중요하다고 봅니다. AI가 들어간 기능은 다 똑같아 보이지만, 실제로는 "데이터베이스 조회 결과"와 "모델의 추측"이 섞여 있습니다. 그 둘을 구분해서 보여주지 않으면 사용자는 전부 같은 무게로 받아들입니다.
이번에 하나 더 배운 게 있습니다. 원칙을 세워두는 것과 그 원칙이 실제로 지켜지는지 세어보는 것은 다른 일이라는 점입니다. "표본이 부족하면 정직하게 밝힌다"는 원칙은 지키고 있었지만, 그게 실행의 61%에서 일어나고 있다는 건 세어보기 전까지 몰랐습니다. 세어보니 그건 예외 처리가 아니라 기본 동작이었고, 고쳐야 할 것은 안내 문구가 아니라 순서였습니다.
읽으시면서 이상하다고 느끼신 부분, 이건 왜 이렇게 했냐 싶은 부분 있으면 알려주세요. 특히 실제로 입찰을 준비하시는 분 입장에서 "이건 쓸모없다" 싶은 기능이 있다면 그게 가장 도움이 됩니다.