한입지도

데이터 파이프라인

1. 한눈에 보는 전체 흐름

동네 식당의 가격은 웹에 흩어져 있지만 소스마다 값이 다르고 시점이 없습니다. 지금 가격의 확실한 근거는 가게에 붙은 메뉴판 사진뿐입니다. 일반 챗봇이 "정확한 가격은 매장에 문의하세요"로 끝나는 이유가 여기에 있습니다. 한입지도는 성균관대학교 공식 웹사이트에서 학식 정보를 가져옴과 동시에 '메뉴판 사진'으로 데이터를 확보합니다.

세 갈래 입력은 각자 다른 처리과정을 거쳐 사람 검수라는 하나의 관문을 통과하게 됩니다. 아래에서 이 흐름을 살펴보고 직접 테스트할 수 있게 만들어 두었습니다.

들어오는 것 처리 메뉴판 사진 학식 웹페이지 가게 주소 Document Parse 자동 수집 주소 → 지도 좌표 사람 검수 한입지도

2. 어떻게 만드나

1수집명륜동·혜화 식당의 메뉴판 사진을 모아 출처와 촬영일을 함께 저장합니다.
2파싱Upstage Document Parse에 ocr=force·coordinates=true로 요청해 메뉴명과 가격을 추출합니다.
3검수파싱 결과는 운영진이 원본 사진과 대조하며 검수 작업을 진행합니다. 최종 검수가 완료된 사항만 실서비스에 노출됩니다.
4보강식당 주소를 바탕으로 위치 좌표를 채우고, 학식 정보는 매일 크롤링으로 데이터를 추가합니다.

가장 중요한 사항 중 하나는 메뉴 하나와 연결되어 있는 메타데이터입니다. 해당 값의 신뢰 제고를 위해 모든 메뉴가 출처와 상태를 함께 갖도록 설계했습니다.

들어가는 값왜 필요한가
출처사진에서 읽음 · 제보 · 직접 입력값이 틀렸을 때 어디로 돌아가 확인할지 알려줍니다.
검수확인 · 제외 · 대기서비스에 내보낼 지 결정하는 스위치입니다.
본메뉴 여부본메뉴 · 곁들임공기밥 1,000원으로 검색에 걸리면 안 됩니다(아래 시행착오).
촬영일 · 수집자날짜 · 이름데이터 갱신 순서를 정하기 위해 필요합니다.
좌표위도 · 경도주소 텍스트만으로는 도보 시간을 측정하지 못합니다.

학교 식단 페이지는 이미 표로 정리된 HTML이므로 메뉴판 이미지와 처리 방식을 달리하였습니다.

입력형태처리
메뉴판사진(비구조)Document Parse → 검수
학식HTML(구조화)자동 수집 → 형식 맞추기
위치주소 텍스트지오코딩 → 좌표

크롤러가 찾은 오늘의 학식

매일 아침 6시 10분에 학식당 4곳을 조회한 값을 처리합니다. "휴무"와 "정보 없음"을 구별하는 방법은 멈추면 어떻게 되나

  • 불러오는 중…

3. 사람이 마지막으로 확인합니다

자동화와 사람 개입의 경계는 분명합니다. AI는 후보를 만들고 사람은 통과 도장을 찍습니다. 검수자는 원본 사진과 AI 파싱 결과를 함께 보면서 확인합니다.

통과한 것과 아닌 것

AI가 만든 후보 개 중 입니다. 제외한 것은 영업시간, 가게 연혁, 옵션 가격처럼 메뉴가 아닌 것들입니다. 사람이 최종 검수함으로써 오류를 줄일 수 있었습니다.

메뉴마다 마지막으로 검수한 날짜를 함께 저장해 두어 오래된 것부터 다시 확인합니다.

이 문을 통과해 지금 서비스에 올라가 있는 것은 다음과 같습니다( 기준, 이 페이지를 열 때 잽니다).

수집한 식당
파싱이 만든 메뉴 후보
검수를 통과해 화면에 나가는 메뉴
4곳조회하는 학식당

통과 데이터 목록

    불러오는 중…

    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%)의 정확도가 나왔습니다. 나머지는 사람의 조정이 필요했습니다.

    가장 큰 성과는 정확도 자체보다 정확도에 영향을 주는 변수를 파악한 것이었습니다. 투입한 메뉴 이미지를 상태별로 비교해본 결과 정확도를 크게 흔드는 요소를 발견했습니다.

    인쇄체 · 해상도 충분 (2장)100% 37/37
    표기 변형 · 손글씨 (2장)64.0% 16/25
    저해상 흐림 (1장)20.0% 2/10

    반사와 거리는 문제가 아니었습니다. 유리창에 붙은 메뉴를 멀리서, 반사까지 얹어 찍은 사진들도 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. 로그인이 없는 이유

      한입지도는 로그인하지 않아도 그대로 사용할 수 있습니다.

      재방문한 사용자가 지난번에 무엇을 찾았는지에 대한 정보는 서버가 아니라 사용자의 브라우저 안에 남도록 했습니다. 그래서 "지난번엔 돈까스 찾으셨는데, 오늘도 그쪽으로 볼까요?" 하고 한입지도에서 이어서 물을 수 있습니다. 이 기록은 사용자 기기에만 있고 지우기를 누르면 사라집니다.

      다만 그 기억이 추천 결과를 바꾸지는 않습니다.