지난 글 ‘23년 동안 살아남은 동네슈퍼를 데이터로 분석해봤습니다’에서 제가 한 일은 단순했습니다. 기존 나들가게 POS에서 월별 매출, 거래 건수, 객단가, 현금매출과 카드매출 데이터를 크롤링하고, 이를 Supabase에 저장해 별도의 대시보드에서 볼 수 있도록 만들었습니다.
기존 POS에서도 데이터를 조회할 수는 있었습니다. 그러나 과거 데이터를 조회할 때마다 직접 하나씩 하나하나 조회해야 했고, 여러 메뉴에 정보가 흩어져 있어 전체 흐름을 한눈에 파악하기 어려웠습니다. 그래서 한 번 수집한 과거 데이터는 데이터베이스에 저장하고, 이후부터는 복잡한 POS 사이트에 다시 접속하지 않고 바로 불러오는 구조로 변경했습니다. 그 결과, POS가 처음 도입된 2011년의 매출과 2026년의 매출을 같은 화면에서 비교할 수 있는 기반이 만들어졌습니다.
이 단계까지만 해도 나름대로 의미가 있었습니다. 오래된 시스템 안에 갇혀 있던 데이터를 외부로 꺼냈고, 과거의 기록을 빠르게 탐색할 수 있게 되었기 때문입니다.
하지만 여전히 알 수 있는 것은 다음 정도였습니다.
매출이 왜 달라졌는지 알려면 결국 무엇이 팔렸는지까지 내려가야 했습니다. 이번 편에서는 프로젝트가 어디까지 확장되었는지, 그리고 데이터를 많이 모으는 것만으로는 왜 점주의 의사결정을 도울 수 없었는지를 정리해 보려고 합니다.
이번에 가장 먼저 추가한 것은 상품 상세 데이터였습니다.
기존에는 하루 총매출과 거래 건수만 가져왔다면, 이제는 POS의 다른 메뉴에 들어가 다음 정보까지 수집하도록 만들었습니다.

이 데이터는 월매출 달력보다 수집하기 더 어려웠습니다.
상품별 이익은 ‘일일 분류별 상품 판매 현황’에 있었고, 몇 시에 어떤 상품이 팔렸는지는 별도의 ‘상품 판매내역 조회’ 화면에 있었습니다. 결국 두 화면을 각각 크롤링한 뒤, 날짜와 상품을 기준으로 다시 연결해야 했습니다.
특히 거래 내역 화면은 하루치 영수증을 하나씩 열어 상품을 확인해야 했습니다. 어떤 날은 하루 데이터를 가져오는 데만 몇 분이 걸렸습니다. 그래서 최근 데이터만 가져오는 것으로 끝내지 않고, 과거 날짜를 하루씩 거슬러 올라가며 자동으로 수집하는 백필 과정을 만들었습니다.
월별 매출 데이터는 2011년까지 수집을 마쳤지만, 상품과 영수증 단위의 상세 데이터는 양이 훨씬 많아 아직도 수집 중입니다. 현재는 대략 2014년의 데이터까지 내려가며 차근차근 데이터베이스에 쌓고 있습니다.
그리고 이 과정을 대시보드에서 직접 확인할 수 있도록 별도의 ‘수집 현황’ 페이지도 만들었습니다.

