한 건이어야 할 결과가 세 건으로 나왔다: 입찰 데이터가 가르쳐준 첫 번째 교훈

(대표 이미지는 이해를 돕기 위해 재구성한 예시 화면입니다.)

정보통신공사업 관련 입찰건에 대해 낙찰예정가를 분석해달라는 의뢰를 받은 적이 있습니다. 이를 위해 공공데이터포털에서 나라장터 입찰공고 API를 발급받고, 관련 데이터를 긁어와서 취합하는 파이프라인을 짜기 시작했습니다. 분석 과정 중에는 특정 업체 하나의 과거 입찰 참가 이력을 정확히 추적해야 하는 작업도 포함되어 있었는데, 여기서 예상 못 한 곳에 발목을 잡혔습니다.

화면을 다시 봤다

(주)동원건설. 찾고 싶은 업체는 이 하나였습니다. 특정 공고 하나를 콕 집어서, 이 업체의 사정율이 얼마였는지 뽑아내려고 했습니다. 입찰 구조상 답은 뻔했습니다. 한 공고에 한 업체는 사정율 하나만 제출합니다. 즉 결과는 무조건 한 줄이어야 했습니다.

그런데 화면에 뜬 건 세 줄이었습니다.

처음엔 데이터 자체를 의심했습니다. “이 업체가 이 공고에 여러 명의로 참가라도 했나?” 공공데이터가 원래 지저분하다는 걸 익히 알고 있었기 때문에, 이번에도 원본 데이터가 오염된 거라고 생각하는 게 자연스러웠습니다. 그런데 곱씹어볼수록 말이 안 됐습니다. 한 업체가 한 공고에 사정율을 여러 개 제출한다는 건, 애초에 입찰 제도 자체가 성립하지 않는다는 뜻이었으니까요.

의심의 방향을 데이터에서 코드로 돌렸습니다.

범인은 한 줄이었다

python

# 문제가 됐던 코드
target_bids = df[df["업체명"].str.contains("동원건설")]

str.contains(). 이름에 “동원건설”이라는 글자가 들어만 있으면 전부 걸러내는 조건이었습니다. (주)동원건설만 걸릴 거라고 생각했지만, 실제로는 이름에 우연히 같은 글자를 품고 있는 다른 업체들까지 전부 그물에 걸려 올라온 겁니다. 결과 리스트를 업체명만 따로 뽑아 눈으로 훑어보니, 낯선 업체명 두 개가 나란히 섞여 있었습니다. 원하는 업체 하나, 그리고 이름만 닮은 남 둘.

문자열 검색에서 “포함”과 “일치”는 코드 한 줄 차이지만, 결과의 신뢰도는 전혀 다른 이야기가 됩니다. contains는 그물이고, ==는 저격입니다. 둘 중 뭘 써야 하는지는 데이터의 성격이 결정하는데, 이번엔 그물을 던져놓고 저격이 될 거라 착각한 셈이었습니다.

python

# 수정된 코드
target_bids = df[df["업체명"] == "(주)동원건설"]

한 줄을 고치고 다시 돌렸습니다. 이번엔 한 줄만 나왔습니다.

완전일치도 만능은 아니다

다만 여기서 짚고 넘어갈 부분이 있습니다. 완전일치(==)로 문제를 해결했다고 해서, 이게 모든 상황에서 안전한 해법은 아닙니다. 공공데이터 특성상 같은 업체가 등록 시점이나 담당자에 따라 미묘하게 다른 표기로 남아있는 경우가 흔합니다. 마지막 글자 하나가 빠졌다거나, 띄어쓰기가 한 칸 더 들어갔다거나 하는 식입니다. 이런 경우 완전일치는 오히려 진짜 같은 업체를 놓치는 반대 방향의 실수를 만들 수 있습니다.

즉 이번 사례에서 정답은 “완전일치가 항상 옳다”가 아니라, 찾으려는 대상의 성격에 맞게 매칭 방식의 엄격도를 조절해야 한다는 것이었습니다. 특정 업체 하나를 정밀 타격해야 하는 지금 같은 경우엔 완전일치가 맞는 선택이었지만, 만약 목적이 “비슷한 이름의 계열사까지 폭넓게 잡아내는 것”이었다면 오히려 contains나 유사도 기반 매칭이 정답이었을 수도 있습니다. 도구를 잘못 고른 게 아니라, 상황에 맞는 도구를 나중에야 알아챈 것에 가까웠습니다.

이 작업이 알려준 것

돌이켜보면 별것 아닌 실수처럼 보일 수 있지만, 데이터 거버넌스에서 진짜 위험한 실수는 화려한 알고리즘의 오류가 아니라 “당연히 이럴 것이다”라고 검증 없이 넘겨짚은 조건문 한 줄이라는 걸 다시 확인한 경험이었습니다. 특히 업체 수가 많고 이름에 겹치는 단어가 흔한 공공 입찰 데이터에서는, 매칭 조건을 얼마나 엄격하게 잡느냐가 그 뒤에 나올 모든 통계와 예측의 신뢰도를 좌우합니다.

다음 편에서는 이렇게 정리된 데이터로 실제 ‘핫존(특정 상황에서 자주 관측되는 사정율 구간)’을 분석하려다가 겪었던 두 번째 난관 — 구간을 너무 넓게 잡아서 오히려 분석 자체가 무의미해졌던 경험을 다루겠습니다.

댓글 남기기