은행 입금 알림을 장부로 — 로컬 Gemma로 만든 나이지리아 POS 운영자용 데이북 (Bank alerts to a day-book, with a local Gemma)
나이지리아의 POS 운영자가 손으로 쓰던 입금 장부를, 브라우저에서 돌아가는 Gemma 3 1B와 규칙 기반 코드로 자동화한 사례입니다. 데이터는 기기 밖으로 나가지 않고, 모델은 고객 이름을 읽는 일 하나만 맡습니다.

Bank alerts to a day-book, with a local Gemma
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle ...
개요 #
개발자 Daniel Nwaneri가 나이지리아 포트하코트(Port Harcourt)에서 POS 가판대를 운영하는 'Mama POS'를 위해 입금 장부 앱을 만들었습니다. 은행 입금 알림 메일을 붙여 넣으면 시간, 금액, 입금 종류, 고객 이름이 자동으로 정리됩니다. 고객이 "보냈어요"라고 할 때 실제로 들어왔는지 확인할 수 있고, 마감 정산도 해 줍니다.
설계 원칙은 하나였습니다. 코드로 할 수 있는 일은 전부 코드가 하고, 모델은 코드가 못 하는 일만 한다. 그래서 브라우저에서 로컬로 실행하는 Gemma 3 1B는 고객 이름을 읽는 일만 맡습니다. 모든 데이터는 브라우저 안에 남고 서버로 전송되지 않습니다.
이 글은 Hacktoberfest Open-Source AI Challenge 2주 차(Side Hustle) 출품작입니다.
본문 #
나이지리아의 POS 가판대는 어떻게 돌아가나 #
나이지리아에서는 ATM 대신 카드 단말기를 둔 작은 가판대에서 현금을 찾는 사람이 많습니다. 고객이 휴대폰으로 운영자에게 계좌이체를 하면, 운영자가 같은 금액을 현금으로 내주고 약간의 수수료를 받는 방식입니다.
Mama POS는 ₦5,000(나이라)당 ₦150, 즉 3%를 수수료로 받고 하루 10~20건을 처리합니다. 거래 은행인 Moniepoint는 입금이 들어올 때마다 메일을 보내는데, 현지에서는 이를 '크레딧 알림(credit alert)'이라고 부릅니다. 운영자는 이 메일을 보고 고객 돈이 들어왔는지 확인합니다.
Photo by RDNE Stock project on Pexels
10월 15일 개발자가 가판대에서 직접 물어보니 이런 답이 돌아왔습니다.
- 입금 건마다 공책에 손으로 적는데 "오래 걸린다".
- 돈이 안 맞으면 "골치 아파서" 아예 맞춰 보지 않게 됐다.
- 돈이 안 들어왔는데 고객이 "보냈다"고 하는 일이 "자주 있다".
- 지금은 돈 내고 쓰는 도구가 없다. 이 일을 해 주는 도구에 월 ₦5,000을 내겠느냐고 묻자 그러겠다고 했다.
앱이 하는 일 #
알림 메일을 붙여 넣으면 다음 기능을 쓸 수 있습니다.
- 자동 기록: 시간, 금액, 입금 종류, 고객 이름이 행 단위로 정리됩니다. 직접 입력할 것은 없습니다. 입금 종류는 고객 이체, 본인이 산 통신 요금 충전(airtime), 은행 리워드, 실패한 이체의 반환 중 하나로 분류합니다.
- 입금 확인: 고객이 보냈다는 금액을 입력하면 "Landed(입금됨)", "Not seen yet(아직 안 보임)", 또는 "같은 금액 2건"이라고 답하고, 마지막 경우에는 이름을 물어봅니다.
- 마감 정산: 하루 입금 총액, 예상 수수료, 확인이 필요한 행(반환 건, 10분 안에 같은 금액이 두 번 들어온 건)을 보여 주고, 사업주에게 보고할 WhatsApp 메시지도 만들어 줍니다.
앱은 mama-pos-daybook.pages.dev에서 써 볼 수 있습니다. 모델을 돌리려면 GPU가 제대로 동작하는 기기(노트북, Pixel 8a 등)가 필요하고, 나머지 기능은 어떤 휴대폰에서든 동작합니다. 데모 영상은 Mama POS의 실제 알림 5건으로 찍었고, 고객 이름은 "NAME"으로 가렸습니다. 실제 앱을 실제 속도로 녹화한 영상입니다.
소스 코드는 GitHub 저장소에 공개되어 있습니다.
코드와 모델의 역할 분담 #
개발자는 실제 알림 데이터로 경계를 직접 재 보고 역할을 나눴습니다.
| 단계 | 담당 | 이유 |
|---|---|---|
| 금액, 시간, 잔액 | 코드 | 메일 안에서 위치가 고정된 필드 |
| 입금 종류 | 코드(키워드 규칙) | 실제 알림에서 규칙은 5/5, Gemma는 4/5 |
| 고객 이름 | Gemma 3 1B | 보낸 사람의 은행마다 표기 방식이 다름 |
| 입금 확인, 마감, 수수료 | 코드 | 산술 계산 |
어려운 건 이름이었습니다. 실제 알림을 보니 입금 건인데도 고객 이름이 적힌 줄이 TRANSFER TO [고객] [은행] 형식이었습니다. 개발자는 "TRANSFER FROM"을 예상하고 키워드 규칙을 짰는데, 이 규칙은 고객 이름 3개 중 하나도 찾지 못했습니다. Gemma는 대부분 찾아냈습니다. 영상 속 실행에서는 3개 중 3개, 그 전 테스트에서는 3개 중 2개였습니다. 놓친 1건은 적요 전체가 고객 이름뿐인 알림이었습니다.
브라우저에서 돌아가는 Gemma #
모델은 Gemma 3 1B instruct(ONNX, q4)이고, transformers.js로 WebGPU 위에서 브라우저 안에서 실행합니다.
- 첫 로딩 때 859MB를 한 번 내려받습니다.
- 그다음부터는 몇 초 만에 뜨고 인터넷 없이도 돌아갑니다. 네트워크를 끄고 테스트했을 때 6초 만에 준비됐습니다.
- 개발자 노트북에서는 이름 하나를 읽는 데 2~5초가 걸립니다.
모델은 '복사'만 할 수 있다 #
모델 응답은 저장하기 전에 전부 검사를 거칩니다.
- JSON 형식이어야 하고, 입금 종류는 정해진 목록 안의 값이어야 합니다.
- 이름은 알림에서 그대로 복사한 텍스트여야 하고, 글자가 들어 있어야 합니다.
- 응답에 나온 숫자는 모두 알림 안에 있는 숫자여야 합니다.
- 이름 끝에 붙은 은행명은 코드가 잘라 냅니다(
[고객] OPay→[고객]). - 한 번 재시도한 뒤에도 실패하면 이름은 "not read"로 남깁니다. 추측해서 채우지 않습니다.
금액 합계는 모델과 전혀 상관없이 계산합니다. Gemma가 로드되지 않았거나 GPU가 중간에 끊겨도 모든 행에 금액, 시간, 종류가 남고 하루 정산도 맞습니다.
실제로 부딪힌 문제들 #
운영자 본인의 휴대폰에서 돌아가지 않았습니다. Mama POS의 휴대폰은 Android 11 기반 Tecno Camon 17 Pro입니다. WebGPU를 지원한다고 나오지만 GPU 어댑터를 내주지 않았습니다. CPU용 모델(int8)은 개발자 노트북에서조차 30건 중 1건만 종류를 맞혔고, 알림 하나에 36초가 걸렸습니다. "JSON으로 답하라"는 지시를 무시하고 문단을 써 내려갔기 때문입니다. 그래서 이 휴대폰에서는 1GB 다운로드를 시작하지 않고 GPU가 필요하다고 안내합니다. GPU가 제대로 동작하는 Pixel 8a에서는 상태 표시가 "ready (webgpu)"로 바뀌고 이름도 채워집니다.
4G에서 다운로드가 소리 없이 멈췄습니다. 에러 없이 데이터만 끊겼습니다. 개발자는 이어받기가 되는 다운로드를 새로 만들었습니다. 파일을 8MB 조각으로 받아 도착하는 대로 저장하고, 15초 동안 데이터가 안 들어오는 조각은 다시 요청하며, 멈춘 다운로드는 마지막 조각부터 이어 받습니다. 이렇게 바꾼 뒤에는 운영자 휴대폰에서도 모델 전체를 끊김 없이 받았습니다.
실행 중에 GPU가 한 번 끊겼습니다("Device was lost"). 지금은 첫 호출이 실패하면 바로 멈추고 새로고침을 요청합니다. 예전처럼 모든 행을 '불확실'로 표시하지 않습니다.
결과 #
전체 결과는 저장소의 bench/RESULTS.md에 있습니다.
| 테스트 | 종류 | 이름 |
|---|---|---|
| 실제 알림 5건, 운영자가 직접 라벨링 | 5/5 | 4/5 |
| 개발자가 직접 만든 합성 알림 30건 | 30/30 * | 23/30 |
이름 점수는 알림 5건 전체 기준입니다. 이 중 2건(통신 요금 충전, 캐시백)은 원래 고객 이름이 없어서 "이름 없음"이 정답입니다. 고객 이체 3건만 보면 이 테스트에서 Gemma는 2건을 맞혔습니다.
* 개발자는 이 종류 점수가 공정하지 않다고 스스로 밝혔습니다. 키워드 규칙과 합성 데이터를 같은 사람이 만들었기 때문입니다. 공정하게 볼 수 있는 건 실제 알림 쪽인데, 5건밖에 안 됩니다. 은행명을 잘라 내는 로직도 실제 알림을 본 뒤에 만들었으니, 실제 알림 결과는 벤치마크가 아니라 현장 점검 정도로 봐 달라고 했습니다.
왜 오픈 모델이었나 #
개발자가 꼽은 이유는 네 가지입니다.
- 개인정보: 알림에는 누가 얼마를 보냈는지, 계좌에 얼마가 있는지가 다 들어 있습니다. 클라우드 API를 쓰면 이 정보를 운영자가 들어 본 적도 없는 회사로 보내야 합니다. 브라우저에서 오픈 모델을 돌리면 아무것도 밖으로 나가지 않습니다.
- 비용: 운영자는 미리 ₦5,000, 미국 달러로 몇 달러를 냈습니다. 알림마다 매일 유료 API를 호출하면 이 금액으로는 감당할 수 없습니다. 사용자 기기에서 도는 모델은 알림당 비용이 들지 않습니다.
- 오프라인: 한 번 내려받고 나면 모델과 앱 모두 네트워크 없이 돌아갑니다. 포트하코트는 전기와 데이터가 모두 자주 끊기는 곳입니다.
- 실패 원인 확인: 가중치가 공개되어 있으니 같은 모델의 두 버전(GPU용 q4, CPU용 int8)을 직접 측정해 고를 수 있었습니다. 덕분에 운영자에게 배포하기 전에 CPU 버전으로는 이 일을 할 수 없다는 사실을 알아냈습니다.
Photo by RDNE Stock project on Pexels
첫 번째 사용자 #
'Mama POS'라는 이름은 본인이 이 글을 위해 직접 골랐습니다. 운영자는 이 프로젝트에서 이런 역할을 했습니다.
- 앞에서 인용한 말로 문제를 설명했습니다.
- 실제 알림 메일 5건을 건넸습니다. 개발자는 쓰기 전에 이름, 계좌번호, 참조번호를 가렸습니다.
- 알림마다 무슨 돈인지(고객 현금, 본인 통신 요금 충전, 캐시백) 라벨을 붙였고, "TRANSFER TO" 뒤의 이름이 고객 이름이 맞다고 확인해 줬습니다. 앞의 실제 알림 점수는 이 라벨을 기준으로 매겼습니다.
운영자는 앞으로 나올 기능을 믿고 ₦5,000을 먼저 냈지만 아직 직접 써 보지는 못했습니다. 휴대폰에서 Gemma가 돌아가지 않아서, 지금은 장부 기능만 휴대폰에서 쓰고 이름 읽기는 노트북에서 해야 합니다. 운영자는 휴대폰을 바꾸겠다고 약속했습니다. Pixel 8a처럼 GPU가 제대로 동작하는 휴대폰이라면 앱 전체가 휴대폰에서 돌아갑니다.
아직 안 되는 기능도 있습니다. 운영자가 쓰는 두 번째 POS 업체인 Nomba의 알림은 읽지 못하고, 지금은 Moniepoint 알림만 처리합니다.
개발자는 이 작품을 'Best Use of Gemma' 부문에 출품했습니다. 앱에서 코드로 풀 수 없었던 일, 즉 은행마다 제각각 쓰는 자유 텍스트에서 고객 이름을 읽어 내는 일을 사용자 기기의 GPU에서 도는 Gemma 3 1B가 맡았다는 점을 출품 이유로 들었습니다.
이 글은 위 출처를 바탕으로 한국 독자를 위해 재작성한 기사입니다. 원문의 사실과 수치에 근거하며, 별도의 견해를 포함하지 않습니다.