브라질 회계사 영수증을 입력하고 오류를 감지하는 로컬 AI
요약
브라질 회계사가 수동으로 처리하던 다양한 형태의 영수증 및 재무 문서를 로컬 AI를 이용해 자동화하는 프로젝트입니다. Ollama에서 구동되는 오픈 웨이트 모델을 활용하여 개인 정보 보호(LGPD)와 비용 효율성을 확보했습니다. 이 시스템은 문서 스캔, 데이터 추출, 검증 과정을 거쳐 사용자가 최종적으로 수정하고 Excel로 내보낼 수 있도록 합니다.
핵심 포인트
- 오픈 웨이트 모델과 Ollama를 사용하여 로컬 환경에서 구동되어 개인 정보 보호가 완벽합니다.
- 다양한 형태의 영수증(PDF, 사진, 손글씨)을 처리할 수 있는 강력한 문서 파싱 능력을 보여줍니다.
- 클라우드 API 의존성을 제거하여 비용 효율적이며 확장성이 뛰어납니다.
이 글은 Hacktoberfest Open-Source AI Challenge: Week 2 — Side Hustle에 제출하는 내용입니다.
제가 만든 것
브라질의 자영업 회계사는 소규모 비즈니스 고객들로부터 매주 약 100개의 문서를 받습니다: NF-e 송장(PDF), 휴대폰으로 촬영한 NFC-e 영수증, Pix 이체 스크린샷, 그리고 가끔 손글씨로 작성된 recibo입니다. 오늘날 그녀는 이 모든 문서를 수동으로 스프레드시트에 타이핑합니다.
Leitor de notas("영수증 리더")는 이러한 문서 더미를 검토가 완료된 Excel 파일로 변환합니다:
- 파일들을 Streamlit 페이지에 드롭하고 회계 기간(competência)을 선택합니다.
- 각 문서는 Ollama에서 로컬 환경으로 구동되는 오픈 웨이트 모델에 의해 읽힙니다.
- Python이 모델이 반환한 모든 내용을 검증하고, 사람이 확인해야 할 행들을 표시(flag)합니다.
- 그녀는 편집 가능한 테이블에서 표시된 행들을 수정하고 Excel로 내보냅니다 (항목별 시트 1개, 카테고리별 요약 시트 1개).
누가 비용을 지불할까요? 회계사는 소규모 비즈니스에 월별 수수료를 청구하며, 데이터 입력은 확장성이 떨어지는 작업입니다. 고객 한 명당 매주 몇 시간을 절약하는 것은 돈이 됩니다. 문제는 이들이 고객의 재무 기록이며, 브라질의 데이터 보호법(LGPD)의 적용을 받는다는 점입니다. "고객의 은행 영수증을 우리 클라우드에 업로드하세요"라는 말은 그녀에게도, 그리고 그녀의 고객들에게도 받아들이기 어려운 제안입니다.
여기서 오픈이 중요한 이유
이 프로젝트가 작동하는 이유는 모델이 오픈 웨이트(open-weight)이며 기기에서 구동되기 _때문_입니다:
- 개인 정보 보호가 핵심입니다. 아무것도 PC를 벗어나지 않습니다. 유일한 네트워크 호출은
localhost:11434의 Ollama로만 이루어집니다. 기본적으로 활성화되어 있는 Streamlit의 사용 통계(gatherUsageStats = false)도 비활성화했으며, 서버를 localhost에 바인딩했습니다. - 문서당 비용이 제로입니다. 여러 클라이언트를 대상으로 주당 약 100개의 문서를 처리할 때, 페이지당 API 요금은 회계사의 마진을 직접적으로 잠식합니다. 하지만 일회성 GPU 구매는 그렇지 않습니다.
- 모델 교체가 자유롭습니다. 모델 이름은 하나의 상수(constant)에만 존재합니다. 더 나은 소형 비전 모델이 출시되면, 새로운 계약이나 가격 책정 없이 한 줄의 코드 변경과 평가 스크립트 재실행만으로 충분합니다.
데모
가상의 10개 문서(텍스트 PDF, Pix 영수증, 기울어진 휴대폰 사진, 손글씨 영수증, 그리고 의도적으로 판독하기 어려운 흐릿한 영수증)에 대한 평가:
| 필드 | 날짜 | 금액 | CNPJ | 카테고리 | 경로 | 검토 필요 여부 |
|---|---|---|---|---|---|---|
| 정확함 | 9/10 | 9/10 | 9/10 | 10/10 | 10/10 | 10/10 |
필드 정확도 95%. 세 번의 누락은 흐릿한 영수증에서 발생했습니다. 모델이 날짜와 CNPJ를 환각(hallucinated)했지만, 검증 과정이 이를 포착하여 해당 문서는 '검토 대기열'에 배치되었고, 이는 정확히 일어나야 하는 상황입니다. AMD RX 9060 XT (Ollama, Vulkan) 기준 평균 시간은 텍스트 PDF당 2.2초, 이미지당 4.0초입니다. 이는 주간 문서 처리 기준으로 약 5분 정도 소요됩니다.
코드
GitHub logo MathFe / leitor-notas-local
Leitor local de notas fiscais e comprovantes de Pix com IA open-source (Ollama) — DEV Hacktoberfest 2026 · Side Hustle
Leitor local de notas fiscais e comprovantes de Pix
**DEV Hacktoberfest 2026 —
leitor_notas/— 애플리케이션(파이프라인, 유효성 검사, 평가 및 Streamlit 인터페이스). README를 확인하세요.amostras-mariana/— 정확도를 평가하기 위한 가상의 문서 10개 + 정답지.
샘플 결과: 필드 95%가 정확함, 문제 있는 문서 2개가 검토를 위해 전송됨.
라이선스: MIT.
구축 과정 (How I Built It)
사용 스택: Python, Ollama, qwen2.5:3b (텍스트), qwen2.5vl:7b (비전), pdfplumber, Pillow, pydantic, pandas/openpyxl, Streamlit.
하이브리드 파이프라인: 작업을 수행할 수 있는 가장 저렴한 모델
PDF와 추출 가능한 텍스트 (> 50자) ──► pdfplumber ──► qwen2.5:3b
스캔된 PDF ──► 페이지를 이미지로 렌더링 ───┐
JPG / PNG ──────────────────────────────┴──► qwen2.5vl:7b
...
대부분의 NF-e 인보이스는 텍스트 레이어가 있는 실제 PDF 형태로 도착하므로, 비전 모델이 필요하지 않습니다. 출력은 Ollama의 format 매개변수로 제한되는데, 이 매개변수는 pydantic 모델의 JSON Schema를 받아 응답이 항상 파싱되도록 합니다.
교훈 1: 작은 모델에게 역할에 대한 추론을 맡기지 마라
제가 처음 프롬프트를 작성했을 때 모델에게
동일한 아이디어는 접근 키 자체에도 적용됩니다. 44자리의 숫자를 복사하는 3B 모델은 하나를 누락할 수 있으므로, 텍스트 PDF의 경우 정규 표현식(regex)이 키를 찾아내고 모듈로-11 검증 자릿수가 이를 확인해 줍니다.
Lesson 2: 결정당 하나의 작은 프롬프트 (one small prompt per decision)
추출 프롬프트가 카테고리까지 지정했을 때의 카테고리 정확도는 **70%**였습니다. 'Limpeza e recarga gás split'(에어컨 청소)는 _청소 용품_으로, 수령한 Pix 결제 내역은 _식료품_으로 분류되었습니다.
두 가지 변경 사항을 거치면서 이 수치는 **100%**가 되었습니다:
- 돈이 들어왔는지 나갔는지는 **규칙(rules)**에 의해 결정됩니다: 문서 유형과 고객 자신의 CNPJ가 수령자인지 여부입니다. 수익(Income)은 항상 'Receita'로 간주되며, 모델이 추측할 기회 자체가 없습니다.
- 비용(Expenses)의 경우 공급업체 이름, 문서 유형, 간단한 설명만으로 3B 모델에 **두 번째의 작고 별도의 호출(second, tiny call)**을 합니다. 여기에 비용 카테고리 목록과 한 줄 요약된 힌트를 추가합니다. 이 과정은 약 0.3초가 소요됩니다.
작은 모델들은 하나의 좁은 질문에는 훨씬 뛰어나지만, 하나의 프롬프트에서 다섯 가지 질문을 처리하는 것에는 그렇지 못합니다.
Lesson 3: 검증이 핵심 기능이다 (validation is the feature)
LLM만으로는 아직 송장 추출이 불가능한 이유에서 Taranpreet Kaur는 모델의 출력 결과가 독립적인 검증을 거쳐야 한다고 주장하며, 이는 제가 본 내용과 일치했습니다. 모든 기록은 누군가가 신뢰하기 전에 Python을 거칩니다:
- CNPJ 검증 자릿수: 2026년 7월부터 시행된 새로운 영숫자 CNPJ 포함
- NF-e 접근 키: 44자리 숫자와 모듈로-11 검증 자릿수, 그리고 CNPJ가 키 내의 발행자와 일치해야 함
- 금액 > 0, 그리고 선택한 _competência_에 날짜가 포함되어야 함
- 공급업체 CNPJ가 없는 비용(예: 개인으로부터 받은 영수증)은 플래그 지정되어 그녀가 원천징수세를 확인할 수 있도록 합니다.
어떤 오류가 발생하든 revisar = True로 설정하고, 그 이유를 사람이 읽을 수 있는 형태로 제공합니다. 예를 들어, "data 29/09/2026 fora da competência 10/2026" (회계 월에 날짜가 벗어남)과 같은 메시지입니다. 파이프라인을 중단시키는 문서는 배치 전체를 망치지 않고 플래그가 지정된 행으로 처리됩니다. 회계사는 AI가 완벽할 필요는 없습니다. 그녀가 알아야 할 것은 10개 중 어떤 2개를 살펴봐야 하는지입니다.
이는 devshakib이 Structured Output From LLMs: A Retry-Repair Loop Your Parser Never Sees Through에서 설명한 레이어링과 동일합니다. 즉, 제약된 디코딩(constrained decoding)을 거친 후 사용자가 직접 검증하는 방식입니다. 저는 실패를 재시도하기보다는 사람에게 라우팅하도록 선택했는데, 흐릿한 사진에 대한 재시도는 단지 다른 환각(hallucination)만을 만들어낼 뿐이기 때문입니다.
감(vibe)이 아닌 측정 (Measure, don't vibe)
avaliar.py는 전체 세트를 gabarito.json (정답지)과 비교하여 필드별 및 문서별 정확도를 출력하고, 추가로 09번과 10번 문서는 검토 대상에 포함되는지 여부를 의무적으로 확인합니다. 위의 모든 변경 사항은 이 표를 기반으로 유지되거나 되돌려졌습니다. 테스트용 문서는 스크립트(가짜 회사, 유효한 가짜 CNPJ 및 접근 키, 회전, 흐림, 노이즈가 적용된 시뮬레이션 전화 사진)에 의해 생성되었기 때문에 누구나 이 수치들을 재현할 수 있습니다.
그녀의 반응 (Her reaction)
제가 이것을 보여주었을 때, 그녀는 매우 좋아했습니다. 이러한 문서를 타이핑하는 것은 그녀가 몇 년 동안 겪어온 문제였지만, 그것이 해결될 수 있는 것이라는 사실은 알지 못했습니다. 그녀는 만약 이 사실을 알고 있었다면 오래전에 했어야 했다고 말했습니다. 그녀는 정말 고마워했고, 이것에 대해 R$ 2,000를 지불하겠다고 했습니다.
다음 단계 (What's next)
- 그녀의 실제 문서를 사용하여 그녀 자신의 기계에서 실행하고, 다음에 필요한 것을 수집할 예정입니다.
- 여러 항목이 포함된 다중 페이지 NF-e와, 비전 모델에 의존하여 키를 읽는 대신 NFC-e QR 코드를 직접 읽기.
- 그녀의 회계 소프트웨어에서 가져오는 레이아웃으로 내보내기(export).
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기