처음에는 단순히 오래 걸리는 작업이라고만 생각했습니다. 하지만 수집 기간이 길어질수록 어느 연도까지 정상적으로 들어왔는지, 누락된 날짜는 없는지, 달력 매출과 세부 상품 매출의 합계가 일치하는지를 확인하는 기능도 중요해졌습니다.
데이터가 많아지면 수집 자체보다 제대로 수집되었는지를 검증하는 일이 더 어려워진다는 사실도 알게 되었습니다.
처음에는 개인 프로젝트이기 때문에 Supabase 무료 요금제로도 충분할 것이라고 생각했습니다. 하지만 영수증 거래 데이터만 60만 건을 넘어섰고, 영수증에 포함된 개별 상품 행은 100만 줄 이상 쌓였습니다. 일별 상품 집계 데이터까지 더해지면서 데이터베이스 용량은 빠르게 증가했습니다.
결국 Supabase 무료 요금제에서 제공하는 500MB 한도를 넘겼습니다. 오래된 데이터를 삭제하거나, 최근 2년 정도만 보관하는 방법도 생각해 볼 수 있었습니다. 하지만 이 프로젝트를 시작한 중요한 이유 중 하나가 2011년부터 이어진 가게의 데이터를 한곳에 모으는 것이었습니다.
과거 데이터를 삭제하면 비용은 줄어들겠지만, 이 프로젝트가 가진 가장 중요한 자산도 함께 사라지게 됩니다. 결국 Supabase Pro 요금제로 업그레이드했고, 현재 매달 약 3만 8천 원 정도를 지불하고 있습니다.
개인 사이드 프로젝트에 매달 비용을 내는 것이 부담스럽지만, 10년이 넘는 실제 가게의 원장을 보존하고 분석하는 비용이라고 생각하고 유지하기로 했습니다.
상품과 시간대 정보가 추가되자 기존 한 화면에 모든 내용을 담기 어려워졌습니다.
그래서 상단 메뉴를 크게 다음과 같이 구분했습니다.
처음 화면을 열면 ‘오늘’ 탭이 나타납니다. 점주가 가게에서 가장 먼저 궁금해할 정보는 과거의 장기 추세보다 오늘 장사가 어떻게 되고 있는지이기 때문입니다.
‘오늘’ 탭에서는 현재까지의 매출, 거래 건수, 객단가, 예상 이익과 함께 오늘 판매된 주요 상품군과 세부 상품을 보여줍니다. 반면, ‘월별’ 탭에서는 한 달 동안 누적된 데이터를 기준으로 다음 내용을 확인할 수 있도록 했습니다.



실제로 데이터를 확인해 보니 담배, 소주, 맥주가 전체 매출에서 매우 큰 비중을 차지하고 있었습니다. 세 상품군을 합치면 매출의 절반에 가까운 기간도 있었습니다.
그런데 여기서 재미있는 점이 하나 있었습니다. 매출이 높은 날이 반드시 돈을 많이 남긴 날은 아니었습니다.
어떤 날은 음식물 쓰레기 종량제 스티커가 많이 판매되면서 매출액 자체는 높게 나타났습니다. 하지만 이 상품은 가게에 남는 이익이 거의 없기 때문에 매출 순위만 보면 좋은 날처럼 보이지만, 실제 이익 측면에서는 그렇지 않았습니다.

