문자가 반송됐습니다. 서류 합격 통보였고, 수신 실패 목록에 몇 명이 올라와 있었습니다.
이력서를 열어보니 번호가 분명히 적혀 있었습니다. 사람 눈에는 보이는데 시스템에는 없었습니다.
숫자만 사라졌습니다
원인은 PDF 폰트였습니다. 일부 채용 플랫폼이 만드는 이력서는 헤더 영역에 별도 폰트를 심는데, 그 폰트에 글리프 → 유니코드 매핑이 빠져 있으면 추출기가 문자를 특정할 수 없습니다. 그 자리에 들어오는 것이 U+FFFE 같은 대체문자입니다.
문제는 이게 부분적으로만 발생한다는 점입니다.
이름과 경력은 본문 폰트라 정상이고, 연락처가 있는 헤더만 깨집니다. 그래서 지원자 한 명이 "이름과 경력은 완벽하고 연락처만 없는" 상태로 등록됩니다. 어느 화면에도 오류 표시가 없습니다.
이력서 100건을 올린 담당자 입장에서는 이걸 발견할 방법이 사실상 없습니다. 발견되는 시점은 통보가 반송된 뒤입니다.
더 위험한 건 그다음이었습니다
연락처가 비어 있으면 채워야 합니다. 그래서 본문 텍스트에서 번호 패턴을 찾고, 그래도 없으면 이미지에서 다시 읽는 경로를 씁니다.
여기서 조용한 함정이 하나 더 있습니다. 깨진 텍스트에서 숫자 몇 개가 지워진 상태로 남으면, 그 손상된 번호가 그럴듯한 번호처럼 보입니다. 열한 자리에서 두 자리가 사라진 값을 모델이 "정리"하면, 존재하지 않는 번호가 완성됩니다.
빈 칸보다 이게 나쁩니다. 빈 칸은 담당자가 이력서를 열어보게 만들지만, 틀린 번호는 아무도 의심하지 않고 그대로 발송됩니다. 다른 사람에게 합격 통보가 갈 수도 있습니다.
그래서 세 가지를 걸었습니다
1. 대체문자를 신호로 씁니다. 추출 텍스트에 U+FFFE·U+FFFD 가 있으면 "글자가 없다"가 아니라 "폰트 매핑이 깨졌다"로 판정합니다. 이 판정이 있어야 다음 두 단계가 작동합니다.
2. OCR 로 다시 읽습니다. 텍스트 레이어를 신뢰할 수 없는 문서이므로, 페이지를 이미지로 렌더해 광학 인식으로 다시 뽑습니다. 텍스트가 있는데도 다시 읽는 것이라 비효율처럼 보이지만, 이 경우엔 텍스트가 있다는 사실 자체가 함정입니다.
3. 본문에 없는 값은 저장하지 않습니다. 이게 핵심입니다. 복구 과정에서 만들어진 번호가 원본 어디에서도 확인되지 않으면 저장하지 않고 빈 칸으로 둡니다. 채워진 오답보다 빈 칸이 낫다는 판단입니다.
빈 칸으로 남으면 담당자에게 알림이 갑니다. 사람이 이력서를 열어 손으로 넣는 것이 이 케이스의 정답입니다.
이 가드가 살아 있는지 매일 확인합니다
기능을 고치는 것보다 고친 상태가 유지되는지가 어렵습니다. 리팩터링 한 번에 조용히 빠질 수 있는 종류의 가드이기 때문입니다.
그래서 아침 운영 리포트가 매일 이 항목을 점검합니다 — 재구성된 연락처 저장 차단이 코드에 존재하는지, 연락처 미확보 비율이 평소 범위인지. 점검이 실패하면 리포트에 경고가 뜹니다.
여기서 한 가지를 배웠습니다. 비율로 봐야 합니다. 처음에는 "연락처 미확보 N건"을 절대 건수로 경보했는데, 지원자가 많은 날에는 정상 상태에서도 경보가 울렸습니다. 매일 울리는 경보는 아무도 보지 않는 경보가 됩니다.
남은 교훈
이 사고는 AI가 틀린 사고가 아닙니다. 입력이 조용히 손상된 사고입니다. 그리고 조용한 손상은 모델을 더 좋은 것으로 바꿔도 해결되지 않습니다 — 손상된 입력을 더 그럴듯하게 완성할 뿐입니다.
그래서 이력서 파싱에서 우리가 가장 신경 쓰는 질문은 "얼마나 잘 읽는가"가 아니라 "못 읽었을 때 못 읽었다고 말하는가" 입니다. 무엇을 뽑아내고 무엇을 사람이 봐야 하는지는 이력서에서 AI가 뽑아내는 것에 정리해 두었습니다.