온디바이스 영수증 추출과 정확한 금액

앱이 회계를 대신 판정하지 않습니다. 기기 안의 OCR 과 규칙이 후보를 내고, 사용자가 영수증 이미지를 보며 확정하며, 확정한 기록만 월 합계와 CSV 에 들어갑니다.

소스 기준: 앱 1.0.0 (3) · 2026년 9월 23일

범위와 스택

v1 은 한국어·영어 영수증을 이미지 한 장당 한 건으로 읽고, 여러 통화를 환산 없이 다룹니다. 품목이나 카테고리가 아니라 영수증마다 요약 필드를 남깁니다. 계정·서버·클라우드 동기화는 없습니다.

UIKotlin·Jetpack Compose, minSdk 26, targetSdk 36
입력ML Kit 문서 스캐너(한 페이지) 또는 Android Photo Picker(이미지 한 장)
OCR앱에 포함된 ML Kit Text Recognition v2 한국어 모델
추출한국어·영어 라벨 사전과 공통 규칙 엔진 — 앱이 소유
저장Room 데이터베이스와 앱 전용 이미지 파일
공용 코드OCR·스캐너용 libs/ai 어댑터와, 명함 앱과 공유하는 순수 Kotlin 코어 계약

공용 모듈은 영수증을 모릅니다. 영수증 라벨·금액 타입·병합 정책은 앱 안에 두어, 두 번째 소비 앱이 영수증 전제를 우연히 물려받지 않게 했습니다.

입력과 이미지 수명

시작 화면에 "촬영"과 "사진에서 선택" 두 버튼을 따로 둡니다. 스캐너 취소는 실패가 아니며 사진 선택기를 자동으로 띄우지 않습니다.

첫 실행 오프라인 동작은 앱에 포함된 OCR 에만 해당합니다. 스캐너 모듈은 Play 서비스가 내려받고, 사진 선택기는 클라우드 사진 공급자를 보여줄 수 있습니다. "앱이 영수증을 서버로 보내지 않는다"와 "이미지를 얻을 때도 네트워크를 쓰지 않는다"는 다른 말입니다.

인식과 필드 상태

앱에 포함된 한국어 Text Recognition v2 모델 하나가 한글과 라틴 문자를 함께 읽으므로 영어·혼합 영수증에 모델을 더 넣지 않습니다. 문자를 지원한다는 것과 영수증에서 정확하다는 것은 별개라 영어 영수증은 따로 시험합니다.

OCR 줄을 눈에 보이는 행으로 다시 묶습니다. ML Kit 은 가로 간격이 넓은 글자를 다른 줄로 나눕니다. 영수증은 라벨과 금액이 한 행의 양 끝에 있어서, Android 11 시험 폰에서 합성 한국어 영수증을 읽었을 때 상호·날짜만 나오고 금액·세금이 비었습니다. 지금은 세로 겹침이 작은 쪽 높이의 절반 이상인 조각을 한 행으로 이은 뒤 규칙을 적용합니다.

필드 상태는 없음·검토 필요·사용자 확정 셋이며, 마지막 상태는 사용자만 만듭니다. 사용자가 고친 필드는 이후 인식이 덮지 않습니다. 상호·거래일·금액·통화가 모두 사용자 확정이고 사용자가 확정 저장했을 때만 기록이 확정이 되고, 나머지는 모두 초안입니다.

설계에는 Gemini Nano(ML Kit GenAI Prompt) 경로가 있습니다. 값과 그 근거가 된 OCR 줄 ID 를 받아 앱이 검증한 뒤에만 쓰는 방식입니다. 이 기준 버전에서는 연결하지 않았습니다. release 빌드에 Prompt 모듈이 들어가지 않고, Galaxy S25 에서 debug 전용 가용성 탐침이 UNAVAILABLE 을 돌려줬습니다. 이 버전의 결과는 모두 OCR 과 규칙에서 나옵니다.

금액과 통화

금액은 Money(currencyCode, unscaled: Long, scale: Int) 로 저장합니다. 12.34 USD 는 USD / 1234 / 2 입니다. 자릿수(scale)를 기록마다 저장하고, ICU·CLDR 버전에 따라 바뀔 수 있는 기기의 통화 메타데이터로 다시 해석하지 않습니다.

실기 시험 뒤 두 규칙을 더했습니다. 영수증의 명시 통화 표기가 모두 같은 한 통화면, 표기 없는 합계 라벨의 문맥 근거로 그 통화를 쓰고 문맥 판정임을 표시해 검토 대상으로 둡니다(합계 줄의 원 은 오인식됐지만 같은 전표의 부가세: 0원 이 남아 있었습니다). 또 통화가 KRW·JPY 처럼 소수 자릿수 0 으로 명시 판정되면 13,500 을 13.5 로 읽는 해석을 뺍니다. 이 예외가 없으면 원화 금액 거의 전부에 가짜 모호성이 붙었습니다. 두 규칙 모두 모호한 기호와 소수 2자리 통화에는 적용하지 않습니다.

합계와 CSV 내보내기

홈 화면은 이번 달을 통화별로 총지출·환불·순액으로 나눠 보여주고 통화 사이를 환산하지 않습니다. 거래 종류가 정해진 확정 기록만 셉니다. 초안과 아직 미확인인 기록은 합계에서 빠집니다.

저장·개인정보·백업

검증과 확인된 제약

금액·파서·추출·행 묶기·병합·CSV·검토 폼·월 합계 로직은 합성 OCR 텍스트와 가상 영수증을 쓰는 JVM 단위 시험으로 확인합니다. 실제 영수증은 저장소에 넣지 않습니다.

release 에서만 난 크래시. 1.0.0 (1) 빌드는 시작하자마자 죽었고 debug 빌드는 정상이었습니다. ML Kit 이 끌어오는 firebase-components 의 consumer 규칙이 등록자 클래스를 멤버 없이 보존해, R8 full mode 가 인자 없는 생성자를 지웠습니다. 그 결과 구성요소 탐색이 조용히 실패하고 텍스트 인식기가 null 구성요소를 받았습니다. 지금은 libs/ai 의 OCR·스캐너 모듈이 등록자 생성자를 보존하는 규칙을 함께 배포하며, 수정본은 1.0.0 (2) 로 빌드했습니다. 그 버전 코드는 출시 없이 Play Console 에서 소진되어, 출시용 빌드는 1.0.0 (3) 입니다.