데이터 파이프라인
1. 한눈에 보는 전체 흐름
동네 식당의 가격은 웹에 흩어져 있지만 소스마다 값이 다르고 시점이 없습니다. 지금 가격의 확실한 근거는 가게에 붙은 메뉴판 사진뿐입니다. 일반 챗봇이 "정확한 가격은 매장에 문의하세요"로 끝나는 이유가 여기에 있습니다. 한입지도는 성균관대학교 공식 웹사이트에서 학식 정보를 가져옴과 동시에 '메뉴판 사진'으로 데이터를 확보합니다.
세 갈래 입력은 각자 다른 처리과정을 거쳐 사람 검수라는 하나의 관문을 통과하게 됩니다. 아래에서 이 흐름을 살펴보고 직접 테스트할 수 있게 만들어 두었습니다.
2. 어떻게 만드나
ocr=force·coordinates=true로 요청해 메뉴명과 가격을 추출합니다.가장 중요한 사항 중 하나는 메뉴 하나와 연결되어 있는 메타데이터입니다. 해당 값의 신뢰 제고를 위해 모든 메뉴가 출처와 상태를 함께 갖도록 설계했습니다.
| 칸 | 들어가는 값 | 왜 필요한가 |
|---|---|---|
| 출처 | 사진에서 읽음 · 제보 · 직접 입력 | 값이 틀렸을 때 어디로 돌아가 확인할지 알려줍니다. |
| 검수 | 확인 · 제외 · 대기 | 서비스에 내보낼 지 결정하는 스위치입니다. |
| 본메뉴 여부 | 본메뉴 · 곁들임 | 공기밥 1,000원으로 검색에 걸리면 안 됩니다(아래 시행착오). |
| 촬영일 · 수집자 | 날짜 · 이름 | 데이터 갱신 순서를 정하기 위해 필요합니다. |
| 좌표 | 위도 · 경도 | 주소 텍스트만으로는 도보 시간을 측정하지 못합니다. |
학교 식단 페이지는 이미 표로 정리된 HTML이므로 메뉴판 이미지와 처리 방식을 달리하였습니다.
| 입력 | 형태 | 처리 |
|---|---|---|
| 메뉴판 | 사진(비구조) | Document Parse → 검수 |
| 학식 | HTML(구조화) | 자동 수집 → 형식 맞추기 |
| 위치 | 주소 텍스트 | 지오코딩 → 좌표 |
3. 사람이 마지막으로 확인합니다
자동화와 사람 개입의 경계는 분명합니다. AI는 후보를 만들고 사람은 통과 도장을 찍습니다. 검수자는 원본 사진과 AI 파싱 결과를 함께 보면서 확인합니다.
AI가 만든 후보 …개 중 …입니다. 제외한 것은 영업시간, 가게 연혁, 옵션 가격처럼 메뉴가 아닌 것들입니다. 사람이 최종 검수함으로써 오류를 줄일 수 있었습니다.
메뉴마다 마지막으로 검수한 날짜를 함께 저장해 두어 오래된 것부터 다시 확인합니다.
이 문을 통과해 지금 서비스에 올라가 있는 것은 다음과 같습니다(… 기준, 이 페이지를 열 때 잽니다).
…
통과 데이터 목록
4. 시행착오
서비스를 구축하며 겪은 문제와 해결의 기록입니다.
| 걸린 것 | 왜 문제인가 | 어떻게 했나 |
|---|---|---|
| 사이드 메뉴 | "8천원 이하"에 공기밥 1,000원이 걸리면 그 가게는 조건을 통과합니다. | 메뉴마다 본메뉴/곁들임을 판정해, 예산 판정과 개수 세기 모두 본메뉴로만 합니다. 판정 규칙은 63개 케이스로 고정해 두었습니다. |
| 천원단위 소수 표기 | 일부 식당은 가격을 7.0·1.5로 표기합니다. |
소수 표기를 1,000배로 환산합니다. OCR이 만든 공백 천단위(4 000, 21 ,000)도 같은 자리에서 흡수합니다. |
| 메뉴판을 그래프로 읽음 | Document Parse가 메뉴 구역을 그래프로 오인해 Chart Type: bar … item_01 12.5를 뱉었고 그 빈칸 이름이 화면에 "item_01 12,500원"으로 나갔습니다. |
사람이 읽을 메뉴 이름이 아닌 기계가 붙인 빈칸 이름은 버립니다. 우리 쪽 버그가 아니라 읽어 온 결과에 섞여 들어온 것이라 받는 자리에서 막는 게 맞습니다. |
| 과거 데이터 기억 | 사용자가 "마라탕버거"(실제 존재하지 않는 메뉴)를 검색한 경우 그것을 관심어로 저장하고 다음 추천 서비스에 활용하는 문제가 있었습니다. | 기억할 키워드를 실제 메뉴·식당 이름과 대조해 통과한 것만 남깁니다. 꺼낼 때도 다시 대조해서 없는 메뉴는 말하지 않습니다. |
| 추천 이유 | 문장은 Solar가 작성합니다. 할루시네이션 여부를 확인하기 어려웠습니다. | 이유 문장을 실제 메뉴·가격·도보 시간과 한 줄씩 대조합니다. 대조는 다른 모델이 진행하고, 근거가 없으면 데이터 사실로 바꿉니다. 추천 결과에서 통과하지 못한 카드에는 "근거 미달 → 데이터 사실로 교체" 표시가 붙습니다. |
5. 얼마나 정확한가
메뉴판 사진 5장·72개 메뉴를 채점한 결과 55개(76.4%)의 정확도가 나왔습니다. 나머지는 사람의 조정이 필요했습니다.
가장 큰 성과는 정확도 자체보다 정확도에 영향을 주는 변수를 파악한 것이었습니다. 투입한 메뉴 이미지를 상태별로 비교해본 결과 정확도를 크게 흔드는 요소를 발견했습니다.
반사와 거리는 문제가 아니었습니다. 유리창에 붙은 메뉴를 멀리서, 반사까지 얹어 찍은 사진들도 25개 항목 전부 파싱에 성공했습니다. 핵심은 '해상도가 부족한 경우' 파싱이 대다수 실패한다는 점이었습니다.
추가적으로 5,000원을 지우고 7,000원을 덧쓴 손글씨는 읽지 못한다는 점도 발견했습니다. 사람의 검수 단계가 필수적인 이유 중 하나입니다.
정확도를 잴 때 Document Parse를 8번 불렀고 비용은 약 $0.08이었습니다. 그때 받은 응답 원본 5건을 파일로 저장해 두었기 때문에, 읽어 온 결과를 다듬는 규칙을 고칠 때마다 API를 다시 부르지 않고 같은 응답으로 다시 채점할 수 있습니다. 실제로 이 방식으로 규칙만 두 번 고쳐 정확도가 0% → 62.5% → 76.4%로 올랐습니다.
이렇게 정한 규칙은 문서가 아니라 테스트로 지킵니다. 곁들임 판정 63개, 대화에서 조건 뽑기 35개, 추천 칩 18개 케이스가 있고, 배포된 사이트를 실제 브라우저로 훑는 점검 16개가 배포할 때마다 함께 돕니다. 규칙을 고치다 예전에 맞던 것이 깨지면 여기서 걸립니다.
채점 방법과 사진별 원본 응답은 깃허브 저장소에 그대로 남겨 두었습니다.
사진으로 파싱 결과 확인해 보기
6. 멈추면 어떻게 되나
앞의 시행착오가 틀린 값을 다뤘다면 여기는 아예 오지 않는 값을 다룹니다. 설계상 부품 하나가 멈췄을 때 화면이 무엇을 말해주느냐에 초점을 두고 진행했습니다.
| 멈추는 곳 | 그냥 두면 | 대응 |
|---|---|---|
| 학식 크롤러 | 새 식단 데이터가 들어오지 않으면 "오늘 휴무"를 내보냅니다. 데이터가 없는 것과 식당이 쉬는 것이 구별되지 않습니다. | 식단이 마지막으로 들어온 날짜를 함께 확인하여 날짜가 오늘이 아닌 경우 "정보 없음"으로 표현합니다. |
| Document Parse | 제보 화면에서 OCR이 실패하면 사용자는 곧바로 나갑니다. | 파싱에 실패하면 직접 입력으로 흐름을 이어가도록 했습니다. |
| 메뉴 찾기 | 기능 하나가 죽으면 서비스 전체가 불능이 됩니다. | 기능을 따로 작동하도록 설계하여 하나가 멈춰도 나머지는 작동합니다. 추천이 안 되더라도 지도와 학식은 그대로 사용자에게 전달됩니다. |
7. 어디에 Upstage를 썼나
Upstage 제품을 쓰는 자리는 네 곳입니다. 데이터가 들어올 때 두 곳, 사용자에게 나갈 때 두 곳입니다.
| 제품 | 무슨 일을 시켰나 | 왜 이 모델인가 |
|---|---|---|
| Document Parse | 메뉴판 사진에서 메뉴명과 가격을 읽습니다ocr=force · coordinates=true |
웹의 가격은 소스마다 어긋나고 시점이 없습니다. 지금 가격의 근거는 가게에 붙은 메뉴판 사진뿐입니다. |
Solar solar-pro2 |
제보받은 사진에서 가게 이름을 찾습니다 | 사람이 타이핑할 칸을 줄입니다. 메뉴판에 이름이 없으면 빈칸을 돌려주게 못 박았습니다. |
Solar solar-pro2 |
사용자 문장에서 예산·도보 시간·태그를 뽑아 대화 답과 함께 돌려주고, 추천이 정해지면 그 이유도 씁니다 조건과 답은 한 번의 왕복으로 함께 받습니다 |
"둘이서 2만원"이면 1인 1만원으로 계산해야 하는데 solar-mini는 이 나눗셈을 틀렸습니다. |
Solar solar-mini |
solar-pro2가 쓴 추천 이유를 데이터와 대조해 채점합니다 |
쓴 모델이 스스로 채점하면 검증이 아니라서 일부러 다른 모델에 맡겼습니다. |
학식 식단 확보에는 사용하지 않습니다. 이미 표로 정리된 HTML이라 사진 파싱을 얹을 이유가 없습니다.
문장을 조건으로 바꿔 보기
문장을 넣고 누르면 Solar가 조건을 뽑아냅니다. 예산 얼마까지, 도보 몇 분까지, 어떤 종류. 이 값으로 식당을 추천합니다.
아직 실행하지 않았습니다.
8. 로그인이 없는 이유
한입지도는 로그인하지 않아도 그대로 사용할 수 있습니다.
재방문한 사용자가 지난번에 무엇을 찾았는지에 대한 정보는 서버가 아니라 사용자의 브라우저 안에 남도록 했습니다. 그래서 "지난번엔 돈까스 찾으셨는데, 오늘도 그쪽으로 볼까요?" 하고 한입지도에서 이어서 물을 수 있습니다. 이 기록은 사용자 기기에만 있고 지우기를 누르면 사라집니다.
다만 그 기억이 추천 결과를 바꾸지는 않습니다.