← 에이전트

ASHD - 영수증 사진을 환불·보증 마감 알림으로 바꾸는 웹 서비스

사진 한 장에서 구매 정보를 읽어 마감 전에 알려 주는 서비스를 두 번 만들고 조용히 지나가던 실패를 고친 뒤 전시물로 남긴 기록

목차
  1. 무엇인가
  2. 왜 만들었나
  3. 어떻게 동작하나
  4. 써 보니
  5. 실패와 고친 것
  6. 지금 상태

무엇인가

영수증이나 보증서 사진을 올리면 상호·구매일·금액을 읽어
환불·보증 마감을 계산해 두고 마감이 다가오면
이메일이나 텔레그램으로 알려 주는 웹 서비스를 만들었다.
7월 하루 동안 실제 키가 든 파일을 걷어내고
제품 저장이 실패해도 작업이 completed로 끝나던 경로를 실패로 드러나게 고쳤다.
그리고 일주일 남짓 뒤 제품을 접고 전시물로 전환했다.

이름 ASHD는 Almost Service Handling Disorder의 줄임말이다.
ADHD에 빗대어 AS(사후 서비스)·환불·보증 같은 뒤처리를
잘 챙기지 못하는 사람을 돕는다는 뜻을 담았다.
2026-10-19 기준으로 라이브 데모와
공개 스냅샷 리포가 남아 있다.

왜 만들었나

첫 버전 개요 문서는 가계부·택배 조회 앱이 소비 기록과 배송 상태에 초점을 맞춘다고 적고
구매 뒤 1~2년 동안 유지되는 보증·AS·환불권을 체계적으로 관리하는 도구는 거의 없다고
적었다.
개요 문서는 정보가 이메일, 카카오톡, 종이 영수증, 박스 스티커 등 여러 채널에 흩어진다고
적는다.
같은 문서는 보증이 끝난 뒤에야 무상 AS를 떠올리고
환불 기간을 놓쳐 초기 불량인데도 환불에 실패하는 일을 문제로 든다.

첫 버전은 12월에 시작했고 FastAPI와 SQLite로 만들었다.
2월 10일에는 v1이 내 워크플로우에 쓰지 않는 FastAPI 방식이라며
큰 틀의 리팩토링이 옳은지 에이전트에게 물었다.
2월 16일에는 "현재 내 스택에 맞춰 supabase+vercel 조합으로 바꿀거야."라고 말했다.
Python 코드를 지우는 커밋이 7868줄을 걷어내고 Next.js 골격을 넣었고
36초 뒤 다음 커밋이 v2 기능을 넣었다.
2월 24일 에이전트가 쓴 전략 브리프는 범위를 업로드, OCR(광학 문자 인식) 추출,
알림 기능 세 가지로만 잡았다.

어떻게 동작하나

2026-10-19에 확인한 코드 기준으로 사진은 presigned URL로
Cloudflare R2에 올라가고, 서버는 작업 번호를 먼저 돌려준 뒤
응답을 보낸 다음에 처리를 이어 간다.
처리 직전에는 R2 객체를 다시 조회해 크기(10MB까지)와 형식을 확인하고
Google Vision OCR 결과에서 룰 추출기가 필드를 추출한다.
부족한 필드는 LLM(대규모 언어 모델, OpenAI·Anthropic 듀얼)이 채운다.

추출 결과는 DB에 넣기 전에 sanitizeExtractedFields를 거친다.
날짜는 네 가지 형식만 받아 YYYY-MM-DD로 맞추고
금액은 정수로 잘라 1 이상 10억 이하만 남긴다.
범위를 벗어난 필드는 오류를 내지 않고 버린다.

구성 파일은 매일 UTC 00:00(KST 09:00)에 크론을 한 번 돌린다.
제품·종류·임계일·채널 조합이 발송 이력에 두 번 기록되지 못하게 막았고
임계일이 지났는데 보내지 못한 알림은 다음 실행에서 따라잡는다.
처리는 사용자당 하루 30건까지이고, 재시도도 그 30건에 포함된다.
스택은 Next.js 15, Supabase(인증·DB), Vercel Hobby 서울 리전이다.

