이 글은 Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle에 제출하는 내용입니다.
제가 만든 것 (What I Built)
Shoebox는 지저분한 영수증과 인보이스 폴더를 정리된 아카이브, CSV 원장(ledger), 그리고 세금 신고 요약본으로 변환하는 명령줄 도구(command-line tool)입니다. 모든 작업은 프리랜서 자신의 기기에서 실행됩니다.
제가 생각할 수 있는 모든 프리랜서는 같은 분기별 의식을 가지고 있습니다: Downloads 폴더를 뒤지고, 이메일 첨부 파일과 휴대폰 사진을 처리하고, 파일을 이름 변경하고, 스프레드시트에 금액을 입력하는 것입니다. 이것은 오후 시간을 잡아먹으며, 사람들이 돈을 내고 건너뛰고 싶어 하는 종류의 잡무입니다.
Shoebox를 폴더에 적용하면 다음 작업을 수행합니다:
- 각 파일의 해시(Hash) 값을 생성하여, 재실행 시에는 새로운 문서만 읽습니다.
- 텍스트(또는 사진)를 로컬 오픈 웨이트 모델(open-weight model)로 전송하고, 판매자(vendor), 날짜(date), 총액(total), 세금(tax), 통화(currency), 카테고리(category), 문서 유형(document type)을 요청합니다.
- 모델이 말한 모든 것을 재검증하는데, 모델은 날짜와 소수점 쉼표를 잘못 처리하는 경우가 있기 때문입니다.
- 파일을
sorted/2026-03/travel/2026-03-09_delta-air-lines_304.00.pdf 경로로 복사합니다.
- 카테고리 및 월별 총액이 담긴
ledger.csv와 summary.md를 작성합니다.
누가 비용을 지불할까요 (Who would pay for it):
- 클라이언트 인보이스를 제3자 API로 보내지 않고도 매 분기 말에
git clone https://github.com/amandewatnitrr/shoebox.git && cd shoebox
python -m unittest discover -s tests -v # unit tests, no model needed
ollama pull gemma3:4b # one-time model download
...
Code
프리랜서를 위한 로컬 우선 영수증 및 인보이스 분류기. 오픈 웨이트 모델 (Ollama를 통한 Gemma 3), 클라우드 연결 없음. Hacktoberfest AI Challenge W2.
Shoebox: receipts in, ledger out
license python deps model cloud hacktoberfest
🥿 Shoebox
노트북에서 실행되는 프리랜서를 위한 영수증 및 인보이스 분류기. 지저분한 폴더를 가리키세요. 이 도구는 오픈 웨이트 모델을 사용하여 모든 영수증을 읽고, 월별 및 카테고리별로 파일을 정리하며, CSV 파일과 세금 신고 요약을 제공합니다. 고객 데이터는 절대 사용자의 기기를 벗어나지 않습니다.
_Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle_을 위해 제작되었습니다.
솔직한 상태: 이것은 초안 프로토타입입니다. 실행해 보지 않고 작성되었으므로 거친 부분이 있을 수 있습니다. 여기에 있는 어떤 것도 실제 모델로 테스트된 적이 없으며, 아직 사용자가 없습니다. 아래 단계는 이를 확인하는 방법입니다.
사이드 허슬 (The side hustle)
모든 프리랜서는 영수증으로 가득 찬 상자(shoebox) 또는 다운로드 폴더를 가지고 있으며, 분기마다 이 일로 인해 오후 시간을 잃습니다. 그 오후 시간은 아낄 가치가 있습니다.
| 누가 비용을 지불하는가 | 무엇에 대한 대가인가 | 로컬 환경이 중요한 이유 |
|---|
| 프리랜서 및 독립 컨설턴트 | 분기별 '세금 | |
…
GitHub에서 보기
MIT 라이선스. 런타임 의존성: 없음 (PDF 지원은 선택적 pypdf 확장 기능입니다).
제작 과정 (How I Built It)
오픈 소스 구성 요소: 기본 오픈 웨이트 모델로 Gemma 3를 사용하며, 이를 Ollama가 로컬에서 서비스합니다. 영수증 사진의 경우 시각 능력을 갖춘 모델이 필요하므로 더 큰 Gemma 모델이 더 적합합니다. 이 모델은 --model이라는 하나의 플래그로 제어됩니다.
전체 시스템은 순수한 Python 표준 라이브러리로 구성되어 있습니다. Ollama 클라이언트는 urllib보다 약 60줄 정도 길게 작성되었습니다.
folder of receipts ──► hash + dedupe ──► text / image
│
Ollama on localhost (Gemma 3)
...
가장 많은 노력을 기울인 세 가지 설계 결정이 있었습니다:
1. JSON을 요청하는 대신 모델의 출력을 제한하기. Ollama의 format 필드는 JSON 스키마를 허용합니다. Shoebox는 카테고리의 enum을 포함한 스키마를 전달하여, 모델이 `
2. 모델의 숫자를 절대 신뢰하지 마세요. schema.py는 모든 필드를 재분석(re-parse)합니다:
parse_amount("$1,234.50") # 1234.5
parse_amount("1.234,50 €") # 1234.5 (마지막 구분자가 소수점임)
parse_amount("1,50") # 1.5
...
파싱할 수 없는 값은 빈칸이 되며, 빈 필드는 요약본의 "사람 검토 필요 (Needs a human look)" 목록에 포함됩니다. 숫자를 조용히 지어내는 장부 정리 도구는 아무런 도구가 없는 것보다 더 나쁩니다.
3. 통화 변환 없음. 합계 금액은 각 통화별로 유지됩니다. 환율을 추측하는 것이 회계 기록을 잘못되게 만드는 방법이며, 두 가지 통화로 청구하는 프리랜서는 두 개의 정직한 숫자를 보는 것을 선호할 것입니다.
원본 데이터는 --move 플래그를 전달하지 않는 한 이동되는 것이 아니라 _복사_됩니다. 진행 상황은 모든 문서 처리 후 ledger.json에 기록되므로, 폴더 중간에 충돌이 발생해도 아무것도 손실되지 않습니다.
솔직한 상태 보고. 저는 이것을 실행해 보지 않고 만들었습니다. 이 빌드는 코드 실행과 모델 다운로드가 불가능한 환경에서 진행되었기 때문에 Shoebox는 실제 모델에 대해 테스트되지 않았습니다. 단위 테스트(unit tests)는 파싱, 정렬 및 요약 로직을 다루며 모델이 필요하지 않지만, 저 역시 이를 실행해 보지 못했습니다. 버그가 발생할 것으로 예상되며, 특히 소형 모델이 흐릿한 사진을 처리하는 방식 주변에서 그럴 가능성이 높습니다. 이들을 수정하는 것이 참고 사항이 아니라 로드맵의 첫 번째 항목입니다.
오픈 혁신은 왜 중요한가요?
이 프로젝트는 모델이 개방적(open)이기 때문에 비즈니스로서만 작동할 수 있습니다.
- 비용(Cost). 클라우드 비전 API는 페이지당 비용을 청구합니다. 로컬 모델의 경우, 5,000번째 영수증에 대한 한계 비용은 전기료입니다. 이것이 회계사에게 평면 요금제 서비스가 가능한 이유입니다.
- 개인 정보 보호(Privacy). 영수증과 송장에는 고객 이름, 일일 요율, 은행 세부 정보 등이 포함됩니다. "당신의 문서는 이 기기를 떠나지 않습니다"라는 말은 프리랜서가 고객에게 할 수 있는 말입니다. Ollama를 사용하면 요청이
localhost로 전송됩니다.
- 종속성 없음(No lock-in). Gemma를 다른 모델로 교체하는 것 자체가 하나의 장점입니다. 특정 국가의 영수증 형식(VAT 레이아웃, GST 송장)에 맞춰 모델을 미세 조정(Fine-tuning)할 수 있는데, 이는 가중치(weights)가 제가 수정할 수 있기 때문이며, 폐쇄형 API에서는 불가능합니다.
- 오프라인 작동(Offline). 기차나 인터넷 연결이 차단된 사무실에서도 작동합니다.
나의 첫 사용자
아직 없습니다. 그리고 저는 임의로 인용문을 만들지 않을 것입니다. 가장 솔직한 다음 단계는 Shoebox를 프리랜서 친구에게 건네주고, 실제 분기 영수증으로 테스트하게 한 후, 그들이 다음에 무엇을 요청하는지, 그리고 비용을 지불할 의향이 있는지 기록하는 것입니다.
수상 부문(Prize Categories)
Gemma의 최적 활용: Gemma 3가 기본 모델이며, 텍스트 영수증과 (비전 기능이 있는 변형 모델을 사용한 경우) 영수증 사진 모두에 대해 Ollama를 통해 호출됩니다. 결과는 기계가 검사할 수 있도록 스키마 제약(schema-constrained output)을 적용합니다.