이 경험을 통해 단순한 매출 순위가 점주에게 잘못된 인상을 줄 수 있다는 사실을 확인했습니다. 얼마나 많이 팔렸는가와 실제로 얼마가 남았는가는 전혀 다른 질문이었습니다.
사실 제가 처음부터 만들고 싶었던 것은 POS를 현대적으로 다시 디자인한 화면이 아니었습니다.
기존보다 정보를 보기 쉽게 만들고, 토스처럼 중요한 숫자가 먼저 들어오도록 UI를 구성하는 것도 필요했습니다. 하지만 그것만으로는 기존 POS를 조금 예쁘게 다시 만든 것에 불과합니다.
제가 정말 만들고 싶었던 것은 다음 구조였습니다.
Raw Data
무엇이 언제 얼마에 팔렸는가
↓
Calculation
요일·시간·상품·이익의 관계를 코드와 공식으로 계산
↓
Decision
그래서 점주가 무엇을 확인하거나 바꿔볼 것인가
여기서 중요한 점은 계산을 LLM에 맡기지 않는 것입니다. 매출 변화율, 판매지수, 동반구매율, 상품별 마진과 같은 숫자는 SQL과 코드로 계산합니다. LLM은 이미 계산된 결과를 점주가 이해할 수 있는 문장으로 바꾸는 역할만 맡도록 했습니다.
LLM이 원본 데이터를 보고 자유롭게 판단하게 하면 그럴듯하지만 근거가 불분명한 설명을 만들 가능성이 있기 때문입니다.
처음에는 이런 판단 카드를 만들었습니다. 13시 방문을 이익으로 전환할 기회가 있어요. 13시는 거래 건수가 많지만 객단가가 낮으므로 계산대 근처에 고마진 상품을 배치해 보라는 내용이었습니다.
처음 화면에 띄웠을 때는 꽤 그럴듯해 보였습니다. 하지만 점주의 입장에서 다시 생각해보니 별로 의미 있는 정보가 아니었습니다.
어느 것도 충분히 설명하지 못했습니다. 결국 ‘데이터를 분석한 문장’처럼 보일 뿐, 실제 행동을 바꿀 만큼 구체적인 판단은 아니었습니다. 그래서 이 카드를 제거하고, 계산 계층을 다시 설계했습니다.
이후에는 하루의 숫자 하나를 보고 인사이트를 만들지 않도록 했습니다.
예를 들어, 특정 상품이 금요일에 많이 팔렸다고 판단하려면 다음 조건을 함께 계산합니다.
이를 바탕으로 다음과 같은 결과를 만들고자 했습니다. 최근 8주 금요일 중 7주에서 12~14시 아이스크림 판매량이 다른 시간대보다 높았습니다. 단순히 “금요일에 아이스크림이 많이 팔립니다”라고 말하는 것보다, 표본과 반복성을 함께 보여주는 방식입니다. ‘더 준비하세요’라는 표현도 조심했습니다.
현재 데이터에는 실제 재고 수량이나 발주 단위가 없기 때문에 정확히 몇 개를 주문해야 하는지는 알 수 없습니다. 그래서 시스템에서는 ‘발주량’이 아니라, 과거 판매량의 중앙값과 최근 추세를 기준으로 목표 준비량을 보여주도록 만들었습니다.
데이터가 충분히 쌓이면서 각각의 표를 나열하는 것보다, 가게의 운영 구조를 한 번에 보고 싶다는 생각이 들었습니다. 그래서 ‘판단’ 탭을 일종의 가게 디지털 트윈처럼 다시 구성했습니다. 물론 공장의 설비나 물류 흐름을 실시간으로 복제하는 거창한 디지털 트윈은 아닙니다. 이 가게에서 반복되는 시간과 상품의 구조를 데이터로 옮겨놓았다는 의미에 가깝습니다.
이 화면은 네 가지 관점으로 구성했습니다.
요일과 시간을 히트맵으로 연결해, 언제 가게의 거래가 집중되는지 보여줍니다. 실제로 데이터를 확인해 보니, 요일에 관계없이 대체로 오후 5시에서 8시 사이가 가장 중요한 시간대로 나타났습니다.
단순히 피크 시간만 보여주는 것이 아니라, 오전 8~12시, 12~16시, 16~20시, 20~24시로 나누어 각 시간대에 어떤 상품과 상품 조합이 주로 판매되는지도 연결했습니다.

월별로 어떤 카테고리의 비중이 달라지는지 여러 해의 평균으로 계산했습니다. 아이스크림처럼 계절성이 명확한 상품뿐 아니라, 같은 계절에도 매년 반복적으로 증가하거나 감소하는 상품군을 확인할 수 있습니다.

2011년보다 지금의 매출이 높다고 해서 가게가 실제로 더 많은 상품을 판매한다고 볼 수는 없습니다. 그동안 상품 가격 자체가 올랐기 때문입니다. 그래서 공식 소비자물가지수와는 별개로, 우리 가게에서 여러 해 동안 공통으로 판매된 상품들의 단가 변화를 이어 붙인 자체 단가 지수를 만들었습니다.
이를 통해 매출이 가격 상승 때문에 오른 것인지, 실제 거래와 상품 판매가 늘어 오른 것인지를 구분해보려 했습니다. 이 지수는 공식 물가지표가 아니라, 어디까지나 우리 가게의 판매가격을 기준으로 계산한 지표라는 점도 화면에 함께 표시했습니다.

같은 카테고리 안에서도 시간이 지나면서 대표 상품이 바뀝니다. 과거에 많이 팔렸던 상품의 점유율이 줄어들고 새로운 상품이 이를 추월하는 시점을 찾아, 어떤 제품이 어떤 제품으로 대체됐는지 시각화했습니다.
데이터에서는 박카스 중심이던 에너지음료 수요가 몬스터 같은 새로운 상품으로 이동하는 모습도 확인할 수 있었습니다.