내 역할은 기획·방향·검수였다.
병합 커밋을 뺀 내 계정 커밋 48건 중 29건에 Claude 공동 작성 표기가 붙어 있다.
3월에 UI를 맡긴 러버블(Lovable)이
핵심인 OCR+LLM 로직을 없애고 수동 등록으로 바꾸는 계획을 제안했을 때,
기존 로직을 그대로 두자고 한 것은 나였다.
같은 날 러버블의 UI를 옮긴 커밋의 메시지는
"OCR+LLM 백엔드 파이프라인은 일체 변경 없이 프론트엔드만 교체"라고 적었다.

7월 4일 밤에는 GJC 세션에서 "내 ASHD 프로젝트는 Opus가 진행했어."라고 알리고
"일단 먼저 해당 프로젝트 딥다이브부터 시작하자. 모든 로직을 매우 정밀하게 분석해."라고
지시했다.
같은 세션에서 "전부 추천내용으로 진행해"라고 입력했다.

써 보니

가입 계정은 몇 개 수준이었고
결제는 Polar.sh(결제 서비스) 웹훅 로그만 남긴 채 과금하지 않았다.
2026-10-19 기준으로 공개 스냅샷 리포의 스타·포크·이슈는 모두 0이다.
사용 효용을 평가한 내 발화 기록은 찾지 못했다.

2월 말에는 "누르면 1~2초후 페이지이동."이라는 증상을 전하며 병목 분석을 맡겼다.
링크를 전부 Next.js Link로 바꿔 클라이언트 측 이동을 켜고
요청마다 중복되던 getUser() 호출을 React cache()로 묶었다.
9월에는 "저번에 ASHD 랜딩페이지만들었는데 지금들어가려고보니 안들어가진다."
라고 말했고 "아니 지금 ashd 구글로그인 ourfolio랑 똑같이 만들려고하는거야"라고 했다.

룰 추출기의 정확도는 7월에 필드별 F1(정밀도와 재현율을 함께 본 점수)로 측정했다.
시드 골드 10건을 처음 측정한 결과는 상호 94%, 구매일 100%, 금액 95%였다.
공개 한국어 영수증 데이터 KORIE의 OCR 테스트 스플릿 314예제로 측정하면서 룰을 고치자
상호는 9%에서 100%, 구매일은 89%에서 99%, 금액은 98%에서 100%가 됐다.

상호 점수가 오른 것은 브랜드 목록 방식을 첫 줄 휴리스틱으로 바꾼 결과다.
시드 골드 세 필드 100%를 기준점으로 고정하고
풀 리퀘스트마다 eval을 실행하는 CI(지속 통합) 회귀 게이트를 걸었다.

실패와 고친 것

v2를 만든 다음 날 커밋은
응답을 돌려주면 함수가 끝나는 Vercel에서는
fire-and-forget 방식이 실패한다고 적고
백그라운드 처리를 Next.js의 after()로 옮겼다.

7월 하드닝의 첫 커밋은 git에 추적되던 환경변수 파일을 지웠다.
그 파일에는 OCR·LLM API 키, 메일 SMTP 비밀번호,
텔레그램 봇 토큰이 실제 값으로 들어 있었다.
커밋 메시지는 두 원격이 모두 비공개라 이력을 다시 쓰지 않았다고 적고
키는 사용자가 교체해야 한다고 명시했다.

7월 12일 공개 스냅샷 매니페스트는 작업 트리에 실제 키가 든 로컬 환경변수 파일과
연구용 라이선스 데이터가 있다고 적었다.
그래서 공개 스냅샷은 허용 목록에 있는 경로만 개발 이력 없이 복사해 만들었다.

7월 5일 하드닝에서는 제품 저장도 고쳤다.
코드 주석은 LLM 출력이 문자열인지 숫자인지만 검사받아
"2026년 3월" 같은 날짜나 터무니없는 금액이 Postgres 삽입을 실패시킬 수 있었다고 적는다.
커밋 메시지는 고치기 전에는 삽입이 실패해도 작업이 제품 없이 completed로 조용히 끝났다고
적는다.

이 커밋 뒤로 저장이 실패하면 오류 메시지를 남기고 실패로 끝나게 됐고
같은 파일로 다시 돌리는 재시도 버튼도 들어갔다.
범위 검사를 통과하던 2026-02-31이나 평년의 2월 29일은
Date.UTC 왕복 검증으로 거른다.