과거에 무엇이 팔렸는지를 단순히 보여주는 것은 “그때는 그랬구나”에서 끝날 가능성이 큽니다. 제가 찾고 싶었던 것은 과거의 기록 자체가 아니라, 현재 상품 운영에 영향을 줄 만큼 반복되거나 구조적으로 변한 패턴이었습니다.
영수증 단위의 상품 데이터가 생기면서 어떤 상품을 함께 구매하는지도 계산할 수 있게 되었습니다. 다만 판매 건수가 많은 상품끼리는 우연히 함께 잡힐 가능성도 높습니다.
그래서 단순 동반 구매 횟수뿐 아니라, 두 상품이 각각 팔릴 확률과 비교해 실제로 함께 구매될 가능성이 몇 배 높은지를 나타내는 lift도 함께 계산했습니다.
그 결과 POS에 기록된 상품명을 그대로 기준으로 다음과 같은 조합을 발견했습니다.
![함께 사가는 조합 리스트, [시원]360ml 보조상표와 카스1000L을 같이 사는 경우가 우연 대비 20.5배로 나타나는 등 동시구매 배수를 정리함](https://www.wishket.com/media/news/3900/img-11.png)
부모님은 오랫동안 가게를 운영했기 때문에 어떤 상품이 함께 팔리는지 어느 정도 이미 알고 계셨을 수 있습니다. 다만 그동안은 경험과 감각으로 알고 있던 사실을 데이터상에서 정확한 횟수와 수치로 꺼낼 수 있게 되었습니다.
그렇다고 이 결과만 보고 곧바로 두 상품을 묶어 팔거나 진열을 바꾸는 것은 아닙니다. 두 상품을 함께 산 사람이 실제로 여러 명인지, 한 명의 단골이 반복해서 구매한 것인지 현재 POS 데이터만으로는 알 수 없기 때문입니다.
결국 동반 구매 분석도 정답이라기보다, 점주가 현장을 다시 살펴볼 질문의 출발점에 가깝습니다.
이번에는 POS 밖의 데이터도 처음으로 연결했습니다. 기상청 데이터를 활용해 2011년부터 현재까지 부산의 기온, 습도, 강수량, 운량 등의 날씨 정보를 수집했습니다.
현재는 약 5,690일의 일별 날씨와 13만 건이 넘는 시간대별 관측 데이터가 쌓여 있습니다. 처음에는 같은 달과 같은 요일 안에서 날씨가 더운 날과 그렇지 않은 날을 비교했습니다. 하지만 15년 가까운 데이터가 생기자 같은 달에만 한정할 필요가 없다는 생각이 들었습니다.
작년 8월과 올해 7월이라도 기온과 습도, 강수 조건이 비슷하다면 가게의 입장에서는 충분히 비교할 수 있는 날이기 때문입니다. 그래서 현재는 강수 여부를 먼저 나누고, 기온·습도·운량이 오늘과 가장 비슷했던 과거 날짜를 전 기간에서 찾는 방식으로 변경했습니다.
그 뒤 비슷한 날들에 평소보다 더 팔렸던 상품과 덜 팔렸던 상품을 보여줍니다.

예를 들어, 단순히 “더운 날에는 아이스크림이 잘 팔립니다”라고 보여주는 것은 큰 의미가 없습니다. 대신 다음과 같이 보여주는 것이 목표입니다. 오늘과 비슷한 날씨였던 과거 20일에서 비비빅은 하루 평균 9개, 메로나는 3.2개 더 판매됐습니다. 판매량이 0.02개에서 0.4개로 올랐다면 수치상으로는 20배지만, 실제로는 하루 0.38개 차이에 불과합니다.
그래서 배수보다 하루에 실제로 몇 개 더 팔렸는지를 기준으로 순위를 정했습니다. 다만 이것 역시 인과관계라고 단정할 수는 없습니다. 기온이 비슷한 날에 특정 상품이 함께 많이 팔렸다는 사실을 보여줄 뿐, 날씨 때문에 해당 상품이 팔렸다고 확정하는 것은 아닙니다. 계절, 요일, 상품의 출시 시점과 단종 여부 같은 다른 요인도 함께 영향을 줍니다.
따라서 날씨 탭도 현재는 정답을 알려주는 기능이라기보다, 내일 무엇을 조금 더 준비해 볼지 판단할 근거를 제공하는 단계에 있습니다.
분석 결과를 점주가 읽기 쉬운 문장으로 바꾸기 위해 LLM API도 연결했습니다. 하지만 LLM이 매출 원장을 직접 보고 계산하거나, 자유롭게 상품 운영 방법을 추천하도록 하지는 않았습니다.
역할을 다음과 같이 분리했습니다.
SQL과 코드
판매량·마진·반복성·동반구매·날씨 효과를 계산
LLM
계산된 수치와 한계를 사람이 이해하기 쉬운 문장으로 정리
LLM이 실패하거나 API를 사용할 수 없는 경우에도 판단 기능이 멈추지 않도록, 같은 내용을 정해진 문장으로 출력하는 템플릿도 함께 만들었습니다. 결국 이 시스템에서 LLM은 판단을 만들어내는 두뇌라기보다, 이미 계산된 결과를 점주의 언어로 번역하는 인터페이스에 가깝습니다.
이번 프로젝트를 하면서 개발자분들이 새삼 대단하다고 느낀 순간도 많았습니다. 이 서비스는 사실상 한 사람, 많아야 가족 몇 명이 사용하는 개인용 서비스입니다. 대규모 트래픽이 발생하지도 않고, 수많은 사용자가 동시에 요청을 보내지도 않습니다.
그런데도 상품 데이터, 판단 탭, 날씨 데이터처럼 기능을 하나씩 추가할 때마다 탭 이동이 느려지고 로딩 시간이 길어졌습니다. 과거 데이터를 매번 다시 계산하면 화면을 열 때 오래 기다려야 했고, 백필 작업이 브라우저를 사용하고 있으면 오늘 데이터를 수집하는 작업이 밀리기도 했습니다.
이를 해결하기 위해 다음과 같은 방법을 계속 추가했습니다.
혼자 사용하는 서비스에서도 이 정도의 고민이 생겼습니다. 그렇다면 토스처럼 수많은 사용자가 동시에 접속하고, 계속 새로운 기능이 추가되는 서비스에서 백엔드 개발자분들은 얼마나 많은 통신 방식과 캐싱, 동시성, 데이터 정합성 문제를 고민하고 있을까 하는 생각이 들었습니다.
화면에서는 버튼을 한 번 누르면 바로 다음 정보가 나오지만, 그 자연스러운 경험 뒤에 얼마나 많은 최적화가 숨어 있는지 조금이나마 체감할 수 있었습니다.
기능이 많아지면서 또 다른 문제가 발생했습니다. Claude나 Codex를 이용해 바이브 코딩을 하다 보면, 새로운 터미널과 새로운 대화창에서 작업을 시작하게 됩니다. 그러면 이전 대화에서 왜 특정 구조를 선택했는지, 어떤 오류 때문에 방어 로직을 추가했는지가 사라집니다.
새로운 AI는 현재 코드만 보고 다음과 같이 판단할 수 있습니다. 이 부분은 복잡해 보이니 제거해도 되겠습니다. 하지만 실제로는 과거에 발생했던 데이터 오염이나 삭제를 막기 위해 일부러 복잡하게 만들어둔 코드일 수 있습니다.
실제로 프로젝트에서는 같은 날 같은 이름을 가진 다른 상품이 한 번에 들어오면서 저장 배치 전체가 실패한 적도 있었습니다. 그 문제로 1,000일이 넘는 상품 데이터가 정상적으로 저장되지 않았고, 백필 작업도 과거로 내려가지 못한 채 같은 구간을 반복하고 있었습니다.
또 수집이 2014년에서 멈춘 이유를 처음에는 데이터베이스 용량이나 POS 보관기간 때문이라고 의심했지만, 실제로는 앞선 저장 오류로 미완료 날짜가 쌓여 과거 날짜까지 내려가지 못한 것이 원인이었습니다.
이런 맥락을 모르는 AI가 코드를 단순화하면 이미 해결한 문제가 다시 발생할 수 있었습니다.
그래서 SSOT.md라는 문서를 만들었습니다.

SSOT는 Single Source of Truth, 즉 하나의 기준이 되는 문서라는 의미입니다.
이 문서에는 다음 내용을 계속 기록했습니다.
AI에 새로운 작업을 맡길 때도 먼저 이 문서를 읽도록 했고, 중요한 구조를 변경하면 코드와 함께 SSOT도 갱신하도록 했습니다. 바이브 코딩은 코드를 빠르게 만드는 데에는 매우 유용합니다.
하지만 프로젝트가 길어질수록 중요한 것은 코드를 빨리 생성하는 능력보다, 왜 이런 코드가 존재하는지를 잊지 않는 능력이라는 사실도 알게 되었습니다.
이 프로젝트에서 계속 경계하고 있는 부분도 있습니다. 우리 가게는 불특정 다수가 끊임없이 방문하는 대형 매장이나 프랜차이즈 편의점이 아닙니다. 주변에 거주하는 단골손님과 매일 담배나 술을 사러 오는 고객의 비중이 높습니다.
따라서 특정 상품 조합이 많이 나타났다고 해서 많은 고객이 공통으로 선호한다고 단정할 수 없습니다. 한 명의 단골이 같은 조합을 반복해서 구매했기 때문에 만들어진 결과일 수도 있습니다. 고객 ID가 없는 POS 데이터만으로는 이를 완전히 구분하기 어렵습니다.
예를 들어, 데이터를 분석하니 월요일에는 캔커피가 평소보다 약 2.3배 많이 팔리는 패턴이 나타났습니다. 하지만 이 수치만 보고 월요일마다 캔커피를 두 배로 발주하는 것은 위험합니다.
먼저 현장에서 다음 질문을 해봐야 합니다.
이 시스템이 해야 할 일은 “월요일에는 캔커피를 더 주문하세요”라고 정답을 말하는 것이 아닐 수 있습니다. 오히려 부모님이 오랜 경험 속에서 놓치고 있던 패턴을 꺼내 다음과 같은 질문을 던지는 것이 더 중요할 수 있습니다.
처음에는 POS 데이터를 가져오는 것 자체가 가장 어려운 문제라고 생각했습니다. 엑셀 다운로드도 제대로 지원하지 않는 오래된 사이트에서 데이터를 크롤링하고, 2011년부터의 기록을 데이터베이스에 저장하는 일이 가장 큰 과제처럼 보였습니다.
그런데 지금은 생각이 달라졌습니다. 데이터를 모으는 것은 어렵지만, 언젠가는 끝납니다. 진짜 어려운 문제는 그다음입니다. 수집한 데이터를 어떤 기준으로 연결하고, 어떤 차이를 의미 있는 변화로 판단하며, 어떤 결과만 점주에게 보여줄 것인가.
수많은 숫자를 보여주는 것은 쉽습니다. 그러나 그중 실제로 부모님의 다음 행동을 바꿀 만한 신호를 찾아내는 것은 전혀 다른 문제입니다. 현재 시스템은 과거와 비슷한 날, 특정 요일과 시간대의 평균, 함께 팔린 상품, 날씨 조건에 따른 판매 차이를 보여줄 수 있는 단계까지 왔습니다. 하지만 아직은 대부분 관찰과 비교에 가깝습니다.
앞으로는 여기서 한 단계 더 나아가고 싶습니다.
결국 제가 만들고 싶은 것은 가게 운영의 정답을 대신 내려주는 AI가 아닙니다. 점주가 그냥 지나쳤을 수 있는 변화를 발견하고, 무엇을 확인하고 시험해 볼지 더 정확한 출발점을 제시하는 시스템입니다.
23년 동안 가게를 운영해 온 부모님의 경험을 데이터로 대체하는 것이 아니라, 그 경험이 새로운 질문을 만날 수 있도록 돕는 것. 그것이 이 프로젝트가 다음 단계에서 풀고 싶은 문제입니다.
<원문>
23년 동안 살아남은 동네슈퍼를 데이터로 분석해보려 합니다. (3편)
ⓒ요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.