9월에는 로그아웃이 405를 냈다.
NextResponse.redirect의 기본값인 307은 메서드를 보존하기 때문에,
브라우저가 POST 핸들러가 없는 로그인 페이지에 POST를 다시 보냈다.
로그인 페이지에 POST 핸들러를 다는 안은
리다이렉트의 의미를 라우트에 떠넘긴다는 이유로 버리고 303을 명시했다.
세션이 실제로 끊기는지는 배포 뒤 브라우저로만 확인할 수 있다고 커밋에 남겼다.

나머지는 한 줄씩 적는다.

시기증상고친 것
3월환경변수 끝 줄바꿈이 R2 presigned URL에 %0A로 섞여 CORS(교차 출처 요청) 사전 요청이 깨짐R2 환경변수를 읽을 때 전부 trim
3월문서 처리 요청의 파일 경로가 요청한 사용자 것인지 확인하지 않음사용자 id 접두사 검증으로 IDOR(남의 객체 직접 접근) 차단
6월Vercel Hobby가 조직 소유 비공개 리포를 배포하지 못함main을 개인 계정 미러 리포로 보내 배포
7월presigned PUT이 선언한 크기를 강제하지 않음처리 직전 객체를 다시 조회해 범위 밖이면 400
9월react만 19.2.8로 올라 빌드 실패, dependabot 풀 리퀘스트는 CI가 돌지 않은 채 자동 병합react-dom도 19.2.8로 맞춤

지금 상태

ASHD는 보관 상태다.
내 마지막 커밋은 9월 로그아웃 수정이고
그 뒤로는 dependabot(의존성 자동 갱신 봇)의 커밋 5건만 들어왔다.
라이브 데모는 2026-10-19 확인 때 응답했고 첫 화면에
"결제 없음. 사용자당 하루 30건까지 처리합니다."라고 적혀 있다.

접은 날은 7월 중순 새벽이다.
"내 ASHD 냉정하게 평가해줘."로 시작해 수익을 낼 수 있는지 묻자, 에이전트는
"결론부터: 수익화 가능성 낮다. 접고 포트폴리오로 남기는 게 맞다."라고 답했다.

이유는 네 가지였다. 직접 경쟁하는 앱이 이미 모든 기능을 무료로 제공하고,
지불 의사가 구조적으로 낮고, 대형 플랫폼이 번들링할 수 있고,
업로드마다 OCR·LLM 비용이 나가서 유닛 이코노믹스가 역방향이라는 것이다.
나는 "그럼 포트폴리오로 전환해서 마무리해보자."라고 답했다.
물어본 지 5분 만에 접었다.

그 평가가 근거로 삼은 시장 조사는 웹 검색 도구 호출 한 번이었다.
검색 결과에는 영수증·보증서를 찍으면 인식해서 만료 전에 알려 주는
무료 서비스 하나가 있었다.
지불, 번들, 유닛 같은 낱말은 결과에 나오지 않았다.
평가는 README를 포트폴리오 모드로 다시 쓰고 "왜 접었나"에 이 시장 분석을 한 단락 넣으라고
권했다.

10월에는 "ASHD는 너가 적절히 조치하자."라고 맡겼고 에이전트는
README의 "실제 상용 운영(결제 연동 실험까지 진행, 미과금)을 거친 뒤"를
"라이브로 배포해 직접 써 본 서비스입니다."로 바꾸면서
가입 계정은 몇 개 수준이었고 과금하지 않았다고 적었다.

"왜 접었나"의 시장 판단은 출처 기록을 찾지 못했지만 이때 고치지 않았다고 커밋에 남았다.
산출물을 닫을 때 검증되지 않은 주장을 지운 사례는
검사기 셋을 지우고 테스트 73건을 71건으로 줄여서 닫았다에 따로 적었다.

공개 README는
v1에서 넘어온 OCR 타임아웃 설정값(15초)이 v2의 OCR 호출에 연결돼 있지 않다고 적는다.
9월 react 수정 커밋은 dependabot 풀 리퀘스트의 CI 승인 게이트 자체는 손대지 않았고
같은 일이 반복될 수 있다고 적었다.